Skip to content

Use of newtonian relaxation within the moist model - #305

Draft
sit23 wants to merge 59 commits into
ExeClim:masterfrom
sit23:top_down_with_moisture_2026
Draft

Use of newtonian relaxation within the moist model#305
sit23 wants to merge 59 commits into
ExeClim:masterfrom
sit23:top_down_with_moisture_2026

Conversation

@sit23

@sit23 sit23 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

One test case I've been keen to have for a while is a version of the model where Newtonian relaxation of temperature is treated like the 'radiation scheme' of the full model, rather than it's own isolated version. This is implemented in this branch. In addition, this allows for the top-down option within hs_forcing to interact with the surface module, and this in turn is the scaffolding allowing the radiative_eq_mars test case to run properly.

Creating this p/r as a draft for now to enable further testing and trip tests etc.

sit23 added 30 commits January 8, 2018 16:09
…thon. This enables comparison between annual-average insolation, and e.g. the Frierson specified incoming radiation.
…own_annual_av

Merging changes from two machines into top-down-annual-av branch
Adding in newtonian cooling as a radiation scheme option.
… mixed-layer. This sets the mixed-layer temperature to the same as the ground temperature, allowing all the surface fluxes to be calculated as normal, but they do not affect the surface temperature, as this stays fixed.
…ns I can run with the monin-obukhov bl scheme only, without also having a rayleigh-drag at the surface.
…ture field, rather than radiative-convective equilibrium. This has the advantage of then feeling justified in using newtonian cooling as a radiaition scheme in a more complex model.
…ective equilibrium calculation, but using radiative equilibrium for equilibrium temperature.
…n runs, whereby the relative humidty is set to 100 percent in the bottom model layer, and the mixed-layer bc is turned off completely. That way there are no worries about dubious surface fluxes.
…tom model layer to something that is not the atmospheric timestep. That way we can allow moist convection to alter the b/l sphum without any changes being immediatly overruled.
…ium. That way we should get a more convectively unstable atmosphere.
…aranteed to be suitably colder than the surface and so have reasonable surface fluxes.
Merging latest master changes to docs and python.
…Means can have seasonal varying teq in rad equil modes.
…op_down_with_moisture

Merging other top-down changes.
…of solar constant. Now incoming sw varies with time when orbit is eccentric.
…at ls=0 as perihelion is always at true-anomaly = 0.. Offset of 1.905637 radians is so that perihelion at ls=251 degrees, as on lmd website. Also as in sol-ls.pro program that Luca wrote that is currently in sharecode. Problem now is that solstices etc in wrong place.
sit23 and others added 28 commits April 23, 2018 10:44
Merging in latest master changes
…moisture source in the model. This often makes it crash if you are doing dry planets other than Earth. Therefore added option, as in mars-dev commit 1ca7b41, such that false temperatures can be fed to escomp so that it doesn't crash, then real temperatures are subsequently used.
Merge in latest master changes.
Merging latest 3 master commits
Conflicts:
	src/atmos_param/hs_forcing/hs_forcing.F90
	src/atmos_spectral/driver/solo/idealized_moist_phys.F90
…sed with gfdl astronomy. Useful as can now apply existing mars orbit numbers to this.
…h that this model will assume no short-wave absorption, which we definitely need for Titan. Aborting this for now.
…ure_2026

# Conflicts:
#	postprocessing/plevel_interpolation/scripts/run_plevel.py
#	src/atmos_param/hs_forcing/hs_forcing.F90
#	src/atmos_spectral/driver/solo/atmosphere.F90
#	src/atmos_spectral/driver/solo/idealized_moist_phys.F90
#	src/atmos_spectral/driver/solo/mixed_layer.F90
#	src/coupler/surface_flux.F90
#	src/extra/python/scripts/remove_certain_restart_and_data_files.py
#	src/shared/astronomy/astronomy.f90
#	src/shared/constants/constants.F90
…ostics

Both were unconditional and used real (unclamped) temperatures, so on a
dry/cold planet run with use_actual_surface_temperatures=.false. (e.g.
radiative_eq_mars) they could crash lookup_es_2d with a saturation vapor
pressure table overflow, even though neither diagnostic (rh_2m, e_sat) was
requested:

- surface_flux.F90: the 2m/10m diagnostic block's escomp(temp_2m, ...) now
  only runs when use_actual_surface_temperatures is true, matching how the
  other escomp calls in this subroutine already treat that flag.
- idealized_moist_phys.F90: escomp(t_surf, e_sat_out) now only runs when the
  e_sat diagnostic (id_e_sat) is actually registered, matching the existing
  guard on the send_data call right after it.

Neither call existed in top_down_with_moisture's original file - both were
added to master after the fork.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Isolated to two independent issues, both now fixed:

1. hs_forcing's generic tracer_source_sink (trflux/trsink, defaulting to a
   nonzero surface flux/sink meant for an optional test tracer) applies to
   every tracer it's passed, including sphum, unless the field_table
   registers a 'tracer_sms' override for that tracer - which the grey
   model's field_table doesn't. radiative_eq_mars routes "radiation" through
   hs_forcing (do_newtonian_cooling_as_rad), so this was silently adding a
   small but steady moisture source to an otherwise fully dry setup every
   timestep. Fixed by zeroing trflux/trsink in the test case's own
   hs_forcing_nml, since this experiment has no optional tracer of its own.

2. gcm_vert_diff_down/up pass the moisture tendency both as a dedicated dt_q
   argument and, aliased to the exact same memory, as part of the full
   dt_tracers(:,:,:,:) tendency array - even though the sphum slice of that
   array is never written by the generic tracer loop (vert_diff_init sets
   tracers(sphum)%do_vert_diff=.false. specifically to avoid that). Passing
   the same actual array to two dummy arguments that can each be defined
   (both intent(out) in gcm_vert_diff_up) isn't standard-conforming
   regardless of which ends up actually written, and is exactly the kind of
   thing that can behave inconsistently across compilers/optimization
   levels. Routed the moisture tendency through its own scratch array
   (dt_qg_vert_diff) so it never shares memory with dt_tracers(:,:,:,:)
   during these two calls.

Also softened surface_flux.F90's q_atm_in>0. dry-model sanity check to a
small tolerance (pure floating-point noise guard, not a real threshold
change) now that q_surf0 is matched to q_atm whenever
use_actual_surface_temperatures=.false., closing the one remaining path
that could otherwise inject an unbounded surface-driven moisture source
into a nominally dry run.

Verified against radiative_eq_mars_test_case.py both shortened (3 mars
days) and at its full, unmodified length (30 mars days): compiles cleanly,
runs to completion with no FATAL errors, and produces NaN-free output in a
physically plausible Mars temperature range (t_surf 96-230K, temp
118-281K).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An aquaplanet-style world with land everywhere (land_option='all_land', no
input mask needed) and bucket hydrology active at every gridpoint, given a
generous non-limiting initial depth. Unlike a standard mixed-layer
aquaplanet - where the ocean is an effectively infinite moisture source/sink
- every gridpoint here has a finite water reservoir, so total column water
(atmosphere + bucket) should be exactly conserved in the absence of
numerical bugs, making it a useful check on tracer-conserving vertical
diffusion.

Used to test whether the gcm_vert_diff_down/up argument-aliasing fix in the
previous commit changes global moisture conservation: ran 10 simulated days
both with and without that fix (same source otherwise) and compared
area-weighted global-mean total water day by day. Results were bit-identical
between the two builds with this compiler/optimization level (gfortran -O2)
- the aliasing violation doesn't appear to be miscompiled here, though it
remains worth having fixed as a matter of correctness, since that's exactly
the kind of undefined behaviour that can vary by compiler or optimization
level.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tp branch

do j=1,size(t,1) should be do j=1,size(t,2) - t's first dimension is the
local per-PE longitude extent, the second is latitude, and h_trop/zfull/teq
are indexed (lon,lat) here. With domain decomposition splitting latitude
across PEs (e.g. X-AXIS=128, Y-AXIS=16 per PE, as trip_test's default 4-core
runs use), this looped j up to 128 instead of 16, writing 8x past the end of
h_trop/zfull's second dimension - a heap-corrupting out-of-bounds write.

This is a pre-existing bug from top_down_with_moisture's original
2018-2020 source (confirmed via git show against that branch directly),
not something introduced by the merge. master's own (simpler, without the
pure_rad_equil option) version of this same loop has the correct
size(t,2), which is what caught this: master's version was never wrong,
top_down_with_moisture's superset version - which the merge conflict
resolution correctly kept for the pure_rad_equil support master lacks -
carried the bug forward silently, apparently never having been trip-tested
against a build that would surface it as a crash rather than quiet memory
corruption.

Found via a "double free or corruption (out)" crash in top_down_test (one
of trip_test's default 19 cases) immediately after hs_forcing_init's
heat-capacity spin-up completes. Verified fixed by rerunning
top_down_test_case.py's full configured length (3 x 90-day segments) to
completion with no crash - previously it crashed within seconds of model
start.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@sit23

sit23 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

Trip test results (summarised by Claude) are as follows:

Trip test results: top_down_with_moisture_2026 vs master

Ran the full default 19-case trip_test suite comparing this branch's HEAD
(106c6604) against master (d1321ac3). Summary: clean for every test
case that could actually be run and compared.
No test case that produced
a valid comparison shows any difference from master.

Summary

Result Count Test cases
✅ Bit-identical to master 13 bucket_model, frierson, giant_planet, held_suarez, MiMA, realistic_continents_fixed_sst, realistic_continents_variable_qflux, variable_co2_rrtm, barotropic_vort_eq_stirring, shallow_water_stirring, column_test, top_down_test, variable_co2_grey
❌ Pre-existing failure, unrelated to this branch 1 axisymmetric
⏭️ Excluded - Socrates not fully built in this environment 3 socrates_aquaplanet, socrates_aquaplanet_cloud, ape_aquaplanet
⏭️ Excluded - trip_test harness gap (see below) 2 grey_mars, radiative_eq_mars

Two real bugs found and fixed along the way

1. Out-of-bounds array write in hs_forcing.F90. top_down_test
crashed with double free or corruption (out). Root cause: the
extend_tp branch of top_down_newtonian_damping looped
do j=1,size(t,1) instead of do j=1,size(t,2) when indexing
h_trop/zfull (which are (lon,lat)), overrunning the local per-PE
latitude extent by up to 8x under a typical 4-core decomposition. Confirmed
via git show against top_down_with_moisture's original 2018-2020
source that this typo predates the merge - master's own (simpler)
version of this same loop already has the correct bound, which is what
caught it. Fixed, and verified by running top_down_test's full configured
length (3 x 90-day segments) to completion with no crash.

2. Undefined-behaviour argument aliasing in gcm_vert_diff_down/_up.
The moisture tendency was passed both as its own dedicated dt_q argument
and, aliased to the same memory, as part of the full dt_tracers(:,:,:,:)
array - not standard-conforming even though the generic tracer loop never
actually touches that slice. Routed it through a dedicated scratch array
instead. Verified via three independent isolation tests (with vs without
just this fix) that it produces no observable difference under this
compiler (gfortran -O2) - worth having fixed as a correctness matter
regardless, since this is exactly the kind of bug that can vary silently
by compiler or optimization level.

On the excluded cases

  • The three Socrates-based cases can't build in this environment
    (missing Socrates source files) - unrelated to this branch.
  • grey_mars/radiative_eq_mars fail on a run-length/timestep mismatch:
    this branch's trip_test_functions.py doesn't have the Mars-specific
    run-length override that exists only on a separate branch
    (mars_dust_lh), never merged in here. Not a regression.
    radiative_eq_mars - the test case that motivated this whole
    reconciliation - has separately been verified extensively outside
    trip_test: both a shortened smoke test and the full, unmodified 30
    mars-day run complete cleanly, with NaN-free output in a physically
    plausible range (t_surf 96-230K, temp 118-281K).

An apparent variable_co2_grey divergence turned out to be a false alarm
from stale cached data (the original run crashed and was resumed several
times against the same default work/data directories) - confirmed clean
with three independent fresh-directory comparisons and cleaned up the
stale cache.

@sit23

sit23 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

So we've verified that these top-down modifications don't alter existing results, and they do allow the radiative-eq-mars test case to run, which is great. I would suggest that the mars dust branch gets merged first, and then we move onto this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant