ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
TwoStreamRadiation Class Reference

The two-stream radiation model (erf.radiation_model = TwoStream). More...

#include <ERF_TwoStreamRadiation.H>

Collaboration diagram for TwoStreamRadiation:

Classes

struct  FluxDiag
 
struct  OrbitalCache
 

Public Member Functions

 TwoStreamRadiation ()=default
 
void resize (int nlevs_max)
 Size the per-level containers; no allocation happens here. More...
 
bool active () const
 True once a level has been defined with the model selected. More...
 
void define_level (int lev, const RadChoice &rad_choice, amrex::Real rdOcp, const amrex::BoxArray &ba2d, const amrex::DistributionMapping &dm)
 Allocate the 2D surface fields of one level on the given horizontal BoxArray and fill them with the scalar defaults. A no-op unless rad_choice.rad_type == TwoStream. More...
 
void advance (int lev, int nstep, amrex::Real time, amrex::Real dt_step, const std::string &call_site, const amrex::MultiFab &cons_old, const amrex::MultiFab *z_phys_nd, const amrex::Geometry &geom, LandSurface &lsm, amrex::MultiFab *qheating, amrex::MultiFab *rad_fluxes, const amrex::MultiFab *t_surf, const amrex::MultiFab *lat_m, const amrex::MultiFab *lon_m, double epoch_time, bool have_datetime)
 One radiation call at one level. More...
 
void write_checkpoint (int lev, const std::string &checkpointname) const
 Write the prognostic surface state of one level (SEB on). More...
 
void read_checkpoint (int lev, const std::string &restart_chkfile)
 Read it back when the checkpoint carries it; else keep defaults. More...
 

Private Attributes

const RadChoicem_rad = nullptr
 
amrex::Real m_rdOcp = RdoCp
 
OrbitalCache m_orbit
 
std::unique_ptr< RadiationDiagnosticsm_diag
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_alb_sw
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_emiss_lw
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_t_sfc
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_sw_flux_sfc
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_lw_flux_sfc
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_hfx_sfc
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_lh_sfc
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_grdflx_sfc
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_q_sfc
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_t_deep
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_q_deep
 
amrex::Vector< FluxDiagm_flux_diag
 

Detailed Description

The two-stream radiation model (erf.radiation_model = TwoStream).

Owns everything the model keeps between steps: the per-level surface property fields it falls back on when no land-surface model supplies them, the surface-energy-balance (SEB) fields and force-restore state, and the domain-mean flux diagnostics of the last column sweep. ERF holds one instance and calls it only after testing solverChoice.rad_type == RadiationType::TwoStream; every method here checks the same predicate and returns without acting otherwise.

Lifecycle:

  • resize(nlevs) once at construction (before the inputs are read);
  • define_level(lev, radChoice, rdOcp, ba2d, dm) whenever a level is (re)built;
  • advance(...) twice per step: "pre_dycore" runs the column sweep on the old state and writes the heating rates, "post_dycore" reuses that sweep's flux diagnostics and advances the SEB state by dt_step;
  • write_checkpoint / read_checkpoint for the prognostic surface state.

The heating-rate MultiFab itself stays in ERF, since RRTMGP writes the same 2-component (SW, LW) array and the RhoTheta source term and the plotfile read it without caring which model filled it.

Constructor & Destructor Documentation

◆ TwoStreamRadiation()

TwoStreamRadiation::TwoStreamRadiation ( )
default

Member Function Documentation

◆ active()

bool TwoStreamRadiation::active ( ) const
inline

True once a level has been defined with the model selected.

50  {
51  return (m_rad != nullptr) && m_rad->enabled;
52  }
const RadChoice * m_rad
Definition: ERF_TwoStreamRadiation.H:147
bool enabled
True when erf.radiation_model = TwoStream. Set by init_params from the model SolverChoice read; the e...
Definition: ERF_RadStruct.H:76

◆ advance()

void TwoStreamRadiation::advance ( int  lev,
int  nstep,
amrex::Real  time,
amrex::Real  dt_step,
const std::string &  call_site,
const amrex::MultiFab &  cons_old,
const amrex::MultiFab *  z_phys_nd,
const amrex::Geometry &  geom,
LandSurface lsm,
amrex::MultiFab *  qheating,
amrex::MultiFab *  rad_fluxes,
const amrex::MultiFab *  t_surf,
const amrex::MultiFab *  lat_m,
const amrex::MultiFab *  lon_m,
double  epoch_time,
bool  have_datetime 
)

One radiation call at one level.

Parameters
levlevel
nstepstep number, used as the diagnostics row index
timetime logged with the diagnostics row [s]
dt_stepstep size, used by the SEB Euler update [s]
call_site"pre_dycore" (column sweep + diagnostics) or "post_dycore" (cached diagnostics + SEB update)
cons_oldold-state conserved variables of this level
z_phys_ndnodal heights (layer interfaces), or nullptr on a uniform grid
geomgeometry of this level
lsmland-surface model (queried for per-column fields)
qheating2-component (SW, LW) heating-rate MultiFab of this level, written at the pre-dycore call; may be nullptr
rad_fluxes4-component (SW up, SW down, LW up, LW down) interface fluxes of this level in RRTMGP's level layout (index k the lower interface of layer k, the top of the atmosphere in the z-ghost cell above the top layer), written at the pre-dycore call; may be nullptr
t_surfthe surface layer's surface temperature, or nullptr; used as the longwave boundary condition where no land-surface field exists, as RRTMGP does
lat_mper-column latitude [degrees], or nullptr (then erf.rad_cons_lat); only the WRF/metgrid inits fill it
lon_mper-column longitude [degrees], likewise
epoch_timeseconds since 1970-01-01 00:00 UTC of this call (start_time + time), the RRTMGP time base; double, as RRTMGP keeps it, since a float cannot resolve a time of day at ~1.7e9 s
have_datetimetrue when start_datetime (or a WRF/metgrid start date) fixed the calendar; without it a dynamic sun cannot be placed and the call aborts
313 {
314  BL_PROFILE("TwoStreamRadiation::advance()");
315 
316  // Only proceed if TwoStream radiation is enabled
317  if (!active()) { return; }
318  const RadChoice& rad_choice = *m_rad;
319 
320  // ---- Contract checks. Each of these would otherwise surface as a wrong
321  // heating rate or an out-of-bounds read several routines away.
322  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(call_site == "pre_dycore" || call_site == "post_dycore",
323  "TwoStreamRadiation::advance: call_site must be pre_dycore or post_dycore");
324  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(lev < m_alb_sw.size() && m_alb_sw[lev] != nullptr,
325  "TwoStreamRadiation::advance called on a level define_level has not built");
326  // The sweep indexes the 2D surface fields with the MFIter of the 3D state,
327  // so both must have the same number of boxes on the same ranks.
329  m_alb_sw[lev]->boxArray().size() == cons_old.boxArray().size() &&
330  m_alb_sw[lev]->DistributionMap() == cons_old.DistributionMap(),
331  "TwoStreamRadiation: the 2D surface fields and the 3D state are laid out differently");
332  if (qheating != nullptr) {
333  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(qheating->nComp() == 2 &&
334  qheating->boxArray() == cons_old.boxArray() &&
335  qheating->DistributionMap() == cons_old.DistributionMap(),
336  "TwoStreamRadiation: qheating must be the 2-component (SW, LW) field on the state's grids");
337  }
338  if (rad_choice.seb_enable) {
340  m_lh_sfc[lev] && m_grdflx_sfc[lev] && m_q_sfc[lev] &&
341  m_t_deep[lev] && m_q_deep[lev],
342  "TwoStreamRadiation: seb_enable is set but the SEB fields were not allocated");
343  }
344  if (call_site == "post_dycore" && rad_choice.seb_prognostic_enable) {
345  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(dt_step > 0.0 && std::isfinite(dt_step),
346  "TwoStreamRadiation: the force-restore update needs a positive, finite dt_step");
347  }
348  // The column kernel would substitute placeholders (rho = 1, rho*theta of
349  // 288 K) for a non-finite or non-positive density or rho*theta and carry
350  // on, hiding a corrupt state behind plausible heating rates. Refuse such
351  // a state here instead.
352  //
353  // This runs every step, so it is one pass over the state and one
354  // collective, not the three that contains_nan() plus two MultiFab::min()
355  // calls would cost. A non-finite value is mapped to -infinity so that the
356  // same minimum answers both questions: -infinity means non-finite, and any
357  // other value <= 0 means non-positive. The reduction is collective, so
358  // every rank takes the same branch.
359  if (call_site == "pre_dycore") {
360  ReduceOps<ReduceOpMin, ReduceOpMin> state_ops;
361  ReduceData<amrex::Real, amrex::Real> state_data(state_ops);
362  using StateTuple = typename decltype(state_data)::Type;
363  for (MFIter mfi(cons_old, TilingIfNotGPU()); mfi.isValid(); ++mfi) {
364  const auto& arr = cons_old.const_array(mfi);
365  state_ops.eval(mfi.tilebox(), state_data,
366  [=] AMREX_GPU_DEVICE (int i, int j, int k) -> StateTuple {
367  constexpr amrex::Real neg_inf = -std::numeric_limits<amrex::Real>::infinity();
368  const amrex::Real rho = arr(i,j,k,Rho_comp);
369  const amrex::Real rth = arr(i,j,k,RhoTheta_comp);
370  return {amrex::Math::isfinite(rho) ? rho : neg_inf,
371  amrex::Math::isfinite(rth) ? rth : neg_inf};
372  });
373  }
374  auto state_tuple = state_data.value(state_ops);
375  amrex::Real mins[2] = {amrex::get<0>(state_tuple), amrex::get<1>(state_tuple)};
376  ParallelDescriptor::ReduceRealMin(mins, 2);
377  const amrex::Real rho_min = mins[0];
378  const amrex::Real rth_min = mins[1];
379  if (!std::isfinite(rho_min) || !std::isfinite(rth_min)) {
380  amrex::Abort("TwoStreamRadiation: the state handed to the column sweep at level " +
381  std::to_string(lev) + ", step " + std::to_string(nstep) +
382  " has a non-finite density or rho*theta");
383  }
384  if (!(rho_min > 0.0) || !(rth_min > 0.0)) {
385  amrex::Abort("TwoStreamRadiation: the state handed to the column sweep at level " +
386  std::to_string(lev) + ", step " + std::to_string(nstep) +
387  " has a non-positive density (min " + std::to_string(rho_min) +
388  ") or rho*theta (min " + std::to_string(rth_min) + ")");
389  }
390  }
391 
392  // The column sweep runs once per step, at the pre-dycore call. The
393  // post-dycore call would sweep the same old state (vars_old is not
394  // swapped until the next step), so it reuses the cached flux diagnostics
395  // and only advances the surface-energy-balance state.
396  const bool do_sweep = (call_site != "post_dycore");
397 
398  // One diagnostics writer for the life of the run, so its header-written
399  // flag and (step, call_site, time) duplicate guard actually carry over
400  // between calls.
401  if (!m_diag) {
402  m_diag = std::make_unique<RadiationDiagnostics>(
403  rad_choice.verbosity, rad_choice.diag_file,
404  rad_choice.diag_enable, rad_choice.diag_stdout_enable,
405  rad_choice.diag_tagged_enable, rad_choice.diag_regtest_line_enable,
406  rad_choice.diag_csv_enable, rad_choice.diag_callsite_mode,
407  rad_choice.diag_dedup_tol);
408  }
409  RadiationDiagnostics& rad_diag = *m_diag;
410 
411  // ========================================
412  // GPU-Safe ParallelFor Implementation with Cloud Fraction
413  // Blending, Diffuse (Scattering) SW Flux, and Per-Level Heating Rate
414  // Output (qheating_rates MultiFab)
415  // ========================================
416 
417  // Initialize global diagnostics
418  amrex::Real SW_surface = 0.0;
419  amrex::Real SW_TOA = 0.0;
420  amrex::Real SW_up_TOA = 0.0;
421  amrex::Real LW_net_surface = 0.0;
422  amrex::Real LW_up_TOA = 0.0;
423  amrex::Real heating_rate_max = 0.0;
424  amrex::Real seb_residual_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
425  amrex::Real seb_residual_max = std::numeric_limits<amrex::Real>::quiet_NaN();
426 
427  // Prognostic SEB surface temperature and moisture diagnostics
428  amrex::Real t_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
429  amrex::Real t_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
430  amrex::Real q_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
431  amrex::Real q_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
432 
433  // Get state at this level (conservative variables: density, RhoTheta, etc.)
434  const MultiFab& state_cons = cons_old;
435 
436  // Only compute radiation if we have valid state data
437  if (state_cons.nComp() > 0 ) {
438 
439  // Trivially copyable parameter set for the device lambdas below
440  // (RadChoice itself holds std::string members and cannot be captured),
441  // with the sun of this call from the inputs shared with RRTMGP. The
442  // incident SW at the top (SW_TOA) is a domain mean formed by the sweep,
443  // since with a calendar sun it varies across the columns.
444  TwoStreamParams ts_params = make_two_stream_params(rad_choice, m_rdOcp);
445  if (do_sweep) { set_solar_state(ts_params, rad_choice, m_orbit, epoch_time, have_datetime, lev, nstep); }
446 
447  // Host-side storage for reduction results (will be set by device-side reduction)
448  amrex::Real max_heating_global = 0.0;
449  amrex::Real sw_surface_sum = 0.0;
450  amrex::Real sw_up_toa_sum = 0.0;
451  amrex::Real lw_net_sum = 0.0;
452  amrex::Real lw_up_toa_sum = 0.0;
453  amrex::Real sw_toa_sum = 0.0;
454  amrex::Long n_columns_total = 0;
455 
456  // SEB residual diagnostics
457  amrex::Real seb_residual_sum = 0.0;
458  amrex::Long n_seb_columns = 0;
459 
460  // cloud fraction used to blend clear-sky and cloudy-column results.
461  // cloud_fraction == 0.0 (default) means only the clear-sky column is
462  // ever evaluated, and the blend below reduces to F = F_clear exactly.
463  amrex::Real cloud_fraction = rad_choice.cloud_fraction;
464 
465  // qheating is ERF's 2-component (SW, LW) heating-rate MultiFab of this
466  // level. If it is not allocated yet, the sweep still runs for the
467  // diagnostics and skips the per-level heating write.
468  MultiFab* qheating_mf = qheating;
469 
470  // Surface radiative fluxes for the SEB. Precedence: an LSM field when
471  // the LSM exposes one; otherwise, with seb_use_radiation_fluxes, the
472  // fluxes the column sweep below computes at the surface (written per
473  // column by the sweep and left in place for the post-dycore call);
474  // otherwise the scalar defaults.
475  const bool sw_flux_from_rad = rad_choice.seb_enable &&
476  rad_choice.seb_use_radiation_fluxes &&
477  !lsm_has_field(lsm, lev, "sav");
478  const bool lw_flux_from_rad = rad_choice.seb_enable &&
479  rad_choice.seb_use_radiation_fluxes &&
480  !lsm_has_field(lsm, lev, "fira");
481 
482  if (rad_choice.seb_enable) {
483  fill_or_copy_seb_field(m_alb_sw[lev].get(), lsm, lev, "sfc_alb_dir_vis", rad_choice.surface_albedo_sw);
484  fill_or_copy_seb_field(m_emiss_lw[lev].get(), lsm, lev, "sfc_emis", rad_choice.surface_emissivity_lw);
485 
486  // Gate t_sfc fill on prognostic mode: when seb_prognostic_enable is true,
487  // t_sfc is owned and evolved by the prognostic update, not reset by fill_or_copy.
488  // This prevents silently overwriting the prognostic state before the update reads it.
489  if (!rad_choice.seb_prognostic_enable) {
490  fill_or_copy_seb_field(m_t_sfc[lev].get(), lsm, lev, "t_sfc", rad_choice.rad_t_sfc);
491  }
492 
493  // Net absorbed shortwave: Noah-MP splits it into the canopy (sav)
494  // and ground (sag) parts. Net longwave: Noah-MP's fira is the
495  // net flux to the atmosphere (positive up); the SEB wants the
496  // absorbed flux, so the sign flips.
497  if (!sw_flux_from_rad) {
498  fill_or_copy_seb_field(m_sw_flux_sfc[lev].get(), lsm, lev, "sav",
499  rad_choice.seb_sw_flux_default, 1.0, "sag");
500  }
501  if (!lw_flux_from_rad) {
502  fill_or_copy_seb_field(m_lw_flux_sfc[lev].get(), lsm, lev, "fira",
503  rad_choice.seb_lw_flux_default, -1.0);
504  }
505  // The LSM data lists carry no sensible or latent heat flux under
506  // these names, so H and LE come from the scalar defaults unless a
507  // model exposes them; G is Noah-MP's grdflx when present.
508  fill_or_copy_seb_field(m_hfx_sfc[lev].get(), lsm, lev, "hfx", rad_choice.seb_hfx_default);
509  fill_or_copy_seb_field(m_lh_sfc[lev].get(), lsm, lev, "lh", rad_choice.seb_lh_default);
510  fill_or_copy_seb_field(m_grdflx_sfc[lev].get(), lsm, lev, "grdflx", rad_choice.seb_grdflx_default);
511 
512  // Gate q_sfc fill on prognostic mode: same reasoning as t_sfc.
513  if (!rad_choice.seb_prognostic_enable) {
514  fill_or_copy_seb_field(m_q_sfc[lev].get(), lsm, lev, "noahmp_water_vapor_mixing_ratio_2m_vegetated", rad_choice.seb_q_sfc_default);
515  }
516  // No LSM exposes a deep-soil temperature or moisture in kg/kg by
517  // name (Noah-MP's smstav / smstot are soil-moisture availability
518  // and total column water), so the reservoir values are the
519  // scalar defaults.
520  m_t_deep[lev]->setVal(rad_choice.seb_t_deep_default);
521  m_q_deep[lev]->setVal(rad_choice.seb_q_deep_default);
522  }
523 
524  // The column sweep integrates the whole atmospheric column in one
525  // kernel, so it needs every k of a box in a single pass. MFIter tiling
526  // would hand it partial columns (the default CPU tile size splits z),
527  // so this loop is deliberately untiled and works on valid boxes; the
528  // horizontal ParallelFor below still provides the parallelism.
529  const Box& rad_domain = geom.Domain();
530  if (do_sweep) {
531  for (MFIter mfi(state_cons, false); mfi.isValid(); ++mfi)
532  {
533  const Box& bx = mfi.validbox();
534 
535  // A box that does not span the domain vertically would give this
536  // column solver a truncated atmosphere: no beam from above, no
537  // cooling to space. ERF only decomposes in z when max_grid_size_z
538  // is smaller than the domain, so refuse that configuration rather
539  // than return heating rates that look plausible and are wrong.
541  bx.smallEnd(2) == rad_domain.smallEnd(2) &&
542  bx.bigEnd(2) == rad_domain.bigEnd(2),
543  "TwoStream radiation requires grids that span the domain in z; "
544  "set amr.max_grid_size_z to at least amr.n_cell in z");
545  const auto& state_arr = state_cons.const_array(mfi);
546  // Geometry::CellSize() is host-only; read it here and hand the
547  // value to the device sweep.
548  const amrex::Real dz_uniform_lev = geom.CellSize(2);
549 
550  // Nodal heights for the layer thicknesses on a stretched or
551  // terrain-following grid (nullptr on a uniform grid).
552  Array4<const amrex::Real> z_phys_nd_arr;
553  if (z_phys_nd != nullptr) {
554  z_phys_nd_arr = z_phys_nd->const_array(mfi);
555  }
556 
557  // Per-column scratch of the sweep (interfaces need nlev + 1 entries),
558  // shared by the clear and cloudy evaluations of the same column.
559  FArrayBox scratch_fab(two_stream_scratch_box(bx), TwoStreamScratch::NCOMP);
560  Array4<amrex::Real> scratch_arr = scratch_fab.array();
561 
562  // Wire LSM surface property fields or use standalone fallback MultiFabs
563  // Priority:
564  // 1. If LSM is active (lsm.Get_DataIdx() returns >=0), use real LSM fields
565  // 2. Otherwise, use standalone fallback MultiFabs (allocated and constant-filled from RadChoice scalars)
566  // The resolve_surface_*() helpers implement the full precedence chain with finite guards
567 
568  // SW albedo: Try LSM field "sfc_alb_dir_vis" (simplified: broadband approx from vis-direct only;
569  // future work: full 4-band vis/nir dir/dif support is planned).
570  bool has_hetero_alb_sw = false;
571  Array4<const amrex::Real> hetero_alb_sw_arr;
572  {
573  std::string varname_alb = "sfc_alb_dir_vis";
574  int lsm_idx = lsm.Get_DataIdx(lev, varname_alb);
575  if (lsm_idx >= 0) {
576  auto lsm_ptr = lsm.Get_Data_Ptr(lev, lsm_idx);
577  if (lsm_ptr) {
578  hetero_alb_sw_arr = lsm_ptr->const_array(mfi);
579  has_hetero_alb_sw = true;
580  }
581  } else if (m_alb_sw[lev]) {
582  hetero_alb_sw_arr = m_alb_sw[lev]->const_array(mfi);
583  has_hetero_alb_sw = true;
584  }
585  }
586 
587  // LW emissivity: Try LSM field "sfc_emis"
588  bool has_hetero_emiss_lw = false;
589  Array4<const amrex::Real> hetero_emiss_lw_arr;
590  {
591  std::string varname_emiss = "sfc_emis";
592  int lsm_idx = lsm.Get_DataIdx(lev, varname_emiss);
593  if (lsm_idx >= 0) {
594  auto lsm_ptr = lsm.Get_Data_Ptr(lev, lsm_idx);
595  if (lsm_ptr) {
596  hetero_emiss_lw_arr = lsm_ptr->const_array(mfi);
597  has_hetero_emiss_lw = true;
598  }
599  } else if (m_emiss_lw[lev]) {
600  hetero_emiss_lw_arr = m_emiss_lw[lev]->const_array(mfi);
601  has_hetero_emiss_lw = true;
602  }
603  }
604 
605  // Keep the surface-temperature sources separate until the column
606  // kernel resolves them per cell. An LSM field can exist globally
607  // while carrying an undefined sentinel in an individual column;
608  // that column must still fall through to SurfaceLayer theta.
609  // The kernel's precedence is valid LSM absolute temperature,
610  // valid prognostic SEB absolute temperature, SurfaceLayer theta,
611  // then the scalar absolute-temperature fallback.
612  bool has_lsm_t_sfc = false;
613  Array4<const amrex::Real> lsm_t_sfc_arr;
614  bool has_seb_t_sfc = false;
615  Array4<const amrex::Real> seb_t_sfc_arr;
616  bool has_surface_layer = false;
617  Array4<const amrex::Real> surface_layer_theta_arr;
618  {
619  std::string varname_t_sfc = "t_sfc";
620  int lsm_idx = lsm.Get_DataIdx(lev, varname_t_sfc);
621  if (lsm_idx >= 0) {
622  auto lsm_ptr = lsm.Get_Data_Ptr(lev, lsm_idx);
623  if (lsm_ptr) {
624  lsm_t_sfc_arr = lsm_ptr->const_array(mfi);
625  has_lsm_t_sfc = true;
626  }
627  }
628  // Prognostic SEB is a level-wide alternative to Noah t_sfc.
629  // When Noah exposes t_sfc on this level, m_t_sfc is not advanced,
630  // so it must not be offered as a per-cell fallback.
631  if (!has_lsm_t_sfc && rad_choice.seb_prognostic_enable &&
632  rad_choice.seb_enable && m_t_sfc[lev]) {
633  seb_t_sfc_arr = m_t_sfc[lev]->const_array(mfi);
634  has_seb_t_sfc = true;
635  }
636  if (t_surf != nullptr) {
637  surface_layer_theta_arr = t_surf->const_array(mfi);
638  has_surface_layer = true;
639  }
640  }
641 
642  // Per-column latitude and longitude for the calendar sun (only
643  // the WRF and metgrid initialisations fill them; otherwise the
644  // erf.rad_cons_lat/lon constants apply).
645  const bool has_latlon = (lat_m != nullptr) && (lon_m != nullptr);
646  Array4<const amrex::Real> lat_arr, lon_arr;
647  if (has_latlon) {
648  lat_arr = lat_m->const_array(mfi);
649  lon_arr = lon_m->const_array(mfi);
650  }
651 
652  // Interface fluxes for ERF's rad_fluxes (SW up, SW down, LW up,
653  // LW down at each layer's lower interface, plus the top of the
654  // atmosphere in the z-ghost cell above the column), the level
655  // layout RRTMGP writes. The cloudy evaluation goes to a scratch
656  // FArrayBox on the same grown box and is blended like the
657  // heating rates.
658  const bool write_fluxes = (rad_fluxes != nullptr);
659  Array4<amrex::Real> rad_flux_clear_arr;
660  FArrayBox rad_flux_cloudy_fab;
661  Array4<amrex::Real> rad_flux_cloudy_arr;
662  if (write_fluxes) {
663  rad_flux_clear_arr = rad_fluxes->array(mfi);
664  if (cloud_fraction > 0.0) {
665  rad_flux_cloudy_fab.resize(two_stream_scratch_box(bx), 4);
666  rad_flux_cloudy_arr = rad_flux_cloudy_fab.array();
667  }
668  }
669 
670 
671  // Surface flux arrays the sweep fills for the SEB when asked to.
672  Array4<amrex::Real> sw_sfc_out;
673  Array4<amrex::Real> lw_sfc_out;
674  if (sw_flux_from_rad) sw_sfc_out = m_sw_flux_sfc[lev]->array(mfi);
675  if (lw_flux_from_rad) lw_sfc_out = m_lw_flux_sfc[lev]->array(mfi);
676 
677  // Create a 2D box for (i,j) iteration over the horizontal extent
678  // One GPU thread per (i,j) column; k-loop is sequential within each thread
679  const auto& lo = bx.loVect();
680  const auto& hi = bx.hiVect();
681  Box xy_box(IntVect(lo[0], lo[1], 0), IntVect(hi[0], hi[1], 0));
682 
683  // Count columns in this box for later averaging
684  amrex::Long n_cols = static_cast<amrex::Long>(bx.length(0)) *
685  static_cast<amrex::Long>(bx.length(1));
686  n_columns_total += n_cols;
687 
688  // Clear-sky-column heating rates are written directly
689  // into the real qheating_rates MultiFab when available.
690  // Fall back to a throwaway local FArrayBox otherwise (keeps the
691  // kernel call GPU-safe even if qheating_rates isn't allocated).
692  FArrayBox qheating_fallback_fab;
693  Array4<amrex::Real> qheating_clear_arr;
694  if (qheating_mf != nullptr) {
695  qheating_clear_arr = qheating_mf->array(mfi);
696  } else {
697  qheating_fallback_fab.resize(bx, 2);
698  qheating_clear_arr = qheating_fallback_fab.array();
699  }
700 
701  // Cloudy-column heating rates always go into a scratch
702  // FArrayBox; only used/blended when cloud_fraction > 0.
703  FArrayBox qheating_cloudy_fab;
704  Array4<amrex::Real> qheating_cloudy_arr;
705  if (cloud_fraction > 0.0) {
706  qheating_cloudy_fab.resize(bx, 2);
707  qheating_cloudy_arr = qheating_cloudy_fab.array();
708  }
709 
710  // GPU-safe reduction using ReduceOps (per-column results aggregated on device)
711  amrex::Real max_heating_box = 0.0;
712  amrex::Real sw_sum_box = 0.0;
713  amrex::Real sw_up_sum_box = 0.0;
714  amrex::Real lw_sum_box = 0.0;
715  amrex::Real lw_up_sum_box = 0.0;
716  amrex::Real sw_toa_sum_box = 0.0;
717 
718  // Device-side reduction: max heating, sums of the surface and
719  // top-of-atmosphere fluxes
720  ReduceOps<ReduceOpMax, ReduceOpSum, ReduceOpSum, ReduceOpSum, ReduceOpSum, ReduceOpSum> reduce_ops;
721  ReduceData<amrex::Real, amrex::Real, amrex::Real, amrex::Real, amrex::Real, amrex::Real> reduce_data(reduce_ops);
722 
723  using ReduceTuple = typename decltype(reduce_data)::Type;
724 
725  // Launch parallel kernel over (i,j) columns
726  reduce_ops.eval(xy_box, reduce_data,
727  [=] AMREX_GPU_DEVICE (int i, int j, int /*k_unused*/) -> ReduceTuple
728  {
729  // Clear-sky column (always evaluated; this is the sole
730  // contributor when cloud_fraction == 0.0, matching earlier behavior)
731  amrex::Real max_heating_clear = 0.0;
732  amrex::Real sw_flux_clear = 0.0;
733  amrex::Real sw_up_clear = 0.0;
734  amrex::Real lw_net_clear = 0.0;
735  amrex::Real lw_up_clear = 0.0;
736  amrex::Real sw_toa_clear = 0.0;
738  i, j, bx, dz_uniform_lev, state_arr, ts_params, /*cloudy=*/false,
739  qheating_clear_arr,
740  max_heating_clear, sw_flux_clear, sw_up_clear, lw_net_clear, lw_up_clear,
741  sw_toa_clear,
742  z_phys_nd_arr, scratch_arr,
743  has_hetero_alb_sw, &hetero_alb_sw_arr,
744  has_hetero_emiss_lw, &hetero_emiss_lw_arr,
745  has_lsm_t_sfc, &lsm_t_sfc_arr,
746  has_seb_t_sfc, &seb_t_sfc_arr,
747  has_surface_layer, &surface_layer_theta_arr,
748  has_latlon, &lat_arr, &lon_arr,
749  write_fluxes ? &rad_flux_clear_arr : nullptr);
750 
751  amrex::Real max_heating_col = max_heating_clear;
752  amrex::Real sw_flux_col = sw_flux_clear;
753  amrex::Real sw_up_col = sw_up_clear;
754  amrex::Real lw_net_col = lw_net_clear;
755  amrex::Real lw_up_col = lw_up_clear;
756 
757  // Cloudy column only needs to be evaluated when there is a
758  // nonzero cloud fraction; this keeps the cloud_fraction==0
759  // path numerically and computationally identical.
760  if (cloud_fraction > 0.0) {
761  amrex::Real max_heating_cloudy = 0.0;
762  amrex::Real sw_flux_cloudy = 0.0;
763  amrex::Real sw_up_cloudy = 0.0;
764  amrex::Real lw_net_cloudy = 0.0;
765  amrex::Real lw_up_cloudy = 0.0;
766  amrex::Real sw_toa_cloudy = 0.0;
768  i, j, bx, dz_uniform_lev, state_arr, ts_params, /*cloudy=*/true,
769  qheating_cloudy_arr,
770  max_heating_cloudy, sw_flux_cloudy, sw_up_cloudy, lw_net_cloudy, lw_up_cloudy,
771  sw_toa_cloudy,
772  z_phys_nd_arr, scratch_arr,
773  has_hetero_alb_sw, &hetero_alb_sw_arr,
774  has_hetero_emiss_lw, &hetero_emiss_lw_arr,
775  has_lsm_t_sfc, &lsm_t_sfc_arr,
776  has_seb_t_sfc, &seb_t_sfc_arr,
777  has_surface_layer, &surface_layer_theta_arr,
778  has_latlon, &lat_arr, &lon_arr,
779  write_fluxes ? &rad_flux_cloudy_arr : nullptr);
780 
781  // Blend clear-sky and cloudy-column results
782  sw_flux_col = (1.0 - cloud_fraction) * sw_flux_clear +
783  cloud_fraction * sw_flux_cloudy;
784  sw_up_col = (1.0 - cloud_fraction) * sw_up_clear +
785  cloud_fraction * sw_up_cloudy;
786  lw_net_col = (1.0 - cloud_fraction) * lw_net_clear +
787  cloud_fraction * lw_net_cloudy;
788  lw_up_col = (1.0 - cloud_fraction) * lw_up_clear +
789  cloud_fraction * lw_up_cloudy;
790  max_heating_col = std::max(max_heating_clear, max_heating_cloudy);
791 
792  // Blend per-level heating rates in place
793  // into qheating_clear_arr (which is the real output
794  // MultiFab when qheating_mf != nullptr).
795  int kmin = bx.smallEnd(2);
796  int kmax = bx.bigEnd(2);
797  for (int k = kmin; k <= kmax; ++k) {
798  for (int comp = 0; comp < 2; ++comp) {
799  amrex::Real q_clear_val = qheating_clear_arr(i, j, k, comp);
800  amrex::Real q_cloudy_val = qheating_cloudy_arr(i, j, k, comp);
801  qheating_clear_arr(i, j, k, comp) =
802  (1.0 - cloud_fraction) * q_clear_val +
803  cloud_fraction * q_cloudy_val;
804  }
805  }
806  if (write_fluxes) {
807  // nlev + 1 levels: the top interface sits at kmax + 1
808  for (int k = kmin; k <= kmax + 1; ++k) {
809  for (int comp = 0; comp < 4; ++comp) {
810  rad_flux_clear_arr(i, j, k, comp) =
811  (1.0 - cloud_fraction) * rad_flux_clear_arr(i, j, k, comp) +
812  cloud_fraction * rad_flux_cloudy_arr(i, j, k, comp);
813  }
814  }
815  }
816  }
817 
818  // Surface fluxes for the SEB: absorbed shortwave, and the
819  // absorbed longwave, which is minus the net (up - down).
820  if (sw_flux_from_rad) sw_sfc_out(i, j, 0) = sw_flux_col;
821  if (lw_flux_from_rad) lw_sfc_out(i, j, 0) = -lw_net_col;
822 
823  // The incident TOA flux is the same for both evaluations.
824  // Return tuple for reduction
825  return {max_heating_col, sw_flux_col, sw_up_col, lw_net_col, lw_up_col, sw_toa_clear};
826  }
827  );
828 
829  // Copy results from device to host
830  amrex::Gpu::synchronize();
831  auto reduce_tuple = reduce_data.value(reduce_ops);
832  max_heating_box = amrex::get<0>(reduce_tuple);
833  sw_sum_box = amrex::get<1>(reduce_tuple);
834  sw_up_sum_box = amrex::get<2>(reduce_tuple);
835  lw_sum_box = amrex::get<3>(reduce_tuple);
836  lw_up_sum_box = amrex::get<4>(reduce_tuple);
837  sw_toa_sum_box = amrex::get<5>(reduce_tuple);
838 
839  // Accumulate box results into global results
840  max_heating_global = std::max(max_heating_global, max_heating_box);
841  sw_surface_sum += sw_sum_box;
842  sw_up_toa_sum += sw_up_sum_box;
843  lw_net_sum += lw_sum_box;
844  lw_up_toa_sum += lw_up_sum_box;
845  sw_toa_sum += sw_toa_sum_box;
846  }
847  // Every accumulator above is rank-local. Reduce before forming means
848  // and maxima, so the diagnostics describe the whole domain and do
849  // not change with the decomposition. These are collective calls; the
850  // conditions around them are input-driven and identical on all ranks.
851  if (do_sweep) {
852  amrex::Real sums[5] = {sw_surface_sum, sw_up_toa_sum, lw_net_sum, lw_up_toa_sum, sw_toa_sum};
853  ParallelDescriptor::ReduceRealSum(sums, 5);
854  sw_surface_sum = sums[0]; sw_up_toa_sum = sums[1];
855  lw_net_sum = sums[2]; lw_up_toa_sum = sums[3];
856  sw_toa_sum = sums[4];
857  ParallelDescriptor::ReduceLongSum(n_columns_total);
858  ParallelDescriptor::ReduceRealMax(max_heating_global);
859  }
860  } // do_sweep
861 
862  // The heating rates go straight into the RhoTheta source term, so a
863  // non-finite value here becomes a non-finite state one step later.
864  if (do_sweep && qheating_mf != nullptr &&
865  (qheating_mf->contains_nan(0, 2, 0) || qheating_mf->contains_inf(0, 2, 0))) {
866  amrex::Abort("TwoStreamRadiation: non-finite heating rate after the column sweep at level " +
867  std::to_string(lev) + ", step " + std::to_string(nstep));
868  }
869 
870  // Warn if diagnostic is requested but SEB infrastructure isn't enabled
871  if (rad_choice.seb_diagnostic_enable && !rad_choice.seb_enable) {
872  static bool warned_seb_misconfig = false;
873  if (!warned_seb_misconfig && ParallelDescriptor::IOProcessor()) {
874  Print() << "WARNING: erf.radiation.seb_diagnostic_enable=true but "
875  "seb_enable=false; SEB residual diagnostics will report NaN. "
876  "Set erf.radiation.seb_enable=true to enable SEB field "
877  "population.\n";
878  warned_seb_misconfig = true;
879  }
880  }
881  // Compute SEB residual diagnostics if enabled
882  if (rad_choice.seb_diagnostic_enable && rad_choice.seb_enable) {
883  seb_residual_max = 0.0;
884  // Second loop over boxes to compute SEB residual from populated SEB
885  // MultiFabs. Untiled: the work below is per surface column, and a
886  // tiled iteration would visit each (i,j) once per z tile.
887  for (MFIter mfi(state_cons, false); mfi.isValid(); ++mfi) {
888  const Box& bx = mfi.validbox();
889  const auto& lo = bx.loVect();
890  const auto& hi = bx.hiVect();
891  Box xy_box(IntVect(lo[0], lo[1], 0), IntVect(hi[0], hi[1], 0));
892 
893  // Get SEB field arrays
894  Array4<const amrex::Real> sw_flux_arr = m_sw_flux_sfc[lev]->const_array(mfi);
895  Array4<const amrex::Real> lw_flux_arr = m_lw_flux_sfc[lev]->const_array(mfi);
896  Array4<const amrex::Real> hfx_arr = m_hfx_sfc[lev]->const_array(mfi);
897  Array4<const amrex::Real> lh_arr = m_lh_sfc[lev]->const_array(mfi);
898  Array4<const amrex::Real> grdflx_arr = m_grdflx_sfc[lev]->const_array(mfi);
899 
900  // Count columns and compute residuals
901  amrex::Long n_cols_box = static_cast<amrex::Long>(bx.length(0)) *
902  static_cast<amrex::Long>(bx.length(1));
903  amrex::Real residual_sum_box = 0.0;
904  amrex::Real residual_max_box = 0.0;
905 
906  // GPU-safe reduction for SEB residuals
907  ReduceOps<ReduceOpSum, ReduceOpMax> seb_reduce_ops;
908  ReduceData<amrex::Real, amrex::Real> seb_reduce_data(seb_reduce_ops);
909 
910  using SEBReduceTuple = typename decltype(seb_reduce_data)::Type;
911 
912  seb_reduce_ops.eval(xy_box, seb_reduce_data,
913  [=] AMREX_GPU_DEVICE (int i, int j, int /*k_unused*/) -> SEBReduceTuple {
914  amrex::Real sw_net = sw_flux_arr(i, j, 0);
915  amrex::Real lw_net = lw_flux_arr(i, j, 0);
916  amrex::Real hfx = hfx_arr(i, j, 0);
917  amrex::Real lh = lh_arr(i, j, 0);
918  amrex::Real grdflx = grdflx_arr(i, j, 0);
919 
920  // Compute residual using helper function
921  amrex::Real residual = diagnose_seb_residual(sw_net, lw_net, hfx, lh, grdflx);
922 
923  // Return sum and abs(max) of residual
924  return {residual, std::abs(residual)};
925  }
926  );
927 
928  // Copy results from device to host
929  amrex::Gpu::synchronize();
930  auto seb_reduce_tuple = seb_reduce_data.value(seb_reduce_ops);
931  residual_sum_box = amrex::get<0>(seb_reduce_tuple);
932  residual_max_box = amrex::get<1>(seb_reduce_tuple);
933 
934  // Accumulate into global results
935  seb_residual_sum += residual_sum_box;
936  seb_residual_max = std::max(seb_residual_max, residual_max_box);
937  n_seb_columns += n_cols_box;
938  }
939  ParallelDescriptor::ReduceRealSum(seb_residual_sum);
940  ParallelDescriptor::ReduceRealMax(seb_residual_max);
941  ParallelDescriptor::ReduceLongSum(n_seb_columns);
942  }
943 
944  // Prognostic SEB surface temperature and moisture evolution
945  // Only run if prognostic mode is enabled and Noah-MP is NOT driving LSM at this level
946  if (rad_choice.seb_prognostic_enable && rad_choice.seb_enable &&
947  call_site == "post_dycore") {
948  // Check if Noah-MP is active at this level by attempting to get the LSM t_sfc field
949  std::string varname_t_sfc_prog = "t_sfc";
950  int lsm_idx_t_sfc = lsm.Get_DataIdx(lev, varname_t_sfc_prog);
951  bool noahmp_active = (lsm_idx_t_sfc >= 0);
952 
953  if (!noahmp_active) {
954  // Noah-MP is NOT active; proceed with prognostic update
955 
956  // Initialize diagnostics for T_s and q_s
957  amrex::Real t_s_sum = 0.0;
958  amrex::Real t_s_max_val = -std::numeric_limits<amrex::Real>::max();
959  amrex::Real q_s_sum = 0.0;
960  amrex::Real q_s_max_val = -std::numeric_limits<amrex::Real>::max();
961  amrex::Long n_prog_columns = 0;
962 
963  // Third loop over boxes for prognostic SEB update. Untiled for
964  // the same reason as above, and here it also matters for
965  // correctness: the force-restore update is applied in place, so
966  // visiting a column twice would advance it twice in one step.
967  for (MFIter mfi(state_cons, false); mfi.isValid(); ++mfi) {
968  const Box& bx = mfi.validbox();
969  const auto& lo = bx.loVect();
970  const auto& hi = bx.hiVect();
971  Box xy_box(IntVect(lo[0], lo[1], 0), IntVect(hi[0], hi[1], 0));
972 
973  // Get SEB field arrays (read-only)
974  Array4<const amrex::Real> sw_flux_arr = m_sw_flux_sfc[lev]->const_array(mfi);
975  Array4<const amrex::Real> lw_flux_arr = m_lw_flux_sfc[lev]->const_array(mfi);
976  Array4<const amrex::Real> hfx_arr = m_hfx_sfc[lev]->const_array(mfi);
977  Array4<const amrex::Real> lh_arr = m_lh_sfc[lev]->const_array(mfi);
978  Array4<const amrex::Real> grdflx_arr = m_grdflx_sfc[lev]->const_array(mfi);
979  Array4<const amrex::Real> t_deep_arr = m_t_deep[lev]->const_array(mfi);
980  Array4<const amrex::Real> q_deep_arr = m_q_deep[lev]->const_array(mfi);
981 
982  // Get SEB state arrays (read-write for prognostic update)
983  Array4<amrex::Real> t_s_arr = m_t_sfc[lev]->array(mfi);
984  Array4<amrex::Real> q_s_arr = m_q_sfc[lev]->array(mfi);
985  // Count columns and prepare for reductions
986  amrex::Long n_cols_box = static_cast<amrex::Long>(bx.length(0)) *
987  static_cast<amrex::Long>(bx.length(1));
988  amrex::Real t_s_sum_box = 0.0;
989  amrex::Real t_s_max_box = -std::numeric_limits<amrex::Real>::max();
990  amrex::Real q_s_sum_box = 0.0;
991  amrex::Real q_s_max_box = -std::numeric_limits<amrex::Real>::max();
992 
993  // GPU-safe update for prognostic T_s and q_s with reductions
994  ReduceOps<ReduceOpSum, ReduceOpMax, ReduceOpSum, ReduceOpMax> prog_reduce_ops;
995  ReduceData<amrex::Real, amrex::Real, amrex::Real, amrex::Real> prog_reduce_data(prog_reduce_ops);
996 
997  using ProgReduceTuple = typename decltype(prog_reduce_data)::Type;
998 
999  prog_reduce_ops.eval(xy_box, prog_reduce_data,
1000  [=, C_s=rad_choice.seb_surface_heat_capacity,
1001  tau=rad_choice.seb_restore_timescale_s,
1002  d_s=rad_choice.seb_moisture_layer_depth_m,
1003  tau_q=rad_choice.seb_moisture_restore_timescale_s,
1004  t_min=rad_choice.seb_prognostic_t_min_k,
1005  t_max=rad_choice.seb_prognostic_t_max_k,
1006  q_min=rad_choice.seb_prognostic_q_min,
1007  q_max=rad_choice.seb_prognostic_q_max]
1008  AMREX_GPU_DEVICE (int i, int j, int /*k_unused*/) -> ProgReduceTuple {
1009  amrex::Real t_s_old = t_s_arr(i, j, 0);
1010  amrex::Real q_s_old = q_s_arr(i, j, 0);
1011 
1012  // Read forcing data
1013  amrex::Real sw_net = sw_flux_arr(i, j, 0);
1014  amrex::Real lw_net = lw_flux_arr(i, j, 0);
1015  amrex::Real hfx = hfx_arr(i, j, 0);
1016  amrex::Real lh = lh_arr(i, j, 0);
1017  amrex::Real grdflx = grdflx_arr(i, j, 0);
1018  amrex::Real t_deep_val = t_deep_arr(i, j, 0);
1019  amrex::Real q_deep_val = q_deep_arr(i, j, 0);
1020 
1021  // Compute SEB residual
1022  amrex::Real seb_res = diagnose_seb_residual(sw_net, lw_net, hfx, lh, grdflx);
1023 
1024  // Compute tendencies
1025  amrex::Real dT_s_dt = prognostic_dTs_dt(seb_res, t_s_old, t_deep_val,
1026  C_s, tau);
1027  amrex::Real dq_s_dt = prognostic_dqs_dt(lh, q_s_old, q_deep_val,
1028  d_s, tau_q);
1029 
1030  // Explicit Euler update over the step size
1031  amrex::Real t_s_new = t_s_old + dt_step * dT_s_dt;
1032  amrex::Real q_s_new = q_s_old + dt_step * dq_s_dt;
1033 
1034  // Clamp to valid ranges
1035  t_s_new = amrex::max(t_min, amrex::min(t_max, t_s_new));
1036  q_s_new = amrex::max(q_min, amrex::min(q_max, q_s_new));
1037 
1038  // Write back updated values (this modifies the device array)
1039  t_s_arr(i, j, 0) = t_s_new;
1040  q_s_arr(i, j, 0) = q_s_new;
1041 
1042  // Return for reduction: sum T_s, max T_s, sum q_s, max q_s
1043  return {t_s_new, std::abs(t_s_new), q_s_new, std::abs(q_s_new)};
1044  });
1045 
1046  // Copy results from device to host
1047  amrex::Gpu::synchronize();
1048  auto prog_reduce_tuple = prog_reduce_data.value(prog_reduce_ops);
1049  t_s_sum_box = amrex::get<0>(prog_reduce_tuple);
1050  t_s_max_box = amrex::get<1>(prog_reduce_tuple);
1051  q_s_sum_box = amrex::get<2>(prog_reduce_tuple);
1052  q_s_max_box = amrex::get<3>(prog_reduce_tuple);
1053 
1054  // Accumulate into global results
1055  t_s_sum += t_s_sum_box;
1056  t_s_max_val = std::max(t_s_max_val, t_s_max_box);
1057  q_s_sum += q_s_sum_box;
1058  q_s_max_val = std::max(q_s_max_val, q_s_max_box);
1059  n_prog_columns += n_cols_box;
1060  }
1061 
1062  // The force-restore kernels return a zero tendency on any
1063  // non-finite input, so a NaN here means the state itself was
1064  // corrupted (a bad restart file, or an overwrite elsewhere).
1065  if (m_t_sfc[lev]->contains_nan(0, 1, 0) || m_q_sfc[lev]->contains_nan(0, 1, 0)) {
1066  amrex::Abort("TwoStreamRadiation: non-finite surface temperature or moisture "
1067  "after the force-restore update at level " + std::to_string(lev) +
1068  ", step " + std::to_string(nstep));
1069  }
1070 
1071  {
1072  amrex::Real sums[2] = {t_s_sum, q_s_sum};
1073  ParallelDescriptor::ReduceRealSum(sums, 2);
1074  t_s_sum = sums[0]; q_s_sum = sums[1];
1075  ParallelDescriptor::ReduceRealMax(t_s_max_val);
1076  ParallelDescriptor::ReduceRealMax(q_s_max_val);
1077  ParallelDescriptor::ReduceLongSum(n_prog_columns);
1078  }
1079 
1080  // Compute mean values from sums
1081  if (n_prog_columns > 0) {
1082  t_s_mean = t_s_sum / static_cast<amrex::Real>(n_prog_columns);
1083  t_s_max = t_s_max_val;
1084  q_s_mean = q_s_sum / static_cast<amrex::Real>(n_prog_columns);
1085  q_s_max = q_s_max_val;
1086  } else {
1087  t_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
1088  t_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
1089  q_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
1090  q_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
1091  }
1092  } else {
1093  // Noah-MP is active; skip prognostic update for this level
1094  // Leave t_s and q_s as populated by LSM passthrough
1095  t_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
1096  t_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
1097  q_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
1098  q_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
1099  }
1100  }
1101  // equivalent to a single-column value for spatially UNIFORM atmospheres
1102  // (as in the current SW_ClearSky_Analytical / LW_Isothermal RegTests).
1103  // Cloud layer and scattering tests are ALSO spatially uniform (identical
1104  // cloud/tau/scattering parameters applied to every column), so the
1105  // domain-averaged value still equals the true single-column flux there.
1106  // True horizontal heterogeneity (e.g., patchy clouds varying by column)
1107  // remains deferred to future work.
1108  if (do_sweep) {
1109  if (n_columns_total > 0) {
1110  const amrex::Real inv_n = 1.0 / static_cast<amrex::Real>(n_columns_total);
1111  SW_surface = sw_surface_sum * inv_n;
1112  SW_TOA = sw_toa_sum * inv_n;
1113  SW_up_TOA = sw_up_toa_sum * inv_n;
1114  LW_net_surface = lw_net_sum * inv_n;
1115  LW_up_TOA = lw_up_toa_sum * inv_n;
1116  }
1117  heating_rate_max = max_heating_global;
1118  m_flux_diag[lev] = FluxDiag{SW_surface, SW_TOA, SW_up_TOA,
1119  LW_net_surface, LW_up_TOA,
1120  heating_rate_max};
1121  } else {
1122  const FluxDiag& cached = m_flux_diag[lev];
1123  SW_surface = cached.SW_surface;
1124  SW_TOA = cached.SW_TOA;
1125  SW_up_TOA = cached.SW_up_TOA;
1126  LW_net_surface = cached.LW_net_surface;
1127  LW_up_TOA = cached.LW_up_TOA;
1128  heating_rate_max = cached.heating_rate_max;
1129  }
1130 
1131  // Compute SEB residual mean from sum
1132  if (rad_choice.seb_diagnostic_enable && rad_choice.seb_enable && n_seb_columns > 0) {
1133  seb_residual_mean = seb_residual_sum / static_cast<amrex::Real>(n_seb_columns);
1134  } else {
1135  // When feature is disabled, use NaN for backward compatibility
1136  seb_residual_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
1137  seb_residual_max = std::numeric_limits<amrex::Real>::quiet_NaN();
1138  }
1139 
1140  }
1141 
1142  // Logging output
1143  if (rad_choice.verbosity >= 1 && ParallelDescriptor::IOProcessor()) {
1144  Print() << "Radiation diagnostics at step " << nstep << ":\n"
1145  << " SW TOA = " << SW_TOA << " W/m^2\n"
1146  << " SW surface = " << SW_surface << " W/m^2\n"
1147  << " SW up (TOA) = " << SW_up_TOA << " W/m^2\n"
1148  << " LW net (surface) = " << LW_net_surface << " W/m^2\n"
1149  << " LW up (TOA) = " << LW_up_TOA << " W/m^2\n"
1150  << " Max heating rate = " << heating_rate_max << " K/s\n";
1151  if (rad_choice.seb_diagnostic_enable && std::isfinite(seb_residual_mean)) {
1152  Print() << " SEB residual (mean) = " << seb_residual_mean << " W/m^2\n"
1153  << " SEB residual (max) = " << seb_residual_max << " W/m^2\n";
1154  }
1155  if (rad_choice.seb_prognostic_enable && std::isfinite(t_s_mean)) {
1156  Print() << " Surface temperature (mean) = " << t_s_mean << " K\n"
1157  << " Surface temperature (max) = " << t_s_max << " K\n"
1158  << " Surface moisture (mean) = " << q_s_mean << " kg/kg\n"
1159  << " Surface moisture (max) = " << q_s_max << " kg/kg\n";
1160  }
1161  }
1162 
1163  rad_diag.append(nstep, time, call_site, SW_surface, SW_TOA,
1164  SW_up_TOA, LW_net_surface, LW_up_TOA, heating_rate_max,
1165  seb_residual_mean, seb_residual_max,
1166  t_s_mean, t_s_max, q_s_mean, q_s_max);
1167 }
#define Rho_comp
Definition: ERF_IndexDefines.H:39
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:40
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_cloud_chamber_config.active, "Cloud Chamber: initializer reached without a parsed configuration")
pp get("wavelength", wavelength)
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_seb_residual(amrex::Real sw_net, amrex::Real lw_net, amrex::Real hfx, amrex::Real lh, amrex::Real grdflx)
Diagnose the surface energy balance residual.
Definition: ERF_SimplifiedSEB.H:64
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real prognostic_dqs_dt(amrex::Real le, amrex::Real q_s, amrex::Real q_deep, amrex::Real d_s, amrex::Real tau_q)
Compute prognostic surface moisture tendency using force-restore formulation.
Definition: ERF_SimplifiedSEB.H:164
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real prognostic_dTs_dt(amrex::Real seb_residual, amrex::Real t_s, amrex::Real t_deep, amrex::Real c_s, amrex::Real tau)
Compute prognostic surface temperature tendency using force-restore formulation.
Definition: ERF_SimplifiedSEB.H:110
TwoStreamParams make_two_stream_params(const RadChoice &rc, const amrex::Real rdOcp)
Copy the column-kernel parameters out of a RadChoice.
Definition: ERF_TwoStreamColumn.H:127
amrex::Box two_stream_scratch_box(const amrex::Box &bx)
Box of the scratch FArrayBox a sweep over box bx needs.
Definition: ERF_TwoStreamColumn.H:612
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void vertical_two_stream_sweep(int i, int j, const amrex::Box &bx, amrex::Real dz_uniform, const amrex::Array4< const amrex::Real > &state_arr, const TwoStreamParams &rad_choice, bool cloudy, const amrex::Array4< amrex::Real > &qheating_arr, amrex::Real &max_heating_rate, amrex::Real &sw_surface_flux, amrex::Real &sw_up_toa, amrex::Real &lw_net_surface, amrex::Real &lw_up_toa, amrex::Real &sw_down_toa, const amrex::Array4< const amrex::Real > &z_phys_nd, const amrex::Array4< amrex::Real > &scratch, bool has_hetero_alb_sw=false, const amrex::Array4< const amrex::Real > *hetero_alb_sw=nullptr, bool has_hetero_emiss_lw=false, const amrex::Array4< const amrex::Real > *hetero_emiss_lw=nullptr, bool has_lsm_t_sfc=false, const amrex::Array4< const amrex::Real > *lsm_t_sfc=nullptr, bool has_seb_t_sfc=false, const amrex::Array4< const amrex::Real > *seb_t_sfc=nullptr, bool has_surface_layer=false, const amrex::Array4< const amrex::Real > *surface_layer_theta=nullptr, bool has_latlon=false, const amrex::Array4< const amrex::Real > *lat_arr=nullptr, const amrex::Array4< const amrex::Real > *lon_arr=nullptr, const amrex::Array4< amrex::Real > *rad_flux_out=nullptr)
GPU-safe per-column vertical integration kernel for two-stream radiation, computing either the clear-...
Definition: ERF_TwoStreamColumn.H:886
int Get_DataIdx(const int &lev, std::string &varname)
Definition: ERF_LandSurface.H:152
amrex::MultiFab * Get_Data_Ptr(const int &lev, const int &varIdx)
Definition: ERF_LandSurface.H:108
CSV logger and debug-print manager for radiation module.
Definition: ERF_RadiationDiagnostics.H:70
void append(int step, amrex::Real time, std::string const &call_site, amrex::Real sw_surface, amrex::Real sw_toa, amrex::Real sw_up_toa, amrex::Real lw_net_surface, amrex::Real lw_up_toa, amrex::Real heating_rate_max, amrex::Real seb_residual_mean=std::numeric_limits< amrex::Real >::quiet_NaN(), amrex::Real seb_residual_max=std::numeric_limits< amrex::Real >::quiet_NaN(), amrex::Real t_s_mean=std::numeric_limits< amrex::Real >::quiet_NaN(), amrex::Real t_s_max=std::numeric_limits< amrex::Real >::quiet_NaN(), amrex::Real q_s_mean=std::numeric_limits< amrex::Real >::quiet_NaN(), amrex::Real q_s_max=std::numeric_limits< amrex::Real >::quiet_NaN())
Append a diagnostic row to the CSV file.
Definition: ERF_RadiationDiagnostics.cpp:75
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_q_sfc
Definition: ERF_TwoStreamRadiation.H:166
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_grdflx_sfc
Definition: ERF_TwoStreamRadiation.H:165
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_lw_flux_sfc
Definition: ERF_TwoStreamRadiation.H:162
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_q_deep
Definition: ERF_TwoStreamRadiation.H:168
amrex::Real m_rdOcp
Definition: ERF_TwoStreamRadiation.H:148
amrex::Vector< FluxDiag > m_flux_diag
Definition: ERF_TwoStreamRadiation.H:170
bool active() const
True once a level has been defined with the model selected.
Definition: ERF_TwoStreamRadiation.H:50
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_emiss_lw
Definition: ERF_TwoStreamRadiation.H:157
std::unique_ptr< RadiationDiagnostics > m_diag
Definition: ERF_TwoStreamRadiation.H:153
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_t_sfc
Definition: ERF_TwoStreamRadiation.H:158
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_lh_sfc
Definition: ERF_TwoStreamRadiation.H:164
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_alb_sw
Definition: ERF_TwoStreamRadiation.H:156
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_sw_flux_sfc
Definition: ERF_TwoStreamRadiation.H:161
OrbitalCache m_orbit
Definition: ERF_TwoStreamRadiation.H:149
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_t_deep
Definition: ERF_TwoStreamRadiation.H:167
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_hfx_sfc
Definition: ERF_TwoStreamRadiation.H:163
@ rho
Definition: ERF_Kessler.H:25
constexpr int NCOMP
Definition: ERF_TwoStreamColumn.H:608
Container holding radiation-related choices and parameters.
Definition: ERF_RadStruct.H:70
std::string diag_file
Output file path for radiation diagnostics CSV. Paths are relative to the simulation working director...
Definition: ERF_RadStruct.H:206
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 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 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 seb_q_sfc_default
Fallback SEB surface moisture [kg/kg].
Definition: ERF_RadStruct.H:401
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
amrex::Real seb_t_deep_default
Fallback SEB deep soil temperature [K].
Definition: ERF_RadStruct.H:406
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 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 seb_prognostic_q_max
Maximum clamping bound for prognostic surface moisture [kg/kg]. After each Euler update,...
Definition: ERF_RadStruct.H:495
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 seb_hfx_default
Fallback SEB sensible heat flux [W/m^2].
Definition: ERF_RadStruct.H:386
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 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
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
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
bool seb_diagnostic_enable
Enable diagnostic SEB residual computation and reporting. When true, computes and reports SEB residua...
Definition: ERF_RadStruct.H:421
Trivially copyable subset of RadChoice used by the column kernels.
Definition: ERF_TwoStreamColumn.H:57
Here is the call graph for this function:

◆ define_level()

void TwoStreamRadiation::define_level ( int  lev,
const RadChoice rad_choice,
amrex::Real  rdOcp,
const amrex::BoxArray &  ba2d,
const amrex::DistributionMapping &  dm 
)

Allocate the 2D surface fields of one level on the given horizontal BoxArray and fill them with the scalar defaults. A no-op unless rad_choice.rad_type == TwoStream.

227 {
228  if (!rad_choice.enabled) { return; }
229  m_rad = &rad_choice;
230  m_rdOcp = rdOcp;
231 
232  // 2D surface fields on the horizontal BoxArray, one ghost cell in x and y
233  const IntVect ng_sfc{1,1,0};
234  m_alb_sw[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
235  m_emiss_lw[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
236  m_t_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
237  m_alb_sw[lev]->setVal(rad_choice.surface_albedo_sw);
238  m_emiss_lw[lev]->setVal(rad_choice.surface_emissivity_lw);
239  m_t_sfc[lev]->setVal(rad_choice.rad_t_sfc);
240 
241  if (rad_choice.seb_enable) {
242  m_sw_flux_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
243  m_lw_flux_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
244  m_hfx_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
245  m_lh_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
246  m_grdflx_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
247  m_q_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
248  m_t_deep[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
249  m_q_deep[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
250  m_sw_flux_sfc[lev]->setVal(rad_choice.seb_sw_flux_default);
251  m_lw_flux_sfc[lev]->setVal(rad_choice.seb_lw_flux_default);
252  m_hfx_sfc[lev]->setVal(rad_choice.seb_hfx_default);
253  m_lh_sfc[lev]->setVal(rad_choice.seb_lh_default);
254  m_grdflx_sfc[lev]->setVal(rad_choice.seb_grdflx_default);
255  m_q_sfc[lev]->setVal(rad_choice.seb_q_sfc_default);
256  m_t_deep[lev]->setVal(rad_choice.seb_t_deep_default);
257  m_q_deep[lev]->setVal(rad_choice.seb_q_deep_default);
258  }
259  m_flux_diag[lev] = FluxDiag{};
260 }
const Real rdOcp
Definition: ERF_InitCustomPert_ABL.H:72

◆ read_checkpoint()

void TwoStreamRadiation::read_checkpoint ( int  lev,
const std::string &  restart_chkfile 
)

Read it back when the checkpoint carries it; else keep defaults.

280 {
281  // Older checkpoints do not carry the state; then the defaults set by
282  // define_level stand.
283  if (!active() || !m_rad->seb_enable) { return; }
284  const std::string tsfc_name =
285  MultiFabFileFullPrefix(lev, restart_chkfile, "Level_", "TwoStream_TSfc");
286  if (m_t_sfc[lev] && amrex::FileExists(tsfc_name + "_H")) {
287  VisMF::Read(*m_t_sfc[lev], tsfc_name);
288  }
289  const std::string qsfc_name =
290  MultiFabFileFullPrefix(lev, restart_chkfile, "Level_", "TwoStream_QSfc");
291  if (m_q_sfc[lev] && amrex::FileExists(qsfc_name + "_H")) {
292  VisMF::Read(*m_q_sfc[lev], qsfc_name);
293  }
294 }

◆ resize()

void TwoStreamRadiation::resize ( int  nlevs_max)

Size the per-level containers; no allocation happens here.

206 {
207  m_alb_sw.resize(nlevs_max);
208  m_emiss_lw.resize(nlevs_max);
209  m_t_sfc.resize(nlevs_max);
210  m_sw_flux_sfc.resize(nlevs_max);
211  m_lw_flux_sfc.resize(nlevs_max);
212  m_hfx_sfc.resize(nlevs_max);
213  m_lh_sfc.resize(nlevs_max);
214  m_grdflx_sfc.resize(nlevs_max);
215  m_q_sfc.resize(nlevs_max);
216  m_t_deep.resize(nlevs_max);
217  m_q_deep.resize(nlevs_max);
218  m_flux_diag.resize(nlevs_max);
219 }

Referenced by ERF::ERF_shared().

Here is the caller graph for this function:

◆ write_checkpoint()

void TwoStreamRadiation::write_checkpoint ( int  lev,
const std::string &  checkpointname 
) const

Write the prognostic surface state of one level (SEB on).

264 {
265  // The force-restore state is the only part of this model a restart must
266  // carry; without it T_s and q_s restart from the scalar defaults.
267  if (!active() || !m_rad->seb_enable) { return; }
268  if (m_t_sfc[lev]) {
269  VisMF::Write(*m_t_sfc[lev],
270  MultiFabFileFullPrefix(lev, checkpointname, "Level_", "TwoStream_TSfc"));
271  }
272  if (m_q_sfc[lev]) {
273  VisMF::Write(*m_q_sfc[lev],
274  MultiFabFileFullPrefix(lev, checkpointname, "Level_", "TwoStream_QSfc"));
275  }
276 }

Member Data Documentation

◆ m_alb_sw

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_alb_sw
private

◆ m_diag

std::unique_ptr<RadiationDiagnostics> TwoStreamRadiation::m_diag
private

◆ m_emiss_lw

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_emiss_lw
private

◆ m_flux_diag

amrex::Vector<FluxDiag> TwoStreamRadiation::m_flux_diag
private

◆ m_grdflx_sfc

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_grdflx_sfc
private

◆ m_hfx_sfc

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_hfx_sfc
private

◆ m_lh_sfc

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_lh_sfc
private

◆ m_lw_flux_sfc

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_lw_flux_sfc
private

◆ m_orbit

OrbitalCache TwoStreamRadiation::m_orbit
private

◆ m_q_deep

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_q_deep
private

◆ m_q_sfc

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_q_sfc
private

◆ m_rad

const RadChoice* TwoStreamRadiation::m_rad = nullptr
private

Referenced by active().

◆ m_rdOcp

amrex::Real TwoStreamRadiation::m_rdOcp = RdoCp
private

◆ m_sw_flux_sfc

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_sw_flux_sfc
private

◆ m_t_deep

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_t_deep
private

◆ m_t_sfc

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_t_sfc
private

The documentation for this class was generated from the following files: