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.
| Type | Intent | Optional | 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 ( |
| 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 |
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