rdb_ice_frazil_uptake Module

Port of add_frazil_SIS2 (SIS2_ice_thm.F90:1342-1444, Apache-2.0), bulk-salinity mode only. Spends the WHOLE per-cell frazil bank (ocean_sea_ice_t%frazil_heat, banked by rdb_ice_frazil’s surface-freezing clamp) once per thermo step by depositing new ice mass, evenly split across the nk_ice layers of category 1 (cat = 1 — the only category this v1 model fills; part_size is untouched, same convention as PR 3a).

TRAP #1 (module docstring precedent: rdb_ice_column, rdb_ice_mass%ice_bottom_freeze): the ocean-water specific enthalpy consumed by the freeze is the LIQUID form enth_ocean = ice_enthalpy_liquid(sst, s_surf), never the frozen/mushy ice_enth_from_ts — see ice_frazil_uptake_column.

Index-flip discipline (PR 3a TRAP #2 precedent): the state arrays enth_ice/sal_ice are BOTTOM-UP (k=1 = ice bottom); the SIS2 algorithm spends the bank TOP-DOWN. The flip happens ONLY at the gather/scatter boundary inside ice_frazil_uptake_column — nothing in the per-layer spend loop ever sees the bottom-up convention.

Two-mode convention (PR 4a, rdb_ice_state module docstring): ncat == 1 — legacy lumped mode, dispatched to the EXISTING ice_frazil_uptake_impl byte-for-byte UNCHANGED: ALL frazil ice deposits into category 1, treated as per unit CELL area (kg per m² of cell) — self-consistent with the frazil bank (J per m² of cell) and the brine-rejection flux. ncat > 1 — SIS2 ITD mode, dispatched to ice_frazil_uptake_multicat_impl: the bank ANNEXES open water into the thinnest occupied-or-empty category (k_merge, SIS2 SIS_slow_thermo.F90:1121-1146) via an area-weighted dilution at constant mass (part(k_merge) += part(0), thickness drops, mass doesn’t — the SIS2 area-creation move), then spends the bank on that category’s column PER UNIT ICE AREA (frazil_col = frazil_heat/part(k_merge), SIS_slow_thermo.F90: 1181-1186) via the SAME UNCHANGED ice_frazil_uptake_column. SIS2’s default SIS2_FILLING_FRAZIL=.true. thin-category-fill mode (which would spread new ice across MULTIPLE thin categories instead of one k_merge) is NOT ported — v1 always merges into a single category, documented divergence.

Salt bookkeeping (Boussinesq virtual-flux convention): freezing m_frozen kg/m² of seawater at salinity s_surf into ice that keeps salt_to_ice = m_frozen * ICE_BULK_SALINITY yields a diag rate salt_flux_diag = (m_frozen*s_surf - salt_to_ice)/dt_therm [PSU·kg/m²/s]. The ocean’s water MASS is not reduced (volume- conserving virtual-salt-flux ocean — MOM6/SIS2 default); the closed-budget identity is exact by construction: rho0*Delta(sum_k hTr_S) + salt_into_ice == m_frozen*s_surf per cell (rho0 = sf%rho0; the surface-flux apply gives Delta(hS) = Q_salt*dt_therm/rho0). Freshwater/mass coupling is PR-3c+ territory. NOTE: when s_surf < ICE_BULK_SALINITY the brine flux goes NEGATIVE (freezing freshens the ocean locally) — this is SIS2-faithful and deliberately not clamped.

Energy accounting: Q_heat is NOT written by this module. The frazil latent heat was already credited to the ocean by the PR-1 surface clamp (which warmed the surface to T_f when it banked the deficit); spending the bank here as ice latent heat is the closing half of that exchange — writing a Q_heat here would double-count. Melt-side Q_heat/Q_salt arrive with PR 3c.

Mirrors the outer-shim + flat-impl + !$acc routine seq column-worker structure of rdb_ice_frazil / rdb_ice_column%ice_thermo_columns.


Uses

  • module~~rdb_ice_frazil_uptake~~UsesGraph module~rdb_ice_frazil_uptake rdb_ice_frazil_uptake module~rdb_constants rdb_constants module~rdb_ice_frazil_uptake->module~rdb_constants module~rdb_eos rdb_eos module~rdb_ice_frazil_uptake->module~rdb_eos module~rdb_grid rdb_grid module~rdb_ice_frazil_uptake->module~rdb_grid module~rdb_ice_column rdb_ice_column module~rdb_ice_frazil_uptake->module~rdb_ice_column module~rdb_ice_enthalpy rdb_ice_enthalpy module~rdb_ice_frazil_uptake->module~rdb_ice_enthalpy module~rdb_ice_mass rdb_ice_mass module~rdb_ice_frazil_uptake->module~rdb_ice_mass module~rdb_ice_state rdb_ice_state module~rdb_ice_frazil_uptake->module~rdb_ice_state module~rdb_multilayer_state rdb_multilayer_state module~rdb_ice_frazil_uptake->module~rdb_multilayer_state pic_types pic_types module~rdb_constants->pic_types module~rdb_eos->module~rdb_constants module~rdb_eos->module~rdb_grid module~rdb_grid->module~rdb_constants module~rdb_ice_column->module~rdb_constants module~rdb_ice_column->module~rdb_ice_enthalpy module~rdb_ice_column->module~rdb_ice_mass module~rdb_ice_optics rdb_ice_optics module~rdb_ice_column->module~rdb_ice_optics module~rdb_ice_enthalpy->module~rdb_constants module~rdb_ice_mass->module~rdb_constants module~rdb_ice_mass->module~rdb_ice_enthalpy module~rdb_ice_state->module~rdb_constants module~rdb_ice_state->module~rdb_grid module~rdb_ice_state->module~rdb_ice_column module~rdb_ice_state->module~rdb_ice_enthalpy iso_fortran_env iso_fortran_env module~rdb_ice_state->iso_fortran_env module~rdb_mem_report rdb_mem_report module~rdb_ice_state->module~rdb_mem_report module~rdb_multilayer_state->module~rdb_constants module~rdb_multilayer_state->module~rdb_grid module~rdb_multilayer_state->iso_fortran_env module~rdb_efp rdb_efp module~rdb_multilayer_state->module~rdb_efp module~rdb_error_ring rdb_error_ring module~rdb_multilayer_state->module~rdb_error_ring module~rdb_multilayer_state->module~rdb_mem_report module~rdb_tracer rdb_tracer module~rdb_multilayer_state->module~rdb_tracer pic_logger pic_logger module~rdb_multilayer_state->pic_logger module~rdb_efp->iso_fortran_env ieee_arithmetic ieee_arithmetic module~rdb_efp->ieee_arithmetic module~rdb_error_ring->pic_logger module~rdb_ice_optics->module~rdb_constants module~rdb_ice_optics->module~rdb_ice_enthalpy module~rdb_mem_report->module~rdb_constants module~rdb_mem_report->iso_fortran_env module~rdb_mem_report->pic_logger pic_strings pic_strings module~rdb_mem_report->pic_strings module~rdb_tracer->module~rdb_constants module~rdb_tracer->module~rdb_grid module~rdb_tracer->iso_fortran_env module~rdb_tracer->module~rdb_mem_report

Used by

  • module~~rdb_ice_frazil_uptake~~UsedByGraph module~rdb_ice_frazil_uptake rdb_ice_frazil_uptake module~rdb_ocean_engine rdb_ocean_engine module~rdb_ocean_engine->module~rdb_ice_frazil_uptake module~rdb_driver rdb_driver module~rdb_driver->module~rdb_ocean_engine module~rdb_handle rdb_handle module~rdb_handle->module~rdb_ocean_engine module~rdb_ocean_api rdb_ocean_api module~rdb_ocean_api->module~rdb_ocean_engine module~rdb_ocean_api->module~rdb_handle

Variables

Type Visibility Attributes Name Initial
real(kind=wp), public, parameter :: ICE_FRAZIL_T_OFFSET = 0.5_wp

SIS2 frazil_temp_offset default (degC) — SIS2_ice_thm.F90:130. The per-layer frazil crystal forms ICE_FRAZIL_T_OFFSET degC BELOW the local layer freezing point, matching the observed slight supercooling of newly nucleated frazil ice.


Subroutines

public pure subroutine ice_frazil_uptake(grid, eos, ms, ice, dt_therm)

Outer shim (outer-shim + flat-impl pattern): dereference the tracer registry (ms%tracers(idx)%hTr) on the HOST and forward bare arrays to the device kernel — NVHPC stdpar cannot follow the array-of-derived-types indirection inside a do-concurrent body (same rule as ice_frazil_accumulate). No-op when either S or T is unregistered.

Read more…

Arguments

Type IntentOptional Attributes Name
type(hgrid_t), intent(in) :: grid
type(eos_t), intent(in) :: eos
type(multilayer_state_t), intent(in) :: ms

ocean-side effect of the uptake (brine rejection) arrives only via Q_salt on the NEXT thermo window, through ice_ocean_brine_flux + ocean_surface_flux_apply_tracers.

type(ocean_sea_ice_t), intent(inout) :: ice
real(kind=wp), intent(in) :: dt_therm

Effective thermo timestep (s) — ocean_dyn_t%therm_dt(dt).

private pure subroutine ice_frazil_uptake_column(nk, frazil, tfw, sst, s_surf, m_snow, m_ice_tot, enth_ice_bu, sal_ice_bu, m_frozen, salt_to_ice)

Per-(cell,category-1) column worker: gather + flip (bottom-up -> top-down, TRAP #2 discipline), spend the whole frazil bank evenly over the nk layers (SIS2:1402), rebalance, scatter + flip back. Port of add_frazil_SIS2 (SIS2_ice_thm.F90:1342-1444), bulk-salinity mode (salin_freeze = ICE_BULK_SALINITY, SIS_slow_thermo.F90: 1207-1209) — the ice_rel_salin mode is NOT ported.

Read more…

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nk

Number of ice layers (declared first — decl-order).

real(kind=wp), intent(in) :: frazil

Whole per-cell frazil bank to spend this call (J/m² of cell).

real(kind=wp), intent(in) :: tfw

Seawater freezing temperature at the surface (degC).

real(kind=wp), intent(in) :: sst

Sea-surface temperature (degC) — feeds the TRAP-#1 liquid ocean enthalpy.

real(kind=wp), intent(in) :: s_surf

Sea-surface salinity (PSU) — feeds the TRAP-#1 liquid ocean enthalpy (unused by the linear formula, kept for call-site parity with ice_enthalpy_liquid).

real(kind=wp), intent(in) :: m_snow

Snow mass per unit CELL area (kg/m²) — untouched, carried through only to seed the local column’s slot 0.

real(kind=wp), intent(inout) :: m_ice_tot

Total category-1 ice mass per unit CELL area (kg/m²); in = prior step, out = post-freeze.

real(kind=wp), intent(inout) :: enth_ice_bu(nk)

BOTTOM-UP ice specific enthalpies (J/kg) — state order, enth_ice_bu(1) = ice bottom.

real(kind=wp), intent(inout) :: sal_ice_bu(nk)

BOTTOM-UP ice bulk salinities (PSU) — state order.

real(kind=wp), intent(out) :: m_frozen

Total new-ice mass formed this call (kg/m² of cell).

real(kind=wp), intent(out) :: salt_to_ice

Salt content retained by the new ice (kg/m² of cell) — m_frozen * ICE_BULK_SALINITY.

private pure subroutine ice_frazil_uptake_impl(hTr_T, hTr_S, h_layer, wet_mask, eos, frazil_heat, m_ice, m_snow, enth_ice, sal_ice, m_frozen_diag, salt_flux_diag, dt_therm, nghost, ncat, nk, nz, nx, ny)

Device kernel over PHYSICAL cells (ghosts excluded — same physical-cells-only contract as ice_frazil_accumulate_impl and ice_thermo_columns). Per wet, non-vanished, banked cell: sample SST/SSS at k = nz, compute the seawater freezing point, spend the WHOLE bank on category-1’s column via ice_frazil_uptake_column, and reset frazil_heat to 0 (fully spent). The per-cell diags (m_frozen_diag, salt_flux_diag) are zeroed UNCONDITIONALLY first, then overwritten under the gate — an unbanked/dry/land/vanished cell reports zero, not a stale value from a prior window.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: hTr_T(nx,ny,nz)
real(kind=wp), intent(in) :: hTr_S(nx,ny,nz)
real(kind=wp), intent(in) :: h_layer(nx,ny,nz)
real(kind=wp), intent(in) :: wet_mask(nx,ny)
type(eos_t), intent(in) :: eos
real(kind=wp), intent(inout) :: frazil_heat(nx,ny)
real(kind=wp), intent(inout) :: m_ice(nx,ny,ncat)
real(kind=wp), intent(inout) :: m_snow(nx,ny,ncat)
real(kind=wp), intent(inout) :: enth_ice(nx,ny,ncat,nk)
real(kind=wp), intent(inout) :: sal_ice(nx,ny,ncat,nk)
real(kind=wp), intent(inout) :: m_frozen_diag(nx,ny)
real(kind=wp), intent(inout) :: salt_flux_diag(nx,ny)
real(kind=wp), intent(in) :: dt_therm
integer, intent(in) :: nghost
integer, intent(in) :: ncat
integer, intent(in) :: nk
integer, intent(in) :: nz
integer, intent(in) :: nx
integer, intent(in) :: ny

private pure subroutine ice_frazil_uptake_multicat_impl(hTr_T, hTr_S, h_layer, wet_mask, eos, frazil_heat, part_size, m_ice, m_snow, enth_ice, sal_ice, m_frozen_diag, salt_flux_diag, dt_therm, nghost, ncat, nk, nz, nx, ny)

ncat>1 SIS2 ITD-mode frazil spend. Port of SIS2 SIS_slow_thermo.F90:1121-1146 + 1181-1186 (non-filling mode — SIS2’s default SIS2_FILLING_FRAZIL=.true. thin-category fill is DEFERRED, see module docstring). Per banked cell (same wet/non-vanished/bank>0 gate as ice_frazil_uptake_impl): 1. k_merge scan (SIS2:1124-1129): first category c with part(0) + part(c) > 0.01; falls back to k_merge = 1 if no category qualifies (SIS2’s k_merge default). 2. Open-water annexation (SIS2:1131-1145): if part(0) > 0, dilute category k_merge’s thickness at CONSTANT MASS — m_ice(k_merge)/m_snow(k_merge) scale by part(k_merge)/(part(k_merge)+part(0)), part(k_merge) absorbs all of part(0), part(0) -> 0. enth/sal are per-MASS intensive — untouched by an area-only dilution. 3. Per-ice-area spend (SIS2:1181-1186): frazil_col = frazil_heat/part(k_merge) (J per m² of category area; the denominator is > 0 by construction — either an occupied category was found, or step 2 just grew part(k_merge) from the Σpart=1 invariant), then the SAME UNCHANGED ice_frazil_uptake_column spends it on category k_merge’s column. 4. Diags (per cell, part-weighted back to CELL-area units to match the ncat==1 diag convention that the couplers and rdb_ice_thermo_driver consume): m_frozen_diag = part(k_merge)*m_frozen_pt; salt_flux_diag = part(k_merge)*(m_frozen_pt*s_surf - salt_to_ice_pt)/dt_therm; frazil_heat reset to 0 (fully spent). Both diags are zeroed UNCONDITIONALLY at loop top, same contract as ice_frazil_uptake_impl.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: hTr_T(nx,ny,nz)
real(kind=wp), intent(in) :: hTr_S(nx,ny,nz)
real(kind=wp), intent(in) :: h_layer(nx,ny,nz)
real(kind=wp), intent(in) :: wet_mask(nx,ny)
type(eos_t), intent(in) :: eos
real(kind=wp), intent(inout) :: frazil_heat(nx,ny)
real(kind=wp), intent(inout) :: part_size(nx,ny,0:ncat)
real(kind=wp), intent(inout) :: m_ice(nx,ny,ncat)
real(kind=wp), intent(inout) :: m_snow(nx,ny,ncat)
real(kind=wp), intent(inout) :: enth_ice(nx,ny,ncat,nk)
real(kind=wp), intent(inout) :: sal_ice(nx,ny,ncat,nk)
real(kind=wp), intent(inout) :: m_frozen_diag(nx,ny)
real(kind=wp), intent(inout) :: salt_flux_diag(nx,ny)
real(kind=wp), intent(in) :: dt_therm
integer, intent(in) :: nghost
integer, intent(in) :: ncat
integer, intent(in) :: nk
integer, intent(in) :: nz
integer, intent(in) :: nx
integer, intent(in) :: ny