ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_RadStruct.H
Go to the documentation of this file.
1 #ifndef ERF_RAD_STRUCT_H_
2 #define ERF_RAD_STRUCT_H_
3 
4 #include <cmath>
5 #include <string>
6 #include <iostream>
7 
8 #include <AMReX_ParmParse.H>
9 #include <AMReX_ParallelDescriptor.H>
10 #include <AMReX_Print.H>
11 #include <AMReX_Gpu.H>
12 #include <ERF_IndexDefines.H>
13 #include <ERF_TurbStruct.H>
14 
15 /**
16  * @file ERF_RadStruct.H
17  * @brief Radiation model type and control parameters.
18  */
19 
20 /**
21  * @brief Optical depth profile type for the two-stream solver.
22  *
23  * - Constant: tau_per_layer / tau_lw_per_layer applied uniformly at every
24  * level, independent of layer thickness.
25  * - CloudLayer: an additional cloud optical depth is added within
26  * [cloud_base_height_m, cloud_top_height_m], on top of the clear-sky
27  * background tau_per_layer / tau_lw_per_layer.
28  */
29 AMREX_ENUM(TauProfileType, Constant, CloudLayer);
30 
31 /**
32  * @brief Aerosol optical depth profile type for the two-stream solver.
33  *
34  * - Constant: uniform aerosol tau_per_layer applied at every level.
35  * - Exponential: aerosol tau decays exponentially with height via
36  * tau_aerosol(k) = tau_surface * exp(-z(k) / scale_height).
37  * - Table: aerosol tau prescribed per level via lookup (future extension).
38  */
39 AMREX_ENUM(AerosolProfileType, Constant, Exponential, Table);
40 
41 /**
42  * @brief How the clear-sky optical depth of a layer is obtained.
43  *
44  * - PerLayer: the fixed tau_per_layer / tau_lw_per_layer for every layer
45  * regardless of its thickness (the original model; the column optical
46  * depth then scales with the number of vertical cells).
47  * - Mass: from the layer mass path, tau = rho dz (k_dry + k_vapor qv + ...),
48  * so the column optical depth is set by the atmosphere, not by the grid.
49  * Shortwave uses sw_kabs_dry, sw_kscat_dry (Rayleigh, scattering),
50  * sw_kabs_vapor and sw_kext_cloud with sw_cloud_omega / sw_cloud_g;
51  * longwave uses lw_kabs_dry, lw_kabs_vapor and lw_kabs_cloud.
52  */
53 AMREX_ENUM(TauModel, PerLayer, Mass);
54 
55 /**
56  * @brief Container holding radiation-related choices and parameters.
57  *
58  * Manages the runtime selection and configuration of radiation solvers.
59  *
60  * The two-stream solver builds up from a clear-sky, no-scattering column of
61  * uniform optical depth. On top of that it offers height-varying optical
62  * depth (a cloud layer) with cloud-fraction masking, which blends clear-sky
63  * and cloudy-column fluxes, and a diffuse shortwave component from a
64  * two-stream scattering approximation parameterized by single-scattering
65  * albedo and asymmetry factor, with independent clear-sky and cloud values.
66  * Every addition is off by default, so the defaults reproduce the plain
67  * clear-sky column.
68  */
69 struct RadChoice
70 {
71 public:
72  /**
73  * @brief True when erf.radiation_model = TwoStream. Set by init_params from
74  * the model SolverChoice read; the erf.radiation.* sub-options are read only then.
75  */
76  bool enabled = false;
77 
78  /**
79  * @brief Enable shortwave (solar) radiation computation.
80  * Only used by the two-stream model.
81  */
82  bool sw_enabled = true;
83 
84  /**
85  * @brief Enable longwave (thermal) radiation computation.
86  * Only used by the two-stream model.
87  */
88  bool lw_enabled = true;
89 
90  /**
91  * @brief Shortwave optical depth per layer (constant for all layers).
92  * Used in Beer-Lambert direct-beam formula.
93  * Only used by the two-stream model.
94  */
96 
97  /**
98  * @brief Longwave optical depth per layer (constant for all layers).
99  * Used in gray-gas LW two-stream solver.
100  * Only used by the two-stream model.
101  */
103 
104  /**
105  * @name Inputs shared with the RRTMGP interface
106  * The two-stream model reads the sun, site and surface-temperature inputs
107  * under the same keys, with the same meaning, as the RRTMGP interface
108  * (Source/Radiation/RRTMGP/ERF_Radiation.H), so a deck switches
109  * between the two models by changing erf.radiation_model alone.
110  * @{
111  */
112 
113  /**
114  * @brief erf.fixed_solar_zenith_angle: the COSINE of the solar zenith
115  * angle (mu0), as RRTMGP takes it, applied to every column when > 0.
116  * Otherwise (default) the sun follows the calendar: start_datetime sets
117  * the date and time, the position is evaluated at rad_cons_lat/lon or at
118  * the per-column latitude and longitude when the grid carries them.
119  */
121 
122  /**
123  * @brief erf.fixed_total_solar_irradiance: top-of-atmosphere irradiance
124  * [W/m^2] when >= 0. Otherwise (default) 1360.9 W/m^2 scaled by the
125  * Earth-Sun distance factor of the date, as RRTMGP does.
126  */
128 
129  /**
130  * @brief erf.rad_t_sfc: surface temperature [K] used as the longwave
131  * boundary condition wherever no land-surface model, surface layer or
132  * prognostic surface energy balance supplies one; also the initial value
133  * of the prognostic surface temperature. Required, as for RRTMGP.
134  */
136 
137  /** @brief erf.rad_cons_lat / erf.rad_cons_lon: site latitude and longitude [degrees] when the grid has no lat/lon fields (RRTMGP defaults). */
139  amrex::Real rad_cons_lon = -98.555183;
140 
141  /**
142  * @brief erf.rad_orbital_year and the orbital overrides: negative means
143  * the year of the timestamp and the Berger (1978) values for that year.
144  */
145  int rad_orbital_year = -9999;
149  /** @} */
150 
151  /**
152  * @brief Optical depth model: "per_layer" (default, fixed tau per layer)
153  * or "mass" (from the layer mass path in both bands; see TauModel).
154  */
155  TauModel tau_model = TauModel::PerLayer;
156 
157  /** @brief Mass model, SW: dry-air gray absorption coefficient [m^2/kg]. */
159 
160  /** @brief Mass model, SW: dry-air (Rayleigh) scattering coefficient [m^2/kg], omega = 1, g = 0. */
162 
163  /** @brief Mass model, SW: water-vapor gray absorption coefficient [m^2/kg]. */
165 
166  /** @brief Mass model, SW: cloud-water extinction coefficient [m^2/kg] (~1.5/r_e with r_e = 10 um). */
168 
169  /** @brief Mass model, SW: cloud-water single-scattering albedo. */
171 
172  /** @brief Mass model, SW: cloud-water asymmetry factor. */
174 
175  /**
176  * @brief Gray longwave optical depth from the layer mass path instead of
177  * the fixed tau_lw_per_layer (also selected by tau_model = mass):
178  * tau_lw(k) = rho dz (lw_kabs_dry + lw_kabs_vapor qv + lw_kabs_cloud qc)
179  * The cloud term is the Stephens (1978) emissivity, eps = 1 - exp(-a LWP)
180  * with a = 0.158 m^2/g, written as an optical depth. The column optical
181  * depth is then independent of the vertical resolution. The cloud-band,
182  * moisture-coefficient and aerosol additions still apply on top.
183  * Default false keeps tau_lw_per_layer.
184  */
186 
187  /** @brief Dry-air gray LW mass absorption coefficient [m^2/kg]. */
189 
190  /** @brief Water-vapor gray LW mass absorption coefficient [m^2/kg]. */
192 
193  /** @brief Cloud-water LW mass absorption coefficient [m^2/kg] (Stephens 1978: 0.158 m^2/g). */
195 
196  /**
197  * @brief Debug verbosity level (0=off, 1+=debug output).
198  * Gated debug prints only emit when this is >= 1.
199  */
200  int verbosity = 0;
201 
202  /**
203  * @brief Output file path for radiation diagnostics CSV.
204  * Paths are relative to the simulation working directory.
205  */
206  std::string diag_file = "radiation_diag.dat";
207 
208  /**
209  * @brief Master switch for radiation diagnostics emission.
210  * When false, no diagnostics output (stdout/tagged/regtest/csv).
211  * Default false: a run that did not ask for the diagnostics should not
212  * write a CSV row and a stdout block on every step. The per-stream
213  * switches below stay on by default, so setting this one input to true
214  * turns the whole set on.
215  */
216  bool diag_enable = false;
217 
218  /**
219  * @brief Enable human-readable stdout diagnostics block.
220  * Controls tagged [RAD][...] debug line emission (when verbosity >= 1).
221  * Default true.
222  */
223  bool diag_stdout_enable = true;
224 
225  /**
226  * @brief Enable tagged [RAD][...] debug lines.
227  * Controls emission of bracketed-tag debug output.
228  * Default true.
229  */
230  bool diag_tagged_enable = true;
231 
232  /**
233  * @brief Enable RADIATION_DIAG: line emission.
234  * Controls regtest-parsing diagnostics line.
235  * Default true.
236  */
238 
239  /**
240  * @brief Enable CSV file append behavior.
241  * When false, no file writes; other enabled streams may still print.
242  * Default true.
243  */
244  bool diag_csv_enable = true;
245 
246  /**
247  * @brief Call-site filtering mode: "both" (pre+post rows),
248  * "pre_only", or "post_only".
249  */
250  std::string diag_callsite_mode = "both";
251 
252  /**
253  * @brief Tolerance for time equality in duplicate guard [s].
254  * Times within this tolerance are considered equal.
255  * Default 1e-12.
256  */
258 
259  /**
260  * @brief Optical depth profile type: "constant" (default,
261  * the thickness-independent tau) or "cloud_layer" (adds height-varying
262  * cloud optical depth between cloud_base_height_m and cloud_top_height_m).
263  */
264  TauProfileType tau_profile_type = TauProfileType::Constant;
265 
266  /**
267  * @brief Cloud layer base height [m]. Only used when
268  * tau_profile_type == CloudLayer.
269  */
271 
272  /**
273  * @brief Cloud layer top height [m]. Only used when
274  * tau_profile_type == CloudLayer. Must be >= cloud_base_height_m.
275  */
277 
278  /**
279  * @brief Additional optical depth per layer contributed by the
280  * cloud, added on top of the clear-sky tau_per_layer/tau_lw_per_layer
281  * background within [cloud_base_height_m, cloud_top_height_m]. Only used
282  * when tau_profile_type == CloudLayer.
283  */
285 
286  /**
287  * @brief Cloud fraction in [0, 1] used to blend clear-sky and
288  * cloudy-column fluxes: F = (1 - cloud_fraction) * F_clear +
289  * cloud_fraction * F_cloudy. Default 0.0 (fully clear sky), which
290  * reduces exactly to the clear-sky column regardless of tau_profile_type.
291  */
293 
294  /**
295  * @brief Clear-sky (background gas) single-scattering albedo
296  * in [0, 1] for the SW diffuse (scattering) two-stream calculation.
297  * Default 0.0 means the clear-sky column is purely absorbing, and the
298  * diffuse SW contribution from the clear-sky column is exactly zero —
299  * reducing exactly to the direct-beam-only result.
300  * Only used by the two-stream model and sw_enabled == true.
301  */
303 
304  /**
305  * @brief Clear-sky (background gas) scattering asymmetry
306  * factor in [-1, 1] (0 = isotropic scattering, close to 1 = strongly
307  * forward-scattering). Only meaningful when single_scattering_albedo
308  * > 0. Default 0.0.
309  */
311 
312  /**
313  * @brief Cloud single-scattering albedo in [0, 1], used
314  * instead of single_scattering_albedo for levels where the
315  * cloud-layer optical depth enhancement applies (i.e., within
316  * [cloud_base_height_m, cloud_top_height_m] on the "cloudy" column
317  * evaluation). Default 0.0 leaves the cloud band non-scattering
318  * for SW_Cloud_Layer unless explicitly configured otherwise. Typical
319  * liquid water cloud values are close to 1 (e.g., 0.9999).
320  * Only used when tau_profile_type == CloudLayer.
321  */
323 
324  /**
325  * @brief Cloud scattering asymmetry factor in [-1, 1], used
326  * instead of asymmetry_factor for levels where the cloud-layer
327  * optical depth enhancement applies. Default 0.0. Typical liquid water
328  * cloud droplet values are around 0.85 (strongly forward-scattering).
329  * Only meaningful when cloud_single_scattering_albedo > 0.
330  */
332 
333  /**
334  * @brief Shortwave surface albedo fallback [0, 1] for per-column
335  * heterogeneous-surface-property resolution. Used when per-column LSM/radiation
336  * interface albedo field is unavailable or invalid. Default 0.3 represents
337  * typical land/water reflectivity. Only used when hetero surface fields not
338  * present or disabled.
339  */
341 
342  /**
343  * @brief Shortwave surface albedo for diffuse light [0,1]. A negative
344  * value (default) uses surface_albedo_sw for both direct and diffuse light.
345  */
347 
348  /**
349  * @brief Longwave surface emissivity fallback [0, 1] for per-column
350  * heterogeneous-surface-property resolution. Used when per-column LSM/radiation
351  * interface emissivity field is unavailable or invalid. Default 0.99 represents
352  * typical land/water/ice emissivity (nearly blackbody). Only used when hetero
353  * surface fields not present or disabled.
354  */
356 
357  /**
358  * @brief Master switch for simplified SEB infrastructure.
359  * When false (default), no SEB MultiFabs are allocated and behavior is unchanged.
360  */
361  bool seb_enable = false;
362 
363  /**
364  * @brief Take the SEB's net surface shortwave and longwave fluxes from the
365  * two-stream sweep itself (per column, absorbed fluxes positive) wherever
366  * the LSM does not expose them. When false (default) those fluxes are the
367  * LSM fields or, without an LSM, the seb_sw_flux_default and
368  * seb_lw_flux_default constants. Sensible, latent and ground heat fluxes
369  * are unaffected.
370  */
372 
373  /**
374  * @brief Fallback SEB surface net shortwave flux [W/m^2].
375  */
377 
378  /**
379  * @brief Fallback SEB surface net longwave flux [W/m^2].
380  */
382 
383  /**
384  * @brief Fallback SEB sensible heat flux [W/m^2].
385  */
387 
388  /**
389  * @brief Fallback SEB latent heat flux [W/m^2].
390  */
392 
393  /**
394  * @brief Fallback SEB ground heat flux [W/m^2].
395  */
397 
398  /**
399  * @brief Fallback SEB surface moisture [kg/kg].
400  */
402 
403  /**
404  * @brief Fallback SEB deep soil temperature [K].
405  */
407 
408  /**
409  * @brief Fallback SEB deep soil moisture [kg/kg].
410  */
412 
413  /**
414  * @brief Enable diagnostic SEB residual computation and reporting.
415  * When true, computes and reports SEB residual = (SW_net + LW_net) - H - LE - G.
416  * When false (default), no residual computation or new diagnostic output.
417  * Requires seb_enable=true to be meaningful; if false but seb_enable=true, auto-enables internally.
418  * Note: this is diagnostic-only; it makes no prognostic T_s or flux update.
419  * Set seb_prognostic_enable for that.
420  */
421  bool seb_diagnostic_enable = false;
422 
423  /**
424  * @brief Enable prognostic SEB surface temperature and moisture evolution.
425  * When true, integrates SEB residual forward in time to evolve T_s and q_s using
426  * force-restore formulation. When false (default), no prognostic update occurs.
427  * Requires seb_enable=true and seb_diagnostic_enable=true; auto-enables internally if needed.
428  * Only active when Noah-MP is NOT driving LSM fields at that level.
429  */
430  bool seb_prognostic_enable = false;
431 
432  /**
433  * @brief Effective surface heat capacity [J/(m^2*K)].
434  * Used in force-restore prognostic T_s equation: dT_s/dt = SEB_residual / C_s - (2*pi/tau)*(T_s - T_deep).
435  * Default 2.0e4 J/(m^2*K) represents a thin surface-active layer.
436  * Only used when seb_prognostic_enable=true. Validated > 0 in init_params().
437  */
439 
440  /**
441  * @brief Force-restore timescale for surface temperature [s].
442  * Relaxation time constant toward T_deep in prognostic T_s equation.
443  * Default 86400.0 s (1 day) provides weak damping on diurnal timescales.
444  * Only used when seb_prognostic_enable=true. Validated > 0 in init_params().
445  */
447 
448  /**
449  * @brief Effective surface moisture layer depth [m].
450  * Used in force-restore prognostic q_s equation: dq_s/dt = -(LE/(L_v*rho_w*d_s)) - (1/tau_q)*(q_s - q_deep).
451  * Default 0.1 m represents a thin surface-active moisture layer.
452  * Only used when seb_prognostic_enable=true. Validated > 0 in init_params().
453  */
455 
456  /**
457  * @brief Force-restore timescale for surface moisture [s].
458  * Relaxation time constant toward q_deep in prognostic q_s equation.
459  * Default 86400.0 s (1 day) provides weak damping on diurnal timescales.
460  * Only used when seb_prognostic_enable=true. Validated > 0 in init_params().
461  */
463 
464  /**
465  * @brief Minimum clamping bound for prognostic surface temperature [K].
466  * After each Euler update, T_s is clamped to [seb_prognostic_t_min_k, seb_prognostic_t_max_k]
467  * to prevent instability if dt is large relative to C_s/tau.
468  * Default 200.0 K (far below realistic surface temperatures).
469  * Only used when seb_prognostic_enable=true.
470  */
472 
473  /**
474  * @brief Maximum clamping bound for prognostic surface temperature [K].
475  * After each Euler update, T_s is clamped to [seb_prognostic_t_min_k, seb_prognostic_t_max_k].
476  * Default 340.0 K (above typical surface temperatures, but prevents unrealistic excursions).
477  * Only used when seb_prognostic_enable=true.
478  */
480 
481  /**
482  * @brief Minimum clamping bound for prognostic surface moisture [kg/kg].
483  * After each Euler update, q_s is clamped to [seb_prognostic_q_min, seb_prognostic_q_max].
484  * Default 0.0 kg/kg prevents negative moisture.
485  * Only used when seb_prognostic_enable=true.
486  */
488 
489  /**
490  * @brief Maximum clamping bound for prognostic surface moisture [kg/kg].
491  * After each Euler update, q_s is clamped to [seb_prognostic_q_min, seb_prognostic_q_max].
492  * Default 1.0 kg/kg ensures physically meaningful moisture.
493  * Only used when seb_prognostic_enable=true.
494  */
496 
497 
498  /**
499  * @brief Enable dynamic longwave optical depth diagnosis from moisture/clouds.
500  * When true, LW optical depth is computed per-level from qv and qc.
501  * When false (default), uses the static tau_lw_per_layer.
502  * Only used by the two-stream model and lw_enabled == true.
503  */
504  bool tau_lw_dynamic_enable = false;
505  bool tau_sw_dynamic_enable = false;
506 
507  /**
508  * @brief Shortwave optical depth coefficient for water vapor [dimensionless].
509  * Dynamic SW tau = tau_sw_coeff_qv * qv + tau_sw_coeff_qc * qc + tau_per_layer.
510  * Default 0.0 means tau is purely from static tau_per_layer (no dynamic component).
511  * Only used when tau_sw_dynamic_enable == true.
512  */
514 
515  /**
516  * @brief Shortwave optical depth coefficient for cloud liquid water [dimensionless].
517  * Default 0.0. When both coefficients are 0, dynamic path still invoked but yields
518  * tau_per_layer unchanged.
519  * Only used when tau_sw_dynamic_enable == true.
520  */
522 
523  /**
524  * @brief Longwave optical depth coefficient for water vapor [dimensionless].
525  * Dynamic LW tau = tau_lw_coeff_qv * qv + tau_lw_coeff_qc * qc + tau_lw_per_layer.
526  * Default 0.0 means tau is purely from static tau_lw_per_layer (no dynamic component).
527  * Only used when tau_lw_dynamic_enable == true.
528  */
530 
531  /**
532  * @brief Longwave optical depth coefficient for cloud liquid water [dimensionless].
533  * Default 0.0. When both coefficients are 0, dynamic path still invoked but yields
534  * tau_lw_per_layer unchanged.
535  * Only used when tau_lw_dynamic_enable == true.
536  */
538 
539  /**
540  * @brief Enable prognostic cloud fraction diagnosis from RH/qc.
541  * When true, cloud fraction is computed per-level from relative humidity and cloud water.
542  * When false (default), uses the static cloud_fraction.
543  * Only used by the two-stream model.
544  */
546 
547  /**
548  * @brief Minimum relative humidity threshold for cloud fraction diagnosis [0, 1].
549  * Cloud fraction ramps from 0 at rh < cloud_fraction_rh_min to 1 at rh >= cloud_fraction_rh_max.
550  * Default 0.0 means cloud fraction depends primarily on qc content.
551  * Only used when cloud_fraction_prog_enable == true.
552  */
554 
555  /**
556  * @brief Maximum relative humidity threshold for cloud fraction diagnosis [0, 1].
557  * Must be >= cloud_fraction_rh_min. Cloud fraction saturates to 1 at this RH.
558  * Default 1.0 means cloud fraction increases linearly from rh_min to 1.0 RH.
559  * Only used when cloud_fraction_prog_enable == true.
560  */
562 
563  /**
564  * @brief Cloud water [kg/kg] at which the liquid-water term alone gives a
565  * cloud fraction of 1: cf += min(1, qc / cloud_fraction_qc_scale).
566  * Default 1.0e-3 kg/kg, so a typical stratus cloud water saturates the term.
567  * Only used when cloud_fraction_prog_enable == true.
568  */
570 
571  /**
572  * @brief Enable prescribed bulk aerosol/turbidity optical depth.
573  * When true, aerosol optical depth is added on top of existing tau_base + cloud + dynamic-moisture contributions.
574  * When false (default), there is no aerosol contribution.
575  * Only used by the two-stream model and sw_enabled == true.
576  */
577  bool aerosol_enable = false;
578 
579  /**
580  * @brief Aerosol optical depth profile type: Constant, Exponential, or Table.
581  * - Constant: uniform aerosol tau per layer (aerosol_tau_per_layer).
582  * - Exponential: tau decays with height via tau_aerosol(k) = aerosol_tau_surface * exp(-z(k) / aerosol_scale_height_m).
583  * - Table: per-level tau prescribed via lookup (future extension, currently not supported).
584  * Default Constant. Only used when aerosol_enable == true.
585  */
586  AerosolProfileType aerosol_profile_type = AerosolProfileType::Constant;
587 
588  /**
589  * @brief Constant aerosol optical depth per layer [dimensionless].
590  * Used for AerosolProfileType::Constant profile. Default 0.0 (no aerosol contribution).
591  * Only used when aerosol_enable == true and aerosol_profile_type == Constant.
592  */
594 
595  /**
596  * @brief Scale height for exponential aerosol profile [m].
597  * Controls decay rate: tau_aerosol(k) = aerosol_tau_surface * exp(-z(k) / scale_height).
598  * Default 2000.0 m (typical scale height for tropospheric aerosols).
599  * Only used when aerosol_enable == true and aerosol_profile_type == Exponential.
600  */
602 
603  /**
604  * @brief Total-column aerosol optical depth at surface [dimensionless].
605  * Used for AerosolProfileType::Exponential profile as the surface boundary condition.
606  * Default 0.0 (no aerosol contribution).
607  * Only used when aerosol_enable == true and aerosol_profile_type == Exponential.
608  */
610 
611  /**
612  * @brief Read radiation options from the input parameter database.
613  *
614  * @param[in] lev AMR level index.
615  * @param[in] max_level Maximum AMR level configured for the run.
616  * @param[in] pp_prefix ParmParse prefix for the ERF input namespace (e.g., "erf").
617  *
618  * Queries:
619  * - erf.radiation_model = TwoStream (read by SolverChoice) selects the model
620  * - erf.radiation.sw_enabled (bool, optional, default true)
621  * - erf.radiation.lw_enabled (bool, optional, default true)
622  * - erf.radiation.tau_per_layer (real, optional)
623  * - erf.radiation.tau_lw_per_layer (real, optional)
624  * - erf.fixed_solar_zenith_angle, erf.fixed_total_solar_irradiance, erf.rad_t_sfc
625  * (required), erf.rad_cons_lat, erf.rad_cons_lon, erf.rad_orbital_year,
626  * erf.rad_orbital_eccentricity, erf.rad_orbital_obliquity, erf.rad_orbital_mvelp:
627  * the RRTMGP interface's keys, with its meanings (see the members above)
628  * - erf.radiation.tau_model (string "per_layer"|"mass", optional, default "per_layer")
629  * - erf.radiation.sw_kabs_dry, sw_kscat_dry, sw_kabs_vapor, sw_kext_cloud (real, optional, mass model)
630  * - erf.radiation.sw_cloud_omega, sw_cloud_g (real, optional, mass model)
631  * - erf.radiation.lw_mass_absorption_enable (bool, optional, default false)
632  * - erf.radiation.lw_kabs_dry (real, optional, default 1.0e-4 m^2/kg)
633  * - erf.radiation.lw_kabs_vapor (real, optional, default 0.1 m^2/kg)
634  * - erf.radiation.lw_kabs_cloud (real, optional, default 158.0 m^2/kg)
635  * - erf.radiation.surface_albedo_sw_diffuse (real, optional, default -1 = same as direct)
636  * - erf.radiation.v (int, optional, debug verbosity)
637  * - erf.radiation.diag_file (string, optional)
638  * - erf.radiation.tau_profile_type (string "constant"|"cloud_layer", optional)
639  * - erf.radiation.cloud_base_height_m (real, optional)
640  * - erf.radiation.cloud_top_height_m (real, optional)
641  * - erf.radiation.cloud_tau_per_layer (real, optional)
642  * - erf.radiation.cloud_fraction (real, optional)
643  * - erf.radiation.single_scattering_albedo (real, optional)
644  * - erf.radiation.asymmetry_factor (real, optional)
645  * - erf.radiation.cloud_single_scattering_albedo (real, optional)
646  * - erf.radiation.cloud_asymmetry_factor (real, optional)
647  * - erf.radiation.diag_enable (bool, optional, default false)
648  * - erf.radiation.diag_stdout_enable (bool, optional, default true)
649  * - erf.radiation.diag_tagged_enable (bool, optional, default true)
650  * - erf.radiation.diag_regtest_line_enable (bool, optional, default true)
651  * - erf.radiation.diag_csv_enable (bool, optional, default true)
652  * - erf.radiation.diag_callsite_mode (string, optional, default "both")
653  * - erf.radiation.diag_dedup_tol (real, optional, default 1e-12)
654  * - erf.radiation.tau_sw_dynamic_enable (bool, optional, default false)
655  * - erf.radiation.tau_lw_dynamic_enable (bool, optional, default false)
656  * - erf.radiation.tau_sw_coeff_qv (real, optional, default 0.0)
657  * - erf.radiation.tau_sw_coeff_qc (real, optional, default 0.0)
658  * - erf.radiation.tau_lw_coeff_qv (real, optional, default 0.0)
659  * - erf.radiation.tau_lw_coeff_qc (real, optional, default 0.0)
660  * - erf.radiation.cloud_fraction_prog_enable (bool, optional, default false)
661  * - erf.radiation.cloud_fraction_rh_min (real, optional, default 0.0)
662  * - erf.radiation.cloud_fraction_rh_max (real, optional, default 1.0)
663  * - erf.radiation.cloud_fraction_qc_scale (real, optional, default 1.0e-3)
664  * - erf.radiation.aerosol_enable (bool, optional, default false)
665  * - erf.radiation.aerosol_profile_type (string "constant"|"exponential"|"table", optional, default "constant")
666  * - erf.radiation.aerosol_tau_per_layer (real, optional, default 0.0)
667  * - erf.radiation.aerosol_scale_height_m (real, optional, default 2000.0)
668  * - erf.radiation.aerosol_tau_surface (real, optional, default 0.0)
669  * - erf.radiation.seb_enable (bool, optional, default false)
670  * - erf.radiation.seb_sw_flux_default (real, optional, default 0.0)
671  * - erf.radiation.seb_lw_flux_default (real, optional, default 0.0)
672  * - erf.radiation.seb_hfx_default (real, optional, default 0.0)
673  * - erf.radiation.seb_lh_default (real, optional, default 0.0)
674  * - erf.radiation.seb_grdflx_default (real, optional, default 0.0)
675  * - erf.radiation.seb_q_sfc_default (real, optional, default 0.0)
676  * - erf.radiation.seb_t_deep_default (real, optional, default 300.0)
677  * - erf.radiation.seb_q_deep_default (real, optional, default 0.0)
678  * - erf.radiation.seb_diagnostic_enable (bool, optional, default false)
679  * - erf.radiation.seb_prognostic_enable (bool, optional, default false)
680  * - erf.radiation.seb_surface_heat_capacity (real, optional, default 2.0e4, validated > 0)
681  * - erf.radiation.seb_restore_timescale_s (real, optional, default 86400.0, validated > 0)
682  * - erf.radiation.seb_moisture_layer_depth_m (real, optional, default 0.1, validated > 0)
683  * - erf.radiation.seb_moisture_restore_timescale_s (real, optional, default 86400.0, validated > 0)
684  * - erf.radiation.seb_prognostic_t_min_k (real, optional, default 200.0)
685  * - erf.radiation.seb_prognostic_t_max_k (real, optional, default 340.0)
686  * - erf.radiation.seb_prognostic_q_min (real, optional, default 0.0, clamped to [0, 1])
687  * - erf.radiation.seb_prognostic_q_max (real, optional, default 1.0, clamped to [0, 1])
688  *
689  * Sub-option queries are only performed when the two-stream model is selected.
690  */
691  /**
692  * @brief Bring an out-of-range input back into its valid range and say so.
693  * Every erf.radiation.* value with a physical range passes through one of
694  * these, so a typo in a deck is reported instead of silently corrected.
695  */
696  static void clamp_low (const char* key, amrex::Real& value, amrex::Real lo)
697  {
698  if (value < lo) {
699  if (amrex::ParallelDescriptor::IOProcessor()) {
700  amrex::Print() << "WARNING: erf.radiation." << key << " = " << value
701  << " is below its minimum " << lo << "; using " << lo << "\n";
702  }
703  value = lo;
704  }
705  }
706  static void clamp_high (const char* key, amrex::Real& value, amrex::Real hi)
707  {
708  if (value > hi) {
709  if (amrex::ParallelDescriptor::IOProcessor()) {
710  amrex::Print() << "WARNING: erf.radiation." << key << " = " << value
711  << " is above its maximum " << hi << "; using " << hi << "\n";
712  }
713  value = hi;
714  }
715  }
716  static void reset_nonpositive (const char* key, amrex::Real& value, amrex::Real fallback)
717  {
718  if (!std::isfinite(value) || value <= 0.0) {
719  if (amrex::ParallelDescriptor::IOProcessor()) {
720  amrex::Print() << "WARNING: erf.radiation." << key << " = " << value
721  << " must be positive; using " << fallback << "\n";
722  }
723  value = fallback;
724  }
725  }
726 
727  void init_params(bool two_stream_selected, int max_level, std::string pp_prefix)
728  {
729  amrex::ParmParse pp(pp_prefix);
730  enabled = two_stream_selected;
731 
732  // The column sweep needs every box to hold the whole atmospheric column
733  // of its level, which fine-level boxes never do; there is no coarse-fine
734  // treatment of the fluxes yet either. Stop here with the reason rather
735  // than at the first fine-level radiation call.
736  if (enabled && max_level > 0) {
737  amrex::Abort("erf.radiation_model = TwoStream runs on a single level; "
738  "set amr.max_level = 0 or select another radiation model.");
739  }
740 
741  // Only query sub-options if the two-stream model is selected
742  if (enabled) {
743  pp.query("radiation.sw_enabled", sw_enabled);
744  pp.query("radiation.lw_enabled", lw_enabled);
745  pp.query("radiation.tau_per_layer", tau_per_layer);
746  pp.query("radiation.tau_lw_per_layer", tau_lw_per_layer);
747 
748  // The sun, the site and the surface temperature come from the keys the
749  // RRTMGP interface reads, so the two models share one set of inputs.
750  // ParmParse ignores a key nobody reads, so a deck still carrying one of
751  // the earlier two-stream-only keys would run with silently different
752  // settings: stop and name the replacement instead.
753  static const char* retired_keys[][2] = {
754  {"radiation.solar_zenith",
755  "erf.fixed_solar_zenith_angle, which is the cosine of the angle (60 degrees is 0.5)"},
756  {"radiation.solar_zenith_deg",
757  "erf.fixed_solar_zenith_angle, which is the cosine of the angle (60 degrees is 0.5)"},
758  {"radiation.S0", "erf.fixed_total_solar_irradiance"},
759  {"radiation.earth_sun_distance_enable",
760  "nothing: leave erf.fixed_total_solar_irradiance unset and the irradiance follows the date"},
761  {"radiation.surface_temp_k", "erf.rad_t_sfc"},
762  {"radiation.solar_geometry_dynamic_enable",
763  "nothing: leave erf.fixed_solar_zenith_angle unset and the sun follows start_datetime"},
764  {"radiation.latitude_deg", "erf.rad_cons_lat"},
765  {"radiation.longitude_deg", "erf.rad_cons_lon"},
766  {"radiation.day_of_year", "start_datetime"},
767  {"radiation.time_zone_offset_hours",
768  "nothing: start_datetime is UTC and the longitude sets the local solar time"},
769  };
770  for (const auto& entry : retired_keys) {
771  if (pp.contains(entry[0])) {
772  amrex::Abort(std::string("erf.") + entry[0] + " is no longer read by the "
773  "two-stream model; use " + entry[1] + ".");
774  }
775  }
776  pp.query("fixed_solar_zenith_angle", fixed_solar_zenith_angle);
777  if (fixed_solar_zenith_angle > 1.0) {
778  amrex::Abort("erf.fixed_solar_zenith_angle = " + std::to_string(fixed_solar_zenith_angle) +
779  " is the cosine of the solar zenith angle (as for RRTMGP) and cannot "
780  "exceed 1; 60 degrees is 0.5.");
781  }
782  pp.query("fixed_total_solar_irradiance", fixed_total_solar_irradiance);
783  if (!pp.query("rad_t_sfc", rad_t_sfc)) {
784  amrex::Abort("erf.radiation_model = TwoStream needs erf.rad_t_sfc, the surface "
785  "temperature [K] used where no land-surface model or surface layer "
786  "supplies one (the same input RRTMGP requires).");
787  }
788  if (!std::isfinite(rad_t_sfc) || rad_t_sfc <= 0.0) {
789  amrex::Abort("erf.rad_t_sfc = " + std::to_string(rad_t_sfc) + " must be a positive temperature [K].");
790  }
791  pp.query("rad_cons_lat", rad_cons_lat);
792  pp.query("rad_cons_lon", rad_cons_lon);
793  if (!std::isfinite(rad_cons_lat) || rad_cons_lat < -90.0 || rad_cons_lat > 90.0) {
794  amrex::Abort("erf.rad_cons_lat = " + std::to_string(rad_cons_lat) + " must lie in [-90, 90] degrees.");
795  }
796  if (!std::isfinite(rad_cons_lon) || rad_cons_lon < -180.0 || rad_cons_lon > 180.0) {
797  amrex::Abort("erf.rad_cons_lon = " + std::to_string(rad_cons_lon) + " must lie in [-180, 180] degrees.");
798  }
799  pp.query("rad_orbital_year", rad_orbital_year);
800  pp.query("rad_orbital_eccentricity", rad_orbital_eccentricity);
801  pp.query("rad_orbital_obliquity", rad_orbital_obliquity);
802  pp.query("rad_orbital_mvelp", rad_orbital_mvelp);
803  if (pp.contains("rad_freq_in_steps") && amrex::ParallelDescriptor::IOProcessor()) {
804  amrex::Print() << "WARNING: erf.rad_freq_in_steps is not read by the two-stream model, "
805  "which runs every step.\n";
806  }
807  std::string tau_model_str = "per_layer";
808  pp.query("radiation.tau_model", tau_model_str);
809  if (tau_model_str == "per_layer") {
810  tau_model = TauModel::PerLayer;
811  } else if (tau_model_str == "mass") {
812  tau_model = TauModel::Mass;
813  } else {
814  // The two models give very different column optical depths, so say so
815  // rather than let a misspelling quietly pick the default.
816  tau_model = TauModel::PerLayer;
817  if (amrex::ParallelDescriptor::IOProcessor()) {
818  amrex::Print() << "WARNING: unknown erf.radiation.tau_model '"
819  << tau_model_str << "'; valid values are per_layer "
820  "and mass. Using per_layer.\n";
821  }
822  }
823  pp.query("radiation.sw_kabs_dry", sw_kabs_dry);
824  pp.query("radiation.sw_kscat_dry", sw_kscat_dry);
825  pp.query("radiation.sw_kabs_vapor", sw_kabs_vapor);
826  pp.query("radiation.sw_kext_cloud", sw_kext_cloud);
827  pp.query("radiation.sw_cloud_omega", sw_cloud_omega);
828  pp.query("radiation.sw_cloud_g", sw_cloud_g);
829  clamp_low("sw_kabs_dry", sw_kabs_dry, 0.0);
830  clamp_low("sw_kscat_dry", sw_kscat_dry, 0.0);
831  clamp_low("sw_kabs_vapor", sw_kabs_vapor, 0.0);
832  clamp_low("sw_kext_cloud", sw_kext_cloud, 0.0);
833  clamp_low("sw_cloud_omega", sw_cloud_omega, 0.0);
834  clamp_high("sw_cloud_omega", sw_cloud_omega, 1.0);
835  clamp_low("sw_cloud_g", sw_cloud_g, -1.0);
836  clamp_high("sw_cloud_g", sw_cloud_g, 1.0);
837  pp.query("radiation.lw_mass_absorption_enable", lw_mass_absorption_enable);
838  pp.query("radiation.lw_kabs_dry", lw_kabs_dry);
839  pp.query("radiation.lw_kabs_vapor", lw_kabs_vapor);
840  pp.query("radiation.lw_kabs_cloud", lw_kabs_cloud);
841  clamp_low("lw_kabs_dry", lw_kabs_dry, 0.0);
842  clamp_low("lw_kabs_vapor", lw_kabs_vapor, 0.0);
843  clamp_low("lw_kabs_cloud", lw_kabs_cloud, 0.0);
844  pp.query("radiation.v", verbosity);
845  pp.query("radiation.diag_file", diag_file);
846 
847  // Cloud optical properties (all optional; the defaults leave the
848  // column clear-sky)
849  std::string tau_profile_type_str = "constant";
850  pp.query("radiation.tau_profile_type", tau_profile_type_str);
851  if (tau_profile_type_str == "constant") {
852  tau_profile_type = TauProfileType::Constant;
853  } else if (tau_profile_type_str == "cloud_layer") {
854  tau_profile_type = TauProfileType::CloudLayer;
855  } else {
856  // Falling back to constant drops a requested cloud layer, so say so.
857  tau_profile_type = TauProfileType::Constant;
858  if (amrex::ParallelDescriptor::IOProcessor()) {
859  amrex::Print() << "WARNING: unknown erf.radiation.tau_profile_type '"
860  << tau_profile_type_str << "'; valid values are "
861  "constant and cloud_layer. Using constant, so no "
862  "cloud layer is applied.\n";
863  }
864  }
865  pp.query("radiation.cloud_base_height_m", cloud_base_height_m);
866  pp.query("radiation.cloud_top_height_m", cloud_top_height_m);
867  pp.query("radiation.cloud_tau_per_layer", cloud_tau_per_layer);
868  pp.query("radiation.cloud_fraction", cloud_fraction);
869 
870  // Parameter validation (R5/R5'): clip cloud_fraction to [0, 1]
871  clamp_low("cloud_fraction", cloud_fraction, 0.0);
872  clamp_high("cloud_fraction", cloud_fraction, 1.0);
873  clamp_low("cloud_tau_per_layer", cloud_tau_per_layer, 0.0);
876  }
877 
878  // Scattering parameters (all optional; the defaults leave the
879  // shortwave direct-beam only)
880  pp.query("radiation.single_scattering_albedo", single_scattering_albedo);
881  pp.query("radiation.asymmetry_factor", asymmetry_factor);
882  pp.query("radiation.cloud_single_scattering_albedo", cloud_single_scattering_albedo);
883  pp.query("radiation.cloud_asymmetry_factor", cloud_asymmetry_factor);
884 
885  // Parameter validation (R5/R5'): clip albedos to [0, 1] and
886  // asymmetry factors to [-1, 1]
887  clamp_low("single_scattering_albedo", single_scattering_albedo, 0.0);
888  clamp_high("single_scattering_albedo", single_scattering_albedo, 1.0);
889  clamp_low("asymmetry_factor", asymmetry_factor, -1.0);
890  clamp_high("asymmetry_factor", asymmetry_factor, 1.0);
891  clamp_low("cloud_single_scattering_albedo", cloud_single_scattering_albedo, 0.0);
892  clamp_high("cloud_single_scattering_albedo", cloud_single_scattering_albedo, 1.0);
893  clamp_low("cloud_asymmetry_factor", cloud_asymmetry_factor, -1.0);
894  clamp_high("cloud_asymmetry_factor", cloud_asymmetry_factor, 1.0);
895 
896  // Runtime diagnostics controls (all optional; every channel is on
897  // by default)
898  pp.query("radiation.diag_enable", diag_enable);
899  pp.query("radiation.diag_stdout_enable", diag_stdout_enable);
900  pp.query("radiation.diag_tagged_enable", diag_tagged_enable);
901  pp.query("radiation.diag_regtest_line_enable", diag_regtest_line_enable);
902  pp.query("radiation.diag_csv_enable", diag_csv_enable);
903  pp.query("radiation.diag_callsite_mode", diag_callsite_mode);
904  pp.query("radiation.diag_dedup_tol", diag_dedup_tol);
905 
906  // Validate the call-site mode
907  if (diag_callsite_mode != "both" &&
908  diag_callsite_mode != "pre_only" &&
909  diag_callsite_mode != "post_only") {
910  if (amrex::ParallelDescriptor::IOProcessor()) {
911  amrex::Print() << "WARNING: unknown erf.radiation.diag_callsite_mode '"
912  << diag_callsite_mode << "'; valid values are both, "
913  "pre_only and post_only. Using both.\n";
914  }
915  diag_callsite_mode = "both";
916  }
917  // Ensure dedup tolerance is non-negative
918  clamp_low("diag_dedup_tol", diag_dedup_tol, 0.0);
919 
920  // Surface heterogeneity fallback parameters (all optional; only used
921  // when hetero
922  // LSM/radiation interface fields are absent or invalid)
923  pp.query("radiation.surface_albedo_sw", surface_albedo_sw);
924  pp.query("radiation.surface_emissivity_lw", surface_emissivity_lw);
925  pp.query("radiation.surface_albedo_sw_diffuse", surface_albedo_sw_diffuse);
926  clamp_high("surface_albedo_sw_diffuse", surface_albedo_sw_diffuse, 1.0);
927 
928  // Clamp albedo/emissivity to [0, 1]
929  clamp_low("surface_albedo_sw", surface_albedo_sw, 0.0);
930  clamp_high("surface_albedo_sw", surface_albedo_sw, 1.0);
931  clamp_low("surface_emissivity_lw", surface_emissivity_lw, 0.0);
932  clamp_high("surface_emissivity_lw", surface_emissivity_lw, 1.0);
933 
934  // Dynamic optical depth diagnosis from moisture/clouds
935  // (all optional; disabled by default)
936  pp.query("radiation.tau_sw_dynamic_enable", tau_sw_dynamic_enable);
937  pp.query("radiation.tau_lw_dynamic_enable", tau_lw_dynamic_enable);
938  pp.query("radiation.tau_sw_coeff_qv", tau_sw_coeff_qv);
939  pp.query("radiation.tau_sw_coeff_qc", tau_sw_coeff_qc);
940  pp.query("radiation.tau_lw_coeff_qv", tau_lw_coeff_qv);
941  pp.query("radiation.tau_lw_coeff_qc", tau_lw_coeff_qc);
942 
943  // Clamp the dynamic tau coefficients
944  // to nonnegative (negative would give unphysical behavior)
945  clamp_low("tau_sw_coeff_qv", tau_sw_coeff_qv, 0.0);
946  clamp_low("tau_sw_coeff_qc", tau_sw_coeff_qc, 0.0);
947  clamp_low("tau_lw_coeff_qv", tau_lw_coeff_qv, 0.0);
948  clamp_low("tau_lw_coeff_qc", tau_lw_coeff_qc, 0.0);
949 
950  // Prognostic cloud fraction diagnosis from RH/qc
951  // (all optional; disabled by default)
952  pp.query("radiation.cloud_fraction_prog_enable", cloud_fraction_prog_enable);
953  pp.query("radiation.cloud_fraction_rh_min", cloud_fraction_rh_min);
954  pp.query("radiation.cloud_fraction_rh_max", cloud_fraction_rh_max);
955  pp.query("radiation.cloud_fraction_qc_scale", cloud_fraction_qc_scale);
956 
957  // Clamp the cloud fraction parameters
958  // to physically reasonable ranges
959  clamp_low("cloud_fraction_rh_min", cloud_fraction_rh_min, 0.0);
960  clamp_high("cloud_fraction_rh_min", cloud_fraction_rh_min, 1.0);
961  clamp_low("cloud_fraction_rh_max", cloud_fraction_rh_max, 0.0);
962  clamp_high("cloud_fraction_rh_max", cloud_fraction_rh_max, 1.0);
965  }
966  clamp_low("cloud_fraction_qc_scale", cloud_fraction_qc_scale, 0.0);
967 
968  // Prescribed bulk aerosol/turbidity optical depth
969  // (all optional; disabled by default)
970  pp.query("radiation.aerosol_enable", aerosol_enable);
971 
972  // Parse aerosol profile type as string
973  std::string aerosol_profile_type_str = "constant";
974  pp.query("radiation.aerosol_profile_type", aerosol_profile_type_str);
975  if (aerosol_profile_type_str == "constant") {
976  aerosol_profile_type = AerosolProfileType::Constant;
977  } else if (aerosol_profile_type_str == "exponential") {
978  aerosol_profile_type = AerosolProfileType::Exponential;
979  } else if (aerosol_profile_type_str == "table") {
980  // The table lookup is not implemented and contributes no optical
981  // depth. Keep the selection so the run is not silently given a
982  // different profile, and say what it means.
983  aerosol_profile_type = AerosolProfileType::Table;
984  if (amrex::ParallelDescriptor::IOProcessor()) {
985  amrex::Print() << "WARNING: erf.radiation.aerosol_profile_type = table is "
986  "not implemented and contributes no aerosol optical "
987  "depth. Use constant or exponential.\n";
988  }
989  } else {
990  // Falling through to constant would hide a misspelling.
991  aerosol_profile_type = AerosolProfileType::Constant;
992  if (amrex::ParallelDescriptor::IOProcessor()) {
993  amrex::Print() << "WARNING: unknown erf.radiation.aerosol_profile_type '"
994  << aerosol_profile_type_str << "'; valid values are "
995  "constant and exponential. Using constant.\n";
996  }
997  }
998 
999  pp.query("radiation.aerosol_tau_per_layer", aerosol_tau_per_layer);
1000  pp.query("radiation.aerosol_scale_height_m", aerosol_scale_height_m);
1001  pp.query("radiation.aerosol_tau_surface", aerosol_tau_surface);
1002 
1003  // Clamp the aerosol parameters
1004  // to physically reasonable ranges
1005  clamp_low("aerosol_tau_per_layer", aerosol_tau_per_layer, 0.0);
1006  clamp_low("aerosol_tau_surface", aerosol_tau_surface, 0.0);
1007  reset_nonpositive("aerosol_scale_height_m", aerosol_scale_height_m, 2000.0);
1008 
1009  // Simplified SEB infrastructure fallback parameters
1010  pp.query("radiation.seb_enable", seb_enable);
1011  pp.query("radiation.seb_use_radiation_fluxes", seb_use_radiation_fluxes);
1012  pp.query("radiation.seb_sw_flux_default", seb_sw_flux_default);
1013  pp.query("radiation.seb_lw_flux_default", seb_lw_flux_default);
1014  pp.query("radiation.seb_hfx_default", seb_hfx_default);
1015  pp.query("radiation.seb_lh_default", seb_lh_default);
1016  pp.query("radiation.seb_grdflx_default", seb_grdflx_default);
1017  pp.query("radiation.seb_q_sfc_default", seb_q_sfc_default);
1018  pp.query("radiation.seb_t_deep_default", seb_t_deep_default);
1019  pp.query("radiation.seb_q_deep_default", seb_q_deep_default);
1020 
1021  // SEB diagnostic mode
1022  pp.query("radiation.seb_diagnostic_enable", seb_diagnostic_enable);
1023 
1024  // Auto-enable seb_enable if seb_diagnostic_enable is requested but seb_enable is false
1026  seb_enable = true; // Internal auto-enable for safety/simplicity
1027  }
1028 
1029  // SEB prognostic mode parameters
1030  pp.query("radiation.seb_prognostic_enable", seb_prognostic_enable);
1031  pp.query("radiation.seb_surface_heat_capacity", seb_surface_heat_capacity);
1032  pp.query("radiation.seb_restore_timescale_s", seb_restore_timescale_s);
1033  pp.query("radiation.seb_moisture_layer_depth_m", seb_moisture_layer_depth_m);
1034  pp.query("radiation.seb_moisture_restore_timescale_s", seb_moisture_restore_timescale_s);
1035  pp.query("radiation.seb_prognostic_t_min_k", seb_prognostic_t_min_k);
1036  pp.query("radiation.seb_prognostic_t_max_k", seb_prognostic_t_max_k);
1037  pp.query("radiation.seb_prognostic_q_min", seb_prognostic_q_min);
1038  pp.query("radiation.seb_prognostic_q_max", seb_prognostic_q_max);
1039 
1040  // The prognostic update needs the SEB fields and the residual, so
1041  // requesting it turns on the infrastructure it depends on. Report that,
1042  // since it changes settings the user did not set.
1044  if (amrex::ParallelDescriptor::IOProcessor()) {
1045  amrex::Print() << "NOTE: erf.radiation.seb_prognostic_enable = true "
1046  "also enables seb_enable and "
1047  "seb_diagnostic_enable, which it depends on.\n";
1048  }
1049  seb_enable = true;
1050  seb_diagnostic_enable = true;
1051  }
1052 
1053  if (!std::isfinite(seb_sw_flux_default)) seb_sw_flux_default = 0.0;
1054  if (!std::isfinite(seb_lw_flux_default)) seb_lw_flux_default = 0.0;
1055  if (!std::isfinite(seb_hfx_default)) seb_hfx_default = 0.0;
1056  if (!std::isfinite(seb_lh_default)) seb_lh_default = 0.0;
1057  if (!std::isfinite(seb_grdflx_default)) seb_grdflx_default = 0.0;
1058  if (!std::isfinite(seb_q_sfc_default)) seb_q_sfc_default = 0.0;
1059  reset_nonpositive("seb_t_deep_default", seb_t_deep_default, 300.0);
1060  if (!std::isfinite(seb_q_deep_default)) seb_q_deep_default = 0.0;
1061 
1062  seb_sw_flux_default = amrex::max(amrex::Real(-2000.0), amrex::min(amrex::Real(2000.0), seb_sw_flux_default));
1063  seb_lw_flux_default = amrex::max(amrex::Real(-2000.0), amrex::min(amrex::Real(2000.0), seb_lw_flux_default));
1064  seb_hfx_default = amrex::max(amrex::Real(-2000.0), amrex::min(amrex::Real(2000.0), seb_hfx_default));
1065  seb_lh_default = amrex::max(amrex::Real(-2000.0), amrex::min(amrex::Real(2000.0), seb_lh_default));
1066  seb_grdflx_default = amrex::max(amrex::Real(-2000.0), amrex::min(amrex::Real(2000.0), seb_grdflx_default));
1067  seb_q_sfc_default = amrex::max(amrex::Real(0.0), amrex::min(amrex::Real(1.0), seb_q_sfc_default));
1068  seb_q_deep_default = amrex::max(amrex::Real(0.0), amrex::min(amrex::Real(1.0), seb_q_deep_default));
1069 
1070 
1071  // Validate the prognostic parameters
1072  if (!std::isfinite(seb_surface_heat_capacity) || seb_surface_heat_capacity <= 0.0) {
1073  seb_surface_heat_capacity = 2.0e4; // Reset to default
1074  }
1075  if (!std::isfinite(seb_restore_timescale_s) || seb_restore_timescale_s <= 0.0) {
1076  seb_restore_timescale_s = 86400.0; // Reset to default
1077  }
1078  if (!std::isfinite(seb_moisture_layer_depth_m) || seb_moisture_layer_depth_m <= 0.0) {
1079  seb_moisture_layer_depth_m = 0.1; // Reset to default
1080  }
1082  seb_moisture_restore_timescale_s = 86400.0; // Reset to default
1083  }
1084 
1085  // Clamp temperature bounds
1086  if (!std::isfinite(seb_prognostic_t_min_k)) seb_prognostic_t_min_k = 200.0;
1087  if (!std::isfinite(seb_prognostic_t_max_k)) seb_prognostic_t_max_k = 340.0;
1092  }
1093 
1094  // Clamp moisture bounds
1095  if (!std::isfinite(seb_prognostic_q_min)) seb_prognostic_q_min = 0.0;
1096  if (!std::isfinite(seb_prognostic_q_max)) seb_prognostic_q_max = 1.0;
1097  seb_prognostic_q_min = amrex::max(amrex::Real(0.0), amrex::min(amrex::Real(1.0), seb_prognostic_q_min));
1098  seb_prognostic_q_max = amrex::max(amrex::Real(0.0), amrex::min(amrex::Real(1.0), seb_prognostic_q_max));
1103  }
1104  }
1105 
1106 }
1107 
1108  /**
1109  * @brief Check if two-stream radiation is active.
1110  *
1111  * @return true when erf.radiation_model = TwoStream.
1112  */
1113  [[nodiscard]] bool uses_two_stream_radiation() const noexcept
1114  {
1115  return enabled;
1116  }
1117 };
1118 
1119 #endif // ERF_RAD_STRUCT_H_
ParmParse pp("prob")
AMREX_ENUM(TauProfileType, Constant, CloudLayer)
Optical depth profile type for the two-stream solver.
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ tmp
Definition: ERF_AdvanceWSM6.cpp:116
Container holding radiation-related choices and parameters.
Definition: ERF_RadStruct.H:70
amrex::Real lw_kabs_dry
Dry-air gray LW mass absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:188
bool aerosol_enable
Enable prescribed bulk aerosol/turbidity optical depth. When true, aerosol optical depth is added on ...
Definition: ERF_RadStruct.H:577
TauModel tau_model
Optical depth model: "per_layer" (default, fixed tau per layer) or "mass" (from the layer mass path i...
Definition: ERF_RadStruct.H:155
amrex::Real sw_kabs_vapor
Mass model, SW: water-vapor gray absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:164
amrex::Real cloud_top_height_m
Cloud layer top height [m]. Only used when tau_profile_type == CloudLayer. Must be >= cloud_base_heig...
Definition: ERF_RadStruct.H:276
static void reset_nonpositive(const char *key, amrex::Real &value, amrex::Real fallback)
Definition: ERF_RadStruct.H:716
amrex::Real aerosol_tau_surface
Total-column aerosol optical depth at surface [dimensionless]. Used for AerosolProfileType::Exponenti...
Definition: ERF_RadStruct.H:609
int rad_orbital_year
erf.rad_orbital_year and the orbital overrides: negative means the year of the timestamp and the Berg...
Definition: ERF_RadStruct.H:145
amrex::Real fixed_solar_zenith_angle
erf.fixed_solar_zenith_angle: the COSINE of the solar zenith angle (mu0), as RRTMGP takes it,...
Definition: ERF_RadStruct.H:120
bool sw_enabled
Enable shortwave (solar) radiation computation. Only used by the two-stream model.
Definition: ERF_RadStruct.H:82
amrex::Real cloud_asymmetry_factor
Cloud scattering asymmetry factor in [-1, 1], used instead of asymmetry_factor for levels where the c...
Definition: ERF_RadStruct.H:331
void init_params(bool two_stream_selected, int max_level, std::string pp_prefix)
Definition: ERF_RadStruct.H:727
std::string diag_file
Output file path for radiation diagnostics CSV. Paths are relative to the simulation working director...
Definition: ERF_RadStruct.H:206
static void clamp_high(const char *key, amrex::Real &value, amrex::Real hi)
Definition: ERF_RadStruct.H:706
amrex::Real tau_lw_per_layer
Longwave optical depth per layer (constant for all layers). Used in gray-gas LW two-stream solver....
Definition: ERF_RadStruct.H:102
amrex::Real surface_albedo_sw
Shortwave surface albedo fallback [0, 1] for per-column heterogeneous-surface-property resolution....
Definition: ERF_RadStruct.H:340
amrex::Real surface_emissivity_lw
Longwave surface emissivity fallback [0, 1] for per-column heterogeneous-surface-property resolution....
Definition: ERF_RadStruct.H:355
amrex::Real rad_orbital_mvelp
Definition: ERF_RadStruct.H:148
amrex::Real seb_grdflx_default
Fallback SEB ground heat flux [W/m^2].
Definition: ERF_RadStruct.H:396
amrex::Real diag_dedup_tol
Tolerance for time equality in duplicate guard [s]. Times within this tolerance are considered equal....
Definition: ERF_RadStruct.H:257
amrex::Real seb_restore_timescale_s
Force-restore timescale for surface temperature [s]. Relaxation time constant toward T_deep in progno...
Definition: ERF_RadStruct.H:446
std::string diag_callsite_mode
Call-site filtering mode: "both" (pre+post rows), "pre_only", or "post_only".
Definition: ERF_RadStruct.H:250
bool diag_regtest_line_enable
Enable RADIATION_DIAG: line emission. Controls regtest-parsing diagnostics line. Default true.
Definition: ERF_RadStruct.H:237
amrex::Real sw_kext_cloud
Mass model, SW: cloud-water extinction coefficient m^2/kg.
Definition: ERF_RadStruct.H:167
amrex::Real tau_sw_coeff_qv
Shortwave optical depth coefficient for water vapor [dimensionless]. Dynamic SW tau = tau_sw_coeff_qv...
Definition: ERF_RadStruct.H:513
amrex::Real fixed_total_solar_irradiance
erf.fixed_total_solar_irradiance: top-of-atmosphere irradiance [W/m^2] when >= 0. Otherwise (default)...
Definition: ERF_RadStruct.H:127
bool tau_lw_dynamic_enable
Enable dynamic longwave optical depth diagnosis from moisture/clouds. When true, LW optical depth is ...
Definition: ERF_RadStruct.H:504
amrex::Real seb_lh_default
Fallback SEB latent heat flux [W/m^2].
Definition: ERF_RadStruct.H:391
amrex::Real seb_moisture_restore_timescale_s
Force-restore timescale for surface moisture [s]. Relaxation time constant toward q_deep in prognosti...
Definition: ERF_RadStruct.H:462
amrex::Real seb_lw_flux_default
Fallback SEB surface net longwave flux [W/m^2].
Definition: ERF_RadStruct.H:381
amrex::Real tau_sw_coeff_qc
Shortwave optical depth coefficient for cloud liquid water [dimensionless]. Default 0....
Definition: ERF_RadStruct.H:521
amrex::Real aerosol_tau_per_layer
Constant aerosol optical depth per layer [dimensionless]. Used for AerosolProfileType::Constant profi...
Definition: ERF_RadStruct.H:593
amrex::Real seb_q_sfc_default
Fallback SEB surface moisture [kg/kg].
Definition: ERF_RadStruct.H:401
amrex::Real rad_orbital_eccentricity
Definition: ERF_RadStruct.H:146
amrex::Real single_scattering_albedo
Clear-sky (background gas) single-scattering albedo in [0, 1] for the SW diffuse (scattering) two-str...
Definition: ERF_RadStruct.H:302
bool diag_csv_enable
Enable CSV file append behavior. When false, no file writes; other enabled streams may still print....
Definition: ERF_RadStruct.H:244
bool tau_sw_dynamic_enable
Definition: ERF_RadStruct.H:505
bool uses_two_stream_radiation() const noexcept
Check if two-stream radiation is active.
Definition: ERF_RadStruct.H:1113
amrex::Real seb_t_deep_default
Fallback SEB deep soil temperature [K].
Definition: ERF_RadStruct.H:406
AerosolProfileType aerosol_profile_type
Aerosol optical depth profile type: Constant, Exponential, or Table.
Definition: ERF_RadStruct.H:586
amrex::Real seb_prognostic_t_max_k
Maximum clamping bound for prognostic surface temperature [K]. After each Euler update,...
Definition: ERF_RadStruct.H:479
amrex::Real rad_t_sfc
erf.rad_t_sfc: surface temperature [K] used as the longwave boundary condition wherever no land-surfa...
Definition: ERF_RadStruct.H:135
bool seb_prognostic_enable
Enable prognostic SEB surface temperature and moisture evolution. When true, integrates SEB residual ...
Definition: ERF_RadStruct.H:430
bool seb_use_radiation_fluxes
Take the SEB's net surface shortwave and longwave fluxes from the two-stream sweep itself (per column...
Definition: ERF_RadStruct.H:371
amrex::Real seb_prognostic_t_min_k
Minimum clamping bound for prognostic surface temperature [K]. After each Euler update,...
Definition: ERF_RadStruct.H:471
amrex::Real tau_lw_coeff_qc
Longwave optical depth coefficient for cloud liquid water [dimensionless]. Default 0....
Definition: ERF_RadStruct.H:537
amrex::Real rad_orbital_obliquity
Definition: ERF_RadStruct.H:147
amrex::Real cloud_fraction
Cloud fraction in [0, 1] used to blend clear-sky and cloudy-column fluxes: F = (1 - cloud_fraction) *...
Definition: ERF_RadStruct.H:292
amrex::Real cloud_single_scattering_albedo
Cloud single-scattering albedo in [0, 1], used instead of single_scattering_albedo for levels where t...
Definition: ERF_RadStruct.H:322
amrex::Real lw_kabs_cloud
Cloud-water LW mass absorption coefficient m^2/kg.
Definition: ERF_RadStruct.H:194
amrex::Real sw_cloud_omega
Mass model, SW: cloud-water single-scattering albedo.
Definition: ERF_RadStruct.H:170
amrex::Real sw_kscat_dry
Mass model, SW: dry-air (Rayleigh) scattering coefficient [m^2/kg], omega = 1, g = 0.
Definition: ERF_RadStruct.H:161
amrex::Real sw_kabs_dry
Mass model, SW: dry-air gray absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:158
amrex::Real asymmetry_factor
Clear-sky (background gas) scattering asymmetry factor in [-1, 1] (0 = isotropic scattering,...
Definition: ERF_RadStruct.H:310
static void clamp_low(const char *key, amrex::Real &value, amrex::Real lo)
Read radiation options from the input parameter database.
Definition: ERF_RadStruct.H:696
amrex::Real cloud_fraction_qc_scale
Cloud water [kg/kg] at which the liquid-water term alone gives a cloud fraction of 1: cf += min(1,...
Definition: ERF_RadStruct.H:569
bool cloud_fraction_prog_enable
Enable prognostic cloud fraction diagnosis from RH/qc. When true, cloud fraction is computed per-leve...
Definition: ERF_RadStruct.H:545
amrex::Real seb_prognostic_q_max
Maximum clamping bound for prognostic surface moisture [kg/kg]. After each Euler update,...
Definition: ERF_RadStruct.H:495
bool lw_enabled
Enable longwave (thermal) radiation computation. Only used by the two-stream model.
Definition: ERF_RadStruct.H:88
amrex::Real surface_albedo_sw_diffuse
Shortwave surface albedo for diffuse light [0,1]. A negative value (default) uses surface_albedo_sw f...
Definition: ERF_RadStruct.H:346
amrex::Real cloud_base_height_m
Cloud layer base height [m]. Only used when tau_profile_type == CloudLayer.
Definition: ERF_RadStruct.H:270
amrex::Real lw_kabs_vapor
Water-vapor gray LW mass absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:191
amrex::Real tau_per_layer
Shortwave optical depth per layer (constant for all layers). Used in Beer-Lambert direct-beam formula...
Definition: ERF_RadStruct.H:95
amrex::Real seb_moisture_layer_depth_m
Effective surface moisture layer depth [m]. Used in force-restore prognostic q_s equation: dq_s/dt = ...
Definition: ERF_RadStruct.H:454
amrex::Real tau_lw_coeff_qv
Longwave optical depth coefficient for water vapor [dimensionless]. Dynamic LW tau = tau_lw_coeff_qv ...
Definition: ERF_RadStruct.H:529
amrex::Real seb_hfx_default
Fallback SEB sensible heat flux [W/m^2].
Definition: ERF_RadStruct.H:386
amrex::Real cloud_fraction_rh_max
Maximum relative humidity threshold for cloud fraction diagnosis [0, 1]. Must be >= cloud_fraction_rh...
Definition: ERF_RadStruct.H:561
amrex::Real sw_cloud_g
Mass model, SW: cloud-water asymmetry factor.
Definition: ERF_RadStruct.H:173
amrex::Real rad_cons_lon
Definition: ERF_RadStruct.H:139
bool lw_mass_absorption_enable
Gray longwave optical depth from the layer mass path instead of the fixed tau_lw_per_layer (also sele...
Definition: ERF_RadStruct.H:185
amrex::Real cloud_tau_per_layer
Additional optical depth per layer contributed by the cloud, added on top of the clear-sky tau_per_la...
Definition: ERF_RadStruct.H:284
amrex::Real aerosol_scale_height_m
Scale height for exponential aerosol profile [m]. Controls decay rate: tau_aerosol(k) = aerosol_tau_s...
Definition: ERF_RadStruct.H:601
bool diag_tagged_enable
Enable tagged [RAD][...] debug lines. Controls emission of bracketed-tag debug output....
Definition: ERF_RadStruct.H:230
amrex::Real seb_q_deep_default
Fallback SEB deep soil moisture [kg/kg].
Definition: ERF_RadStruct.H:411
amrex::Real rad_cons_lat
erf.rad_cons_lat / erf.rad_cons_lon: site latitude and longitude [degrees] when the grid has no lat/l...
Definition: ERF_RadStruct.H:138
amrex::Real seb_sw_flux_default
Fallback SEB surface net shortwave flux [W/m^2].
Definition: ERF_RadStruct.H:376
bool diag_enable
Master switch for radiation diagnostics emission. When false, no diagnostics output (stdout/tagged/re...
Definition: ERF_RadStruct.H:216
amrex::Real seb_prognostic_q_min
Minimum clamping bound for prognostic surface moisture [kg/kg]. After each Euler update,...
Definition: ERF_RadStruct.H:487
amrex::Real cloud_fraction_rh_min
Minimum relative humidity threshold for cloud fraction diagnosis [0, 1]. Cloud fraction ramps from 0 ...
Definition: ERF_RadStruct.H:553
bool seb_enable
Master switch for simplified SEB infrastructure. When false (default), no SEB MultiFabs are allocated...
Definition: ERF_RadStruct.H:361
bool diag_stdout_enable
Enable human-readable stdout diagnostics block. Controls tagged [RAD][...] debug line emission (when ...
Definition: ERF_RadStruct.H:223
bool enabled
True when erf.radiation_model = TwoStream. Set by init_params from the model SolverChoice read; the e...
Definition: ERF_RadStruct.H:76
int verbosity
Debug verbosity level (0=off, 1+=debug output). Gated debug prints only emit when this is >= 1.
Definition: ERF_RadStruct.H:200
amrex::Real seb_surface_heat_capacity
Effective surface heat capacity [J/(m^2*K)]. Used in force-restore prognostic T_s equation: dT_s/dt =...
Definition: ERF_RadStruct.H:438
TauProfileType tau_profile_type
Optical depth profile type: "constant" (default, the thickness-independent tau) or "cloud_layer" (add...
Definition: ERF_RadStruct.H:264
bool seb_diagnostic_enable
Enable diagnostic SEB residual computation and reporting. When true, computes and reports SEB residua...
Definition: ERF_RadStruct.H:421