From 6c5cea40096b2a09e09cfb0c4a2209f142911776 Mon Sep 17 00:00:00 2001 From: sit23 Date: Wed, 26 Aug 2026 15:08:21 +0100 Subject: [PATCH] Restart pbltop and gust in idealized_moist_phys to fix moist restart inconsistency pbltop and gust are one-timestep-lagged coupling variables: vert_turb_driver diagnoses each of them once per step, and the value is consumed by damping_driver (pbltop) or surface_flux (gust) at the start of the *next* step. Neither was ever written to or read from a restart file, so every restarted leg re-entered with gust reset to its cold-start default (1.0) and pbltop reset to whatever was left in freshly allocated (uninitialized) memory, instead of the value a continuous run would have carried forward. This injected a real perturbation at every restart boundary that grew through the coupled nonlinear dynamics, so a moist run split into several restarted legs diverged from the same run executed in one go. Held-Suarez was unaffected because it never exercises this turbulence/diffusion code path (turb=.true.). Confirmed via reproduction: before this change, 2 days run continuously vs. 1+1 days with a restart in between gave bit-identical results for Held-Suarez but substantially different results for the Frierson aquaplanet test case, with the divergence already present after a single post-restart timestep. After restoring pbltop/gust via a new idealized_moist_phys.res restart file (same read_data/write_data pattern already used by mixed_layer.F90), both cases are now bit-for-bit identical either way. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01VKDGrWKLSWXdYytsu4Gx8P --- .../driver/solo/idealized_moist_phys.F90 | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/atmos_spectral/driver/solo/idealized_moist_phys.F90 b/src/atmos_spectral/driver/solo/idealized_moist_phys.F90 index cfcbb460..db598717 100644 --- a/src/atmos_spectral/driver/solo/idealized_moist_phys.F90 +++ b/src/atmos_spectral/driver/solo/idealized_moist_phys.F90 @@ -7,7 +7,7 @@ module idealized_moist_phys_mod #endif use fms_mod, only: write_version_number, file_exist, close_file, stdlog, error_mesg, NOTE, & - FATAL, WARNING, read_data, field_size, uppercase, mpp_pe, check_nml_error + FATAL, WARNING, read_data, write_data, field_size, uppercase, mpp_pe, check_nml_error ! cp_air needed for rrtmg and pstd_mks needed for pref calculation use constants_mod, only: grav, rdgas, rvgas, cp_air, PSTD_MKS, dens_h2o, dens_vapor @@ -505,6 +505,14 @@ subroutine idealized_moist_phys_init(Time, Time_step_in, nhum, rad_lon_2d, rad_l allocate(rough_heat (is:ie, js:je)); rough_heat = roughness_heat allocate(rough_moist (is:ie, js:je)); rough_moist = roughness_moist allocate(gust (is:ie, js:je)); gust = 1.0 +! gust, like pbltop below, is a one-timestep-lagged coupling variable: it is +! consumed by surface_flux() before vert_turb_driver recomputes it each step +! (see the "turb" block further down). Restore it from a restart if present so +! a restarted leg does not reset the surface gustiness back to its cold-start +! default of 1.0 for one spurious timestep. +if (file_exist('INPUT/idealized_moist_phys.res.nc')) then + call read_data(trim('INPUT/idealized_moist_phys.res'), 'gust', gust, grid_domain) +endif allocate(z_pbl (is:ie, js:je)) allocate(flux_t (is:ie, js:je)) allocate(flux_q (is:ie, js:je)) @@ -580,6 +588,15 @@ subroutine idealized_moist_phys_init(Time, Time_step_in, nhum, rad_lon_2d, rad_l allocate (albedo (is:ie, js:je)) ! allocate for albedo, to be set in mixed_layer_init. allocate(coszen (is:ie, js:je)) ! allocate coszen to be set in run_rrtmg allocate(pbltop (is:ie, js:je)) ! allocate coszen to be set in run_rrtmg +pbltop = 0.0 +! pbltop is a one-timestep-lagged coupling variable fed from vert_turb_driver's +! diagnosed PBL height into damping_driver's sponge calculation (see below). Without +! restarting it, every restarted leg re-enters with pbltop reset to 0 instead of the +! value a continuous run would have carried in memory, perturbing the sponge tendency +! and causing restarted moist runs to diverge from an equivalent continuous run. +if (file_exist('INPUT/idealized_moist_phys.res.nc')) then + call read_data(trim('INPUT/idealized_moist_phys.res'), 'pbltop', pbltop, grid_domain) +endif allocate(pref(num_levels+1)) ! reference pressure profile, as in spectral_physics.f90 in FMS 2006 and original MiMA. allocate(p_half_1d(num_levels+1), ln_p_half_1d(num_levels+1)) @@ -1511,6 +1528,9 @@ end subroutine idealized_moist_phys !================================================================================================================================= subroutine idealized_moist_phys_end +call write_data(trim('RESTART/idealized_moist_phys.res'), 'pbltop', pbltop, grid_domain) +call write_data(trim('RESTART/idealized_moist_phys.res'), 'gust', gust, grid_domain) + deallocate (dt_bucket, filt) if(two_stream_gray) call two_stream_gray_rad_end if(lwet_convection) call qe_moist_convection_end