Sea-ice state slot. All fields default to the inert
(enable=.false.) configuration so an ocean run that never sets
&ocean_ice_nml is byte-identical to a build without this slot.
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | public, | allocatable | :: | atm_dsfdt(:,:) |
SEB slope dSF/dT (W/m^2/K) — |
||
| real(kind=wp), | public, | allocatable | :: | atm_fprec(:,:) |
PR 26: frozen-precipitation rate onto the ice top (kg/m^2/s),
|
||
| real(kind=wp), | public, | allocatable | :: | atm_sf0(:,:) |
Net upward surface flux at T_surf=0 (W/m^2), the SEB intercept
|
||
| real(kind=wp), | public, | allocatable | :: | atm_sw_dn(:,:) |
Downwelling shortwave into the ice top (W/m^2) — |
||
| logical, | public | :: | dynamics | = | .false. |
EVP rheology master switch ( |
|
| logical, | public | :: | enable | = | .false. |
Master switch ( |
|
| real(kind=wp), | public, | allocatable | :: | enth_ice(:,:,:,:) |
Ice specific enthalpy (J/kg), shape (nx_total, ny_total,
ncat, nk_ice). BOTTOM-UP: k=1 = ice BOTTOM (ocean side),
k=nk_ice = ice TOP (atm/snow side) — the opposite of the
SIS2/ |
||
| real(kind=wp), | public, | allocatable | :: | enth_snow(:,:,:,:) |
Snow specific enthalpy (J/kg), shape (nx_total, ny_total, ncat, 1). |
||
| type(evp_workspace_t), | public | :: | evp_ws |
Persistent EVP-dynamics scratch ( |
|||
| real(kind=wp), | public, | allocatable | :: | fb(:,:) |
Ocean -> ice-base heat flux (W/m^2), filled by
|
||
| real(kind=wp), | public, | allocatable | :: | fb_part_sum(:,:) |
Pre-column fb-charged ice-cover fraction snapshot (shape
(nx_total, ny_total)) — |
||
| real(kind=wp), | public, | allocatable | :: | fprec_ocn_diag(:,:) |
PR 26: frozen precipitation delivered directly to the ocean
(kg/m^2/s, per unit CELL area, >= 0) — the share of
|
||
| real(kind=wp), | public, | allocatable | :: | frazil_heat(:,:) |
Ocean-side frazil supercooling BANK (J/m², T-cells, shape
(nx_total, ny_total)). |
||
| real(kind=wp), | public, | allocatable | :: | fxoc(:,:) |
Subcycle-averaged ice->ocean stress [Pa], u-faces, shape
(nx_total+1, ny_total). SIS2 |
||
| real(kind=wp), | public, | allocatable | :: | fyoc(:,:) |
Ditto, v-faces, shape (nx_total, ny_total+1). SIS2 |
||
| real(kind=wp), | public, | allocatable | :: | h2o_ice_to_ocn(:,:,:) |
Meltwater mass to the ocean (kg/m²), shape (nx_total, ny_total, ncat), >= 0. SCRATCH. |
||
| real(kind=wp), | public, | allocatable | :: | h2o_ocn_to_ice(:,:,:) |
Mass FROZEN from the ocean onto the ice base (kg/m²), shape (nx_total, ny_total, ncat), >= 0. SCRATCH. |
||
| real(kind=wp), | public, | allocatable | :: | h_lim(:) |
Category lower thickness limits (m), shape (1:ncat+1) — SIS2
|
||
| logical, | public | :: | has_snowfall | = | .false. |
Host-side dispatch gate for the PR 26 snowfall source term,
latched from |
|
| real(kind=wp), | public, | allocatable | :: | heat_budget_frazil(:,:,:) |
hTr (K·m) change per cell per outer step attributed to the
frazil clamp — the |
||
| real(kind=wp), | public, | allocatable | :: | heat_flux_diag(:,:) |
Ice -> ocean net surface heat flux from the last thermo window
(W/m^2, POSITIVE DOWN into the ocean — the Q_heat convention),
shape (nx_total, ny_total). Q_heat = heat_to_ocn/dt_therm - fb
(see |
||
| real(kind=wp), | public, | allocatable | :: | heat_to_ocn(:,:,:) |
Leftover melt energy drained back to the ocean (J/m²), shape (nx_total, ny_total, ncat), >= 0. SCRATCH. |
||
| real(kind=wp), | public, | allocatable | :: | hl_x_work(:,:) |
PPM face-edge reconstruction of the category-SUMMED zonal mass,
FACE-shaped |
||
| real(kind=wp), | public, | allocatable | :: | hl_y_work(:,:) |
North-face PPM edge, |
||
| real(kind=wp), | public, | allocatable | :: | hlim_cfg(:) |
hlim |
||
| real(kind=wp), | public, | allocatable | :: | hr_x_work(:,:) |
Right-cell zonal face-edge companion to |
||
| real(kind=wp), | public, | allocatable | :: | hr_y_work(:,:) |
North-face right-cell PPM edge, |
||
| real(kind=wp), | public, | allocatable | :: | htot_work(:,:) |
Category-SUMMED mass, 2-D scratch (kg/m² of CELL area), shape
(nx_total, ny_total). Reused for the ice solve then the snow
solve, per direction (SIS2 |
||
| logical, | public | :: | is_init | = | .false. |
True between |
|
| real(kind=wp), | public, | allocatable | :: | m_frozen_diag(:,:) |
New frazil-ice mass formed in the LAST thermo window (kg per
m² of CELL area — v1 category convention, see
|
||
| real(kind=wp), | public, | allocatable | :: | m_ice(:,:,:) |
Total ice mass per category (kg/m²), shape (nx_total,
ny_total, ncat). TWO-MODE CONVENTION: |
||
| real(kind=wp), | public, | allocatable | :: | m_melt_diag(:,:) |
Net meltwater mass to the ocean in the last window (kg/m^2 of cell, = h2o_ice_to_ocn - h2o_ocn_to_ice summed over cats; POSITIVE = net melt). OVERWRITTEN each window. Restart-carried for post-resume diagnostic continuity (like m_frozen_diag). |
||
| real(kind=wp), | public, | allocatable | :: | m_snow(:,:,:) |
Snow mass per category (kg/m²), shape (nx_total, ny_total,
ncat). Same two-mode area convention as |
||
| real(kind=wp), | public, | allocatable | :: | mca_ice(:,:,:) |
Cell-averaged ice mass per category (CAS space, kg/m² of CELL
area — SIS2 |
||
| real(kind=wp), | public, | allocatable | :: | mca_snow(:,:,:) |
Cell-averaged snow mass per category (CAS space, kg/m² of CELL
area), shape (nx_total, ny_total, ncat). Same lifecycle as
|
||
| real(kind=wp), | public, | allocatable | :: | mh_lim(:) |
Category lower MASS limits (kg/m²), shape (1:ncat+1),
|
||
| integer, | public | :: | ncat | = | 5 |
Number of ice thickness categories ( |
|
| integer, | public | :: | nk_ice | = | 2 |
Vertical ice layers per category ( |
|
| integer, | public | :: | nx_total | = | 0 | ||
| integer, | public | :: | ny_total | = | 0 | ||
| real(kind=wp), | public, | allocatable | :: | part_size(:,:,:) |
Fractional ice-category area, shape (nx_total, ny_total,
0:ncat). Category 0 = OPEN WATER; Σ_cat part_size = 1.
TWO-MODE CONVENTION (module docstring): |
||
| real(kind=wp), | public, | allocatable | :: | sal_ice(:,:,:,:) |
Ice bulk salinity (PSU), shape (nx_total, ny_total, ncat,
nk_ice), same BOTTOM-UP k convention as |
||
| real(kind=wp), | public, | allocatable | :: | salt_flux_diag(:,:) |
Ice -> ocean virtual salt flux from the last uptake
(PSU·kg/m²/s, POSITIVE SALINIFIES — the surface-flux Q_salt
convention), shape (nx_total, ny_total). PERSISTENT +
restart-carried: |
||
| logical, | public | :: | snow_ice | = | .false. |
Archimedes freeboard snow-ice flood master switch
( |
|
| real(kind=wp), | public, | allocatable | :: | snow_part_ocn(:,:) |
PR 26: pre-column ice-free-cover fraction snapshot (shape
(nx_total, ny_total)), |
||
| real(kind=wp), | public, | allocatable | :: | snow_to_ice(:,:,:) |
PR 27: Archimedes freeboard snow-ice conversion this thermo
window (kg/m² per category), shape (nx_total, ny_total,
ncat), >= 0 — SIS2 |
||
| real(kind=wp), | public, | allocatable | :: | sst_seam(:,:) |
Sampled sea-surface temperature (degC) at the one-step-lagged
surface, filled by |
||
| real(kind=wp), | public, | allocatable | :: | ssurf_seam(:,:) |
Sampled sea-surface salinity (PSU), same sharing contract as
|
||
| real(kind=wp), | public, | allocatable | :: | str_d(:,:) |
Divergence stress tensor component [Pa*m], T-cells, shape
(nx_total, ny_total). SIS2 |
||
| real(kind=wp), | public, | allocatable | :: | str_s(:,:) |
Shearing stress tensor component (cross term) [Pa*m],
corners, shape (nx_total+1, ny_total+1). SIS2 |
||
| real(kind=wp), | public, | allocatable | :: | str_t(:,:) |
Tension stress tensor component [Pa*m], T-cells, shape
(nx_total, ny_total). SIS2 |
||
| real(kind=wp), | public, | allocatable | :: | sw_thru(:,:,:) |
Shortwave transmitted through the ice to the ocean (W/m²),
shape (nx_total, ny_total, ncat), >= 0. Per-category column
output; PR 31 reduces it to the per-cell |
||
| real(kind=wp), | public, | allocatable | :: | sw_thru_diag(:,:) |
PR 31: ice -> ocean shortwave transmitted through the ice to
the water below, reduced to per unit CELL area from the
per-category |
||
| real(kind=wp), | public, | allocatable | :: | tau_a_x(:,:) |
Atmospheric (wind) stress felt by the ice, snapshotted onto u-faces [Pa], shape (nx_total+1, ny_total). Configure-time copy of the ocean’s wind-stress field (D7: one atmospheric stress field, no ice-specific bulk drag law) — NOT restart- carried (rebuilt at configure from the wind-stress config on every run, resumed or fresh). |
||
| real(kind=wp), | public, | allocatable | :: | tau_a_y(:,:) |
Ditto, v-faces, shape (nx_total, ny_total+1). |
||
| real(kind=wp), | public | :: | tau_ocn_valid | = | 0.0_wp |
HOST-ONLY (PR 63). Exactly 0.0 or 1.0. |
|
| real(kind=wp), | public, | allocatable | :: | tau_ocn_x(:,:) |
PR 63. Mirror of the exact value |
||
| real(kind=wp), | public, | allocatable | :: | tau_ocn_y(:,:) |
Ditto, v-faces, shape (nx_total, ny_total+1). SIS2
|
||
| real(kind=wp), | public, | allocatable | :: | tfw_seam(:,:) |
Sampled seawater freezing temperature at the surface (degC),
same sharing contract as |
||
| real(kind=wp), | public, | allocatable | :: | tr_flux_x_work(:,:,:) |
Per-category zonal DONOR-VALUE buffer ( |
||
| real(kind=wp), | public, | allocatable | :: | tr_flux_y_work(:,:,:) |
Meridional twin of |
||
| logical, | public | :: | transport | = | .false. |
Horizontal category transport switch ( |
|
| real(kind=wp), | public, | allocatable | :: | tsurf_out(:,:,:) |
Column skin temperature (degC), shape (nx_total, ny_total, ncat) — diagnostic, recomputed every thermo step. SCRATCH. |
||
| real(kind=wp), | public, | allocatable | :: | u_ice(:,:) |
Ice velocity at C-grid u-faces (m/s), shape (nx_total+1,
ny_total). v1 INTERIM: sampled from the ocean surface layer
every transport call ( |
||
| real(kind=wp), | public, | allocatable | :: | uh_ice(:,:,:) |
Per-category zonal ice-mass face transport (kg/s), shape (nx_total+1, ny_total, ncat). SCRATCH, recomputed every pass. |
||
| real(kind=wp), | public, | allocatable | :: | uh_snow(:,:,:) |
Per-category zonal snow-mass face transport (kg/s), shape (nx_total+1, ny_total, ncat). SCRATCH. |
||
| real(kind=wp), | public, | allocatable | :: | uhtot_work(:,:) |
Category-SUMMED zonal face transport (kg/s), shape (nx_total+1, ny_total). SCRATCH, reused per medium. |
||
| real(kind=wp), | public, | allocatable | :: | v_ice(:,:) |
Ice velocity at C-grid v-faces (m/s), shape (nx_total,
ny_total+1). Same interim/persistent contract as |
||
| real(kind=wp), | public, | allocatable | :: | vh_ice(:,:,:) |
Per-category meridional ice-mass face transport (kg/s), shape (nx_total, ny_total+1, ncat). SCRATCH. |
||
| real(kind=wp), | public, | allocatable | :: | vh_snow(:,:,:) |
Per-category meridional snow-mass face transport (kg/s), shape (nx_total, ny_total+1, ncat). SCRATCH. |
||
| real(kind=wp), | public, | allocatable | :: | vhtot_work(:,:) |
Category-SUMMED meridional face transport (kg/s), shape (nx_total, ny_total+1). SCRATCH, reused per medium. |
Counted allocatable footprint of the sea-ice slot (one
arr_bytes term per array, mirroring ocean_meke_bytes).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_sea_ice_t), | intent(in) | :: | this |
Reverse of init. Clears is_init first (use-after-destroy
guard), then releases allocations.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_sea_ice_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_sea_ice_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_sea_ice_t), | intent(inout) | :: | this |
Cache the grid extents, allocate the frazil (PR 1) + Winton
column (PR 3a) prognostics, and mark the slot live. Allocation
is gated on enable at the parent call site (memory Rule 2),
so an ice-off run never carries these arrays.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_sea_ice_t), | intent(inout) | :: | this | |||
| type(hgrid_t), | intent(in) | :: | grid |
type :: ocean_sea_ice_t !! Sea-ice state slot. All fields default to the inert !! (`enable=.false.`) configuration so an ocean run that never sets !! `&ocean_ice_nml` is byte-identical to a build without this slot. logical :: is_init = .false. !! True between `init` and `destroy`; gate on this (never on !! `allocated`, which misses the GPU mapping). logical :: enable = .false. !! Master switch (`&ocean_ice_nml enable`). Off (default) ⇒ the !! parent state never inits / maps / steps this slot ⇒ no-op. integer :: ncat = 5 !! Number of ice thickness categories (`&ocean_ice_nml ncat`). !! Category 0 is open water; SIS2 default 5. Sizes the !! per-category arrays when PR 3+ adds them. integer :: nk_ice = 2 !! Vertical ice layers per category (`&ocean_ice_nml nk_ice`). !! 2 = Winton two-layer (v1). Bottom-up once arrays land !! (k=1 ice bottom — SIS2's top-down index is flipped at this !! state layer, per the plan). logical :: transport = .false. !! Horizontal category transport switch (`&ocean_ice_nml !! transport`), set from config BEFORE `init` (same convention as !! `enable`/`ncat`). Gates allocation of the transport-only !! workspace below (memory: the per-category face fluxes are !! ~4·ncat·N reals — do not pay it by default). `u_ice`/`v_ice` !! are unconditional (allocated whenever the slot is live) since !! PR 5's EVP dynamics will need them regardless of whether !! category transport is on. logical :: dynamics = .false. !! EVP rheology master switch (`&ocean_ice_nml dynamics`), set !! from config BEFORE `init` (same convention as `enable`/ !! `transport`). Gates the driver's `ice_evp_step` call and the !! transport sampler skip; does NOT gate any allocation here — !! `str_d`/`str_t`/`str_s`/`tau_a_*`/`fxoc`/`fyoc`/`tau_ocn_*` !! are unconditional (cheap 2-D arrays), same contract as !! `u_ice`/`v_ice`. logical :: has_snowfall = .false. !! Host-side dispatch gate for the PR 26 snowfall source term, !! latched from `(cfg%ocean%ice%snowfall /= 0.0_wp)` BEFORE `init` !! (same convention as `transport`/`dynamics`). Gates the !! pre-column `snow_part_ocn` fill and the `ice_snowfall_ocean_ !! share` contributor call; does NOT gate allocation of `atm_fprec`/ !! `snow_part_ocn`/`fprec_ocn_diag` (cheap 2-D arrays, same !! contract as `atm_sw_dn`/`fb_part_sum`) — the `has_sw`/`has_heat` !! idiom that makes `snowfall=0` byte-identical by construction, !! not by IEEE argument. real(wp) :: tau_ocn_valid = 0.0_wp !! HOST-ONLY (PR 63). Exactly 0.0 or 1.0. `1.0` iff !! `tau_ocn_x`/`tau_ocn_y` hold a blend written by this run's !! `ice_ocean_stress_flux` or restored from a checkpoint that !! carried one; `0.0` on a fresh run or a resume from a !! pre-PR-63 checkpoint. Set by `ice_ocean_stress_flux`, carried !! by `register_scalar` (which forces `device_mapped=.false.`), !! read only by `ice_ocean_stress_resume_apply`. NEVER !! device-mapped and NEVER read in a kernel — the SIS2 !! `query_initialized('stress_mag')` idiom !! (`ice_model.F90:2410`), one host scalar instead of a !! per-field registry query. logical :: snow_ice = .false. !! Archimedes freeboard snow-ice flood master switch !! (`&ocean_ice_nml snow_ice`, PR 27), latched from config !! BEFORE `init` (same convention as `transport`/`dynamics`). !! Read by `ice_thermo_driver_step` and passed to !! `ice_thermo_columns` as `do_snow_ice`; does NOT gate !! allocation of `snow_to_ice` (cheap per-category array, same !! contract as `sw_thru`) — `.false.` (default) is what makes !! `snow_ice=.false.` byte-identical by construction. real(wp), allocatable :: u_ice(:, :) !! Ice velocity at C-grid u-faces (m/s), shape (nx_total+1, !! ny_total). v1 INTERIM: sampled from the ocean surface layer !! every transport call (`ice_transport_step` Phase 0); PR 5's !! EVP dynamics will write this field instead of the sampler, same !! faces. Persistent (unconditional allocation whenever the ice !! slot is live). real(wp), allocatable :: v_ice(:, :) !! Ice velocity at C-grid v-faces (m/s), shape (nx_total, !! ny_total+1). Same interim/persistent contract as `u_ice`. ! ---- PR 5: C-grid EVP ice dynamics ---- real(wp), allocatable :: str_d(:, :) !! Divergence stress tensor component [Pa*m], T-cells, shape !! (nx_total, ny_total). SIS2 `CS%str_d`. Persistent, restart- !! carried (optional). Relaxes to `-0.5*pres_mice*mice` at zero !! strain rate (the elliptical yield curve's pressure intercept — !! ice-free cells have `mice=0` so this is exactly 0 there, an !! emergent property, never a Dirichlet ice-edge special case). real(wp), allocatable :: str_t(:, :) !! Tension stress tensor component [Pa*m], T-cells, shape !! (nx_total, ny_total). SIS2 `CS%str_t`. Persistent, restart- !! carried (optional). real(wp), allocatable :: str_s(:, :) !! Shearing stress tensor component (cross term) [Pa*m], !! corners, shape (nx_total+1, ny_total+1). SIS2 `CS%str_s`. !! Persistent, restart-carried (optional). real(wp), allocatable :: tau_a_x(:, :) !! Atmospheric (wind) stress felt by the ice, snapshotted onto !! u-faces [Pa], shape (nx_total+1, ny_total). Configure-time !! copy of the ocean's wind-stress field (D7: one atmospheric !! stress field, no ice-specific bulk drag law) — NOT restart- !! carried (rebuilt at configure from the wind-stress config on !! every run, resumed or fresh). real(wp), allocatable :: tau_a_y(:, :) !! Ditto, v-faces, shape (nx_total, ny_total+1). real(wp), allocatable :: fxoc(:, :) !! Subcycle-averaged ice->ocean stress [Pa], u-faces, shape !! (nx_total+1, ny_total). SIS2 `fxoc`. Persistent, restart- !! carried (optional): `ice_ocean_stress_flux` blends this into !! `ocean_surface_stress_t%tau_x` every outer step. PR 63: !! this registration is no longer what makes the resume !! bit-exact — `tau_ocn_x`/`tau_ocn_y` below carry the blend's !! OUTPUT directly, so the resume path never needs to !! reconstruct it from `fxoc` + the checkpointed `ci`. Kept !! restart-carried for diagnostic continuity across a resume !! (like `m_frozen_diag`/`m_melt_diag`) — its only cross-step !! reader inside a call is `ice_evp_dynamics`'s own zeroing at !! entry, so it carries no state INTO the EVP solve either. real(wp), allocatable :: fyoc(:, :) !! Ditto, v-faces, shape (nx_total, ny_total+1). SIS2 `fyoc`. real(wp), allocatable :: tau_ocn_x(:, :) !! PR 63. Mirror of the exact value `ice_ocean_stress_flux` last !! wrote into `ocean_surface_stress_t%tau_x` [Pa], u-faces, shape !! (nx_total+1, ny_total). PERSISTENT, restart-carried !! (optional). Formula-agnostic by construction — it is a COPY !! of the blend's output, never a recomputation, so it stays !! exact under any future change to the blend formula (SIS2 !! `Ice%flux_u`, `ice_type.F90:242`). Valid to read on the host !! only after `!$acc update self` of the COMPONENT array (never !! the aggregate `ice` — commit `72152870`). See !! `ice_ocean_stress_resume_apply` (`rdb_ice_ocean_coupler`). real(wp), allocatable :: tau_ocn_y(:, :) !! Ditto, v-faces, shape (nx_total, ny_total+1). SIS2 !! `Ice%flux_v`. type(evp_workspace_t) :: evp_ws !! Persistent EVP-dynamics scratch (`rdb_ice_evp`'s subcycle !! kernels). Allocated + GPU-mapped ONLY when `dynamics` is on !! (see `evp_workspace_t`) — the successor to the retired !! `rdb_ice_evp` module-level `save` workspace. Rides !! `ocean_state_enter_data` via this slot's `enter_data`. ! ---- PR 4b: category transport workspace (allocated iff `transport`) ---- real(wp), allocatable :: mca_ice(:, :, :) !! Cell-averaged ice mass per category (CAS space, kg/m² of CELL !! area — SIS2 `CAS%m_ice`), shape (nx_total, ny_total, ncat). !! SCRATCH: filled at Phase 1 (IST->CAS), consumed/updated through !! Phase 2, discarded after Phase 3 (CAS->IST). real(wp), allocatable :: mca_snow(:, :, :) !! Cell-averaged snow mass per category (CAS space, kg/m² of CELL !! area), shape (nx_total, ny_total, ncat). Same lifecycle as !! `mca_ice`. real(wp), allocatable :: uh_ice(:, :, :) !! Per-category zonal ice-mass face transport (kg/s), shape !! (nx_total+1, ny_total, ncat). SCRATCH, recomputed every pass. real(wp), allocatable :: vh_ice(:, :, :) !! Per-category meridional ice-mass face transport (kg/s), shape !! (nx_total, ny_total+1, ncat). SCRATCH. real(wp), allocatable :: uh_snow(:, :, :) !! Per-category zonal snow-mass face transport (kg/s), shape !! (nx_total+1, ny_total, ncat). SCRATCH. real(wp), allocatable :: vh_snow(:, :, :) !! Per-category meridional snow-mass face transport (kg/s), shape !! (nx_total, ny_total+1, ncat). SCRATCH. real(wp), allocatable :: htot_work(:, :) !! Category-SUMMED mass, 2-D scratch (kg/m² of CELL area), shape !! (nx_total, ny_total). Reused for the ice solve then the snow !! solve, per direction (SIS2 `zonal_mass_flux`'s `htot`). real(wp), allocatable :: hl_x_work(:, :) !! PPM face-edge reconstruction of the category-SUMMED zonal mass, !! FACE-shaped `(nx_total+1, ny_total)` (mirrors `rdb_continuity`'s !! `h_face_left_x`). `hl_x_work(i,j)` is the value AT east face !! `i` extrapolated from the LEFT cell `(i-1)` (i.e. cell i-1's !! own right/downwind edge); `hr_x_work(i,j)` is AT face `i` from !! the RIGHT cell `i` (cell i's own left edge). FACE shape is !! load-bearing: the array-edge fallback writes index `nx+1` !! (out of bounds for a `(nx,ny)` array — the pre-fix bug). !! SCRATCH, reused per medium. real(wp), allocatable :: hr_x_work(:, :) !! Right-cell zonal face-edge companion to `hl_x_work`, !! `(nx_total+1, ny_total)`. SCRATCH. real(wp), allocatable :: hl_y_work(:, :) !! North-face PPM edge, `(nx_total, ny_total+1)` — the meridional !! twin of `hl_x_work` (`h_face_left_y` convention). SCRATCH. real(wp), allocatable :: hr_y_work(:, :) !! North-face right-cell PPM edge, `(nx_total, ny_total+1)`. !! SCRATCH. real(wp), allocatable :: uhtot_work(:, :) !! Category-SUMMED zonal face transport (kg/s), shape !! (nx_total+1, ny_total). SCRATCH, reused per medium. real(wp), allocatable :: vhtot_work(:, :) !! Category-SUMMED meridional face transport (kg/s), shape !! (nx_total, ny_total+1). SCRATCH, reused per medium. real(wp), allocatable :: tr_flux_x_work(:, :, :) !! Per-category zonal DONOR-VALUE buffer (`val(donor of face !! I)`), shape (nx_total+1, ny_total, ncat). GPU-race-free !! gather/scatter split (mirrors `rdb_continuity`'s !! `Tr_face_left_x` pattern): a first `do concurrent` over FACES !! reads `val` only at each face's OWN donor cell and writes here !! (never the same cell two faces disagree on); the cell-update !! kernel then reads only this face buffer (to recover `val_e`/ !! `val_w` directly — NOT flux-weighted, so no back-division is !! needed) plus its OWN cell's prior mass/value — no kernel ever !! reads a NEIGHBOUR cell's `val` while another iteration writes !! that neighbour. Reused across every riding field (`m_ice`, !! each `enth_ice`/`sal_ice` layer, `enth_snow`) one at a time !! within a pass — SCRATCH. real(wp), allocatable :: tr_flux_y_work(:, :, :) !! Meridional twin of `tr_flux_x_work`, shape (nx_total, !! ny_total+1, ncat). real(wp), allocatable :: frazil_heat(:, :) !! Ocean-side frazil supercooling BANK (J/m², T-cells, shape !! (nx_total, ny_total)). `ice_frazil_accumulate` clamps the !! surface layer at the freezing point and deposits the removed !! heat deficit `ρ·Cp·h·(T_f − T)⁺` here; the ice thermodynamics !! (PR 3) will SPEND it as new-ice formation. Persistent state !! (accumulates across steps, restart-carried) — distinct from !! the drain-and-zero budget accumulator below. real(wp), allocatable :: heat_budget_frazil(:, :, :) !! hTr (K·m) change per cell per outer step attributed to the !! frazil clamp — the `budgets` HEAT contributor mirroring the !! `heat_budget_surface` convention (positive = heat added to !! the ocean; the clamp WARMS the surface layer up to T_f). !! Shape (nx_total, ny_total, 1): the clamp only ever touches !! the surface layer, and the drain integrates over k anyway. !! Drained-and-zeroed by `ocean_budgets_drain_contributors`. ! ---- PR 3a: Winton column prognostics ---- real(wp), allocatable :: part_size(:, :, :) !! Fractional ice-category area, shape (nx_total, ny_total, !! 0:ncat). Category 0 = OPEN WATER; Σ_cat part_size = 1. !! TWO-MODE CONVENTION (module docstring): `ncat==1` — legacy !! lumped mode, deliberately UNTOUCHED by every kernel !! (`part_size(:,:,0)=1` forever, category 1 unused, thermo runs !! per unit CELL area). `ncat>1` — SIS2 ITD mode (PR 4a), LIVE: !! `ice_frazil_uptake_multicat_impl` annexes open water into an !! occupied category, and `ice_adjust_categories` restores the !! per-cell partition each thermo window. real(wp), allocatable :: m_ice(:, :, :) !! Total ice mass per category (kg/m²), shape (nx_total, !! ny_total, ncat). TWO-MODE CONVENTION: `ncat==1` — per unit !! CELL area (legacy). `ncat>1` — per unit ICE-COVERED area !! (SIS2 `mH_ice`, intensive quantity DIVIDED by `part_size`, !! not multiplied). Layer masses are always `m_ice/nk_ice` in !! EITHER mode — the column step always ends with !! `ice_rebalance_layers`, so equal-mass layering is a state !! invariant regardless of the area convention. real(wp), allocatable :: m_snow(:, :, :) !! Snow mass per category (kg/m²), shape (nx_total, ny_total, !! ncat). Same two-mode area convention as `m_ice` (per-CELL at !! ncat==1, per-ICE-area at ncat>1). real(wp), allocatable :: enth_ice(:, :, :, :) !! Ice specific enthalpy (J/kg), shape (nx_total, ny_total, !! ncat, nk_ice). BOTTOM-UP: k=1 = ice BOTTOM (ocean side), !! k=nk_ice = ice TOP (atm/snow side) — the opposite of the !! SIS2/`rdb_ice_column` internal top-down convention; the !! flip happens at the `ice_column_step` gather/scatter !! boundary (`rdb_ice_column`'s module docstring, TRAP #2). real(wp), allocatable :: enth_snow(:, :, :, :) !! Snow specific enthalpy (J/kg), shape (nx_total, ny_total, !! ncat, 1). real(wp), allocatable :: sal_ice(:, :, :, :) !! Ice bulk salinity (PSU), shape (nx_total, ny_total, ncat, !! nk_ice), same BOTTOM-UP k convention as `enth_ice`. ! ---- PR 3b: frazil->ice uptake + brine-rejection coupling ---- real(wp), allocatable :: m_frozen_diag(:, :) !! New frazil-ice mass formed in the LAST thermo window (kg per !! m² of CELL area — v1 category convention, see !! `rdb_ice_frazil_uptake`), shape (nx_total, ny_total). !! OVERWRITTEN (not accumulated) each uptake; zero where no !! freezing occurred. Restart-carried for post-resume !! diagnostic continuity. real(wp), allocatable :: salt_flux_diag(:, :) !! Ice -> ocean virtual salt flux from the last uptake !! (PSU·kg/m²/s, POSITIVE SALINIFIES — the surface-flux Q_salt !! convention), shape (nx_total, ny_total). PERSISTENT + !! restart-carried: `rdb_ice_ocean_coupler` refills `Q_salt` !! from this field each thermo step, and the driver's !! configure-time resume fold re-applies it so the first !! post-resume window sees the same flux the uninterrupted run !! would have. PR 3c: the column driver ADDS its net-melt !! contribution here on top of what the frazil uptake (which !! runs first each window) wrote — see `rdb_ice_thermo_driver`. ! ---- PR 3c: coupleable atmospheric-forcing seam ---- real(wp), allocatable :: atm_sf0(:, :) !! Net upward surface flux at T_surf=0 (W/m^2), the SEB intercept !! `sf_0` fed to ice_thermo_columns. Filled every thermo step by !! ice_atm_forcing_restoring (v1) or the future bulk-flux subsystem. !! SCRATCH (recomputed each step) — NOT restart-carried. real(wp), allocatable :: atm_dsfdt(:, :) !! SEB slope dSF/dT (W/m^2/K) — `dsf_dt`. Same seam contract. real(wp), allocatable :: atm_sw_dn(:, :) !! Downwelling shortwave into the ice top (W/m^2) — `sw_dn`. Same. real(wp), allocatable :: atm_fprec(:, :) !! PR 26: frozen-precipitation rate onto the ice top (kg/m^2/s), !! >= 0 — the seam `ice_atm_forcing_restoring` fills from !! `&ocean_ice_nml snowfall` exactly like `atm_sw_dn` <- `sw_down`. !! Same SCRATCH contract (refilled every thermo step, NOT restart- !! carried). PR 55 fills this from data instead of a uniform !! scalar; the shape/units/lifecycle are fixed by this PR (see !! `rdb_ice_snow`'s module docstring for the binding seam spec). ! ---- PR 3c: ocean -> ice basal flux + the sample seam it shares ! with the column driver ---- real(wp), allocatable :: fb(:, :) !! Ocean -> ice-base heat flux (W/m^2), filled by !! `rdb_ice_basal_flux%ice_compute_basal_flux` from the !! above-freezing SST. SCRATCH (recomputed each step) — NOT !! restart-carried. Same lifecycle as the `atm_*` seam. real(wp), allocatable :: sst_seam(:, :) !! Sampled sea-surface temperature (degC) at the one-step-lagged !! surface, filled by `ice_compute_basal_flux` and reused by !! `rdb_ice_thermo_driver` — one sample, shared by `fb` and the !! column's ocean-side inputs. SCRATCH. real(wp), allocatable :: ssurf_seam(:, :) !! Sampled sea-surface salinity (PSU), same sharing contract as !! `sst_seam`. SCRATCH. real(wp), allocatable :: tfw_seam(:, :) !! Sampled seawater freezing temperature at the surface (degC), !! same sharing contract as `sst_seam`. SCRATCH. ! ---- PR 3c: column per-category scratch outputs ---- real(wp), allocatable :: tsurf_out(:, :, :) !! Column skin temperature (degC), shape (nx_total, ny_total, !! ncat) — diagnostic, recomputed every thermo step. SCRATCH. real(wp), allocatable :: h2o_ocn_to_ice(:, :, :) !! Mass FROZEN from the ocean onto the ice base (kg/m²), shape !! (nx_total, ny_total, ncat), >= 0. SCRATCH. real(wp), allocatable :: h2o_ice_to_ocn(:, :, :) !! Meltwater mass to the ocean (kg/m²), shape (nx_total, !! ny_total, ncat), >= 0. SCRATCH. real(wp), allocatable :: heat_to_ocn(:, :, :) !! Leftover melt energy drained back to the ocean (J/m²), shape !! (nx_total, ny_total, ncat), >= 0. SCRATCH. real(wp), allocatable :: sw_thru(:, :, :) !! Shortwave transmitted through the ice to the ocean (W/m²), !! shape (nx_total, ny_total, ncat), >= 0. Per-category column !! output; PR 31 reduces it to the per-cell `sw_thru_diag` below !! (which IS coupled to the ocean surface-flux slot). SCRATCH !! (recomputed every thermo step, `delete`-mapped). real(wp), allocatable :: snow_to_ice(:, :, :) !! PR 27: Archimedes freeboard snow-ice conversion this thermo !! window (kg/m² per category), shape (nx_total, ny_total, !! ncat), >= 0 — SIS2 `SN2IC`. Filled by `ice_thermo_columns` !! (zeroed unconditionally, written only when `snow_ice` is on !! and the column floods). Same lifecycle as `sw_thru`: filled, !! not yet consumed by any diagnostic/coupling seam !! (a future diagnostics PR wires it in — `sw_thru` precedent). !! NOT restart-carried (window scratch). ! ---- PR 3c: melt-side ocean coupling diags ---- real(wp), allocatable :: heat_flux_diag(:, :) !! Ice -> ocean net surface heat flux from the last thermo window !! (W/m^2, POSITIVE DOWN into the ocean — the Q_heat convention), !! shape (nx_total, ny_total). Q_heat = heat_to_ocn/dt_therm - fb !! (see `rdb_ice_thermo_driver`). PERSISTENT + restart-carried !! (mirrors salt_flux_diag): `rdb_ice_ocean_coupler` refills !! Q_heat from this each thermo step, and the driver resume fold !! re-applies it. This is the NON-shortwave heat share only — the !! penetrating shortwave `sw_thru_diag` below is a SEPARATE field !! (PR 31), never folded in here (that would double-count against !! the `q_sw` component / the components-off Q_heat fold — see !! `ice_ocean_sw_flux` in `rdb_ice_ocean_coupler`). real(wp), allocatable :: sw_thru_diag(:, :) !! PR 31: ice -> ocean shortwave transmitted through the ice to !! the water below, reduced to per unit CELL area from the !! per-category `sw_thru` (W/m^2, >= 0, POSITIVE DOWN), shape !! (nx_total, ny_total). Refilled every thermo step by !! `ice_thermo_driver_reduce{,_multicat}_impl` (area-weighted at !! ncat>1, lumped at ncat==1 — the same convention as !! `heat_flux_diag`), consumed by `ice_ocean_sw_flux` !! (`rdb_ice_ocean_coupler`): with `&ocean_forcing_nml !! enable_components` on it fills the `q_sw` surface-flux !! component (assembler sums it into `Q_heat`); off, it is added !! directly into `Q_heat`. PERSISTENT + restart-carried (mirrors !! `heat_flux_diag`): the coupler rebuilds the ocean SW every !! thermo step from this and the driver's resume fold re-applies !! it, so a resume does not cold-start the ice-SW contribution for !! one thermo window. Distinct from the per-category `sw_thru` !! above (SCRATCH, recomputed, `delete`-mapped). real(wp), allocatable :: m_melt_diag(:, :) !! Net meltwater mass to the ocean in the last window (kg/m^2 of !! cell, = h2o_ice_to_ocn - h2o_ocn_to_ice summed over cats; !! POSITIVE = net melt). OVERWRITTEN each window. Restart-carried !! for post-resume diagnostic continuity (like m_frozen_diag). real(wp), allocatable :: fprec_ocn_diag(:, :) !! PR 26: frozen precipitation delivered directly to the ocean !! (kg/m^2/s, per unit CELL area, >= 0) — the share of !! `atm_fprec` that lands where there is no ice !! (`snow_part_ocn`-weighted; open water at ncat>1, ice-free cells !! at ncat==1). Zero on land and on fully ice-covered cells. !! SCRATCH: zeroed + rewritten every thermo window by !! `ice_snowfall_ocean_share`, NOT restart-carried. Binding seam !! spec (consumed by PR-16 as a `net_massin` source): see !! `rdb_ice_snow`'s module docstring. ! ---- PR 4a: multi-category ITD ---- real(wp), allocatable :: h_lim(:) !! Category lower thickness limits (m), shape (1:ncat+1) — SIS2 !! `cat_thick_lim` (`SIS_state_initialization.F90:45-78`). !! `h_lim(c)` is the lower bound of category `c`; `h_lim(ncat+1)` !! is stored but never used as an upper cap (category `ncat` is !! unbounded above — SIS2 keeps it too). Computed at `init` by !! `ice_itd_category_bounds` from `ncat` and (PR-58) the optional !! `hlim_cfg` override below — NOT !! restart-carried (cheap to recompute, and doing so avoids a !! stale-`ncat` mismatch after a restart that changes `ncat`). real(wp), allocatable :: mh_lim(:) !! Category lower MASS limits (kg/m²), shape (1:ncat+1), !! `= ICE_RHO_ICE*h_lim` — SIS2 `mH_cat_bound` !! (`SIS_state_initialization.F90:75-77`). `ice_adjust_categories` !! compares `m_ice` against THIS array (mass space, like SIS2), !! never against `h_lim` directly. Same non-restart-carried !! lifecycle as `h_lim`. real(wp), allocatable :: hlim_cfg(:) !! PR-58: HOST-ONLY config latch: the user's `&ocean_ice_nml !! hlim` list, trimmed to its supplied length, set by !! `ocean_state_init_from_config` BEFORE `init`. Unallocated => !! the SIS2 default table. NOT device-mapped (no kernel reads !! it — `h_lim`/`mh_lim` are the mapped artefacts, computed at !! `init` from this before `enter_data`) and NOT !! restart-carried (same lifecycle as `h_lim`). real(wp), allocatable :: fb_part_sum(:, :) !! Pre-column fb-charged ice-cover fraction snapshot (shape !! (nx_total, ny_total)) — `Σ_c part_size(c)` over the categories !! that pass the column's OWN entry gate !! (`m_ice(i,j,c) > ICE_RHO_ICE*H_VANISHED`), evaluated !! POST-uptake / PRE-column by `rdb_ice_thermo_driver` so the !! weight matches exactly the categories the column charges `fb` !! to before `ice_thermo_columns` mutates `m_ice`. SCRATCH !! (recomputed every thermo step at ncat>1 only) — zero-init, !! NOT restart-carried, same lifecycle as the `atm_*`/`fb` !! seam fields. real(wp), allocatable :: snow_part_ocn(:, :) !! PR 26: pre-column ice-free-cover fraction snapshot (shape !! (nx_total, ny_total)), `1 - Σ_c part_size(c)` over the !! categories that pass the column's OWN entry gate !! (`m_ice(i,j,c) > ICE_RHO_ICE*H_VANISHED`), evaluated PRE-column !! by `ice_snow_part_ocn_fill_impl` for the same reason as !! `fb_part_sum` — the weight must match the cover snow actually !! caught, before `ice_thermo_columns` mutates `m_ice`. At ncat==1 !! this is 0 or 1 (binary concentration). SCRATCH (recomputed !! every thermo step, only when `has_snowfall`) — zero-init, NOT !! restart-carried, same lifecycle as `fb_part_sum`. ! ---- Cached extents ---- integer :: nx_total = 0 integer :: ny_total = 0 contains procedure, non_overridable :: init => ocean_sea_ice_init procedure, non_overridable :: destroy => ocean_sea_ice_destroy procedure, non_overridable :: enter_data => ocean_sea_ice_enter_data procedure, non_overridable :: exit_data => ocean_sea_ice_exit_data procedure, non_overridable :: bytes => ocean_sea_ice_bytes end type ocean_sea_ice_t