Port CLM cryosphere and hydrology fixes into ELM - #8638
Conversation
Allow frac_sno_eff and melt-compaction ddz3 fractional forms for all non-urban, non-lake columns when subgrid fluxes are on, matching ctsm5.4.042 behavior for glacier and wetland columns. Co-authored-by: Cursor <cursoragent@cursor.com>
Do not scale bedrock volumetric heat capacity by soil porosity (1-watsat). Below nlevbed, use solid-rock heat capacity times layer thickness, consistent with ctsm5.4.042. Co-authored-by: Cursor <cursoragent@cursor.com>
Skip inactive subgrid points when updating timeavg, runmean, and runaccum fields; track per-point nsteps for correct averaging and restart, matching the ctsm5.4.042 accumulator redesign. Co-authored-by: Cursor <cursoragent@cursor.com>
| !if (use_modified_infil) then | ||
| if (frac_h2osfc_act(c) <= pc .and. frac_h2osfc(c) <= pc) then | ||
| frac_infclust=0.0_r8 | ||
| else | ||
| if (frac_h2osfc(c) <= pc) then | ||
| if (frac_h2osfc_act(c) <= pc) then | ||
| frac_infclust=(frac_h2osfc_act(c)-pc)**mu | ||
| else | ||
| frac_infclust=(frac_h2osfc(c)-pc)**mu | ||
| frac_infclust=(frac_h2osfc_act(c)-pc)**mu | ||
| endif | ||
| endif | ||
| else | ||
| if (frac_h2osfc(c) <= pc) then | ||
| frac_infclust=0.0_r8 | ||
| else | ||
| frac_infclust=(frac_h2osfc(c)-pc)**mu | ||
| endif | ||
| endif | ||
| !else | ||
| ! if (frac_h2osfc(c) <= pc) then | ||
| ! frac_infclust=0.0_r8 | ||
| ! else | ||
| ! frac_infclust=(frac_h2osfc(c)-pc)**mu | ||
| ! endif | ||
| !endif |
There was a problem hiding this comment.
It seems to me that we are making the use_modified_infil scheme as the default. So, why are we not doing it through namelist instead of commented out the old code and making the use_modified_infil as the default?
Or, if use_modified_infil needs to be made default, let's remove it from the namelist and delete the code corresponding to .not. use_modified_infil.
There was a problem hiding this comment.
use_modified_infil is still used to control how the infiltration capacity is computed for different sub fractions. This modification just remove the control of use_modified_infil on the surface water runoff generation.
There was a problem hiding this comment.
Pull request overview
This pull request ports a set of CLM cryosphere and hydrology fixes into ELM, focusing on snow energy / compaction behavior, soil thermal properties, accumulator handling for inactive subgrid elements, surface-water runoff, and snow-balance accounting within ELM biogeophysics and accumulator logic.
Changes:
- Update ELM accumulators to skip inactive / zero-weight subgrid elements and track active-step counts via restartable
_NSTEPS. - Apply CLM fixes to bedrock heat capacity handling and fractional-snow treatments (energy and melt compaction) across glacier/wetland-related pathways.
- Adjust surface-water runoff / infiltration logic and update glacier snow water-balance accounting for fractional snow cover effects.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| components/elm/src/main/accumulMod.F90 | Adds active-element masking to accumulators and introduces per-point step counts persisted via restart. |
| components/elm/src/biogeophys/SoilTemperatureMod.F90 | Fixes bedrock-layer heat capacity calculation below the soil-bedrock boundary. |
| components/elm/src/biogeophys/SoilHydrologyMod.F90 | Updates surface-water runoff / infiltration fraction logic and runoff coefficient behavior. |
| components/elm/src/biogeophys/SnowHydrologyMod.F90 | Adjusts melt-compaction gating to follow CLM fractional-snow behavior outside lake/urban points. |
| components/elm/src/biogeophys/CanopyHydrologyMod.F90 | Extends fractional-snow effective coverage logic beyond soil/crop to non-urban, non-lake columns under subgrid fluxes. |
| components/elm/src/biogeophys/BalanceCheckMod.F90 | Updates snow-balance accounting on glacier columns to reflect fractional snow-covered flux partitioning. |
Suppressed comments (1)
components/elm/src/main/accumulMod.F90:588
- Same issue as the single-level updater: in the multi-level timeavg path, reset and normalization are skipped for inactive points because they are inside
if (accum(nf)%active(k)). This can leave staleval/nstepsfor inactive points (especially after restart or dynamic reweighting) and can contaminate averages when points become active later. Apply reset/normalization for all points; only addfieldfor active points.
time_to_reset = (mod(nstep,accum(nf)%period) == 1 .or. accum(nf)%period == 1) .and. (nstep /= 0)
do j = 1,numlev
do k = beg,end
if (accum(nf)%active(k)) then
if (time_to_reset) then
accum(nf)%val(k,j) = 0._r8
accum(nf)%nsteps(k,j) = 0
end if
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| time_to_reset = (mod(nstep,accum(nf)%period) == 1 .or. accum(nf)%period == 1) .and. (nstep /= 0) | ||
| do k = beg,end | ||
| if (accum(nf)%active(k)) then | ||
| if (time_to_reset) then | ||
| accum(nf)%val(k,1) = 0._r8 | ||
| accum(nf)%nsteps(k,1) = 0 | ||
| end if | ||
| accum(nf)%val(k,1) = accum(nf)%val(k,1) + field(k) | ||
| accum(nf)%nsteps(k,1) = accum(nf)%nsteps(k,1) + 1 | ||
| if (mod(nstep,accum(nf)%period) == 0) then | ||
| if (accum(nf)%nsteps(k,1) > 0) then | ||
| accum(nf)%val(k,1) = accum(nf)%val(k,1) / real(accum(nf)%nsteps(k,1),r8) | ||
| else | ||
| accum(nf)%val(k,1) = accum(nf)%initval | ||
| end if | ||
| end if | ||
| end if | ||
| end do |
| if (h2osfcflag==1) then | ||
| ! calculate runoff from h2osfc ------------------------------------- | ||
| if (use_modified_infil) then | ||
| !if (use_modified_infil) then | ||
| if (frac_h2osfc_act(c) <= pc .and. frac_h2osfc(c) <= pc) then | ||
| frac_infclust=0.0_r8 | ||
| else | ||
| if (frac_h2osfc(c) <= pc) then | ||
| if (frac_h2osfc_act(c) <= pc) then | ||
| frac_infclust=(frac_h2osfc_act(c)-pc)**mu | ||
| else | ||
| frac_infclust=(frac_h2osfc(c)-pc)**mu | ||
| frac_infclust=(frac_h2osfc_act(c)-pc)**mu | ||
| endif |
There was a problem hiding this comment.
I agree with Copilot's review: Why is the second frac_infclust=(frac_h2osfc(c)-pc)**mu being changed to frac_infclust=(frac_h2osfc_act(c)-pc)**mu?
| ! Port of CLM fractional-snow energy fix: when subgrid fluxes are on, | ||
| ! allow fractional frac_sno_eff for all non-urban, non-lake columns | ||
| ! (including glacier and wetland), matching soil/crop treatment. | ||
| if (subgridflag == 1 .and. .not. urbpoi(l) .and. ltype(l) /= istdlak) then |
There was a problem hiding this comment.
Is the default value of subgridflag 1 or 0?
| if(h2osfc(c) >= h2osfc_thresh(c) .and. h2osfcflag/=0) then | ||
| ! spatially variable k_wet | ||
| k_wet=1.0_r8 * sin((rpi/180._r8) * col_pp%topo_slope(c)) | ||
| k_wet=1.0e-4_r8 * sin((rpi/180._r8) * max(col_pp%topo_slope(c), 1.0e-3_r8)) |
There was a problem hiding this comment.
What is the logic of reducing k_wet by four-orders of magnitude? Was this a bug identified by CLM that we are fixing here?
There was a problem hiding this comment.
A bug from the misuse of unit
czender
left a comment
There was a problem hiding this comment.
These changes are beyond my level of ELM expertise. I couldn't find anything that looked problematic that had not already been noted.
Summary
Commits
Summary
Testing
e3sm_land_developeron Frontier / craygnu (projectcli115), 2026-08-14.lnd/port-clm-cryosphere-fixes-master@fbfcc93f52master@a899004464, then compared on this branch/lustre/orion/cli115/world-shared/wangd/kmELM/baselinesa899004464Namelists (
NLCOMP) unchanged. All completed ERS tests passedCOMPARE_base_rest(restart is bit-for-bit with itself). Debug builds DIFF, they do not crash.The two walltime FAILs (no gold, so no baseline compare):
ERS.r05_r05.ICNPRDCTCBC.frontier_craygnu.elm-cbudgetERS.r05_r05.IELM.frontier_craygnu.elm-V2_ELM_MOSART_features