awiesm3: coupled-slab ice surface temperature, cavity hnode reset, iceberg bounds-check option - #966
Open
JanStreffing wants to merge 16 commits into
Open
awiesm3: coupled-slab ice surface temperature, cavity hnode reset, iceberg bounds-check option#966JanStreffing wants to merge 16 commits into
JanStreffing wants to merge 16 commits into
Conversation
The received atmosphere->ice flux a2ihf is frozen over the OASIS coupling
interval; the explicit skin relaxation therefore has no flux-temperature
feedback and its equilibrium t* = TFrezs + a2ihf*zsniced/con scales with the
snow/ice insulation. ice_surftemp now linearizes the flux around the anchor
tref (the ist actually transmitted to the atmosphere, captured at the OASIS
send into ice%atmcoupl%ist_ref):
Q(t) = a2ihf + zlam*(tref - t), zlam = 4*eps*sigma*tref^3 + zlam_turb
zlam_turb (16 W/m2/K) is the bulk near-neutral turbulent sensitivity
rho*cp*C_H*|U|; radiative + turbulent match the ~20 W/m2/K flux derivative
used as the standard fallback in NEMO/SI3-family couplings (EC-Earth4's
exported A_dQns_dT is the radiative part only). Energy the linearization
adds/removes is repaid from latent heat via qlam in the ice growth budget
(Qatmice = -qres-qcon+qlam), which reduces to the ECHAM-branch -a2ihf in
steady state, so the atmosphere-booked flux is conserved exactly. The melt
residual qres includes zlam for consistent booking at the 273.15 K cap.
Also carries a temporary log-only ICEDBG diagnostic (received a2ihf + state
at t < 225 K) for the descent-episode investigation; strip before merging.
…d growth flux Companion to the OIFS coupled-slab change (atmosphere solves its ice-tile skin implicitly with slab conduction through the coupled FESOM ice state): - new o2a field sit_feom = m_ice (effective grid-mean sea-ice thickness; nsend 7->8). The atmosphere divides by the received ice fraction for the per-ice thickness its slab conduction needs. - ist send restored to the concentration-weighted convention (ist*a_ice), paired with ECE_CPL_NEMO_WEIGHTED_ICE; inert while the atmosphere owns the ice temperature (LNEMOLIMTEMP=.false.) but keeps the o2a blend representative if the ingest is ever re-enabled. - new namelist.ice logical latm_owns_ist (default .false.): when the atmosphere owns the ice surface temperature, the ice growth budget takes the received atmosphere flux directly (Qatmice=-a2ihf, ECHAM convention) and FESIM's own surface solve is internal-only (albedo/melt-pond state). - ice_surftemp: bulk near-neutral turbulent term added to the implicit flux linearization (total ~20 W/m2/K, the NEMO/SI3-family fallback derivative). - temporary log-only ICEDBG descent diagnostic (strip before merging).
…_ale Cavity columns and their open-ocean edge ring (ulevels_nod2D_max>1, the same exclusion class as the per-step hnode<->hnode_new sync) keep layer thicknesses fixed at the mesh nominal. A restart hnode deviating from that nominal (e.g. remapped from a formerly ice-free zstar column after a moving-cavity mesh change) persists forever, and the tracer T* update turns the mismatch into a multiplicative content drain every step (both tracers decay by (hnode_new-hnode)/hnode_new per step; validated with per-operator probes). Reset hnode to hnode_new after the restart read and rebuild the depth arrays.
The cold-blowup descent investigation these lines served is closed (multi-year coupled spin-ups run clean below the polar-night range), so drop the log-only diagnostic block and its line counter.
…ns-ist path The atmosphere owns the ice surface temperature (OIFS coupled-slab mode) in every production configuration, so the latm_owns_ist runtime switch and the FESIM-owns-ist growth-flux convention it selected (Qatmice=-qres-qcon+qlam, with the qlam latent-heat closure) are dead. Remove the switch, the dead branch, and the qlam bookkeeping; Qatmice=-a2ihf unconditionally. The dQ/dT (zlam/tref/ist_ref) linearization stays: ice_surftemp still runs to set the internal skin temperature used for albedo/melt-pond state.
fer_solve_Gamma solves the GM streamfunction over nzmin = ulevels_nod2D_max(n), nzmax = nlevels_nod2D_min(n) (max/min over the node's surrounding elements). When a node touches both a deep cavity element and a shallow one these can invert (nzmin >= nzmax). Both sweeps then run zero times, but "tr(:,nzmax) = tp(:,nzmax)" still executes and publishes an unassigned element of the automatic array tp into fer_gamma. Such a node carries no GM transport, so zero its column and skip. Also guard two non-short-circuit .and. expressions that read index 0: ice_maEVP.F90 ulevels(edge_tri(2,ed)) at boundary edges (edge_tri(2,ed)==0) io_xios.F90 angles(k) at the end of each insertion-sort sweep (cherry picked from commit 58ccb44)
'alb' (ice%atmcoupl%ice_alb) is registered as an output stream in io_meandata and declared detect_missing_value="true" in field_def, but it is missing from io_xios_is_ice_field. The sender therefore never writes _FillValue into ice-free cells and the field falls through to the generic wet-point mask, so XIOS time-means of sea-ice albedo include open water, where the quantity is not defined. Add 'alb' to the ice-field list alongside ist/a_ice and the rest. (cherry picked from commit 93b8526)
An ice-sheet-coupled run legitimately reaches zero icebergs: a cold start before the ice sheet has discharged anything, or a leg where it discharges into no basin. Nothing in the iceberg code tests ib_num against zero, and the stepping itself is fine with it -- the loops are do ib = 1, ib_num and the work arrays are dimension(16*ib_num), both harmless when empty. Only the diagnostic output is not. init_buoy_output does nf_def_dim(ncid, 'number_tracer', ib_num, dimid_ib) nf_def_dim(ncid, 'time', NF_UNLIMITED, dimid_rec) and in classic netCDF a zero-size dimension is unlimited, so the second call fails with NC_UNLIMITED size already in use and takes the run down at step 1. Guard the five call sites on ib_num > 0. Switching the file to NF_NETCDF4 would also fix it, since there zero-size dimensions are ordinary and several unlimited ones are allowed, but that changes the output format for everyone.
The guard that skips halo nodes puts both tests in one .or., where the left test is meant to keep the right one from running. Fortran does not work that way and ifort evaluates both sides, so ulevels_nod2d(0) is read on exactly the case the guard exists to skip. Split it into two tests.
Brings:
- cap_ibhf_n, capping iceberg-driven cooling at -1.85 C per cell/step,
toggled by namelist icebergs%l_cap_ibhf_n (default .true.)
- a guard marking a berg melted when iceberg_elem falls outside
1..elem2D, instead of the par_ex+stop that hangs every rank
- trajectory() skipped only when l_allowgrounding==2 AND the berg is
actually grounded, not globally
- cos(lat) floor in trajectory() against divide-by-zero at high latitudes
It was defaulted ON to chase the corrupted iceberg_elem, and it did its job: it caught the ulevels_nod2d(0) read within 100 timesteps, now #963. That hunt is over, and -check bounds at -O0 on the code that runs per berg per step is not something a production leg should pay for. Still available with -DFESOM_ICB_BOUNDS_CHECK=ON.
local_idx_of is saved but was allocated at each of the five iceberg dump sites and never deallocated, so the second node to blow up on a rank hit "forrtl: severe (151): allocatable array is already allocated" inside the reporter. The run then died on the diagnostic rather than on the blowup, and the message naming the node and the failing check was never reached. Only reachable with use_icebergs, which is why it sat unnoticed since the sites were added a year ago.
global2local zeroes every element the rank does not own, so for an iceberg elsewhere in the domain local_idx_of(iceberg_elem(ib)) is 0 and the next line indexes elem2d_nodes at 0. With icebergs spread over all ranks that is almost every iceberg on almost every rank, so the dump segfaults as soon as it reaches the loop. It never reached it before: the allocate above aborted first. Same shape as 8aaca2d, which stopped ulevels_nod2d being indexed with a halo node marked 0.
Brings in the 3-equation cavity melt fix (#875), without which cavity_heat_water_fluxes_3eq takes sqrt() of a negative discriminant and fills every ice-shelf column with NaN. On the corrected CORE3 mesh that killed a coupled leg at mstep=1 on 121 ranks, all at Antarctic cavity nodes, while the same mesh ran a full standalone year on a FESOM that had the fix. Also brings in 501a808, which drops the obsolete per-tracer _AB restart variables, so restarts written by current main are readable here. One conflict, in gen_forcing_couple.F90 after cpl_oasis3mct_send: this branch adds the __oifs ist_ref anchor (#957) and main adds the __recom/__usetp endif, and both sides opened a block sharing one #endif. Resolved by keeping both, each with its own guard.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The branch AWI-ESM3
develop-isis built from. Opening it for visibility and review, not to be merged yet: see the ordering note at the bottom.What is in it that is not already on main
cpl(__oifs)coupled-slab ice surface temperature. The implicit,dQ/dT-linearised skin solve,sit_feomsent as effective ice thickness, and the atmosphere owning the growth flux in coupled-slab mode (Qatmice = -a2ihf). This is the same work as cpl(__oifs): coupled-slab ice surface temperature + sea-ice-thickness exchange #957, carried here because the AWI-ESM3 build needs it: a binary withoutsit_feomaborts every component at startup with(oasis_coupler_setup) ERROR: namcouple variable not used: sit_feom.cavity: reset restarthnodeto the fixed nominal inrestart_thickness_ale, which is cavity: reset restart hnode to the mesh nominal in restart_thickness_ale (moving cavity) #958.FESOM_ICB_BOUNDS_CHECK, an opt-in CMake option that builds only the iceberg sources with-check bounds -check pointers -traceback -g -O0, leaving the rest of the model optimised. Off by default. It is here because it earned its keep: it caught theulevels_nod2d(0)read of icebergs: do not index ulevels_nod2d with a halo node marked 0 #963 within 100 timesteps of a coupled run, on code that had been reading out of bounds silently.Already on main, listed so reviewers are not surprised to see the commits
#962, #963 and #964 all merged while this branch was in use, so the branch's own copies of them are redundant. The merge commit for #964 is still in the history here.
Do not merge this before the others
This must go after #960 and #961, which are the same changes as standalone PRs and should be reviewed on their own terms rather than swept in as part of a large branch. If #957 is the preferred home for the coupled-slab work, that should merge first too and this branch should be rebased onto it, leaving only the cavity
hnodereset and the CMake option here.Merging this first would land four separable changes in one commit and make the individual PRs harder to review or revert.
Validation
Runs coupled in AWI-ESM3 (FESOM2 CORE3 cavity + OIFS 48r1 + PISM), including across a mesh-change leg with icebergs: a full year with
ib_num = 1883, 948 carried and 935 calved, completing normally.