configure_ocean_cavity_melt Subroutine

public subroutine configure_ocean_cavity_melt(cfg, ocean_state, grid, compute_rank, ierr)

Uses

  • proc~~configure_ocean_cavity_melt~~UsesGraph proc~configure_ocean_cavity_melt configure_ocean_cavity_melt module~rdb_ocean_cavity_melt rdb_ocean_cavity_melt proc~configure_ocean_cavity_melt->module~rdb_ocean_cavity_melt ieee_arithmetic ieee_arithmetic module~rdb_ocean_cavity_melt->ieee_arithmetic module~rdb_constants rdb_constants module~rdb_ocean_cavity_melt->module~rdb_constants module~rdb_eos rdb_eos module~rdb_ocean_cavity_melt->module~rdb_eos pic_types pic_types module~rdb_constants->pic_types module~rdb_eos->module~rdb_constants module~rdb_grid rdb_grid module~rdb_eos->module~rdb_grid module~rdb_grid->module~rdb_constants

Configure the ice-shelf basal-melt slot (&ocean_cavity_melt_nml, P2b): copy the knobs onto the slot’s three flat parameter bundles, resolve the gamma_s sentinel, build the per-column Coriolis array the hj99 law needs, and CHECK that the interface pressure the liquidus will read has actually been loaded.

Runs immediately after configure_ocean_cavity — which is the SOLE producer of ms%p_top — and before ocean_state_enter_data, like every other static fill the device map has to capture.

ms%p_top: this routine is a CONSUMER, not a writer. ms%p_top is THE interface pressure (src/core/ocean/README.md, the p_top seam contract), and the melt liquidus is its THIRD consumer alongside the FV_MOM6 pa(nz+1) surface boundary condition and the in-situ EOS. It has exactly ONE producer, configure_ocean_cavity, which assembles ms%p_top = metrics%p_ice_ref + sf%p_surf (P5.2) — rebuilt per outer step in ocean_dyn_step_split when the psurf seam makes sf%p_surf live. A second writer here would be a silent clobber, so there is none: what this routine does instead is ASSERT that every ice-covered column carries at least its own isostatic load, which catches a producer that was skipped or reordered rather than trusting the call order.

Arguments

Type IntentOptional Attributes Name
type(config_t), intent(in) :: cfg
type(ocean_state_t), intent(inout) :: ocean_state
type(hgrid_t), intent(in) :: grid
integer, intent(in) :: compute_rank
integer, intent(out), optional :: ierr

Non-zero on a melt configuration conflict when present; absent behaves as today (error stop).


Calls

proc~~configure_ocean_cavity_melt~~CallsGraph proc~configure_ocean_cavity_melt configure_ocean_cavity_melt info info proc~configure_ocean_cavity_melt->info proc~cavity_count_unloaded_p_top cavity_count_unloaded_p_top proc~configure_ocean_cavity_melt->proc~cavity_count_unloaded_p_top proc~cavity_count_zero_f cavity_count_zero_f proc~configure_ocean_cavity_melt->proc~cavity_count_zero_f proc~cavity_resolve_gamma_s cavity_resolve_gamma_s proc~configure_ocean_cavity_melt->proc~cavity_resolve_gamma_s proc~fail fail proc~configure_ocean_cavity_melt->proc~fail proc~fill_coriolis_centre fill_coriolis_centre proc~configure_ocean_cavity_melt->proc~fill_coriolis_centre proc~parse_cavity_exchange_law parse_cavity_exchange_law proc~configure_ocean_cavity_melt->proc~parse_cavity_exchange_law proc~parse_cavity_freshwater parse_cavity_freshwater proc~configure_ocean_cavity_melt->proc~parse_cavity_freshwater proc~parse_cavity_ice_mode parse_cavity_ice_mode proc~configure_ocean_cavity_melt->proc~parse_cavity_ice_mode proc~parse_cavity_volume_comp parse_cavity_volume_comp proc~configure_ocean_cavity_melt->proc~parse_cavity_volume_comp to_string to_string proc~configure_ocean_cavity_melt->to_string warning warning proc~configure_ocean_cavity_melt->warning error error proc~fail->error proc~error_ring_push error_ring_push proc~fail->proc~error_ring_push proc~metrics_fill_coriolis metrics_fill_coriolis proc~fill_coriolis_centre->proc~metrics_fill_coriolis proc~parse_coriolis_scheme parse_coriolis_scheme proc~fill_coriolis_centre->proc~parse_coriolis_scheme

Called by

proc~~configure_ocean_cavity_melt~~CalledByGraph proc~configure_ocean_cavity_melt configure_ocean_cavity_melt proc~engine_setup engine_setup proc~engine_setup->proc~configure_ocean_cavity_melt proc~complete_ocean_create complete_ocean_create proc~complete_ocean_create->proc~engine_setup proc~driver_run_ocean driver_run_ocean proc~driver_run_ocean->proc~engine_setup proc~driver_validate driver_validate proc~driver_validate->proc~engine_setup proc~driver_run driver_run proc~driver_run->proc~driver_run_ocean proc~rdb_ocean_create_finalize rdb_ocean_create_finalize proc~rdb_ocean_create_finalize->proc~complete_ocean_create proc~rdb_ocean_create_from_string rdb_ocean_create_from_string proc~rdb_ocean_create_from_string->proc~complete_ocean_create

Variables

Type Visibility Attributes Name Initial
real(kind=wp), private :: gamma_s_eff
integer, private :: n_unloaded
integer, private :: n_zero_f
integer, private :: nx
integer, private :: ny

Source Code

   subroutine configure_ocean_cavity_melt(cfg, ocean_state, grid, compute_rank, ierr)
      !! Configure the ice-shelf basal-melt slot (`&ocean_cavity_melt_nml`,
      !! P2b): copy the knobs onto the slot's three flat parameter
      !! bundles, resolve the `gamma_s` sentinel, build the per-column
      !! Coriolis array the `hj99` law needs, and CHECK that the
      !! interface pressure the liquidus will read has actually been
      !! loaded.
      !!
      !! Runs immediately after `configure_ocean_cavity` — which is the
      !! SOLE producer of `ms%p_top` — and before
      !! `ocean_state_enter_data`, like every other static fill the
      !! device map has to capture.
      !!
      !! **`ms%p_top`: this routine is a CONSUMER, not a writer.**
      !! `ms%p_top` is THE interface pressure
      !! (`src/core/ocean/README.md`, the `p_top` seam contract), and the
      !! melt liquidus is its THIRD consumer alongside the FV_MOM6
      !! `pa(nz+1)` surface boundary condition and the in-situ EOS.  It
      !! has exactly ONE producer, `configure_ocean_cavity`, which
      !! assembles `ms%p_top = metrics%p_ice_ref + sf%p_surf` (P5.2) —
      !! rebuilt per outer step in `ocean_dyn_step_split` when the psurf
      !! seam makes `sf%p_surf` live.  A second writer here would be a
      !! silent clobber, so there is none: what this routine does instead
      !! is ASSERT that every ice-covered column carries at least its own
      !! isostatic load, which catches a producer that was skipped or
      !! reordered rather than trusting the call order.
      use rdb_ocean_cavity_melt, only: parse_cavity_exchange_law, parse_cavity_ice_mode, &
                                       CAVITY_LAW_HJ99, parse_cavity_freshwater, &
                                       parse_cavity_volume_comp, CAVITY_FW_MASS, &
                                       CAVITY_VC_UNIFORM_OPEN
      type(config_t), intent(in) :: cfg
      type(ocean_state_t), intent(inout) :: ocean_state
      type(hgrid_t), intent(in) :: grid
      integer, intent(in) :: compute_rank
      integer, intent(out), optional :: ierr
         !! Non-zero on a melt configuration conflict when present;
         !! absent behaves as today (`error stop`).
      integer :: nx, ny, n_zero_f, n_unloaded
      real(wp) :: gamma_s_eff

      if (.not. ocean_state%cavity_flux%enable) then
         if (present(ierr)) ierr = OCEAN_STATUS_OK
         return
      end if

      nx = size(ocean_state%cavity_flux%f_cor, 1)
      ny = size(ocean_state%cavity_flux%f_cor, 2)

      ! ---- knobs -> slot ----
      ocean_state%cavity_flux%far_field_depth = cfg%ocean%cavity_melt%far_field_depth
      ocean_state%cavity_flux%cdrag_top = cfg%ocean%cavity_melt%cdrag_top
      ocean_state%cavity_flux%u_tide = cfg%ocean%cavity_melt%u_tide
      ocean_state%cavity_flux%ustar_min = cfg%ocean%cavity_melt%ustar_min
      ocean_state%cavity_flux%s_ice = cfg%ocean%cavity_melt%s_ice
      ocean_state%cavity_flux%freshwater = &
         parse_cavity_freshwater(cfg%ocean%cavity_melt%freshwater)
      ocean_state%cavity_flux%volume_comp = &
         parse_cavity_volume_comp(cfg%ocean%cavity_melt%volume_compensation)

      ! THE Boussinesq reference density, taken from the surface-flux
      ! slot rather than re-read from `cfg`, because the real-mass path's
      ! salt correction has to be the EXACT negation of the increment
      ! `apply_surface_src_2d_impl` stamped with `dt/sf%rho0`.  One
      ! source, no second literal, and `configure_ocean_reference_density`
      ! has already run (it seeds `sf%rho0` from `&ocean_ic_nml rho_0`).
      ocean_state%cavity_flux%rho0 = ocean_state%surface_flux%rho0
      if (ocean_state%cavity_flux%rho0 <= 0.0_wp) then
         call fail("&ocean_cavity_melt_nml: the Boussinesq reference density "// &
                   "reaching the melt slot is non-positive, so the mass -> volume "// &
                   "conversion m/rho_0 has no value.  configure_ocean_reference_"// &
                   "density must run before configure_ocean_cavity_melt.", ierr, &
                   OCEAN_STATUS_ERR_SETUP)
         return
      end if

      ! `gamma_s` carries the repo's negative "unset" sentinel; resolve
      ! it to the ISOMIP+ `gamma_t/35` exactly once, here, so the value
      ! the kernel is handed is the value the log prints.
      gamma_s_eff = cavity_resolve_gamma_s(cfg%ocean%cavity_melt%gamma_s, &
                                           cfg%ocean%cavity_melt%gamma_t)
      ocean_state%cavity_flux%par%law = &
         parse_cavity_exchange_law(cfg%ocean%cavity_melt%exchange_law)
      ocean_state%cavity_flux%par%gamma_t_coeff = cfg%ocean%cavity_melt%gamma_t
      ocean_state%cavity_flux%par%gamma_s_coeff = gamma_s_eff
      ocean_state%cavity_flux%ice%mode = &
         parse_cavity_ice_mode(cfg%ocean%cavity_melt%ice_conduction)
      ocean_state%cavity_flux%ice%T_ice = cfg%ocean%cavity_melt%t_ice

      ! `cavity_flux%const` is deliberately LEFT at its ISOMIP+ defaults
      ! (Asay-Davis et al. (2016) Table 4): `rho_w`, `c_w`, `alpha_T`,
      ! `beta_S` there are the melt law's own calibrated constants, not
      ! copies of the Boussinesq `rho_0` — the same "out of scope on
      ! purpose" category the README's reference-density table already
      ! lists `RHO_WATER` and `&ocean_ice_nml rho_ocean` under.
      ! Overriding them would silently break ISOMIP+ comparability,
      ! which is the reason this path exists.

      ! ---- per-column Coriolis for the hj99 law ----
      call fill_coriolis_centre(cfg, ocean_state%metrics, grid, ocean_state%cavity_flux%f_cor)
      if (ocean_state%cavity_flux%par%law == CAVITY_LAW_HJ99) then
         n_zero_f = cavity_count_zero_f(ocean_state%cavity_flux%f_cor, &
                                        ocean_state%metrics%cover_frac, &
                                        ocean_state%multilayer%wet_mask, nx, ny)
         if (n_zero_f > 0) then
            call fail("&ocean_cavity_melt_nml exchange_law='hj99': "// &
                      to_string(n_zero_f)//" ice-covered wet column(s) sit at "// &
                      "f = 0.  Holland & Jenkins (1999) eq. (15) takes "// &
                      "ln(.../|f| h_nu) and eq. (18) divides by f*L_O, so the law "// &
                      "has no value there and the kernel would return "// &
                      "CAVITY_MELT_NO_CORIOLIS for every one of them.  Use "// &
                      "exchange_law='const_gamma', or move the cavity off the "// &
                      "f = 0 line.", ierr, OCEAN_STATUS_ERR_SETUP)
            return
         end if
      end if

      ! ---- the interface pressure: ASSERT, never write (see the docstring) ----
      ! `p_surf >= 0` by contract, so a correctly assembled `p_top` is
      ! `>= p_ice_ref` on every cell.  A zero `p_top` under a loaded
      ! draft means the producer never ran (or ran before the load was
      ! built), which would melt against a surface-pressure liquidus and
      ! silently delete the entire ice pump.
      n_unloaded = cavity_count_unloaded_p_top(ocean_state%multilayer%p_top, &
                                               ocean_state%metrics%p_ice_ref, nx, ny)
      if (n_unloaded > 0) then
         call fail("&ocean_cavity_melt_nml: "//to_string(n_unloaded)//" column(s) "// &
                   "have ms%p_top < metrics%p_ice_ref, so the top-of-column load was "// &
                   "never assembled.  The melt liquidus is a CONSUMER of ms%p_top; "// &
                   "its sole producer is configure_ocean_cavity "// &
                   "(p_top = p_ice_ref + sf%p_surf), which must run before this "// &
                   "routine.", ierr, OCEAN_STATUS_ERR_SETUP)
         return
      end if

      if (compute_rank == 0) then
         call logger%info("Ice-shelf basal melt: exchange_law='"// &
                          trim(adjustl(cfg%ocean%cavity_melt%exchange_law))// &
                          "', Gamma_T = "//to_string(cfg%ocean%cavity_melt%gamma_t)// &
                          ", Gamma_S = "//to_string(gamma_s_eff)// &
                          ", ice='"// &
                          trim(adjustl(cfg%ocean%cavity_melt%ice_conduction))//"'")
         call logger%info("                      far_field_depth = "// &
                          to_string(cfg%ocean%cavity_melt%far_field_depth)// &
                          " m (METRES below the ice base, not 'layer nz'), "// &
                          "C_d,top = "//to_string(cfg%ocean%cavity_melt%cdrag_top)// &
                          ", u_tide = "//to_string(cfg%ocean%cavity_melt%u_tide)//" m/s")
         call logger%info("                      liquidus evaluated at ms%p_top "// &
                          "(max = "// &
                          to_string(maxval(ocean_state%multilayer%p_top))// &
                          " Pa), assembled by configure_ocean_cavity as "// &
                          "p_ice_ref + sf%p_surf")
         if (ocean_state%cavity_flux%freshwater == CAVITY_FW_MASS) then
            call logger%info("                      freshwater = 'mass': the "// &
                             "meltwater is a REAL Boussinesq volume on the top "// &
                             "layer, dh = m*dt/rho_0 with rho_0 = "// &
                             to_string(ocean_state%cavity_flux%rho0)//" kg/m3.  The "// &
                             "virtual salt flux stays in Q_salt as the KPP/EPBL B_0 "// &
                             "buoyancy forcing and is removed again from the tracer "// &
                             "in the same stage.")
            if (ocean_state%cavity_flux%volume_comp == CAVITY_VC_UNIFORM_OPEN) then
               call logger%info("                      volume_compensation = "// &
                                "'uniform_open_ocean': the domain-integrated melt "// &
                                "volume is removed again each thermo step over the "// &
                                "uncovered wet cells, carrying their own T and S, "// &
                                "and is tracked as a sink in all three budgets.")
            else
               call logger%warning("Ice-shelf basal melt: freshwater='mass' with "// &
                                   "volume_compensation='none' — a CLOSED domain "// &
                                   "gains the melt volume and its sea level rises "// &
                                   "(for an ISOMIP+ Ocean0 box that is metres per "// &
                                   "year).  Correct for a short run or an open "// &
                                   "boundary; otherwise set "// &
                                   "volume_compensation='uniform_open_ocean'.")
            end if
         else
            call logger%warning("Ice-shelf basal melt: the meltwater is a VIRTUAL SALT "// &
                                "FLUX — it carries no mass, so it adds no volume and no "// &
                                "direct buoyancy.  That is a first-order limitation for "// &
                                "cavity circulation; set &ocean_cavity_melt_nml "// &
                                "freshwater='mass' for the real volume source.")
         end if
      end if
      if (present(ierr)) ierr = OCEAN_STATUS_OK
   end subroutine configure_ocean_cavity_melt