Complements rdb_ice_mass%ice_snow_accumulate (the on-ice snow add):
the share of &ocean_ice_nml snowfall that lands where there is no
ice (open water at ncat>1, ice-free cells at ncat==1, and any
category that fails the column’s own entry gate,
m_ice(i,j,c) > ICE_RHO_ICE*H_VANISHED) does not accumulate — it
melts on contact with seawater. This module delivers that mass’s
latent heat + virtual freshening to the ocean through the EXISTING
heat_flux_diag/salt_flux_diag contributor seam
(rdb_ice_thermo_driver.F90’s ordering contract), the same one
ice_frazil_uptake and the melt-side reduce kernels already use.
Without this, snowfall > 0 over open water would silently
annihilate mass and energy (the sw_thru failure mode,
PLAN_PR26_snowfall.md §2 / roadmap §5 trap 15).
Physics (PLAN_PR26_snowfall.md §3). Per unit CELL area, the mass
delivered to the ocean over the thermo window is
m_ocn = snow_part_ocn * atm_fprec * dt_therm [kg/m^2]
It arrives as fresh solid at 0 degC, specific enthalpy
enth_fall = ice_enth_from_ts(0, 0) = ICE_ENTH_LIQ_0 - ICE_LAT_FUS
= -3.34e5 J/kg (SIS2’s “-LI” convention for frozen
precip entering the ocean, SIS_sum_output.F90:783),
module constant ENTH_SNOWFALL below. Bringing it to the ocean’s
own liquid enthalpy enth_ocean = ice_enthalpy_liquid(sst, s_surf)
costs the ocean dE = m_ocn*(enth_ocean - ENTH_SNOWFALL) [J/m^2],
so in the heat_flux_diag sign convention (positive DOWN into the
ocean, rdb_ice_thermo_driver.F90:14):
heat_flux_diag -= m_ocn(enth_ocean - ENTH_SNOWFALL)/dt_therm
and, in the ice model’s existing virtual-salt convention
(salt_flux_diag += m_net*(s_surf - S_ice)/dt_therm, m_net
positive = freeze), fresh water added to the ocean is
m_net = -m_ocn with S_snow = 0:
salt_flux_diag -= m_ocnssurf_seam/dt_therm
The ocean’s water MASS is not increased – Roundabout’s ocean is a
volume-conserving virtual-salt-flux model today, the same
convention rdb_ice_frazil_uptake’s docstring states for the
freeze side. PR-16 converts this (and the melt/frazil paths) to
real mass; see the TODO(PR-16) marker below and this module’s
binding seam spec.
Contributor ordering (mandatory, driver-enforced): this module
MUST run AFTER ice_thermo_driver_step (which unconditionally
zeroes heat_flux_diag and writes the melt-side salt_flux_diag
contribution) and BEFORE ice_ocean_brine_flux/ice_ocean_heat_flux
(which overwrite Q_salt/Q_heat from salt_flux_diag/
heat_flux_diag) – see rdb_driver.F90’s mandated-order comment
block. It ADDS to both diags, never zeroes them (third contributor
on heat_flux_diag, after the column; second on salt_flux_diag,
after the melt-side reduce).
Binding seam spec (PLAN_PR26_snowfall.md §13, owner: PR-16).
ocean_sea_ice_t%fprec_ocn_diag(nx_total, ny_total): real(wp),
kg/m^2/s, per unit CELL area, positive = frozen fresh water
delivered to the ocean surface, zero on land and fully ice-covered
cells. SCRATCH: zeroed + rewritten every thermo window by this
module; not restart-carried; device-mapped copyin by
ocean_sea_ice_enter_data_impl. When PR-16 makes ice<->ocean mass
real, it consumes fprec_ocn_diag as a net_massin source at
h_layer(:,:,nz) and MUST remove this module’s virtual-salt term
(marked TODO(PR-16) below) or the dilution double-counts; the
heat term stays (becomes the heat_content_fprec companion, not a
duplicate).
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | private, | parameter | :: | ENTH_SNOWFALL | = | ICE_ENTH_LIQ_0-ICE_LAT_FUS |
Specific enthalpy of frozen precipitation entering the ocean
(J/kg) – |
Outer shim (outer-shim + flat-impl pattern): forward the ice
slot’s arrays to the device kernel. Called only when
ice%has_snowfall (driver gate, rdb_driver.F90) – default
snowfall=0 never reaches this module.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(hgrid_t), | intent(in) | :: | grid | |||
| type(ocean_sea_ice_t), | intent(inout) | :: | ice | |||
| real(kind=wp), | intent(in) | :: | dt_therm |
Effective thermo timestep (s) – |
Device kernel over PHYSICAL cells (ghosts excluded). Gated on
snow_part_ocn(i,j) > 0 rather than a separate wet_mask arg:
ice_snow_part_ocn_fill_impl (rdb_ice_thermo_driver) already
zeroes snow_part_ocn on land/dry/fully-ice-covered cells, so
that field IS the wet-and-ice-free gate this kernel needs –
functionally identical to (and cheaper than) re-deriving a
wet_mask > 0.5 test here, and keeps this module’s signature to
the (grid, ice, dt_therm) seam (no ms dependency).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | snow_part_ocn(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | atm_fprec(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | sst_seam(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | ssurf_seam(nx,ny) | |||
| real(kind=wp), | intent(inout) | :: | fprec_ocn_diag(nx,ny) | |||
| real(kind=wp), | intent(inout) | :: | heat_flux_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) | :: | nx | |||
| integer, | intent(in) | :: | ny |