(0.5.0) Share phase_transitions and bulk densities at the model level#134
(0.5.0) Share phase_transitions and bulk densities at the model level#134simone-silvestri wants to merge 5 commits intomainfrom
Conversation
Moves phase_transitions onto SeaIceModel as a shared parameter and introduces ice_density and snow_density as top-level bulk fields. The per-layer SlabThermodynamics is slimmed to the state that is genuinely layer-specific (surface T, heat BCs, raw internal-flux coefficient, concentration evolution). latent_heat is rewritten as a per-mass function; tendency kernels multiply externally by the appropriate bulk density. This fixes the sign error in the Stefan correction and separates the mass-budget role of rho from its microscopic role inside L(T). Removes the sea_ice_model.jl rebuild hack: the combined snow+ice flux and the snow-ice interface temperature are now computed inline in the layered kernel, so neither slab's top BC is rewritten at construction time. Adds an internal-flux extension mechanism: users can plug in ConductiveFlux, a bare Function, a pre-assembled FluxFunction, or a custom struct via `flux_kernel` dispatch. Resolves #133. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
Quick comment, for this:
I suggest defining "ice" as "pure ice" and then use the term "sea ice" to refer to the porous matrix of pure ice and brine. Therefore, the top-level |
|
Is the latent heat solution correct? For a bulk media representation, the latent heat will be the weighted sum of components, eg where For sea ice, we have two components, ice and brine. For snow, we have ice and air. In the case of snow, it may be a reasonable approximation to neglect the latent heat of the air. But we shouldn't do this flippantly or by prescription (also it may not even really be necessary, since it won't save any computation and leads to come confusion about the physics -- we just need to know the latent heat of saturated air?) Also for snow we may need to have the density and latent heat of air as |
|
I have changed the |
A (Very substantial!!) refactor. Closes #133.
Summary
Reorganises the thermodynamic state of
SeaIceModelso that sharedphysical parameters live at the model level rather than being duplicated
inside each
SlabThermodynamics. Along the way, corrects a sign errorin the Stefan correction and deletes the
sea_ice_model.jlrebuildhack.
Key moves:
PhaseTransitionsonSeaIceModel(shared microscopicpure-ice ↔ water parameters), replacing one per slab.
SeaIceModelfields,ice_densityandsnow_density, carrying the bulk porous-medium densities.SlabThermodynamicsslimmed down to the state that is genuinelyper-layer (surface T, heat BCs, internal-flux coefficient,
concentration evolution).
latent_heatrewritten as a per-mass function (with the correctsign); tendency kernels multiply externally by the appropriate bulk
density.
sea_ice_model.jl:174–204rebuild hack that wasforcing
ice_thermodynamics.top_heat_boundary_condition = PrescribedTemperature(0)and assembling the combined snow+ice fluxon the snow side at construction time.
flux_kernel) plus passthrough forpre-assembled
FluxFunctionasinternal_heat_flux.Problem → solution
P1. Duplicated liquid properties
SlabThermodynamicsused to own aPhaseTransitions, which holdsliquid_density,liquid_heat_capacity,reference_temperature, andliquidus. These describe the same liquid water regardless of whetherit emerges from melting snow or sea ice. Two copies made it
representationally possible for the two slabs to disagree.
Fix. A single
PhaseTransitionslives onSeaIceModelasphase_transitions. Bothice_thermodynamicsandsnow_thermodynamicsread from it.P2. Misplaced bulk density inside
PhaseTransitions(#133)Per @glwagner in #133:
PhaseTransitions.densityandPhaseTransitions.heat_capacityrefer to the microscopic pure-icecrystal — the same substance in both the snow and sea-ice porous
media.
snow_slab_thermodynamicsused to overridedensity = 330inthe snow's
PhaseTransitions, which is a bulk snow density (porousmedium, including trapped air).
Fix.
PhaseTransitionsis now documented and used as a purelymicroscopic descriptor. The two bulk densities live as top-level
SeaIceModelfields:ice_density(default 900 kg/m³, bulk sea ice including brine)snow_density(default 330 kg/m³, bulk snow including trapped air)Both are wrapped as
Field{Center, Center, Nothing}by theconstructor (scalars become
ConstantField), matching the oldmodel.ice_densitytreatment.P3. Two roles for ρ in the latent-heat expression
The old
latent_heat(pt, T)returned the volumetric formρ × ℒ(T),with
ρsourced fromPhaseTransitions. Theρplayed two distinctphysical roles simultaneously:
per-volume of the melting medium. Must be the bulk density.
ρℒ(T) = ρℒ₀ + (ρ_ℓ c_ℓ − ρ c)(T − T₀). Must be themicroscopic pure-ice density.
A single
ρcan play both correctly only when ρ_bulk = ρ_pure.When the snow helper overrode
density = 330, theρ × ctermbecame physically incorrect.
Fix.
latent_heat(pt, T)returns a per-mass Stefan-correctedvalue using only microscopic properties:
Call sites multiply externally by
ρ_ice_bulkorρ_snow_bulk.Matches CICE and CLM/Noah-MP conventions. (A sign error that appeared
during an earlier iteration of the refactor was detected by the energy
conservation tests — now closes to
< 1e-15across all combinations.)P4. Rebuild hack when both layers are present
The pre-refactor
SeaIceModelconstructor rebuilt both thermodynamicswhen snow was supplied:
ice_thermodynamicsgotPrescribedTemperature(0)as its top BC soits surface solve would be skipped.
snow_thermodynamicsgot a combinedIceSnowConductiveFlux(ks, ki)as its internal flux.
This coupled the two slabs at construction time and made the ice's
BC depend on whether snow existed elsewhere on the model.
Fix. The combined flux is constructed inside the layered
kernel from the two conductivities. The ice's melt/freeze tendency is
factored into a pure helper
ice_melt_freeze_tendency(…, Tui, …)thattakes the ice-top temperature as an argument and performs no surface
solve. The layered kernel solves for
T_us, computesT_sianalytically from the resistance ratio, and calls the helper with
T_sidirectly. No BC rewriting.P5. Liquidus plumbing
The
liquidusis only ever used viabottom_temperature(bc, liquidus)for
IceWaterThermalEquilibrium. An interim design storedliquidusinside each slab's
internal_heat_flux.parameters, forcing theSeaIceModelconstructor to rebind it frommodel.phase_transitions.liquidusinto every slab.Fix.
SlabThermodynamicsstores only the raw flux coefficient(e.g.
ConductiveFlux). TheFluxFunctionwrapper is assembledinside the tendency kernel, pulling
liquidusfrommodel.phase_transitionsat call time. No per-slab liquidus state.Breaking changes to the UI
SlabThermodynamicsand its helpersPhaseTransitionsis no longer stored insideSlabThermodynamics.Construct it once on the model:
snow_slab_thermodynamicsno longer acceptsdensity,heat_capacity, orreference_latent_heat:SlabThermodynamics.internal_heat_fluxnow stores the raw fluxcoefficient rather than a pre-wrapped
FluxFunction:SeaIceModelNew keyword arguments with safe defaults:
latent_heatNow returns per-mass enthalpy. If you were using it for diagnostics,
multiply by the bulk density of the medium:
The new UI in action
Bare sea ice (default):
Layered snow + ice:
Dynamics-only (no phase physics):
Custom liquidus / phase transition parameters:
Pluggable internal flux. Four shapes accepted, documented in
docs/src/physics/thermodynamics.md:Pros and cons
Pros
PhaseTransitionsmakes it structurallyimpossible for snow and ice to disagree about the liquid; the old
duplication had no guardrail.
ρ × ℒ(T)split intoper-mass
ℒ(T)× bulk density disentangles the two roles of ρ. Thepreviously-hidden sign behaviour of the Stefan correction is now
covered by machine-precision energy-conservation tests.
thermodynamics) is simpler because the two slabs are no longer
welded together by the constructor hack. Custom internal fluxes are
supported via four documented paths.
of state surgery from
sea_ice_model.jl.ice_thermodynamicsandsnow_thermodynamicskeyword args continueto work, because the new
phase_transitionsandsnow_densitydefault sensibly.
Cons
density,heat_capacity,or
reference_latent_heatonsnow_slab_thermodynamics, or readingphase_transitionsoff aSlabThermodynamics, or indexing into.internal_heat_flux.parameters.flux, needs to be updated. Users ofdefault construction are unaffected.
SeaIceModel. Two new kwargs(
phase_transitions,snow_density) add API surface. Both havesafe defaults.
layered snow+ice kernel hardcodes
IceSnowConductiveFlux(ks, ki)built from each layer's
.conductivity. A user who wants anon-conductive layered coupling still has to touch the layered
kernel. A future PR can lift this via a
combined_internal_flux(ice, snow)trait.SlabThermodynamics.internal_heat_fluxtype is looser. It nowholds a raw flux coefficient and the tendency kernel wraps it
inline. Users who relied on the pre-wrapped
FluxFunctionshapefor introspection need to adapt.
Test plan
test_snow_thermodynamics.jl— 20/20 (layered physics,flooding, accumulation, melt ordering).
test_energy_conservation.jl— 6/6 at machine precision(
< 1e-15) across bare/snow/freeze/melt/precipitation.test_sea_ice_advection.jl— 10/10 (bare, layered,dynamics-only).
test_checkpointing.jl— 155/155 (all pickup modes; bare,ice+dynamics, snow+dynamics).
144-combo matrix in
test_time_stepping.jlskipped locally forCI time; will be exercised on CI.
🤖 Generated with Claude Code