rdb_ice_state Module

Gated ocean_sea_ice_t slot for the SIS2-derived sea-ice model (PLAN_SEA_ICE.md). PR 0 was plumbing only; PR 1 added the first prognostic array — the ocean-side frazil supercooling accumulator frazil_heat (filled by rdb_ice_frazil) plus its heat-budget contributor array. PR 2 added the enthalpy library (rdb_ice_enthalpy). PR 3a lands the Winton column prognostics: part_size, m_ice, m_snow, enth_ice, enth_snow, sal_ice — driven only by the new rdb_ice_column test suite (nothing in the driver calls the column kernels yet). A run with &ocean_ice_nml enable = .false. (the default) never calls into this module beyond the parent gates, so existing configurations stay byte-identical.

PR 3b lands the ocean<->ice coupling seam: rdb_ice_frazil_uptake spends the frazil bank as new category-1 ice (m_frozen_diag) and rdb_ice_ocean_coupler refreshes the surface Q_salt field from the resulting brine-rejection rate (salt_flux_diag) — both new fields below.

PR 3c lands the coupleable atmospheric-forcing seam (atm_sf0/atm_dsfdt/atm_sw_dn, filled by rdb_ice_atm_forcing), the ocean->ice basal-flux field (fb, filled by rdb_ice_basal_flux, which also samples the sst_seam/ssurf_seam/ tfw_seam scratch reused by rdb_ice_thermo_driver), the column’s per-category scratch outputs (tsurf_out/h2o_ocn_to_ice/ h2o_ice_to_ocn/heat_to_ocn/sw_thru), and the melt-side ocean-coupling diags heat_flux_diag/m_melt_diag (parallel to salt_flux_diag/m_frozen_diag) that rdb_ice_ocean_coupler’s ice_ocean_heat_flux and the driver’s resume fold consume.

PR 4a lands the multi-category ITD: h_lim/mh_lim (SIS2 category thickness/mass bounds, ice_itd_category_bounds, THIS module) and the fb_part_sum scratch (the rdb_ice_thermo_driver fb-cover snapshot). Mirrors ocean_meke_t (the default-off, gated- allocation, restart-persistent precedent). PR-58 adds an optional hlim_cfg override of the bin edges (ice_itd_category_bounds’s hlim_vals dummy) — see hlim_cfg below.

PR 4b lands horizontal category transport (rdb_ice_transport): u_ice/v_ice (C-grid face velocities, persistent, allocated whenever the ice slot is live — PR 5’s EVP dynamics will fill them instead of the v1 ocean-surface-velocity sampler) and the transport-only scratch workspace (mca_ice/mca_snow, uh_ice/vh_ice/uh_snow/vh_snow, htot_work/hl_x_work/ hr_x_work/hl_y_work/hr_y_work/uhtot_work/vhtot_work) allocated ONLY when &ocean_ice_nml transport = .true. (the this%transport flag, set from config before init, mirrors enable/ncat). Later rungs: PR 5 C-grid EVP dynamics (replaces the u_ice/v_ice filler).

PR 5 lands C-grid EVP rheology (rdb_ice_evp): dynamics (master flag, set from config before init like transport) gates the driver’s ice_evp_step call (which now WRITES u_ice/v_ice instead of the transport sampler) and turns on the stress fields str_d/str_t (T-cells) + str_s (corners) — the H&D (1997) divergence/tension/shear stress tensor components, persistent + restart-carried. tau_a_x/tau_a_y are a configure-time snapshot of the wind stress felt by the ice (no ice-specific bulk drag law, D7); fxoc/fyoc are the subcycle-averaged ice->ocean stress that ice_ocean_stress_flux (rdb_ice_ocean_coupler) blends into ocean_surface_stress_t%tau_x/tau_y by concentration. All seven fields are allocated UNCONDITIONALLY whenever the ice slot is live (same contract as u_ice/v_ice — cheap 2-D arrays), zero-init.

PR 63 closes the resume hole PR 5 documented (F4): fxoc/fyoc restart-carry the subcycle-averaged drag, but ice_ocean_stress_flux blends it against the PRE-thermo ci of that same step, and the checkpoint only ever sees the POST-thermo ci — so reconstructing the blend at resume from the checkpointed masses gives a different answer whenever thermo/transport changed ci after the blend. tau_ocn_x/tau_ocn_y mirror the exact blended value ice_ocean_stress_flux last wrote into ocean_surface_stress_t%tau_x/tau_y — restart-carried, so the resume path COPIES it instead of recomputing it (formula-agnostic: the carry stays exact under any future change to the blend formula). tau_ocn_valid is the host-only “was a blend ever written” flag (0.0 fresh run / pre-PR-63 checkpoint, 1.0 otherwise) — SIS2’s query_initialized('stress_mag') fallback idiom (ice_type.F90:264, ice_model.F90:2410), needed because an unconditional copy on a fresh run would hand the ocean a zeroed wind stress on step 1. See ice_ocean_stress_resume_apply (rdb_ice_ocean_coupler).

Two-mode convention (part_size/m_ice/m_snow), load-bearing. ncat == 1 is the LEGACY LUMPED mode landed by PR 3b/3c: part_size is never maintained (part_size(:,:,0)=1 forever, category 1 unused), and m_ice/m_snow/enth_ice/enth_snow/ sal_ice are per unit CELL area. All existing code paths (ice_frazil_uptake_impl, the ncat=1 ice_thermo_driver_reduce_impl) run byte-for-byte unchanged under this mode — bit-identity with PR 3c is guaranteed BY CONSTRUCTION (dispatch happens only at the three shim points in rdb_ice_frazil_uptake/rdb_ice_thermo_driver/ rdb_ice_itd, never inside a kernel). ncat > 1 is the SIS2 ITD mode (PR 4a): part_size is LIVE (Σ_cat part_size = 1, category 0 = open water), and m_ice/m_snow become per unit ICE-COVERED-area intensive quantities (SIS2 mH_ice/mH_snow) — the frazil uptake annexes open water into an occupied/thinnest category (ice_frazil_uptake_multicat_impl), the thermo-driver reduce part-weights the per-cell diags, and ice_adjust_categories (rdb_ice_itd) restores the ITD (whole-category “move all of it” shift, SIS_transport.F90:611-891) once per thermo window. enth_ice/enth_snow/sal_ice stay per-MASS intensive in BOTH modes (unchanged meaning). Restart files written by PR-3c at ncat>1 carry the OLD lumped meaning — the feature branch is unreleased, so this break is accepted (not carried forward).

PR 26 lands the snowfall source term: has_snowfall (host-side dispatch gate, latched from &ocean_ice_nml snowfall /= 0 before init, mirrors transport/dynamics), the fourth atmospheric-seam field atm_fprec (kg/m^2/s, filled by rdb_ice_atm_forcing alongside atm_sw_dn, consumed by rdb_ice_column’s ice_snow_accumulate), and the ocean-delivery pair snow_part_ocn/fprec_ocn_diag (the PRE-column ice-free-cover snapshot and the resulting ocean-bound frozen-precipitation share, filled by rdb_ice_thermo_driver/rdb_ice_snow — same PRE-column-snapshot contract as fb_part_sum). All three arrays are allocated UNCONDITIONALLY whenever the ice slot is live (cheap 2-D arrays, same contract as the rest of the atm_* seam); has_snowfall is what makes snowfall=0 byte-identical by construction. fprec_ocn_diag is the binding seam PR-16 consumes as a net_massin source (see rdb_ice_snow’s module docstring).

PR 27 lands the Archimedes freeboard snow-ice flood: snow_ice (host-side gate, latched from &ocean_ice_nml snow_ice BEFORE init, same convention as has_snowfall/transport/dynamics) and snow_to_ice (per-category kg/m² output, SIS2 SN2IC, allocated UNCONDITIONALLY whenever the ice slot is live, same contract as sw_thru — filled by ice_thermo_columns, zeroed unconditionally every thermo step, copyin/delete mapped, NOT restart-carried, no downstream consumer yet). snow_ice=.false. (default) is what makes the flood byte-identical by construction.


Uses

  • module~~rdb_ice_state~~UsesGraph module~rdb_ice_state rdb_ice_state iso_fortran_env iso_fortran_env module~rdb_ice_state->iso_fortran_env module~rdb_constants rdb_constants module~rdb_ice_state->module~rdb_constants module~rdb_grid rdb_grid module~rdb_ice_state->module~rdb_grid module~rdb_ice_column rdb_ice_column module~rdb_ice_state->module~rdb_ice_column module~rdb_ice_enthalpy rdb_ice_enthalpy module~rdb_ice_state->module~rdb_ice_enthalpy module~rdb_mem_report rdb_mem_report module~rdb_ice_state->module~rdb_mem_report pic_types pic_types module~rdb_constants->pic_types module~rdb_grid->module~rdb_constants module~rdb_ice_column->module~rdb_constants module~rdb_ice_column->module~rdb_ice_enthalpy module~rdb_ice_mass rdb_ice_mass 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_mem_report->iso_fortran_env module~rdb_mem_report->module~rdb_constants pic_logger pic_logger module~rdb_mem_report->pic_logger pic_strings pic_strings module~rdb_mem_report->pic_strings module~rdb_ice_mass->module~rdb_constants module~rdb_ice_mass->module~rdb_ice_enthalpy module~rdb_ice_optics->module~rdb_constants module~rdb_ice_optics->module~rdb_ice_enthalpy

Used by

  • module~~rdb_ice_state~~UsedByGraph module~rdb_ice_state rdb_ice_state module~rdb_ice_atm_forcing rdb_ice_atm_forcing module~rdb_ice_atm_forcing->module~rdb_ice_state module~rdb_ice_basal_flux rdb_ice_basal_flux module~rdb_ice_basal_flux->module~rdb_ice_state module~rdb_ice_evp rdb_ice_evp module~rdb_ice_evp->module~rdb_ice_state module~rdb_ocean_halo rdb_ocean_halo module~rdb_ice_evp->module~rdb_ocean_halo module~rdb_ice_frazil_uptake rdb_ice_frazil_uptake module~rdb_ice_frazil_uptake->module~rdb_ice_state module~rdb_ice_init rdb_ice_init module~rdb_ice_init->module~rdb_ice_state module~rdb_ice_itd rdb_ice_itd module~rdb_ice_itd->module~rdb_ice_state module~rdb_ice_ocean_coupler rdb_ice_ocean_coupler module~rdb_ice_ocean_coupler->module~rdb_ice_state module~rdb_ocean_halo_state rdb_ocean_halo_state module~rdb_ice_ocean_coupler->module~rdb_ocean_halo_state module~rdb_ice_snow rdb_ice_snow module~rdb_ice_snow->module~rdb_ice_state module~rdb_ice_thermo_driver rdb_ice_thermo_driver module~rdb_ice_thermo_driver->module~rdb_ice_state module~rdb_ice_transport rdb_ice_transport module~rdb_ice_transport->module~rdb_ice_state module~rdb_ice_transport->module~rdb_ice_itd module~rdb_ice_transport->module~rdb_ocean_halo_state module~rdb_halo rdb_halo module~rdb_ice_transport->module~rdb_halo module~rdb_ice_transport->module~rdb_ocean_halo module~rdb_ocean_halo_state->module~rdb_ice_state module~rdb_ocean_halo_state->module~rdb_ocean_halo module~rdb_ocean_state rdb_ocean_state module~rdb_ocean_state->module~rdb_ice_state module~rdb_config rdb_config module~rdb_ocean_state->module~rdb_config module~rdb_ocean_data_forcing rdb_ocean_data_forcing module~rdb_ocean_state->module~rdb_ocean_data_forcing module~rdb_ocean_dyn rdb_ocean_dyn module~rdb_ocean_state->module~rdb_ocean_dyn module~rdb_decomp rdb_decomp module~rdb_ocean_state->module~rdb_decomp module~rdb_ocean_data_input rdb_ocean_data_input module~rdb_ocean_state->module~rdb_ocean_data_input module~rdb_ocean_z_init rdb_ocean_z_init module~rdb_ocean_state->module~rdb_ocean_z_init module~rdb_ocean_restart_io rdb_ocean_restart_io module~rdb_ocean_state->module~rdb_ocean_restart_io module~rdb_config->module~rdb_ice_init module~rdb_driver rdb_driver module~rdb_driver->module~rdb_ocean_state module~rdb_driver->module~rdb_config module~rdb_driver->module~rdb_ocean_dyn module~rdb_ocean_engine rdb_ocean_engine module~rdb_driver->module~rdb_ocean_engine module~rdb_driver->module~rdb_halo module~rdb_handle rdb_handle module~rdb_handle->module~rdb_ocean_state module~rdb_handle->module~rdb_config module~rdb_handle->module~rdb_ocean_engine module~rdb_ocean_data_forcing->module~rdb_ocean_halo_state module~rdb_ocean_data_forcing->module~rdb_config module~rdb_ocean_data_forcing->module~rdb_ocean_data_input module~rdb_ocean_diag_derived rdb_ocean_diag_derived module~rdb_ocean_diag_derived->module~rdb_ocean_state module~rdb_ocean_diag_fills rdb_ocean_diag_fills module~rdb_ocean_diag_derived->module~rdb_ocean_diag_fills module~rdb_ocean_diag_fills->module~rdb_ocean_state module~rdb_ocean_dyn->module~rdb_ocean_halo_state module~rdb_ocean_dyn->module~rdb_ocean_halo module~rdb_ocean_engine->module~rdb_ice_atm_forcing module~rdb_ocean_engine->module~rdb_ice_basal_flux module~rdb_ocean_engine->module~rdb_ice_evp module~rdb_ocean_engine->module~rdb_ice_frazil_uptake module~rdb_ocean_engine->module~rdb_ice_init module~rdb_ocean_engine->module~rdb_ice_itd module~rdb_ocean_engine->module~rdb_ice_ocean_coupler module~rdb_ocean_engine->module~rdb_ice_snow module~rdb_ocean_engine->module~rdb_ice_thermo_driver module~rdb_ocean_engine->module~rdb_ice_transport module~rdb_ocean_engine->module~rdb_ocean_halo_state module~rdb_ocean_engine->module~rdb_ocean_state module~rdb_ocean_engine->module~rdb_config module~rdb_ocean_engine->module~rdb_ocean_data_forcing module~rdb_ocean_engine->module~rdb_ocean_diag_derived module~rdb_ocean_engine->module~rdb_ocean_diag_fills module~rdb_ocean_engine->module~rdb_ocean_dyn module~rdb_ocean_setup rdb_ocean_setup module~rdb_ocean_engine->module~rdb_ocean_setup module~rdb_ocean_engine->module~rdb_decomp module~rdb_ocean_engine->module~rdb_ocean_data_input module~rdb_ocean_stability_audit rdb_ocean_stability_audit module~rdb_ocean_engine->module~rdb_ocean_stability_audit module~rdb_state rdb_state module~rdb_ocean_engine->module~rdb_state module~rdb_ocean_engine->module~rdb_halo module~rdb_ocean_fold_exchange rdb_ocean_fold_exchange module~rdb_ocean_engine->module~rdb_ocean_fold_exchange module~rdb_ocean_engine->module~rdb_ocean_halo module~rdb_ocean_setup->module~rdb_ocean_halo_state module~rdb_ocean_setup->module~rdb_ocean_state module~rdb_ocean_setup->module~rdb_config module~rdb_ocean_setup->module~rdb_ocean_dyn module~rdb_ocean_setup->module~rdb_decomp module~rdb_ocean_setup->module~rdb_halo module~rdb_ocean_setup->module~rdb_ocean_fold_exchange module~rdb_ocean_setup->module~rdb_ocean_halo module~rdb_config_schema rdb_config_schema module~rdb_config_schema->module~rdb_config module~rdb_decomp->module~rdb_config module~rdb_ocean_api rdb_ocean_api module~rdb_ocean_api->module~rdb_config module~rdb_ocean_api->module~rdb_handle module~rdb_ocean_api->module~rdb_ocean_diag_derived module~rdb_ocean_api->module~rdb_ocean_diag_fills module~rdb_ocean_api->module~rdb_ocean_dyn module~rdb_ocean_api->module~rdb_ocean_engine module~rdb_ocean_data_input->module~rdb_config module~rdb_ocean_stability_audit->module~rdb_config module~rdb_ocean_z_init->module~rdb_config module~rdb_state->module~rdb_config module~rdb_halo->module~rdb_decomp module~rdb_ocean_fold_exchange->module~rdb_decomp module~rdb_ocean_halo->module~rdb_decomp module~rdb_ocean_restart_io->module~rdb_decomp module~rdb_barotropic_substep rdb_barotropic_substep module~rdb_barotropic_substep->module~rdb_ocean_fold_exchange module~rdb_barotropic_substep->module~rdb_ocean_halo module~rdb_continuity rdb_continuity module~rdb_continuity->module~rdb_ocean_fold_exchange module~rdb_continuity->module~rdb_ocean_halo module~rdb_ocean_bt_wide rdb_ocean_bt_wide module~rdb_ocean_bt_wide->module~rdb_ocean_halo module~rdb_ocean_chksum rdb_ocean_chksum module~rdb_ocean_chksum->module~rdb_halo module~rdb_ocean_console_stats rdb_ocean_console_stats module~rdb_ocean_console_stats->module~rdb_halo module~rdb_ocean_fold_apply rdb_ocean_fold_apply module~rdb_ocean_fold_apply->module~rdb_ocean_fold_exchange proc~ocean_cavity_mass_step ocean_cavity_mass_step proc~ocean_cavity_mass_step->module~rdb_halo

Derived Types

type, public ::  evp_workspace_t

Persistent EVP-rheology scratch (SIS2 C-grid dynamics), hung off ocean_sea_ice_t as the evp_ws slot — eagerly allocated in the ice state’s init and GPU-mapped via enter_data, so it matches the rest of src/core/ocean/ (which carries ZERO module-level save allocatables — every kernel puts its scratch on a state DT). Replaces the retired rdb_ice_evp module-level save workspace + its lazy evp_workspace_ensure/ice_evp_cleanup lifecycle. Allocated / mapped ONLY when ocean_sea_ice_t%dynamics is on (the workspace is EVP-dynamics-only).

Read more…

Components

Type Visibility Attributes Name Initial
real(kind=wp), public, allocatable :: a_u_w(:,:)

PR 62: face ice concentration, a_u_w(i,j) = 0.5*(ci_w(i-1,j) + ci_w(i,j)) (same expression/edge convention as evp_mi_face_impl’s mi_u, and as ice_ocean_stress_flux_impl’s a_u). Filled by evp_mi_face_impl(ci_w, ...) ONLY when par%a_face_stress; read ONLY under the same gate — uninitialised device memory is unreachable when the knob is off.

real(kind=wp), public, allocatable :: a_v_w(:,:)

PR 62: face ice concentration, a_u_w(i,j) = 0.5*(ci_w(i-1,j) + ci_w(i,j)) (same expression/edge convention as evp_mi_face_impl’s mi_u, and as ice_ocean_stress_flux_impl’s a_u). Filled by evp_mi_face_impl(ci_w, ...) ONLY when par%a_face_stress; read ONLY under the same gate — uninitialised device memory is unreachable when the knob is off.

real(kind=wp), public, allocatable :: ci_in_w(:,:)
real(kind=wp), public, allocatable :: ci_w(:,:)
real(kind=wp), public, allocatable :: del_sh_min_pr_w(:,:)
real(kind=wp), public, allocatable :: del_sh_w(:,:)
real(kind=wp), public, allocatable :: mask_q_w(:,:)
real(kind=wp), public, allocatable :: mask_t_w(:,:)
real(kind=wp), public, allocatable :: mask_u_w(:,:)
real(kind=wp), public, allocatable :: mask_v_w(:,:)
real(kind=wp), public, allocatable :: mi_ratio_a_q_w(:,:)
real(kind=wp), public, allocatable :: mi_u_w(:,:)
real(kind=wp), public, allocatable :: mi_v_w(:,:)
real(kind=wp), public, allocatable :: mice_in_w(:,:)
real(kind=wp), public, allocatable :: mice_w(:,:)
real(kind=wp), public, allocatable :: mis_in_w(:,:)
real(kind=wp), public, allocatable :: mis_w(:,:)
integer, public :: nx = 0

Cached T-cell x extent (nx_total).

integer, public :: ny = 0

Cached T-cell y extent (ny_total).

real(kind=wp), public, allocatable :: pres_mice_w(:,:)
real(kind=wp), public, allocatable :: q_w(:,:)
real(kind=wp), public, allocatable :: sh_dd_w(:,:)
real(kind=wp), public, allocatable :: sh_ds_w(:,:)
real(kind=wp), public, allocatable :: sh_dt_w(:,:)
real(kind=wp), public, allocatable :: u_tmp_w(:,:)
real(kind=wp), public, allocatable :: zeta_w(:,:)

Type-Bound Procedures

procedure, public, non_overridable :: bytes => evp_workspace_bytes
procedure, public, non_overridable :: destroy => evp_workspace_destroy
procedure, public, non_overridable :: enter_data => evp_workspace_enter_data
procedure, public, non_overridable :: exit_data => evp_workspace_exit_data
procedure, public, non_overridable :: init => evp_workspace_init

type, public ::  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.

Components

Type Visibility Attributes Name Initial
real(kind=wp), public, allocatable :: atm_dsfdt(:,:)

SEB slope dSF/dT (W/m^2/K) — dsf_dt. Same seam contract.

real(kind=wp), public, allocatable :: atm_fprec(:,:)

PR 26: frozen-precipitation rate onto the ice top (kg/m^2/s),

Read more…
real(kind=wp), public, 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(kind=wp), public, allocatable :: atm_sw_dn(:,:)

Downwelling shortwave into the ice top (W/m^2) — sw_dn. Same.

logical, public :: 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, public :: enable = .false.

Master switch (&ocean_ice_nml enable). Off (default) ⇒ the parent state never inits / maps / steps this slot ⇒ no-op.

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/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(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 (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.

real(kind=wp), public, 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(kind=wp), public, 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(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 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.

real(kind=wp), public, 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(kind=wp), public, 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(kind=wp), public, allocatable :: fyoc(:,:)

Ditto, v-faces, shape (nx_total, ny_total+1). SIS2 fyoc.

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 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).

logical, public :: 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(kind=wp), public, 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.

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 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(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 (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(kind=wp), public, 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(kind=wp), public, allocatable :: hlim_cfg(:)

hlimlist, trimmed to its supplied length, set byocean_state_init_from_configBEFOREinit. Unallocated => the SIS2 default table. NOT device-mapped (no kernel reads it —h_lim/mh_limare the mapped artefacts, computed atinitfrom this beforeenter_data) and NOT restart-carried (same lifecycle ash_lim`).

real(kind=wp), public, allocatable :: hr_x_work(:,:)

Right-cell zonal face-edge companion to hl_x_work, (nx_total+1, ny_total). SCRATCH.

real(kind=wp), public, allocatable :: hr_y_work(:,:)

North-face right-cell PPM edge, (nx_total, ny_total+1). SCRATCH.

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 zonal_mass_flux’s htot).

logical, public :: is_init = .false.

True between init and destroy; gate on this (never on allocated, which misses the GPU mapping).

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 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(kind=wp), public, 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(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 m_ice (per-CELL at ncat==1, per-ICE-area at ncat>1).

real(kind=wp), public, 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(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 mca_ice.

real(kind=wp), public, 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.

integer, public :: 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, public :: 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).

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): 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(kind=wp), public, allocatable :: sal_ice(:,:,:,:)

Ice bulk salinity (PSU), shape (nx_total, ny_total, ncat, nk_ice), same BOTTOM-UP k convention as enth_ice.

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: 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.

logical, public :: 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(kind=wp), public, 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.

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 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).

real(kind=wp), public, 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(kind=wp), public, allocatable :: ssurf_seam(:,:)

Sampled sea-surface salinity (PSU), same sharing contract as sst_seam. SCRATCH.

real(kind=wp), public, 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(kind=wp), public, 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(kind=wp), public, 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(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 sw_thru_diag below (which IS coupled to the ocean surface-flux slot). SCRATCH (recomputed every thermo step, delete-mapped).

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 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(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. 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.

real(kind=wp), public, 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(kind=wp), public, allocatable :: tau_ocn_y(:,:)

Ditto, v-faces, shape (nx_total, ny_total+1). SIS2 Ice%flux_v.

real(kind=wp), public, allocatable :: tfw_seam(:,:)

Sampled seawater freezing temperature at the surface (degC), same sharing contract as sst_seam. SCRATCH.

real(kind=wp), public, 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(kind=wp), public, allocatable :: tr_flux_y_work(:,:,:)

Meridional twin of tr_flux_x_work, shape (nx_total, ny_total+1, ncat).

logical, public :: 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.

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 (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(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 u_ice.

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.

Type-Bound Procedures

procedure, public, non_overridable :: bytes => ocean_sea_ice_bytes
procedure, public, non_overridable :: destroy => ocean_sea_ice_destroy
procedure, public, non_overridable :: enter_data => ocean_sea_ice_enter_data
procedure, public, non_overridable :: exit_data => ocean_sea_ice_exit_data
procedure, public, non_overridable :: init => ocean_sea_ice_init

Functions

private pure function evp_workspace_bytes(this) result(nbytes)

Counted allocatable footprint of the EVP scratch (0 when unallocated, i.e. whenever &ocean_ice_nml dynamics is off).

Read more…

Arguments

Type IntentOptional Attributes Name
class(evp_workspace_t), intent(in) :: this

Return Value integer(kind=int64)

private pure function ocean_sea_ice_bytes(this) result(nbytes)

Counted allocatable footprint of the sea-ice slot (one arr_bytes term per array, mirroring ocean_meke_bytes).

Arguments

Type IntentOptional Attributes Name
class(ocean_sea_ice_t), intent(in) :: this

Return Value integer(kind=int64)


Subroutines

public pure subroutine ice_cell_concentration_impl(wet_T, part_size, m_ice, m_snow, mis, mice, ci, ncat, nx, ny)

Two-mode per-cell concentration/mass gather (module docstring convention), shared by rdb_ice_evp (ice_evp_step) and rdb_ice_ocean_coupler (ice_ocean_stress_flux) so neither module depends on the other.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: wet_T(nx,ny)
real(kind=wp), intent(in) :: part_size(nx,ny,0:ncat)
real(kind=wp), intent(in) :: m_ice(nx,ny,ncat)
real(kind=wp), intent(in) :: m_snow(nx,ny,ncat)
real(kind=wp), intent(out) :: mis(nx,ny)
real(kind=wp), intent(out) :: mice(nx,ny)
real(kind=wp), intent(out) :: ci(nx,ny)
integer, intent(in) :: ncat
integer, intent(in) :: nx
integer, intent(in) :: ny

public pure subroutine ice_itd_category_bounds(ncat, h_lim, mh_lim, hlim_vals)

SIS2 initialize_ice_categories (SIS_state_initialization.F90:45-78): absent hlim_vals fills the first min(ncat+1, 8) entries from the default lower-thickness-limit table HLIM_DFLT_TABLE; a supplied hlim_vals fills the first min(ncat+1, size(hlim_vals)) entries from IT instead (PR-58 — the SIS2 hLim_vals optional dummy Roundabout’s port originally declined to carry). Either way, the remainder extrapolates by constant width, h_lim(k) = 2*h_lim(k-1) - h_lim(k-2), resuming at ONE PAST however many entries were actually supplied — not at a fixed index 9. mh_lim = ICE_RHO_ICE*h_lim (SIS2 mH_cat_bound, SIS_state_initialization.F90:75-77). h_lim(c)/mh_lim(c) is the LOWER bound of category c (1-based); index ncat+1 is the lower bound that WOULD start category ncat+1 — stored (SIS2 keeps it) but never used as an upper cap on category ncat (ice_adjust_categories’s upward pass stops at c = ncat-1). No fixed-size local arrays — ncat is unbounded here (unlike the per-column ICE_NK_MAX-capped layer arrays).

Read more…

Arguments

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

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

real(kind=wp), intent(out) :: h_lim(ncat+1)

Category lower thickness limits (m), h_lim(1:ncat+1).

real(kind=wp), intent(out) :: mh_lim(ncat+1)

Category lower mass limits (kg/m²), = ICE_RHO_ICE*h_lim.

real(kind=wp), intent(in), optional :: hlim_vals(:)

entries, strictly increasing, hlim_vals(1) > 0 — SIS2 initialize_ice_categories’s hLim_vals (SIS_state_initialization.F90:50). Assumed-shape is correct here: this is a host-side, once-per-run init routine with no do concurrent, not a per-step kernel. Absent => the pre-PR-58 behaviour, bit-for-bit (the default-table branch below is unmodified). Validated by the caller (ice_hlim_spec_is_valid) before it ever reaches here — this routine trusts its shape.

private subroutine evp_workspace_destroy(this)

Reverse of init (host deallocation). Idempotent — safe on an already-clean workspace. Device unmapping is exit_data’s job (call it first).

Arguments

Type IntentOptional Attributes Name
class(evp_workspace_t), intent(inout) :: this

private subroutine evp_workspace_enter_data(this)

Arguments

Type IntentOptional Attributes Name
class(evp_workspace_t), intent(inout) :: this

private subroutine evp_workspace_enter_data_impl(this)

Attach the scratch to the device with create — every array is pure scratch, fully (re)written before it is read on every ice_evp_dynamics call (masks rebuilt, cell fields filled, the subcycle kernels overwrite the rest), so create (not copyin) is correct. One-level components of the type(...) dummy — same shape as ocean_sea_ice_enter_data_impl, no associate-leaf needed.

Arguments

Type IntentOptional Attributes Name
type(evp_workspace_t), intent(inout) :: this

private subroutine evp_workspace_exit_data(this)

Arguments

Type IntentOptional Attributes Name
class(evp_workspace_t), intent(inout) :: this

private subroutine evp_workspace_exit_data_impl(this)

Reverse of enter_data_impl — pure scratch, delete throughout.

Arguments

Type IntentOptional Attributes Name
type(evp_workspace_t), intent(inout) :: this

private subroutine evp_workspace_init(this, nx, ny)

Allocate the EVP scratch for an (nx,ny) T-cell domain, zero-init. Called ONCE per workspace lifetime — from ocean_sea_ice_init (gated on dynamics) and from the EVP test harness. nx/ny declared before the arrays that use them is unnecessary here (all allocatables), but the extents are cached for destroy.

Arguments

Type IntentOptional Attributes Name
class(evp_workspace_t), intent(inout) :: this
integer, intent(in) :: nx
integer, intent(in) :: ny

private subroutine ocean_sea_ice_destroy(this)

Reverse of init. Clears is_init first (use-after-destroy guard), then releases allocations.

Arguments

Type IntentOptional Attributes Name
class(ocean_sea_ice_t), intent(inout) :: this

private subroutine ocean_sea_ice_enter_data(this)

Arguments

Type IntentOptional Attributes Name
class(ocean_sea_ice_t), intent(inout) :: this

private subroutine ocean_sea_ice_enter_data_impl(this)

Attach the slot’s allocatables to the device. copyin (not create) throughout: every array is host-initialised (0, or part_size/sal_ice’s non-zero defaults) at init, and a restart read seeds the persistent ones host-side before the mapping. PR 3c’s seam + scratch fields are also copyin: they are host-zeroed at init and (re)filled device-side every thermo step, so a plain copyin is correct (and cheap — one-time). PR 4b: u_ice/v_ice are copyin (host-zeroed at init, and the v1 sampler / PR-5 EVP both WRITE them device-side every call — copyin establishes presence, same contract as the atm_* seam). PR 5: str_d/str_t/str_s are copyin (host-zeroed at init or restart-seeded; ice_evp_dynamics reads them before the first write on every call — limit_stresses runs first). tau_a_x/ tau_a_y are copyin (host-snapshotted at configure, then read device-side every substep — never written on-device). fxoc/ fyoc are copyin (host-zeroed at init or restart-seeded; the EVP core zeros them itself at call entry before accumulating). PR 63: tau_ocn_x/tau_ocn_y are copyin (host-zeroed at init or restart-seeded; ice_tau_mirror_impl WRITES them device-side every call to ice_ocean_stress_flux — same reasoning as fxoc/fyoc). tau_ocn_valid is NEVER mapped — it is host-only scalar state, set on the host by ice_ocean_stress_flux and read on the host by ice_ocean_stress_resume_apply; register_scalar forces device_mapped=.false. on the restart side for the same reason. The transport workspace is create (pure scratch, recomputed from scratch every pass — never read before written), mapped only if (this%transport) (memory Rule 2, same gate as the host-side allocation).

Arguments

Type IntentOptional Attributes Name
type(ocean_sea_ice_t), intent(inout) :: this

private subroutine ocean_sea_ice_exit_data(this)

Arguments

Type IntentOptional Attributes Name
class(ocean_sea_ice_t), intent(inout) :: this

private subroutine ocean_sea_ice_exit_data_impl(this)

Reverse of enter_data_impl — all six PR-3a fields are prognostic state (post-run host inspection + restart write), same as the persistent frazil bank; the drain-and-zero budget scratch is dropped. PR 3b’s m_frozen_diag/salt_flux_diag are both PERSISTENT (restart-carried) — copyout, not delete. PR 3c: heat_flux_diag/m_melt_diag are likewise PERSISTENT (copyout); PR 31’s sw_thru_diag is PERSISTENT too (copyout, restart-carried — NOT its per-category parent sw_thru, which stays delete); the atm_* seam, fb/sst_seam/ssurf_seam/ tfw_seam, and the per-category column scratch (tsurf_out/h2o_ocn_to_ice/h2o_ice_to_ocn/heat_to_ocn/ sw_thru) are all recomputed every thermo step — delete. PR 4a: h_lim/mh_lim are delete (recomputed from ncat at init, never mutated after); fb_part_sum is delete (recomputed every thermo step, same lifecycle as the other scratch seams). PR 4b: u_ice/v_ice are copyout (post-run host inspection, same contract as the melt/frazil diags); the transport workspace is delete (pure scratch), gated the same if (this%transport) as the enter_data map. PR 5: str_d/str_t/str_s/fxoc/fyoc are copyout (PERSISTENT, restart-carried); tau_a_x/tau_a_y are delete (configure-time snapshot, not restart-carried — rebuilt fresh on every run). PR 26: atm_fprec is delete (same lifecycle as the rest of the atm_* seam); fprec_ocn_diag/ snow_part_ocn are delete (SCRATCH, recomputed every thermo step, same lifecycle as fb_part_sum). PR 63: tau_ocn_x/tau_ocn_y are copyout — PERSISTENT, restart-carried, and the restart write reads the host copy (opposite lifecycle to tau_a_x/tau_a_y, do not copy that pattern). tau_ocn_valid is never mapped, so there is nothing to unmap.

Arguments

Type IntentOptional Attributes Name
type(ocean_sea_ice_t), intent(inout) :: this

private subroutine ocean_sea_ice_init(this, grid)

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.

Arguments

Type IntentOptional Attributes Name
class(ocean_sea_ice_t), intent(inout) :: this
type(hgrid_t), intent(in) :: grid