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 #include <ERF_NoahMPSoilTable.H>
16 #include <ERF_SimplifiedSEB.H>
17 #include <cstdio>
18 
19 /**
20  * @file ERF_RadStruct.H
21  * @brief Radiation model type and control parameters.
22  */
23 
24 /**
25  * @brief Optical depth profile type for the two-stream solver.
26  *
27  * - Constant: tau_per_layer / tau_lw_per_layer applied uniformly at every
28  * level, independent of layer thickness.
29  * - CloudLayer: an additional cloud optical depth is added within
30  * [cloud_base_height_m, cloud_top_height_m], on top of the clear-sky
31  * background tau_per_layer / tau_lw_per_layer.
32  */
33 AMREX_ENUM(TauProfileType, Constant, CloudLayer);
34 
35 /**
36  * @brief Aerosol optical depth profile type for the two-stream solver.
37  *
38  * - Constant: uniform aerosol tau_per_layer applied at every level.
39  * - Exponential: aerosol tau decays exponentially with height via
40  * tau_aerosol(k) = tau_surface * exp(-z(k) / scale_height).
41  * - Table: aerosol tau prescribed per level via lookup (future extension).
42  */
43 AMREX_ENUM(AerosolProfileType, Constant, Exponential, Table);
44 
45 /**
46  * @brief How the clear-sky optical depth of a layer is obtained.
47  *
48  * - PerLayer: the fixed tau_per_layer / tau_lw_per_layer for every layer
49  * regardless of its thickness (the original model; the column optical
50  * depth then scales with the number of vertical cells).
51  * - Mass: from the layer mass path, tau = rho dz (k_dry + k_vapor qv + ...),
52  * so the column optical depth is set by the atmosphere, not by the grid.
53  * Shortwave uses sw_kabs_dry, sw_kscat_dry (Rayleigh, scattering),
54  * sw_kabs_vapor and sw_kext_cloud with sw_cloud_omega / sw_cloud_g;
55  * longwave uses lw_kabs_dry, lw_kabs_vapor and lw_kabs_cloud.
56  */
57 AMREX_ENUM(TauModel, PerLayer, Mass);
58 
59 /**
60  * @brief Where the simplified surface energy balance takes its sensible (H) and
61  * latent (LE) heat fluxes from when no land-surface model supplies them.
62  *
63  * - SurfaceLayer: the fluxes the lower surface layer applies to the air, in
64  * W/m^2 (the same conversion as the sensible_heat_flux / latent_heat_flux 2D
65  * outputs), so the ground loses what the air receives. Used only where a zlo
66  * surface layer exists and applies a flux; otherwise the scalar defaults.
67  * - Defaults: the constants seb_hfx_default and seb_lh_default everywhere.
68  */
69 AMREX_ENUM(SEBTurbulentFluxSource, SurfaceLayer, Defaults);
70 
71 /** Inputs shared by RRTMGP and the topographic surface correction. */
73 {
74  amrex::Real fixed_mu = -9999.0;
75  amrex::Real latitude = 39.809860;
76  amrex::Real longitude = -98.555183;
78 
79  void read (const std::string& prefix)
80  {
81  amrex::ParmParse pp(prefix);
82  pp.queryAdd("fixed_solar_zenith_angle", fixed_mu);
83  pp.queryAdd("rad_cons_lat", latitude);
84  pp.queryAdd("rad_cons_lon", longitude);
85  pp.queryAdd("rad_freq_in_steps", radiation_interval);
86  if (!std::isfinite(latitude) || latitude < -90.0 || latitude > 90.0) {
87  amrex::Abort("erf.rad_cons_lat = " + std::to_string(latitude) +
88  " must lie in [-90, 90] degrees.");
89  }
90  if (!std::isfinite(longitude) || longitude < -180.0 || longitude > 180.0) {
91  amrex::Abort("erf.rad_cons_lon = " + std::to_string(longitude) +
92  " must lie in [-180, 180] degrees.");
93  }
94  if (fixed_mu > 1.0) {
95  amrex::Abort("erf.fixed_solar_zenith_angle = " + std::to_string(fixed_mu) +
96  " is the cosine of the solar zenith angle and cannot exceed 1; 60 degrees is 0.5.");
97  }
98  }
99 };
100 
101 /** Inputs for the optional topographic correction of RRTMGP land forcing. */
103 {
104  bool slope_rad = false;
105  bool topo_shading = false;
108  amrex::Real fixed_azimuth = -1; // degrees clockwise from geographic north
112 
113  bool active () const { return slope_rad || topo_shading; }
114  void read (const std::string& prefix, const RRTMGPSolarChoice& solar);
115 };
116 
117 /**
118  * @brief Container holding radiation-related choices and parameters.
119  *
120  * Manages the runtime selection and configuration of radiation solvers.
121  *
122  * The two-stream solver builds up from a clear-sky, no-scattering column of
123  * uniform optical depth. On top of that it offers height-varying optical
124  * depth (a cloud layer) with cloud-fraction masking, which blends clear-sky
125  * and cloudy-column fluxes, and a diffuse shortwave component from a
126  * two-stream scattering approximation parameterized by single-scattering
127  * albedo and asymmetry factor, with independent clear-sky and cloud values.
128  * Every addition is off by default, so the defaults reproduce the plain
129  * clear-sky column.
130  */
131 struct RadChoice
132 {
133 public:
134  /**
135  * @brief True when erf.radiation_model = TwoStream. Set by init_params from
136  * the model SolverChoice read; the erf.radiation.* sub-options are read only then.
137  */
138  bool enabled = false;
139 
140  /**
141  * @brief Enable shortwave (solar) radiation computation.
142  * Only used by the two-stream model.
143  */
144  bool sw_enabled = true;
145 
146  /**
147  * @brief Enable longwave (thermal) radiation computation.
148  * Only used by the two-stream model.
149  */
150  bool lw_enabled = true;
151 
152  /**
153  * @brief Shortwave optical depth per layer (constant for all layers).
154  * Used in Beer-Lambert direct-beam formula.
155  * Only used by the two-stream model.
156  */
158 
159  /**
160  * @brief Longwave optical depth per layer (constant for all layers).
161  * Used in gray-gas LW two-stream solver.
162  * Only used by the two-stream model.
163  */
165 
166  /**
167  * @name Inputs shared with the RRTMGP interface
168  * The two-stream model reads the sun, site and surface-temperature inputs
169  * under the same keys, with the same meaning, as the RRTMGP interface
170  * (Source/Radiation/RRTMGP/ERF_Radiation.H), so a deck switches
171  * between the two models by changing erf.radiation_model alone.
172  * @{
173  */
174 
175  /**
176  * @brief erf.fixed_solar_zenith_angle: the COSINE of the solar zenith
177  * angle (mu0), as RRTMGP takes it, applied to every column when > 0.
178  * Otherwise (default) the sun follows the calendar: start_datetime sets
179  * the date and time, the position is evaluated at rad_cons_lat/lon or at
180  * the per-column latitude and longitude when the grid carries them.
181  */
183 
184  /**
185  * @brief erf.fixed_total_solar_irradiance: top-of-atmosphere irradiance
186  * [W/m^2] when >= 0. Otherwise (default) 1360.9 W/m^2 scaled by the
187  * Earth-Sun distance factor of the date, as RRTMGP does.
188  */
190 
191  /**
192  * @brief erf.rad_t_sfc: surface temperature [K] used as the longwave
193  * boundary condition wherever no land-surface model, surface layer or
194  * prognostic surface energy balance supplies one; also the initial value
195  * of the prognostic surface temperature. Required, as for RRTMGP.
196  */
198 
199  /** @brief erf.rad_cons_lat / erf.rad_cons_lon: site latitude and longitude [degrees] when the grid has no lat/lon fields (RRTMGP defaults). */
201  amrex::Real rad_cons_lon = -98.555183;
202 
203  /**
204  * @brief erf.rad_orbital_year and the orbital overrides: negative means
205  * the year of the timestamp and the Berger (1978) values for that year.
206  */
207  int rad_orbital_year = -9999;
211  /** @} */
212 
213  /**
214  * @brief Optical depth model: "per_layer" (default, fixed tau per layer)
215  * or "mass" (from the layer mass path in both bands; see TauModel).
216  */
217  TauModel tau_model = TauModel::PerLayer;
218 
219  /** @brief Mass model, SW: dry-air gray absorption coefficient [m^2/kg]. */
221 
222  /** @brief Mass model, SW: dry-air (Rayleigh) scattering coefficient [m^2/kg], omega = 1, g = 0. */
224 
225  /** @brief Mass model, SW: water-vapor gray absorption coefficient [m^2/kg]. */
227 
228  /** @brief Mass model, SW: cloud-water extinction coefficient [m^2/kg] (~1500/r_e[um]: 150 for r_e = 10 um). */
230 
231  /** @brief Mass model, SW: cloud-water single-scattering albedo. */
233 
234  /** @brief Mass model, SW: cloud-water asymmetry factor. */
236 
237  /**
238  * @brief Gray longwave optical depth from the layer mass path instead of
239  * the fixed tau_lw_per_layer (also selected by tau_model = mass):
240  * tau_lw(k) = rho dz (lw_kabs_dry + lw_kabs_vapor qv + lw_kabs_cloud qc)
241  * The cloud term is the Stephens (1978) emissivity, eps = 1 - exp(-a LWP)
242  * with a = 0.158 m^2/g, written as an optical depth. The column optical
243  * depth is then independent of the vertical resolution. The cloud-band,
244  * moisture-coefficient and aerosol additions still apply on top.
245  * Default false keeps tau_lw_per_layer.
246  */
248 
249  /** @brief Dry-air gray LW mass absorption coefficient [m^2/kg]. */
251 
252  /** @brief Water-vapor gray LW mass absorption coefficient [m^2/kg]. */
254 
255  /** @brief Cloud-water LW mass absorption coefficient [m^2/kg] (Stephens 1978: 0.158 m^2/g). */
257 
258  /**
259  * @brief Debug verbosity level (0=off, 1+=debug output).
260  * Gated debug prints only emit when this is >= 1.
261  */
262  int verbosity = 0;
263 
264  /**
265  * @brief Output file path for radiation diagnostics CSV.
266  * Paths are relative to the simulation working directory.
267  */
268  std::string diag_file = "radiation_diag.dat";
269 
270  /**
271  * @brief Master switch for radiation diagnostics emission.
272  * When false, no diagnostics output (stdout/tagged/regtest/csv).
273  * Default false: a run that did not ask for the diagnostics should not
274  * write a CSV row and a stdout block on every step. The per-stream
275  * switches below stay on by default, so setting this one input to true
276  * turns the whole set on.
277  */
278  bool diag_enable = false;
279 
280  /**
281  * @brief Enable human-readable stdout diagnostics block.
282  * Controls tagged [RAD][...] debug line emission (when verbosity >= 1).
283  * Default true.
284  */
285  bool diag_stdout_enable = true;
286 
287  /**
288  * @brief Enable tagged [RAD][...] debug lines.
289  * Controls emission of bracketed-tag debug output.
290  * Default true.
291  */
292  bool diag_tagged_enable = true;
293 
294  /**
295  * @brief Enable RADIATION_DIAG: line emission.
296  * Controls regtest-parsing diagnostics line.
297  * Default true.
298  */
300 
301  /**
302  * @brief Enable CSV file append behavior.
303  * When false, no file writes; other enabled streams may still print.
304  * Default true.
305  */
306  bool diag_csv_enable = true;
307 
308  /**
309  * @brief Call-site filtering mode: "both" (pre+post rows),
310  * "pre_only", or "post_only".
311  */
312  std::string diag_callsite_mode = "both";
313 
314  /**
315  * @brief Tolerance for time equality in duplicate guard [s].
316  * Times within this tolerance are considered equal.
317  * Default 1e-12.
318  */
320 
321  /**
322  * @brief Optical depth profile type: "constant" (default,
323  * the thickness-independent tau) or "cloud_layer" (adds height-varying
324  * cloud optical depth between cloud_base_height_m and cloud_top_height_m).
325  */
326  TauProfileType tau_profile_type = TauProfileType::Constant;
327 
328  /**
329  * @brief Cloud layer base height [m]. Only used when
330  * tau_profile_type == CloudLayer.
331  */
333 
334  /**
335  * @brief Cloud layer top height [m]. Only used when
336  * tau_profile_type == CloudLayer. Must be >= cloud_base_height_m.
337  */
339 
340  /**
341  * @brief Additional optical depth per layer contributed by the
342  * cloud, added on top of the clear-sky tau_per_layer/tau_lw_per_layer
343  * background within [cloud_base_height_m, cloud_top_height_m]. Only used
344  * when tau_profile_type == CloudLayer.
345  */
347 
348  /**
349  * @brief Cloud fraction in [0, 1] used to blend clear-sky and
350  * cloudy-column fluxes: F = (1 - cloud_fraction) * F_clear +
351  * cloud_fraction * F_cloudy. Default 0.0 (fully clear sky), which
352  * reduces exactly to the clear-sky column regardless of tau_profile_type.
353  */
355 
356  /**
357  * @brief Clear-sky (background gas) single-scattering albedo
358  * in [0, 1] for the SW diffuse (scattering) two-stream calculation.
359  * Default 0.0 means the clear-sky column is purely absorbing, and the
360  * diffuse SW contribution from the clear-sky column is exactly zero —
361  * reducing exactly to the direct-beam-only result.
362  * Only used by the two-stream model and sw_enabled == true.
363  */
365 
366  /**
367  * @brief Clear-sky (background gas) scattering asymmetry
368  * factor in [-1, 1] (0 = isotropic scattering, close to 1 = strongly
369  * forward-scattering). Only meaningful when single_scattering_albedo
370  * > 0. Default 0.0.
371  */
373 
374  /**
375  * @brief Cloud single-scattering albedo in [0, 1], used
376  * instead of single_scattering_albedo for levels where the
377  * cloud-layer optical depth enhancement applies (i.e., within
378  * [cloud_base_height_m, cloud_top_height_m] on the "cloudy" column
379  * evaluation). Default 0.0 leaves the cloud band non-scattering
380  * for SW_Cloud_Layer unless explicitly configured otherwise. Typical
381  * liquid water cloud values are close to 1 (e.g., 0.9999).
382  * Only used when tau_profile_type == CloudLayer.
383  */
385 
386  /**
387  * @brief Cloud scattering asymmetry factor in [-1, 1], used
388  * instead of asymmetry_factor for levels where the cloud-layer
389  * optical depth enhancement applies. Default 0.0. Typical liquid water
390  * cloud droplet values are around 0.85 (strongly forward-scattering).
391  * Only meaningful when cloud_single_scattering_albedo > 0.
392  */
394 
395  /**
396  * @brief Shortwave surface albedo fallback [0, 1] for per-column
397  * heterogeneous-surface-property resolution. Used when per-column LSM/radiation
398  * interface albedo field is unavailable or invalid. Default 0.3 represents
399  * typical land/water reflectivity. Only used when hetero surface fields not
400  * present or disabled.
401  */
403 
404  /**
405  * @brief Shortwave surface albedo for diffuse light [0,1]. A negative
406  * value (default) uses surface_albedo_sw for both direct and diffuse light.
407  */
409 
410  /**
411  * @brief Longwave surface emissivity fallback [0, 1] for per-column
412  * heterogeneous-surface-property resolution. Used when per-column LSM/radiation
413  * interface emissivity field is unavailable or invalid. Default 0.99 represents
414  * typical land/water/ice emissivity (nearly blackbody). Only used when hetero
415  * surface fields not present or disabled.
416  */
418 
419  /**
420  * @brief Master switch for simplified SEB infrastructure.
421  * When false (default), no SEB MultiFabs are allocated and behavior is unchanged.
422  */
423  bool seb_enable = false;
424 
425  /**
426  * @brief Take the SEB's net surface shortwave and longwave fluxes from the
427  * two-stream sweep itself (per column, absorbed fluxes positive) wherever
428  * the LSM does not expose them. When false (default) those fluxes are the
429  * LSM fields or, without an LSM, the seb_sw_flux_default and
430  * seb_lw_flux_default constants. Sensible, latent and ground heat fluxes
431  * are unaffected.
432  */
434 
435  /**
436  * @brief Fallback SEB surface net shortwave flux [W/m^2].
437  */
439 
440  /**
441  * @brief Fallback SEB surface net longwave flux [W/m^2].
442  */
444 
445  /**
446  * @brief Source of the SEB's sensible and latent heat fluxes when no
447  * land-surface model supplies them: "surface_layer" (default) or "defaults".
448  * See SEBTurbulentFluxSource.
449  */
450  SEBTurbulentFluxSource seb_turbulent_flux_source = SEBTurbulentFluxSource::SurfaceLayer;
451 
452  /**
453  * @brief Two-way coupling with the zlo surface layer: the surface layer takes
454  * its land surface temperature from the SEB's prognostic skin temperature T_s
455  * (converted to potential temperature), so its sensible heat flux responds to
456  * T_s. Requires seb_prognostic_enable and seb_turbulent_flux_source =
457  * surface_layer. Default false: the surface layer keeps its own temperature.
458  */
460 
461  /**
462  * @brief Two-way coupling of moisture as well: the surface layer takes its land surface
463  * mixing ratio from the balance's surface water, so its moisture flux follows the soil
464  * and dries as the balance's q_s does. Without seb_soil_type it is
465  * beta * q_sat(T_s, p_s) + (1 - beta) * q_air with beta the soil-water factor
466  * clamp((q_s - seb_soil_moisture_wilt) / (seb_soil_moisture_fc - seb_soil_moisture_wilt),
467  * 0, 1). With seb_soil_type, the bare soil evaporates from its pore air (Noah-MP's
468  * relative humidity exp(psi g / (R_v T_s)) times q_sat) through Noah-MP's bare-soil
469  * resistance, and with seb_vegetation_type the canopy transpires through its own, each in
470  * series with the aerodynamic one (erf_surface_moisture::two_source_surface_mixing_ratio).
471  * q_s is then the
472  * volumetric water content [m^3/m^3] of the top seb_moisture_layer_depth_m of soil.
473  * Requires seb_surface_layer_uses_skin. Default false: the surface layer keeps its own
474  * surface moisture (erf.most.surf_moist).
475  */
477 
478  /**
479  * @brief Wilting point [m^3/m^3]: at or below it the soil-water factor is 0, so the
480  * vegetation does not transpire and, without seb_soil_type, the surface does not
481  * evaporate (with one, the bare soil's nearly dry pore air keeps it near zero)
482  * (seb_surface_layer_uses_moisture; giving it otherwise stops at start-up). Default
483  * 0.066, Noah-MP's loam (WLTSMC, STAS soil type 6).
484  */
486 
487  /**
488  * @brief Field capacity [m^3/m^3]: at or above it the soil-water factor is 1, so without
489  * seb_soil_type the surface evaporates at the potential rate
490  * (seb_surface_layer_uses_moisture; giving it otherwise stops at start-up). Default
491  * 0.329, Noah-MP's loam (REFSMC, STAS soil type 6).
492  */
494 
495  /**
496  * @brief Noah-MP soil category (STAS, 1-19, not 14 (water), as in NoahmpTable.TBL; see
497  * ERF_NoahMPSoilTable.H). The balance takes its wilting point (WLTSMC) and field
498  * capacity (REFSMC) -- giving either seb_soil_moisture_wilt or seb_soil_moisture_fc as
499  * well is an error --, the surface heat capacity of the soil unless
500  * seb_surface_heat_capacity is given, with seb_surface_layer_uses_moisture the bare-soil
501  * resistance (vegetation or not), and with seb_surface_layer_uses_skin the surface
502  * layer's land roughness unless
503  * erf.most.z0 is given. 0 (the default): none of these.
504  */
505  int seb_soil_type = 0;
506 
507  /**
508  * @brief Noah-MP land-use category (modified IGBP MODIS, 1-20 except the unvegetated 15,
509  * 16 and 17, as in NoahmpTable.TBL; see ERF_NoahMPVegetationTable.H) of the vegetated
510  * fraction. With it, the surface's moisture
511  * availability comes from a canopy resistance (Jarvis, Noah-MP's parameters RS, RGL, HS,
512  * TOPT, RSMAX) for the vegetated fraction and a bare-soil resistance (Noah-MP's
513  * Sakaguchi-Zeng form, on seb_soil_type) for the rest, each in series with the
514  * aerodynamic one. Needs seb_surface_layer_uses_moisture and seb_soil_type. 0 (the
515  * default): bare soil, the soil's resistance alone (f_veg = 0).
516  */
518 
519  /**
520  * @brief Vegetated fraction of the surface [0, 1] (seb_vegetation_type; giving it otherwise
521  * stops at start-up). Default 1.
522  */
524 
525  /**
526  * @brief Leaf area index [m^2/m^2] of the vegetated fraction (seb_vegetation_type;
527  * giving it otherwise stops at start-up). Default: the land-use category's monthly
528  * values interpolated to the run's start date and time (start_datetime), as Noah-MP does
529  * with its table LAI (noahmp_day_of_year: day 0 at 00:00 on 1 January), shifted half a year when erf.rad_cons_lat < 0 (one latitude for the domain,
530  * where Noah-MP uses each column's); fixed for the run.
531  */
533 
534  /**
535  * @brief Land roughness length [m] the coupled surface layer takes when erf.most.z0 is
536  * not given (derived, not an input): with seb_surface_layer_uses_skin and seb_soil_type,
537  * f_veg Z0MVT + (1 - f_veg) Z0SOIL from Noah-MP's tables (f_veg = 0 without
538  * seb_vegetation_type); -1 otherwise. ERF::InitData_post hands it to the zlo surface
539  * layer as it builds it (SurfaceLayer::set_default_roughness).
540  */
542 
543  /**
544  * @brief Fallback SEB sensible heat flux [W/m^2].
545  */
547 
548  /**
549  * @brief Fallback SEB latent heat flux [W/m^2].
550  */
552 
553  /**
554  * @brief Fallback SEB ground heat flux [W/m^2]. The force-restore restoring
555  * term already represents the flux into the soil, so a nonzero value with
556  * seb_prognostic_enable counts that flux twice.
557  */
559 
560  /**
561  * @brief Fallback SEB surface moisture, and the initial value of the prognostic one.
562  * With seb_prognostic_enable q_s follows prognostic_dqs_dt, a volumetric water content
563  * [m^3/m^3] of the top seb_moisture_layer_depth_m of soil; with a land model that has
564  * one, the balance shows the land model's 2 m mixing ratio [kg/kg] instead.
565  */
567 
568  /**
569  * @brief Fallback SEB deep soil temperature [K].
570  */
572 
573  /**
574  * @brief Deep soil moisture the prognostic q_s restores to, in q_s's units (see
575  * seb_q_sfc_default).
576  */
578 
579  /**
580  * @brief Enable diagnostic SEB residual computation and reporting.
581  * When true, computes and reports SEB residual = (SW_net + LW_net) - H - LE - G.
582  * When false (default), no residual computation or new diagnostic output.
583  * Requires seb_enable=true to be meaningful; if false but seb_enable=true, auto-enables internally.
584  * Note: this is diagnostic-only; it makes no prognostic T_s or flux update.
585  * Set seb_prognostic_enable for that.
586  */
587  bool seb_diagnostic_enable = false;
588 
589  /**
590  * @brief Enable prognostic SEB surface temperature and moisture evolution.
591  * When true, integrates SEB residual forward in time to evolve T_s and q_s using
592  * force-restore formulation. When false (default), no prognostic update occurs.
593  * Requires seb_enable=true and seb_diagnostic_enable=true; auto-enables internally if needed.
594  * Only active when Noah-MP is NOT driving LSM fields at that level.
595  */
596  bool seb_prognostic_enable = false;
597 
598  /**
599  * @brief Effective surface heat capacity [J/(m^2*K)].
600  * Used in force-restore prognostic T_s equation: dT_s/dt = SEB_residual / C_s - (2*pi/tau)*(T_s - T_deep).
601  * Default 2.0e4 J/(m^2*K) represents a thin surface-active layer. With seb_soil_type
602  * it is derived from Noah-MP's soil instead (seb_force_restore_heat_capacity of the
603  * soil's heat capacity and conductivity at seb_q_sfc_default, over
604  * seb_restore_timescale_s) unless it is given explicitly.
605  * Only used when seb_prognostic_enable=true. Must be > 0: init_params() stops otherwise.
606  */
608 
609  /**
610  * @brief Force-restore timescale for surface temperature [s].
611  * Relaxation time constant toward T_deep in prognostic T_s equation.
612  * Default 86400.0 s (1 day) provides weak damping on diurnal timescales.
613  * Only used when seb_prognostic_enable=true. Must be > 0: init_params() stops otherwise.
614  */
616 
617  /**
618  * @brief Effective surface moisture layer depth [m].
619  * 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).
620  * Default 0.1 m represents a thin surface-active moisture layer.
621  * Only used when seb_prognostic_enable=true. Must be > 0: init_params() stops otherwise.
622  */
624 
625  /**
626  * @brief Force-restore timescale for surface moisture [s].
627  * Relaxation time constant toward q_deep in prognostic q_s equation.
628  * Default 86400.0 s (1 day) provides weak damping on diurnal timescales.
629  * Only used when seb_prognostic_enable=true. Must be > 0: init_params() stops otherwise.
630  */
632 
633  /**
634  * @brief Minimum clamping bound for prognostic surface temperature [K].
635  * After each Euler update, T_s is clamped to [seb_prognostic_t_min_k, seb_prognostic_t_max_k]
636  * to prevent instability if dt is large relative to C_s/tau.
637  * Default 200.0 K (far below realistic surface temperatures).
638  * Only used when seb_prognostic_enable=true.
639  */
641 
642  /**
643  * @brief Maximum clamping bound for prognostic surface temperature [K].
644  * After each Euler update, T_s is clamped to [seb_prognostic_t_min_k, seb_prognostic_t_max_k].
645  * Default 340.0 K (above typical surface temperatures, but prevents unrealistic excursions).
646  * Only used when seb_prognostic_enable=true.
647  */
649 
650  /**
651  * @brief Minimum clamping bound for prognostic surface moisture [kg/kg].
652  * After each Euler update, q_s is clamped to [seb_prognostic_q_min, seb_prognostic_q_max].
653  * Default 0.0 kg/kg prevents negative moisture.
654  * Only used when seb_prognostic_enable=true.
655  */
657 
658  /**
659  * @brief Maximum clamping bound for prognostic surface moisture [kg/kg].
660  * After each Euler update, q_s is clamped to [seb_prognostic_q_min, seb_prognostic_q_max].
661  * Default 1.0 kg/kg ensures physically meaningful moisture.
662  * Only used when seb_prognostic_enable=true.
663  */
665 
666 
667  /**
668  * @brief Enable dynamic longwave optical depth diagnosis from moisture/clouds.
669  * When true, LW optical depth is computed per-level from qv and qc.
670  * When false (default), uses the static tau_lw_per_layer.
671  * Only used by the two-stream model and lw_enabled == true.
672  */
673  bool tau_lw_dynamic_enable = false;
674  bool tau_sw_dynamic_enable = false;
675 
676  /**
677  * @brief Shortwave optical depth coefficient for water vapor [dimensionless].
678  * Dynamic SW tau = tau_sw_coeff_qv * qv + tau_sw_coeff_qc * qc + tau_per_layer.
679  * Default 0.0 means tau is purely from static tau_per_layer (no dynamic component).
680  * Only used when tau_sw_dynamic_enable == true.
681  */
683 
684  /**
685  * @brief Shortwave optical depth coefficient for cloud liquid water [dimensionless].
686  * Default 0.0. When both coefficients are 0, dynamic path still invoked but yields
687  * tau_per_layer unchanged.
688  * Only used when tau_sw_dynamic_enable == true.
689  */
691 
692  /**
693  * @brief Longwave optical depth coefficient for water vapor [dimensionless].
694  * Dynamic LW tau = tau_lw_coeff_qv * qv + tau_lw_coeff_qc * qc + tau_lw_per_layer.
695  * Default 0.0 means tau is purely from static tau_lw_per_layer (no dynamic component).
696  * Only used when tau_lw_dynamic_enable == true.
697  */
699 
700  /**
701  * @brief Longwave optical depth coefficient for cloud liquid water [dimensionless].
702  * Default 0.0. When both coefficients are 0, dynamic path still invoked but yields
703  * tau_lw_per_layer unchanged.
704  * Only used when tau_lw_dynamic_enable == true.
705  */
707 
708  /**
709  * @brief Enable prognostic cloud fraction diagnosis from RH/qc.
710  * When true, cloud fraction is computed per-level from relative humidity and cloud water.
711  * When false (default), uses the static cloud_fraction.
712  * Only used by the two-stream model.
713  */
715 
716  /**
717  * @brief Minimum relative humidity threshold for cloud fraction diagnosis [0, 1].
718  * Cloud fraction ramps from 0 at rh < cloud_fraction_rh_min to 1 at rh >= cloud_fraction_rh_max.
719  * Default 0.0 means cloud fraction depends primarily on qc content.
720  * Only used when cloud_fraction_prog_enable == true.
721  */
723 
724  /**
725  * @brief Maximum relative humidity threshold for cloud fraction diagnosis [0, 1].
726  * Must be >= cloud_fraction_rh_min. Cloud fraction saturates to 1 at this RH.
727  * Default 1.0 means cloud fraction increases linearly from rh_min to 1.0 RH.
728  * Only used when cloud_fraction_prog_enable == true.
729  */
731 
732  /**
733  * @brief Cloud water [kg/kg] at which the liquid-water term alone gives a
734  * cloud fraction of 1: cf += min(1, qc / cloud_fraction_qc_scale).
735  * Default 1.0e-3 kg/kg, so a typical stratus cloud water saturates the term.
736  * Only used when cloud_fraction_prog_enable == true.
737  */
739 
740  /**
741  * @brief Enable prescribed bulk aerosol/turbidity optical depth.
742  * When true, aerosol optical depth is added on top of existing tau_base + cloud + dynamic-moisture contributions.
743  * When false (default), there is no aerosol contribution.
744  * Only used by the two-stream model and sw_enabled == true.
745  */
746  bool aerosol_enable = false;
747 
748  /**
749  * @brief Aerosol optical depth profile type: Constant, Exponential, or Table.
750  * - Constant: uniform aerosol tau per layer (aerosol_tau_per_layer).
751  * - Exponential: tau decays with height via tau_aerosol(k) = aerosol_tau_surface * exp(-z(k) / aerosol_scale_height_m).
752  * - Table: per-level tau prescribed via lookup (future extension, currently not supported).
753  * Default Constant. Only used when aerosol_enable == true.
754  */
755  AerosolProfileType aerosol_profile_type = AerosolProfileType::Constant;
756 
757  /**
758  * @brief Constant aerosol optical depth per layer [dimensionless].
759  * Used for AerosolProfileType::Constant profile. Default 0.0 (no aerosol contribution).
760  * Only used when aerosol_enable == true and aerosol_profile_type == Constant.
761  */
763 
764  /**
765  * @brief Scale height for exponential aerosol profile [m].
766  * Controls decay rate: tau_aerosol(k) = aerosol_tau_surface * exp(-z(k) / scale_height).
767  * Default 2000.0 m (typical scale height for tropospheric aerosols).
768  * Only used when aerosol_enable == true and aerosol_profile_type == Exponential.
769  */
771 
772  /**
773  * @brief Total-column aerosol optical depth at surface [dimensionless].
774  * Used for AerosolProfileType::Exponential profile as the surface boundary condition.
775  * Default 0.0 (no aerosol contribution).
776  * Only used when aerosol_enable == true and aerosol_profile_type == Exponential.
777  */
779 
780  /**
781  * @brief Read radiation options from the input parameter database.
782  *
783  * @param[in] two_stream_selected Whether the two-stream solver is the selected model.
784  * @param[in] pp_prefix ParmParse prefix for the ERF input namespace (e.g., "erf").
785  *
786  * Queries:
787  * - erf.radiation_model = TwoStream (read by SolverChoice) selects the model
788  * - erf.radiation.sw_enabled (bool, optional, default true)
789  * - erf.radiation.lw_enabled (bool, optional, default true)
790  * - erf.radiation.tau_per_layer (real, optional)
791  * - erf.radiation.tau_lw_per_layer (real, optional)
792  * - erf.fixed_solar_zenith_angle, erf.fixed_total_solar_irradiance, erf.rad_t_sfc
793  * (required), erf.rad_cons_lat, erf.rad_cons_lon, erf.rad_orbital_year,
794  * erf.rad_orbital_eccentricity, erf.rad_orbital_obliquity, erf.rad_orbital_mvelp:
795  * the RRTMGP interface's keys, with its meanings (see the members above)
796  * - erf.radiation.tau_model (string "per_layer"|"mass", optional, default "per_layer")
797  * - erf.radiation.sw_kabs_dry, sw_kscat_dry, sw_kabs_vapor, sw_kext_cloud (real, optional, mass model)
798  * - erf.radiation.sw_cloud_omega, sw_cloud_g (real, optional, mass model)
799  * - erf.radiation.lw_mass_absorption_enable (bool, optional, default false)
800  * - erf.radiation.lw_kabs_dry (real, optional, default 1.0e-4 m^2/kg)
801  * - erf.radiation.lw_kabs_vapor (real, optional, default 0.1 m^2/kg)
802  * - erf.radiation.lw_kabs_cloud (real, optional, default 158.0 m^2/kg)
803  * - erf.radiation.surface_albedo_sw_diffuse (real, optional, default -1 = same as direct)
804  * - erf.radiation.v (int, optional, debug verbosity)
805  * - erf.radiation.diag_file (string, optional)
806  * - erf.radiation.tau_profile_type (string "constant"|"cloud_layer", optional)
807  * - erf.radiation.cloud_base_height_m (real, optional)
808  * - erf.radiation.cloud_top_height_m (real, optional)
809  * - erf.radiation.cloud_tau_per_layer (real, optional)
810  * - erf.radiation.cloud_fraction (real, optional)
811  * - erf.radiation.single_scattering_albedo (real, optional)
812  * - erf.radiation.asymmetry_factor (real, optional)
813  * - erf.radiation.cloud_single_scattering_albedo (real, optional)
814  * - erf.radiation.cloud_asymmetry_factor (real, optional)
815  * - erf.radiation.diag_enable (bool, optional, default false)
816  * - erf.radiation.diag_stdout_enable (bool, optional, default true)
817  * - erf.radiation.diag_tagged_enable (bool, optional, default true)
818  * - erf.radiation.diag_regtest_line_enable (bool, optional, default true)
819  * - erf.radiation.diag_csv_enable (bool, optional, default true)
820  * - erf.radiation.diag_callsite_mode (string, optional, default "both")
821  * - erf.radiation.diag_dedup_tol (real, optional, default 1e-12)
822  * - erf.radiation.tau_sw_dynamic_enable (bool, optional, default false)
823  * - erf.radiation.tau_lw_dynamic_enable (bool, optional, default false)
824  * - erf.radiation.tau_sw_coeff_qv (real, optional, default 0.0)
825  * - erf.radiation.tau_sw_coeff_qc (real, optional, default 0.0)
826  * - erf.radiation.tau_lw_coeff_qv (real, optional, default 0.0)
827  * - erf.radiation.tau_lw_coeff_qc (real, optional, default 0.0)
828  * - erf.radiation.cloud_fraction_prog_enable (bool, optional, default false)
829  * - erf.radiation.cloud_fraction_rh_min (real, optional, default 0.0)
830  * - erf.radiation.cloud_fraction_rh_max (real, optional, default 1.0)
831  * - erf.radiation.cloud_fraction_qc_scale (real, optional, default 1.0e-3)
832  * - erf.radiation.aerosol_enable (bool, optional, default false)
833  * - erf.radiation.aerosol_profile_type (string "constant"|"exponential"|"table", optional, default "constant")
834  * - erf.radiation.aerosol_tau_per_layer (real, optional, default 0.0)
835  * - erf.radiation.aerosol_scale_height_m (real, optional, default 2000.0)
836  * - erf.radiation.aerosol_tau_surface (real, optional, default 0.0)
837  * - erf.radiation.seb_enable (bool, optional, default false)
838  * - erf.radiation.seb_sw_flux_default (real, optional, default 0.0)
839  * - erf.radiation.seb_lw_flux_default (real, optional, default 0.0)
840  * - erf.radiation.seb_turbulent_flux_source (string "surface_layer"|"defaults", optional, default "surface_layer")
841  * - erf.radiation.seb_surface_layer_uses_skin (bool, optional, default false)
842  * - erf.radiation.seb_surface_layer_uses_moisture (bool, optional, default false)
843  * - erf.radiation.seb_soil_moisture_wilt (real, optional, default 0.066, 0 <= wilt < fc)
844  * - erf.radiation.seb_soil_moisture_fc (real, optional, default 0.329, wilt < fc <= 1)
845  * - erf.radiation.seb_soil_type (int, optional, default 0 = unset; 1-19, a Noah-MP STAS soil category)
846  * - erf.radiation.seb_vegetation_type (int, optional, default 0 = unset; 1-20 except 15-17 (no vegetation), a Noah-MP MODIS land-use category)
847  * - erf.radiation.seb_vegetation_fraction (real, optional, default 1, in [0, 1])
848  * - erf.radiation.seb_leaf_area_index (real, optional, default from the table at the start date, >= 0)
849  * - erf.radiation.seb_hfx_default (real, optional, default 0.0)
850  * - erf.radiation.seb_lh_default (real, optional, default 0.0)
851  * - erf.radiation.seb_grdflx_default (real, optional, default 0.0)
852  * - erf.radiation.seb_q_sfc_default (real, optional, default 0.0)
853  * - erf.radiation.seb_t_deep_default (real, optional, default 300.0)
854  * - erf.radiation.seb_q_deep_default (real, optional, default 0.0)
855  * - erf.radiation.seb_diagnostic_enable (bool, optional, default false)
856  * - erf.radiation.seb_prognostic_enable (bool, optional, default false)
857  * - erf.radiation.seb_surface_heat_capacity (real, optional, default 2.0e4, > 0, stops at start-up otherwise)
858  * - erf.radiation.seb_restore_timescale_s (real, optional, default 86400.0, > 0, stops at start-up otherwise)
859  * - erf.radiation.seb_moisture_layer_depth_m (real, optional, default 0.1, > 0, stops at start-up otherwise)
860  * - erf.radiation.seb_moisture_restore_timescale_s (real, optional, default 86400.0, > 0, stops at start-up otherwise)
861  * - erf.radiation.seb_prognostic_t_min_k (real, optional, default 200.0)
862  * - erf.radiation.seb_prognostic_t_max_k (real, optional, default 340.0)
863  * - erf.radiation.seb_prognostic_q_min (real, optional, default 0.0, clamped to [0, 1])
864  * - erf.radiation.seb_prognostic_q_max (real, optional, default 1.0, clamped to [0, 1])
865  *
866  * Sub-option queries are only performed when the two-stream model is selected.
867  */
868  /**
869  * @brief Bring an out-of-range input back into its valid range and say so.
870  * Every erf.radiation.* value with a physical range passes through one of
871  * these, so a typo in a deck is reported instead of silently corrected.
872  */
873  static void clamp_low (const char* key, amrex::Real& value, amrex::Real lo)
874  {
875  if (value < lo) {
876  if (amrex::ParallelDescriptor::IOProcessor()) {
877  amrex::Print() << "WARNING: erf.radiation." << key << " = " << value
878  << " is below its minimum " << lo << "; using " << lo << "\n";
879  }
880  value = lo;
881  }
882  }
883  static void clamp_high (const char* key, amrex::Real& value, amrex::Real hi)
884  {
885  if (value > hi) {
886  if (amrex::ParallelDescriptor::IOProcessor()) {
887  amrex::Print() << "WARNING: erf.radiation." << key << " = " << value
888  << " is above its maximum " << hi << "; using " << hi << "\n";
889  }
890  value = hi;
891  }
892  }
893  static void reset_nonpositive (const char* key, amrex::Real& value, amrex::Real fallback)
894  {
895  if (!std::isfinite(value) || value <= 0.0) {
896  if (amrex::ParallelDescriptor::IOProcessor()) {
897  amrex::Print() << "WARNING: erf.radiation." << key << " = " << value
898  << " must be positive; using " << fallback << "\n";
899  }
900  value = fallback;
901  }
902  }
903 
904  void init_params (bool two_stream_selected, std::string pp_prefix)
905  {
906  amrex::ParmParse pp(pp_prefix);
907  enabled = two_stream_selected;
908 
909  // A refined hierarchy is supported: every level runs its own column sweep
910  // and writes its own heating rates. The one requirement is that each
911  // level's grids span that level's domain in z, because the sweep imposes
912  // top-of-atmosphere and surface boundary conditions at the ends of a box.
913  // That is checked where the grids exist, not here: ERF_RefineBox.cpp for
914  // an explicit erf.boxN refinement box, and TwoStreamRadiation::define_level
915  // for every path including tagged refinement and regrid.
916 
917  // Only query sub-options if the two-stream model is selected
918  if (enabled) {
919  pp.query("radiation.sw_enabled", sw_enabled);
920  pp.query("radiation.lw_enabled", lw_enabled);
921  pp.query("radiation.tau_per_layer", tau_per_layer);
922  pp.query("radiation.tau_lw_per_layer", tau_lw_per_layer);
923 
924  // The sun, the site and the surface temperature come from the keys the
925  // RRTMGP interface reads, so the two models share one set of inputs.
926  // ParmParse ignores a key nobody reads, so a deck still carrying one of
927  // the earlier two-stream-only keys would run with silently different
928  // settings: stop and name the replacement instead.
929  static const char* retired_keys[][2] = {
930  {"radiation.solar_zenith",
931  "erf.fixed_solar_zenith_angle, which is the cosine of the angle (60 degrees is 0.5)"},
932  {"radiation.solar_zenith_deg",
933  "erf.fixed_solar_zenith_angle, which is the cosine of the angle (60 degrees is 0.5)"},
934  {"radiation.S0", "erf.fixed_total_solar_irradiance"},
935  {"radiation.earth_sun_distance_enable",
936  "nothing: leave erf.fixed_total_solar_irradiance unset and the irradiance follows the date"},
937  {"radiation.surface_temp_k", "erf.rad_t_sfc"},
938  {"radiation.solar_geometry_dynamic_enable",
939  "nothing: leave erf.fixed_solar_zenith_angle unset and the sun follows start_datetime"},
940  {"radiation.latitude_deg", "erf.rad_cons_lat"},
941  {"radiation.longitude_deg", "erf.rad_cons_lon"},
942  {"radiation.day_of_year", "start_datetime"},
943  {"radiation.time_zone_offset_hours",
944  "nothing: start_datetime is UTC and the longitude sets the local solar time"},
945  };
946  for (const auto& entry : retired_keys) {
947  if (pp.contains(entry[0])) {
948  amrex::Abort(std::string("erf.") + entry[0] + " is no longer read by the "
949  "two-stream model; use " + entry[1] + ".");
950  }
951  }
952  pp.query("fixed_solar_zenith_angle", fixed_solar_zenith_angle);
953  if (fixed_solar_zenith_angle > 1.0) {
954  amrex::Abort("erf.fixed_solar_zenith_angle = " + std::to_string(fixed_solar_zenith_angle) +
955  " is the cosine of the solar zenith angle (as for RRTMGP) and cannot "
956  "exceed 1; 60 degrees is 0.5.");
957  }
958  pp.query("fixed_total_solar_irradiance", fixed_total_solar_irradiance);
959  if (!pp.query("rad_t_sfc", rad_t_sfc)) {
960  amrex::Abort("erf.radiation_model = TwoStream needs erf.rad_t_sfc, the surface "
961  "temperature [K] used where no land-surface model or surface layer "
962  "supplies one (the same input RRTMGP requires).");
963  }
964  if (!std::isfinite(rad_t_sfc) || rad_t_sfc <= 0.0) {
965  amrex::Abort("erf.rad_t_sfc = " + std::to_string(rad_t_sfc) + " must be a positive temperature [K].");
966  }
967  pp.query("rad_cons_lat", rad_cons_lat);
968  pp.query("rad_cons_lon", rad_cons_lon);
969  if (!std::isfinite(rad_cons_lat) || rad_cons_lat < -90.0 || rad_cons_lat > 90.0) {
970  amrex::Abort("erf.rad_cons_lat = " + std::to_string(rad_cons_lat) + " must lie in [-90, 90] degrees.");
971  }
972  if (!std::isfinite(rad_cons_lon) || rad_cons_lon < -180.0 || rad_cons_lon > 180.0) {
973  amrex::Abort("erf.rad_cons_lon = " + std::to_string(rad_cons_lon) + " must lie in [-180, 180] degrees.");
974  }
975  pp.query("rad_orbital_year", rad_orbital_year);
976  pp.query("rad_orbital_eccentricity", rad_orbital_eccentricity);
977  pp.query("rad_orbital_obliquity", rad_orbital_obliquity);
978  pp.query("rad_orbital_mvelp", rad_orbital_mvelp);
979  if (pp.contains("rad_freq_in_steps") && amrex::ParallelDescriptor::IOProcessor()) {
980  amrex::Print() << "WARNING: erf.rad_freq_in_steps is not read by the two-stream model, "
981  "which runs every step.\n";
982  }
983  std::string tau_model_str = "per_layer";
984  pp.query("radiation.tau_model", tau_model_str);
985  if (tau_model_str == "per_layer") {
986  tau_model = TauModel::PerLayer;
987  } else if (tau_model_str == "mass") {
988  tau_model = TauModel::Mass;
989  } else {
990  // The two models give very different column optical depths, so say so
991  // rather than let a misspelling quietly pick the default.
992  tau_model = TauModel::PerLayer;
993  if (amrex::ParallelDescriptor::IOProcessor()) {
994  amrex::Print() << "WARNING: unknown erf.radiation.tau_model '"
995  << tau_model_str << "'; valid values are per_layer "
996  "and mass. Using per_layer.\n";
997  }
998  }
999  pp.query("radiation.sw_kabs_dry", sw_kabs_dry);
1000  pp.query("radiation.sw_kscat_dry", sw_kscat_dry);
1001  pp.query("radiation.sw_kabs_vapor", sw_kabs_vapor);
1002  pp.query("radiation.sw_kext_cloud", sw_kext_cloud);
1003  pp.query("radiation.sw_cloud_omega", sw_cloud_omega);
1004  pp.query("radiation.sw_cloud_g", sw_cloud_g);
1005  clamp_low("sw_kabs_dry", sw_kabs_dry, 0.0);
1006  clamp_low("sw_kscat_dry", sw_kscat_dry, 0.0);
1007  clamp_low("sw_kabs_vapor", sw_kabs_vapor, 0.0);
1008  clamp_low("sw_kext_cloud", sw_kext_cloud, 0.0);
1009  clamp_low("sw_cloud_omega", sw_cloud_omega, 0.0);
1010  clamp_high("sw_cloud_omega", sw_cloud_omega, 1.0);
1011  clamp_low("sw_cloud_g", sw_cloud_g, -1.0);
1012  clamp_high("sw_cloud_g", sw_cloud_g, 1.0);
1013  pp.query("radiation.lw_mass_absorption_enable", lw_mass_absorption_enable);
1014  pp.query("radiation.lw_kabs_dry", lw_kabs_dry);
1015  pp.query("radiation.lw_kabs_vapor", lw_kabs_vapor);
1016  pp.query("radiation.lw_kabs_cloud", lw_kabs_cloud);
1017  clamp_low("lw_kabs_dry", lw_kabs_dry, 0.0);
1018  clamp_low("lw_kabs_vapor", lw_kabs_vapor, 0.0);
1019  clamp_low("lw_kabs_cloud", lw_kabs_cloud, 0.0);
1020  pp.query("radiation.v", verbosity);
1021  pp.query("radiation.diag_file", diag_file);
1022 
1023  // Cloud optical properties (all optional; the defaults leave the
1024  // column clear-sky)
1025  std::string tau_profile_type_str = "constant";
1026  pp.query("radiation.tau_profile_type", tau_profile_type_str);
1027  if (tau_profile_type_str == "constant") {
1028  tau_profile_type = TauProfileType::Constant;
1029  } else if (tau_profile_type_str == "cloud_layer") {
1030  tau_profile_type = TauProfileType::CloudLayer;
1031  } else {
1032  // Falling back to constant drops a requested cloud layer, so say so.
1033  tau_profile_type = TauProfileType::Constant;
1034  if (amrex::ParallelDescriptor::IOProcessor()) {
1035  amrex::Print() << "WARNING: unknown erf.radiation.tau_profile_type '"
1036  << tau_profile_type_str << "'; valid values are "
1037  "constant and cloud_layer. Using constant, so no "
1038  "cloud layer is applied.\n";
1039  }
1040  }
1041  pp.query("radiation.cloud_base_height_m", cloud_base_height_m);
1042  pp.query("radiation.cloud_top_height_m", cloud_top_height_m);
1043  pp.query("radiation.cloud_tau_per_layer", cloud_tau_per_layer);
1044  pp.query("radiation.cloud_fraction", cloud_fraction);
1045 
1046  // Parameter validation (R5/R5'): clip cloud_fraction to [0, 1]
1047  clamp_low("cloud_fraction", cloud_fraction, 0.0);
1048  clamp_high("cloud_fraction", cloud_fraction, 1.0);
1049  clamp_low("cloud_tau_per_layer", cloud_tau_per_layer, 0.0);
1052  }
1053 
1054  // Scattering parameters (all optional; the defaults leave the
1055  // shortwave direct-beam only)
1056  pp.query("radiation.single_scattering_albedo", single_scattering_albedo);
1057  pp.query("radiation.asymmetry_factor", asymmetry_factor);
1058  pp.query("radiation.cloud_single_scattering_albedo", cloud_single_scattering_albedo);
1059  pp.query("radiation.cloud_asymmetry_factor", cloud_asymmetry_factor);
1060 
1061  // Parameter validation (R5/R5'): clip albedos to [0, 1] and
1062  // asymmetry factors to [-1, 1]
1063  clamp_low("single_scattering_albedo", single_scattering_albedo, 0.0);
1064  clamp_high("single_scattering_albedo", single_scattering_albedo, 1.0);
1065  clamp_low("asymmetry_factor", asymmetry_factor, -1.0);
1066  clamp_high("asymmetry_factor", asymmetry_factor, 1.0);
1067  clamp_low("cloud_single_scattering_albedo", cloud_single_scattering_albedo, 0.0);
1068  clamp_high("cloud_single_scattering_albedo", cloud_single_scattering_albedo, 1.0);
1069  clamp_low("cloud_asymmetry_factor", cloud_asymmetry_factor, -1.0);
1070  clamp_high("cloud_asymmetry_factor", cloud_asymmetry_factor, 1.0);
1071 
1072  // Runtime diagnostics controls (all optional; every channel is on
1073  // by default)
1074  pp.query("radiation.diag_enable", diag_enable);
1075  pp.query("radiation.diag_stdout_enable", diag_stdout_enable);
1076  pp.query("radiation.diag_tagged_enable", diag_tagged_enable);
1077  pp.query("radiation.diag_regtest_line_enable", diag_regtest_line_enable);
1078  pp.query("radiation.diag_csv_enable", diag_csv_enable);
1079  pp.query("radiation.diag_callsite_mode", diag_callsite_mode);
1080  pp.query("radiation.diag_dedup_tol", diag_dedup_tol);
1081 
1082  // Validate the call-site mode
1083  if (diag_callsite_mode != "both" &&
1084  diag_callsite_mode != "pre_only" &&
1085  diag_callsite_mode != "post_only") {
1086  if (amrex::ParallelDescriptor::IOProcessor()) {
1087  amrex::Print() << "WARNING: unknown erf.radiation.diag_callsite_mode '"
1088  << diag_callsite_mode << "'; valid values are both, "
1089  "pre_only and post_only. Using both.\n";
1090  }
1091  diag_callsite_mode = "both";
1092  }
1093  // Ensure dedup tolerance is non-negative
1094  clamp_low("diag_dedup_tol", diag_dedup_tol, 0.0);
1095 
1096  // Surface heterogeneity fallback parameters (all optional; only used
1097  // when hetero
1098  // LSM/radiation interface fields are absent or invalid)
1099  pp.query("radiation.surface_albedo_sw", surface_albedo_sw);
1100  pp.query("radiation.surface_emissivity_lw", surface_emissivity_lw);
1101  pp.query("radiation.surface_albedo_sw_diffuse", surface_albedo_sw_diffuse);
1102  clamp_high("surface_albedo_sw_diffuse", surface_albedo_sw_diffuse, 1.0);
1103 
1104  // Clamp albedo/emissivity to [0, 1]
1105  clamp_low("surface_albedo_sw", surface_albedo_sw, 0.0);
1106  clamp_high("surface_albedo_sw", surface_albedo_sw, 1.0);
1107  clamp_low("surface_emissivity_lw", surface_emissivity_lw, 0.0);
1108  clamp_high("surface_emissivity_lw", surface_emissivity_lw, 1.0);
1109 
1110  // Dynamic optical depth diagnosis from moisture/clouds
1111  // (all optional; disabled by default)
1112  pp.query("radiation.tau_sw_dynamic_enable", tau_sw_dynamic_enable);
1113  pp.query("radiation.tau_lw_dynamic_enable", tau_lw_dynamic_enable);
1114  pp.query("radiation.tau_sw_coeff_qv", tau_sw_coeff_qv);
1115  pp.query("radiation.tau_sw_coeff_qc", tau_sw_coeff_qc);
1116  pp.query("radiation.tau_lw_coeff_qv", tau_lw_coeff_qv);
1117  pp.query("radiation.tau_lw_coeff_qc", tau_lw_coeff_qc);
1118 
1119  // Clamp the dynamic tau coefficients
1120  // to nonnegative (negative would give unphysical behavior)
1121  clamp_low("tau_sw_coeff_qv", tau_sw_coeff_qv, 0.0);
1122  clamp_low("tau_sw_coeff_qc", tau_sw_coeff_qc, 0.0);
1123  clamp_low("tau_lw_coeff_qv", tau_lw_coeff_qv, 0.0);
1124  clamp_low("tau_lw_coeff_qc", tau_lw_coeff_qc, 0.0);
1125 
1126  // Prognostic cloud fraction diagnosis from RH/qc
1127  // (all optional; disabled by default)
1128  pp.query("radiation.cloud_fraction_prog_enable", cloud_fraction_prog_enable);
1129  pp.query("radiation.cloud_fraction_rh_min", cloud_fraction_rh_min);
1130  pp.query("radiation.cloud_fraction_rh_max", cloud_fraction_rh_max);
1131  pp.query("radiation.cloud_fraction_qc_scale", cloud_fraction_qc_scale);
1132 
1133  // Clamp the cloud fraction parameters
1134  // to physically reasonable ranges
1135  clamp_low("cloud_fraction_rh_min", cloud_fraction_rh_min, 0.0);
1136  clamp_high("cloud_fraction_rh_min", cloud_fraction_rh_min, 1.0);
1137  clamp_low("cloud_fraction_rh_max", cloud_fraction_rh_max, 0.0);
1138  clamp_high("cloud_fraction_rh_max", cloud_fraction_rh_max, 1.0);
1141  }
1142  // qc is divided by this scale, and the kernel drops the qc term when it is not
1143  // positive; clamping a bad value to zero would switch that term off silently
1144  if (!(cloud_fraction_qc_scale > 0.0)) {
1145  amrex::Abort("erf.radiation.cloud_fraction_qc_scale = " + std::to_string(cloud_fraction_qc_scale) +
1146  " must be positive [kg/kg].");
1147  }
1148 
1149  // Prescribed bulk aerosol/turbidity optical depth
1150  // (all optional; disabled by default)
1151  pp.query("radiation.aerosol_enable", aerosol_enable);
1152 
1153  // Parse aerosol profile type as string
1154  std::string aerosol_profile_type_str = "constant";
1155  pp.query("radiation.aerosol_profile_type", aerosol_profile_type_str);
1156  if (aerosol_profile_type_str == "constant") {
1157  aerosol_profile_type = AerosolProfileType::Constant;
1158  } else if (aerosol_profile_type_str == "exponential") {
1159  aerosol_profile_type = AerosolProfileType::Exponential;
1160  } else if (aerosol_profile_type_str == "table") {
1161  // The table lookup is not implemented and contributes no optical
1162  // depth. Keep the selection so the run is not silently given a
1163  // different profile, and say what it means.
1164  aerosol_profile_type = AerosolProfileType::Table;
1165  if (amrex::ParallelDescriptor::IOProcessor()) {
1166  amrex::Print() << "WARNING: erf.radiation.aerosol_profile_type = table is "
1167  "not implemented and contributes no aerosol optical "
1168  "depth. Use constant or exponential.\n";
1169  }
1170  } else {
1171  // Falling through to constant would hide a misspelling.
1172  aerosol_profile_type = AerosolProfileType::Constant;
1173  if (amrex::ParallelDescriptor::IOProcessor()) {
1174  amrex::Print() << "WARNING: unknown erf.radiation.aerosol_profile_type '"
1175  << aerosol_profile_type_str << "'; valid values are "
1176  "constant and exponential. Using constant.\n";
1177  }
1178  }
1179 
1180  pp.query("radiation.aerosol_tau_per_layer", aerosol_tau_per_layer);
1181  pp.query("radiation.aerosol_scale_height_m", aerosol_scale_height_m);
1182  pp.query("radiation.aerosol_tau_surface", aerosol_tau_surface);
1183 
1184  // Clamp the aerosol parameters
1185  // to physically reasonable ranges
1186  clamp_low("aerosol_tau_per_layer", aerosol_tau_per_layer, 0.0);
1187  clamp_low("aerosol_tau_surface", aerosol_tau_surface, 0.0);
1188  reset_nonpositive("aerosol_scale_height_m", aerosol_scale_height_m, 2000.0);
1189 
1190  // Simplified SEB infrastructure fallback parameters
1191  pp.query("radiation.seb_enable", seb_enable);
1192  pp.query("radiation.seb_use_radiation_fluxes", seb_use_radiation_fluxes);
1193  pp.query("radiation.seb_sw_flux_default", seb_sw_flux_default);
1194  pp.query("radiation.seb_lw_flux_default", seb_lw_flux_default);
1195  // Through the AMREX_ENUM, case-insensitive and ignoring underscores, so
1196  // surface_layer, SurfaceLayer and surfacelayer all select SurfaceLayer. The
1197  // parser throws on any other value; turn that into an abort naming the input.
1198  try {
1199  pp.query_enum_sloppy("radiation.seb_turbulent_flux_source",
1201  } catch (...) {
1202  std::string value;
1203  pp.query("radiation.seb_turbulent_flux_source", value);
1204  amrex::Abort("erf.radiation.seb_turbulent_flux_source must be surface_layer "
1205  "or defaults, not '" + value + "'");
1206  }
1207  pp.query("radiation.seb_surface_layer_uses_skin", seb_surface_layer_uses_skin);
1208  pp.query("radiation.seb_surface_layer_uses_moisture", seb_surface_layer_uses_moisture);
1209  pp.query("radiation.seb_soil_moisture_wilt", seb_soil_moisture_wilt);
1210  pp.query("radiation.seb_soil_moisture_fc", seb_soil_moisture_fc);
1211  pp.query("radiation.seb_soil_type", seb_soil_type);
1212  if (seb_soil_type != 0) {
1213  if (pp.contains("radiation.seb_soil_moisture_wilt") ||
1214  pp.contains("radiation.seb_soil_moisture_fc")) {
1215  amrex::Abort("erf.radiation.seb_soil_type sets the wilting point and field "
1216  "capacity from Noah-MP's soil table; do not also give "
1217  "seb_soil_moisture_wilt or seb_soil_moisture_fc");
1218  }
1220  if (soil == nullptr || !(soil->smc_ref > soil->smc_wilt)) {
1221  amrex::Abort("erf.radiation.seb_soil_type = " + std::to_string(seb_soil_type) +
1222  " is not a Noah-MP soil category with a field capacity above its "
1223  "wilting point (1-19, not 14, water; see ERF_NoahMPSoilTable.H)");
1224  }
1226  seb_soil_moisture_fc = soil->smc_ref;
1227  }
1228  pp.query("radiation.seb_vegetation_type", seb_vegetation_type);
1229  pp.query("radiation.seb_vegetation_fraction", seb_vegetation_fraction);
1230  pp.query("radiation.seb_leaf_area_index", seb_leaf_area_index);
1231  if (seb_vegetation_type != 0) {
1233  if (veg == nullptr) {
1234  amrex::Abort("erf.radiation.seb_vegetation_type = " + std::to_string(seb_vegetation_type) +
1235  " is not a Noah-MP MODIS land-use category (1-20; see "
1236  "ERF_NoahMPVegetationTable.H)");
1237  }
1238  // Snow and ice (15), barren (16) and water (17) have no vegetation in the table
1239  // (Z0MVT = 0, LAI 0 all year): Noah-MP treats such a surface as bare ground.
1240  if (!(veg->z0 > 0.0)) {
1241  amrex::Abort("erf.radiation.seb_vegetation_type = " + std::to_string(seb_vegetation_type) +
1242  " has no vegetation in Noah-MP's table (Z0MVT = 0, LAI 0 all year). "
1243  "For bare land leave seb_vegetation_type at 0: the soil's resistance "
1244  "and roughness (Z0SOIL) then apply.");
1245  }
1247  amrex::Abort("erf.radiation.seb_vegetation_type needs "
1248  "erf.radiation.seb_surface_layer_uses_moisture = true and "
1249  "erf.radiation.seb_soil_type (the bare-soil resistance uses the "
1250  "soil's porosity and Clapp-Hornberger b)");
1251  }
1252  if (!(std::isfinite(seb_vegetation_fraction) && seb_vegetation_fraction >= 0.0 &&
1253  seb_vegetation_fraction <= 1.0)) {
1254  amrex::Abort("erf.radiation.seb_vegetation_fraction must be in [0, 1]; got " +
1255  std::to_string(seb_vegetation_fraction));
1256  }
1257  if (pp.contains("radiation.seb_leaf_area_index")) {
1258  if (!(std::isfinite(seb_leaf_area_index) && seb_leaf_area_index >= 0.0)) {
1259  amrex::Abort("erf.radiation.seb_leaf_area_index must be >= 0; got " +
1260  std::to_string(seb_leaf_area_index));
1261  }
1262  } else {
1263  // The table's monthly LAI at the start date and time, as Noah-MP interpolates it
1264  // (its day of the year counts from 0 at 00:00 on 1 January).
1265  std::string start;
1266  amrex::ParmParse pp_top;
1267  amrex::Real day = 0.0, days_in_year = 0.0;
1268  if (!pp_top.query("start_datetime", start) ||
1269  !noahmp_day_of_year(start, day, days_in_year)) {
1270  amrex::Abort("erf.radiation.seb_vegetation_type: give "
1271  "erf.radiation.seb_leaf_area_index, or a valid start_datetime "
1272  "(YYYY-MM-DD HH:MM:SS) so that it can be taken from the table");
1273  }
1274  seb_leaf_area_index = noahmp_leaf_area_index(*veg, day, days_in_year,
1275  rad_cons_lat < 0.0);
1276  if (amrex::ParallelDescriptor::IOProcessor()) {
1277  amrex::Print() << "NOTE: erf.radiation.seb_leaf_area_index = "
1278  << seb_leaf_area_index << ", Noah-MP's table value for land-use "
1279  "category " << seb_vegetation_type << " at " << start
1280  << ", fixed for the run.\n";
1281  }
1282  }
1283  }
1284  pp.query("radiation.seb_hfx_default", seb_hfx_default);
1285  pp.query("radiation.seb_lh_default", seb_lh_default);
1286  pp.query("radiation.seb_grdflx_default", seb_grdflx_default);
1287  pp.query("radiation.seb_q_sfc_default", seb_q_sfc_default);
1288  pp.query("radiation.seb_t_deep_default", seb_t_deep_default);
1289  pp.query("radiation.seb_q_deep_default", seb_q_deep_default);
1290 
1291  // SEB diagnostic mode
1292  pp.query("radiation.seb_diagnostic_enable", seb_diagnostic_enable);
1293 
1294  // Auto-enable seb_enable if seb_diagnostic_enable is requested but seb_enable is false
1296  seb_enable = true; // Internal auto-enable for safety/simplicity
1297  }
1298 
1299  // SEB prognostic mode parameters
1300  pp.query("radiation.seb_prognostic_enable", seb_prognostic_enable);
1301  pp.query("radiation.seb_surface_heat_capacity", seb_surface_heat_capacity);
1302  pp.query("radiation.seb_restore_timescale_s", seb_restore_timescale_s);
1303  pp.query("radiation.seb_moisture_layer_depth_m", seb_moisture_layer_depth_m);
1304  pp.query("radiation.seb_moisture_restore_timescale_s", seb_moisture_restore_timescale_s);
1305  pp.query("radiation.seb_prognostic_t_min_k", seb_prognostic_t_min_k);
1306  pp.query("radiation.seb_prognostic_t_max_k", seb_prognostic_t_max_k);
1307  pp.query("radiation.seb_prognostic_q_min", seb_prognostic_q_min);
1308  pp.query("radiation.seb_prognostic_q_max", seb_prognostic_q_max);
1309 
1310  // The prognostic update needs the SEB fields and the residual, so
1311  // requesting it turns on the infrastructure it depends on. Report that,
1312  // since it changes settings the user did not set.
1314  if (amrex::ParallelDescriptor::IOProcessor()) {
1315  amrex::Print() << "NOTE: erf.radiation.seb_prognostic_enable = true "
1316  "also enables seb_enable and "
1317  "seb_diagnostic_enable, which it depends on.\n";
1318  }
1319  seb_enable = true;
1320  seb_diagnostic_enable = true;
1321  }
1322 
1323  if (!std::isfinite(seb_sw_flux_default)) seb_sw_flux_default = 0.0;
1324  if (!std::isfinite(seb_lw_flux_default)) seb_lw_flux_default = 0.0;
1325  if (!std::isfinite(seb_hfx_default)) seb_hfx_default = 0.0;
1326  if (!std::isfinite(seb_lh_default)) seb_lh_default = 0.0;
1327  if (!std::isfinite(seb_grdflx_default)) seb_grdflx_default = 0.0;
1328  if (!std::isfinite(seb_q_sfc_default)) seb_q_sfc_default = 0.0;
1329  reset_nonpositive("seb_t_deep_default", seb_t_deep_default, 300.0);
1330  if (!std::isfinite(seb_q_deep_default)) seb_q_deep_default = 0.0;
1331 
1332  seb_sw_flux_default = amrex::max(amrex::Real(-2000.0), amrex::min(amrex::Real(2000.0), seb_sw_flux_default));
1333  seb_lw_flux_default = amrex::max(amrex::Real(-2000.0), amrex::min(amrex::Real(2000.0), seb_lw_flux_default));
1334  seb_hfx_default = amrex::max(amrex::Real(-2000.0), amrex::min(amrex::Real(2000.0), seb_hfx_default));
1335  seb_lh_default = amrex::max(amrex::Real(-2000.0), amrex::min(amrex::Real(2000.0), seb_lh_default));
1336  seb_grdflx_default = amrex::max(amrex::Real(-2000.0), amrex::min(amrex::Real(2000.0), seb_grdflx_default));
1337  seb_q_sfc_default = amrex::max(amrex::Real(0.0), amrex::min(amrex::Real(1.0), seb_q_sfc_default));
1338  seb_q_deep_default = amrex::max(amrex::Real(0.0), amrex::min(amrex::Real(1.0), seb_q_deep_default));
1339 
1340  // Two-way coupling needs a skin that evolves, and a balance that removes the
1341  // flux the surface layer computes from it.
1343  if (!seb_prognostic_enable) {
1344  amrex::Abort("erf.radiation.seb_surface_layer_uses_skin = true needs "
1345  "erf.radiation.seb_prognostic_enable = true: without it the "
1346  "skin temperature does not evolve");
1347  }
1348  if (seb_turbulent_flux_source != SEBTurbulentFluxSource::SurfaceLayer) {
1349  amrex::Abort("erf.radiation.seb_surface_layer_uses_skin = true needs "
1350  "erf.radiation.seb_turbulent_flux_source = surface_layer: the "
1351  "balance must remove the heat flux the surface layer computes "
1352  "from the skin");
1353  }
1354  }
1355 
1356  // Moisture coupling: the surface mixing ratio is q_sat at the skin temperature, so
1357  // it needs the skin coupling; and beta needs a wilting point below field capacity.
1358  // The wilting point and field capacity: checked whenever they are given or used.
1359  if (seb_surface_layer_uses_moisture || pp.contains("radiation.seb_soil_moisture_wilt") ||
1360  pp.contains("radiation.seb_soil_moisture_fc")) {
1361  if (!(std::isfinite(seb_soil_moisture_wilt) && std::isfinite(seb_soil_moisture_fc) &&
1363  seb_soil_moisture_fc <= 1.0)) {
1364  amrex::Abort("erf.radiation.seb_soil_moisture_wilt and seb_soil_moisture_fc must "
1365  "satisfy 0 <= wilt < fc <= 1 (volumetric water contents); got wilt = " +
1366  std::to_string(seb_soil_moisture_wilt) + ", fc = " +
1367  std::to_string(seb_soil_moisture_fc));
1368  }
1369  }
1370  // Inputs that only the moisture coupling or a vegetation type use: say so rather than
1371  // ignore them.
1373  (pp.contains("radiation.seb_soil_moisture_wilt") ||
1374  pp.contains("radiation.seb_soil_moisture_fc"))) {
1375  amrex::Abort("erf.radiation.seb_soil_moisture_wilt and seb_soil_moisture_fc are used "
1376  "only with erf.radiation.seb_surface_layer_uses_moisture = true");
1377  }
1378  if (seb_vegetation_type == 0 &&
1379  (pp.contains("radiation.seb_vegetation_fraction") ||
1380  pp.contains("radiation.seb_leaf_area_index"))) {
1381  amrex::Abort("erf.radiation.seb_vegetation_fraction and seb_leaf_area_index are used "
1382  "only with erf.radiation.seb_vegetation_type");
1383  }
1384 
1387  amrex::Abort("erf.radiation.seb_surface_layer_uses_moisture = true needs "
1388  "erf.radiation.seb_surface_layer_uses_skin = true: the surface "
1389  "mixing ratio is the saturation value at the balance's skin "
1390  "temperature");
1391  }
1393  amrex::ParallelDescriptor::IOProcessor()) {
1394  amrex::Print() << "NOTE: erf.radiation.seb_surface_layer_uses_moisture: the soil "
1395  "starts at or below the wilting point (seb_q_sfc_default = "
1396  << seb_q_sfc_default << ", seb_soil_moisture_wilt = "
1397  << seb_soil_moisture_wilt << "), so its soil-water factor is zero: "
1398  << (seb_soil_type != 0
1399  ? "no transpiration, and the bare soil's pore air is nearly "
1400  "dry (Noah-MP's relative humidity exp(psi g / (R_v T))), so it "
1401  "hardly evaporates and may take up vapour."
1402  : "no evaporation at all.")
1403  << " Set seb_q_sfc_default (and seb_q_deep_default, which q_s "
1404  "restores to) to the soil's water content if that is not meant.\n";
1405  }
1406  // q_s restores toward q_deep, which defaults to 0: a forgotten q_deep dries the soil
1407  // over seb_moisture_restore_timescale_s.
1409  amrex::ParallelDescriptor::IOProcessor()) {
1410  amrex::Print() << "NOTE: erf.radiation.seb_surface_layer_uses_moisture: "
1411  "seb_q_deep_default = " << seb_q_deep_default
1412  << " is below the wilting point (" << seb_soil_moisture_wilt
1413  << "), and q_s restores toward it over seb_moisture_restore_timescale_s"
1414  " (" << seb_moisture_restore_timescale_s << " s), drying the soil."
1415  " Set seb_q_deep_default to the deep soil's water content if that "
1416  "is not meant.\n";
1417  }
1418  }
1419 
1420  // The restoring term -(2 pi / tau)(T_s - T_deep) of the force-restore update
1421  // is the ground heat flux, so a ground flux in the balance as well removes
1422  // that heat twice.
1423  if (seb_prognostic_enable && seb_grdflx_default != 0.0 &&
1424  amrex::ParallelDescriptor::IOProcessor()) {
1425  amrex::Print() << "WARNING: erf.radiation.seb_grdflx_default = " << seb_grdflx_default
1426  << " with seb_prognostic_enable: the force-restore term already "
1427  "carries the ground heat flux, so this counts it twice.\n";
1428  }
1429 
1430 
1431  // Validate the prognostic parameters: each must be positive and finite when given
1432  // (the defaults are), so a typo stops here rather than running on another value.
1433  auto require_positive = [] (const char* name, amrex::Real value) {
1434  if (!(std::isfinite(value) && value > 0.0)) {
1435  amrex::Abort(std::string("erf.radiation.") + name + " must be > 0; got " +
1436  std::to_string(value));
1437  }
1438  };
1439  require_positive("seb_surface_heat_capacity", seb_surface_heat_capacity);
1440  require_positive("seb_restore_timescale_s", seb_restore_timescale_s);
1441  require_positive("seb_moisture_layer_depth_m", seb_moisture_layer_depth_m);
1442  require_positive("seb_moisture_restore_timescale_s", seb_moisture_restore_timescale_s);
1443 
1444  // With a Noah-MP soil, the surface heat capacity of that soil at its initial water
1445  // content: the layer the restore period's temperature wave reaches.
1446  // The coupled surface layer's land roughness, from the same tables.
1448  const NoahMPVegetationParams* veg = (seb_vegetation_type != 0)
1450  const amrex::Real f_veg = veg ? seb_vegetation_fraction : amrex::Real(0.0);
1451  seb_land_roughness = f_veg * (veg ? veg->z0 : amrex::Real(0.0)) +
1452  (amrex::Real(1.0) - f_veg) * noahmp_soil_roughness;
1453  // At least min(Z0MVT, Z0SOIL) > 0: the categories with Z0MVT = 0 stop above.
1455  "erf.radiation.seb_*: the land roughness from Noah-MP's tables is not positive");
1456  }
1457 
1458  // A value given explicitly stands.
1459  if (seb_prognostic_enable && seb_soil_type != 0) {
1461  const amrex::Real theta = amrex::min(seb_q_sfc_default, soil->smc_max);
1463  const amrex::Real lambda = seb_soil_thermal_conductivity(theta, soil->smc_max, soil->quartz);
1465  const bool given = pp.contains("radiation.seb_surface_heat_capacity");
1466  if (!given) { seb_surface_heat_capacity = derived; }
1467  if (amrex::ParallelDescriptor::IOProcessor()) {
1468  amrex::Print() << "NOTE: erf.radiation.seb_surface_heat_capacity = "
1469  << seb_surface_heat_capacity << " J/m^2/K"
1470  << (given ? " as given; Noah-MP soil category " :
1471  ", from Noah-MP soil category ")
1472  << seb_soil_type << " at a water content of " << theta
1473  << " (heat capacity " << c << " J/m^3/K, conductivity " << lambda
1474  << " W/m/K) over " << seb_restore_timescale_s << " s"
1475  << (given ? " gives " + std::to_string(derived) : std::string())
1476  << ".\n";
1477  }
1478  }
1479 
1480  // Clamp temperature bounds
1481  if (!std::isfinite(seb_prognostic_t_min_k)) seb_prognostic_t_min_k = 200.0;
1482  if (!std::isfinite(seb_prognostic_t_max_k)) seb_prognostic_t_max_k = 340.0;
1487  }
1488 
1489  // Clamp moisture bounds
1490  if (!std::isfinite(seb_prognostic_q_min)) seb_prognostic_q_min = 0.0;
1491  if (!std::isfinite(seb_prognostic_q_max)) seb_prognostic_q_max = 1.0;
1492  seb_prognostic_q_min = amrex::max(amrex::Real(0.0), amrex::min(amrex::Real(1.0), seb_prognostic_q_min));
1493  seb_prognostic_q_max = amrex::max(amrex::Real(0.0), amrex::min(amrex::Real(1.0), seb_prognostic_q_max));
1498  }
1499 
1500  }
1501 
1502 }
1503 
1504  /**
1505  * @brief Check if two-stream radiation is active.
1506  *
1507  * @return true when erf.radiation_model = TwoStream.
1508  */
1509  [[nodiscard]] bool uses_two_stream_radiation () const noexcept
1510  {
1511  return enabled;
1512  }
1513 };
1514 
1515 #endif // ERF_RAD_STRUCT_H_
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")
ParmParse pp("prob")
Noah-MP's soil parameters by soil category, for the two-stream surface energy balance (erf....
constexpr amrex::Real noahmp_soil_roughness
Definition: ERF_NoahMPSoilTable.H:42
const NoahMPSoilParams * noahmp_soil_params(int category) noexcept
The parameters of STAS soil category category (1-19), as Noah-MP reads them.
Definition: ERF_NoahMPSoilTable.H:49
constexpr amrex::Real noahmp_soil_heat_capacity
Definition: ERF_NoahMPSoilTable.H:35
Noah-MP's canopy-resistance parameters, roughness length and monthly leaf area index by land-use cate...
amrex::Real noahmp_leaf_area_index(const NoahMPVegetationParams &params, amrex::Real day, amrex::Real days_in_year, bool southern) noexcept
Leaf area index of params on day of year day, interpolated between the monthly values as Noah-MP does...
Definition: ERF_NoahMPVegetationTable.H:98
const NoahMPVegetationParams * noahmp_vegetation_params(int category) noexcept
The parameters of MODIS land-use category category (1-20), as Noah-MP reads them.
Definition: ERF_NoahMPVegetationTable.H:41
bool noahmp_day_of_year(const std::string &datetime, amrex::Real &day, amrex::Real &days_in_year)
The day of the year, as Noah-MP counts it, of a date "YYYY-MM-DD[ HH:MM[:SS]]" (ERF's start_datetime)...
Definition: ERF_NoahMPVegetationTable.H:130
std::string name
Definition: ERF_Plotfile2DCatalog.cpp:116
AMREX_ENUM(TauProfileType, Constant, CloudLayer)
Optical depth profile type for the two-stream solver.
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Simplified SEB – the two-stream model's surface energy balance, as pure functions.
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real seb_soil_thermal_conductivity(amrex::Real theta, amrex::Real theta_sat, amrex::Real quartz)
Thermal conductivity of unfrozen soil [W/m/K], Noah-MP's Johansen form (SoilThermalPropertyMod): soli...
Definition: ERF_SimplifiedSEB.H:352
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real seb_force_restore_heat_capacity(amrex::Real lambda, amrex::Real c, amrex::Real tau)
Surface heat capacity [J/m^2/K] of the force-restore balance for a soil of volumetric heat capacity c...
Definition: ERF_SimplifiedSEB.H:374
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real seb_soil_heat_capacity(amrex::Real theta, amrex::Real theta_sat, amrex::Real c_soil)
Volumetric heat capacity of unfrozen soil [J/m^3/K], as Noah-MP forms it (SoilThermalPropertyMod): wa...
Definition: ERF_SimplifiedSEB.H:338
Definition: ERF_SurfaceLayer.H:60
@ theta
Definition: ERF_Kessler.H:26
@ tmp
Definition: ERF_AdvanceWSM6.cpp:116
Definition: ERF_Derive.cpp:172
Definition: ERF_NoahMPSoilTable.H:20
amrex::Real smc_ref
REFSMC reference soil moisture, field capacity [m^3/m^3].
Definition: ERF_NoahMPSoilTable.H:24
amrex::Real smc_wilt
WLTSMC wilting point [m^3/m^3].
Definition: ERF_NoahMPSoilTable.H:27
amrex::Real smc_max
MAXSMC saturated soil moisture (porosity) [m^3/m^3].
Definition: ERF_NoahMPSoilTable.H:23
amrex::Real quartz
QTZ quartz fraction [1].
Definition: ERF_NoahMPSoilTable.H:28
Definition: ERF_NoahMPVegetationTable.H:23
amrex::Real z0
Z0MVT momentum roughness length of the vegetation [m].
Definition: ERF_NoahMPVegetationTable.H:29
Definition: ERF_RadStruct.H:73
amrex::Real fixed_mu
Definition: ERF_RadStruct.H:74
amrex::Real latitude
Definition: ERF_RadStruct.H:75
void read(const std::string &prefix)
Definition: ERF_RadStruct.H:79
amrex::Real longitude
Definition: ERF_RadStruct.H:76
int radiation_interval
Definition: ERF_RadStruct.H:77
Container holding radiation-related choices and parameters.
Definition: ERF_RadStruct.H:132
amrex::Real lw_kabs_dry
Dry-air gray LW mass absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:250
bool aerosol_enable
Enable prescribed bulk aerosol/turbidity optical depth. When true, aerosol optical depth is added on ...
Definition: ERF_RadStruct.H:746
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:217
amrex::Real sw_kabs_vapor
Mass model, SW: water-vapor gray absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:226
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:338
static void reset_nonpositive(const char *key, amrex::Real &value, amrex::Real fallback)
Definition: ERF_RadStruct.H:893
amrex::Real aerosol_tau_surface
Total-column aerosol optical depth at surface [dimensionless]. Used for AerosolProfileType::Exponenti...
Definition: ERF_RadStruct.H:778
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:207
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:182
bool sw_enabled
Enable shortwave (solar) radiation computation. Only used by the two-stream model.
Definition: ERF_RadStruct.H:144
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:393
int seb_vegetation_type
Noah-MP land-use category (modified IGBP MODIS, 1-20 except the unvegetated 15, 16 and 17,...
Definition: ERF_RadStruct.H:517
std::string diag_file
Output file path for radiation diagnostics CSV. Paths are relative to the simulation working director...
Definition: ERF_RadStruct.H:268
static void clamp_high(const char *key, amrex::Real &value, amrex::Real hi)
Definition: ERF_RadStruct.H:883
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:164
amrex::Real surface_albedo_sw
Shortwave surface albedo fallback [0, 1] for per-column heterogeneous-surface-property resolution....
Definition: ERF_RadStruct.H:402
amrex::Real surface_emissivity_lw
Longwave surface emissivity fallback [0, 1] for per-column heterogeneous-surface-property resolution....
Definition: ERF_RadStruct.H:417
amrex::Real rad_orbital_mvelp
Definition: ERF_RadStruct.H:210
amrex::Real seb_grdflx_default
Fallback SEB ground heat flux [W/m^2]. The force-restore restoring term already represents the flux i...
Definition: ERF_RadStruct.H:558
amrex::Real diag_dedup_tol
Tolerance for time equality in duplicate guard [s]. Times within this tolerance are considered equal....
Definition: ERF_RadStruct.H:319
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:615
std::string diag_callsite_mode
Call-site filtering mode: "both" (pre+post rows), "pre_only", or "post_only".
Definition: ERF_RadStruct.H:312
bool diag_regtest_line_enable
Enable RADIATION_DIAG: line emission. Controls regtest-parsing diagnostics line. Default true.
Definition: ERF_RadStruct.H:299
amrex::Real sw_kext_cloud
Mass model, SW: cloud-water extinction coefficient m^2/kg.
Definition: ERF_RadStruct.H:229
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:682
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:189
bool tau_lw_dynamic_enable
Enable dynamic longwave optical depth diagnosis from moisture/clouds. When true, LW optical depth is ...
Definition: ERF_RadStruct.H:673
amrex::Real seb_lh_default
Fallback SEB latent heat flux [W/m^2].
Definition: ERF_RadStruct.H:551
void init_params(bool two_stream_selected, std::string pp_prefix)
Definition: ERF_RadStruct.H:904
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:631
amrex::Real seb_lw_flux_default
Fallback SEB surface net longwave flux [W/m^2].
Definition: ERF_RadStruct.H:443
amrex::Real tau_sw_coeff_qc
Shortwave optical depth coefficient for cloud liquid water [dimensionless]. Default 0....
Definition: ERF_RadStruct.H:690
amrex::Real aerosol_tau_per_layer
Constant aerosol optical depth per layer [dimensionless]. Used for AerosolProfileType::Constant profi...
Definition: ERF_RadStruct.H:762
bool seb_surface_layer_uses_skin
Two-way coupling with the zlo surface layer: the surface layer takes its land surface temperature fro...
Definition: ERF_RadStruct.H:459
amrex::Real seb_q_sfc_default
Fallback SEB surface moisture, and the initial value of the prognostic one. With seb_prognostic_enabl...
Definition: ERF_RadStruct.H:566
amrex::Real rad_orbital_eccentricity
Definition: ERF_RadStruct.H:208
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:364
bool diag_csv_enable
Enable CSV file append behavior. When false, no file writes; other enabled streams may still print....
Definition: ERF_RadStruct.H:306
bool tau_sw_dynamic_enable
Definition: ERF_RadStruct.H:674
bool uses_two_stream_radiation() const noexcept
Check if two-stream radiation is active.
Definition: ERF_RadStruct.H:1509
amrex::Real seb_t_deep_default
Fallback SEB deep soil temperature [K].
Definition: ERF_RadStruct.H:571
AerosolProfileType aerosol_profile_type
Aerosol optical depth profile type: Constant, Exponential, or Table.
Definition: ERF_RadStruct.H:755
amrex::Real seb_prognostic_t_max_k
Maximum clamping bound for prognostic surface temperature [K]. After each Euler update,...
Definition: ERF_RadStruct.H:648
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:197
bool seb_prognostic_enable
Enable prognostic SEB surface temperature and moisture evolution. When true, integrates SEB residual ...
Definition: ERF_RadStruct.H:596
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:433
amrex::Real seb_prognostic_t_min_k
Minimum clamping bound for prognostic surface temperature [K]. After each Euler update,...
Definition: ERF_RadStruct.H:640
amrex::Real tau_lw_coeff_qc
Longwave optical depth coefficient for cloud liquid water [dimensionless]. Default 0....
Definition: ERF_RadStruct.H:706
amrex::Real rad_orbital_obliquity
Definition: ERF_RadStruct.H:209
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:354
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:384
amrex::Real lw_kabs_cloud
Cloud-water LW mass absorption coefficient m^2/kg.
Definition: ERF_RadStruct.H:256
amrex::Real sw_cloud_omega
Mass model, SW: cloud-water single-scattering albedo.
Definition: ERF_RadStruct.H:232
amrex::Real sw_kscat_dry
Mass model, SW: dry-air (Rayleigh) scattering coefficient [m^2/kg], omega = 1, g = 0.
Definition: ERF_RadStruct.H:223
amrex::Real sw_kabs_dry
Mass model, SW: dry-air gray absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:220
amrex::Real seb_soil_moisture_wilt
Wilting point [m^3/m^3]: at or below it the soil-water factor is 0, so the vegetation does not transp...
Definition: ERF_RadStruct.H:485
amrex::Real seb_leaf_area_index
Leaf area index [m^2/m^2] of the vegetated fraction (seb_vegetation_type; giving it otherwise stops a...
Definition: ERF_RadStruct.H:532
amrex::Real asymmetry_factor
Clear-sky (background gas) scattering asymmetry factor in [-1, 1] (0 = isotropic scattering,...
Definition: ERF_RadStruct.H:372
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:873
bool seb_surface_layer_uses_moisture
Two-way coupling of moisture as well: the surface layer takes its land surface mixing ratio from the ...
Definition: ERF_RadStruct.H:476
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:738
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:714
amrex::Real seb_prognostic_q_max
Maximum clamping bound for prognostic surface moisture [kg/kg]. After each Euler update,...
Definition: ERF_RadStruct.H:664
bool lw_enabled
Enable longwave (thermal) radiation computation. Only used by the two-stream model.
Definition: ERF_RadStruct.H:150
int seb_soil_type
Noah-MP soil category (STAS, 1-19, not 14 (water), as in NoahmpTable.TBL; see ERF_NoahMPSoilTable....
Definition: ERF_RadStruct.H:505
amrex::Real seb_soil_moisture_fc
Field capacity [m^3/m^3]: at or above it the soil-water factor is 1, so without seb_soil_type the sur...
Definition: ERF_RadStruct.H:493
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:408
amrex::Real cloud_base_height_m
Cloud layer base height [m]. Only used when tau_profile_type == CloudLayer.
Definition: ERF_RadStruct.H:332
amrex::Real lw_kabs_vapor
Water-vapor gray LW mass absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:253
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:157
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:623
amrex::Real seb_vegetation_fraction
Vegetated fraction of the surface [0, 1] (seb_vegetation_type; giving it otherwise stops at start-up)...
Definition: ERF_RadStruct.H:523
SEBTurbulentFluxSource seb_turbulent_flux_source
Source of the SEB's sensible and latent heat fluxes when no land-surface model supplies them: "surfac...
Definition: ERF_RadStruct.H:450
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:698
amrex::Real seb_hfx_default
Fallback SEB sensible heat flux [W/m^2].
Definition: ERF_RadStruct.H:546
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:730
amrex::Real sw_cloud_g
Mass model, SW: cloud-water asymmetry factor.
Definition: ERF_RadStruct.H:235
amrex::Real rad_cons_lon
Definition: ERF_RadStruct.H:201
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:247
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:346
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:770
amrex::Real seb_land_roughness
Land roughness length [m] the coupled surface layer takes when erf.most.z0 is not given (derived,...
Definition: ERF_RadStruct.H:541
bool diag_tagged_enable
Enable tagged [RAD][...] debug lines. Controls emission of bracketed-tag debug output....
Definition: ERF_RadStruct.H:292
amrex::Real seb_q_deep_default
Deep soil moisture the prognostic q_s restores to, in q_s's units (see seb_q_sfc_default).
Definition: ERF_RadStruct.H:577
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:200
amrex::Real seb_sw_flux_default
Fallback SEB surface net shortwave flux [W/m^2].
Definition: ERF_RadStruct.H:438
bool diag_enable
Master switch for radiation diagnostics emission. When false, no diagnostics output (stdout/tagged/re...
Definition: ERF_RadStruct.H:278
amrex::Real seb_prognostic_q_min
Minimum clamping bound for prognostic surface moisture [kg/kg]. After each Euler update,...
Definition: ERF_RadStruct.H:656
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:722
bool seb_enable
Master switch for simplified SEB infrastructure. When false (default), no SEB MultiFabs are allocated...
Definition: ERF_RadStruct.H:423
bool diag_stdout_enable
Enable human-readable stdout diagnostics block. Controls tagged [RAD][...] debug line emission (when ...
Definition: ERF_RadStruct.H:285
bool enabled
True when erf.radiation_model = TwoStream. Set by init_params from the model SolverChoice read; the e...
Definition: ERF_RadStruct.H:138
int verbosity
Debug verbosity level (0=off, 1+=debug output). Gated debug prints only emit when this is >= 1.
Definition: ERF_RadStruct.H:262
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:607
TauProfileType tau_profile_type
Optical depth profile type: "constant" (default, the thickness-independent tau) or "cloud_layer" (add...
Definition: ERF_RadStruct.H:326
bool seb_diagnostic_enable
Enable diagnostic SEB residual computation and reporting. When true, computes and reports SEB residua...
Definition: ERF_RadStruct.H:587
Definition: ERF_RadStruct.H:103
void read(const std::string &prefix, const RRTMGPSolarChoice &solar)
Definition: ERF_TopographicShading.cpp:39
amrex::Real shadlen
Definition: ERF_RadStruct.H:106
bool active() const
Definition: ERF_RadStruct.H:113
amrex::Real longitude
Definition: ERF_RadStruct.H:110
amrex::Real fixed_mu
Definition: ERF_RadStruct.H:107
bool slope_rad
Definition: ERF_RadStruct.H:104
int radiation_interval
Definition: ERF_RadStruct.H:111
amrex::Real latitude
Definition: ERF_RadStruct.H:109
amrex::Real fixed_azimuth
Definition: ERF_RadStruct.H:108
bool topo_shading
Definition: ERF_RadStruct.H:105