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...
 
const amrex::MultiFab * prognostic_surface_temperature_state (int lev) const noexcept
 Read-only view of the per-level prognostic surface-temperature state. More...
 
void define_level (int lev, const RadChoice &rad_choice, amrex::Real rdOcp, const amrex::BoxArray &ba2d, const amrex::DistributionMapping &dm, const amrex::BoxArray &ba, const amrex::Box &domain, bool supply_land_forcing=false)
 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, const amrex::Vector< const amrex::MultiFab * > &radiation_inputs, bool noahmp_active, amrex::MultiFab *qheating, amrex::MultiFab *rad_fluxes, const amrex::MultiFab *t_surf, const amrex::MultiFab *sfc_sens_flux, const amrex::MultiFab *sfc_laten_flux, const amrex::MultiFab *lat_m, const amrex::MultiFab *lon_m, double epoch_time, bool have_datetime)
 One radiation call at one level. More...
 
amrex::MultiFab * seb_t_sfc (int lev)
 The prognostic surface state of one level, or nullptr when the SEB is off or the level has not been built. More...
 
amrex::MultiFab * seb_q_sfc (int lev)
 
const amrex::MultiFab * seb_hfx (int lev) const
 The sensible and latent heat fluxes [W/m^2] the surface energy balance of one level last used, or nullptr when the SEB is off or the level has not been built. Read-only: for output. More...
 
const amrex::MultiFab * seb_lh (int lev) const
 
const amrex::MultiFab * seb_surface_moisture (int lev)
 What level lev's surface hands the surface layer for its surface mixing ratio (erf.radiation.seb_surface_layer_uses_moisture), per column, in the components of erf_surface_moisture::Component: the soil-water factor (seb_moisture_availability of the current q_s); with erf.radiation.seb_soil_type, the bare-soil resistance and the relative humidity of its pore air at the skin temperature; and with erf.radiation.seb_vegetation_type as well, the vegetated fraction and the canopy resistance without its vapour-deficit factor (from the net shortwave the balance holds divided by (1 - surface albedo) – the sweep's with seb_use_radiation_fluxes, seb_sw_flux_default otherwise – and the skin temperature). Absent pieces are a fraction of 0 and resistances of 1e6 s/m. nullptr when the option is off or the level has not been built. Recomputed on every call, on the valid cells; the halo is not filled. More...
 
bool seb_prognostic_active () const
 True when the force-restore state is being evolved and so needs transferring. More...
 
bool supplies_land_forcing (int lev) const noexcept
 True when define_level allocated the land-model forcing fields on lev. More...
 
const amrex::MultiFab * land_forcing_sw_dn (int lev) const noexcept
 The surface forcing of the last sweep on lev (2D: i,j), or nullptr when the level does not supply it: total downwelling shortwave [W/m^2], downwelling longwave [W/m^2] and the cosine of the solar zenith angle floored at zero [1]. lsm_undefined until a sweep has run on the level, so a copy made before one is caught by the land model's missing-input check instead of reading as zero. More...
 
const amrex::MultiFab * land_forcing_lw_dn (int lev) const noexcept
 
const amrex::MultiFab * land_forcing_cos_zenith (int lev) const noexcept
 
void write_land_forcing (int lev, amrex::MultiFab *sw_flux_dn, amrex::MultiFab *lw_flux_dn, amrex::MultiFab *cos_zenith_angle) const
 Copy the surface forcing of the last sweep on lev into a land-surface model's radiation inputs (SurfaceModel's "sw_flux_dn", "lw_flux_dn" and "cos_zenith_angle" outputs). A null destination is skipped. Each destination must lie on this level's horizontal grids; see copy_surface_plane. Before the level's first sweep this copies the lsm_undefined sentinel, not zero. More...
 
void supply_canopy_forcing (int lev, int m_top)
 Have every later sweep on lev write what the building faces need besides the interface fluxes (erf.ibseb.radiation = two_stream; ERF_TwoStreamCanopyForcing.H): the direct beam on a horizontal surface at the interfaces 0 .. m_top of each column, counted from the surface, and the cosine of the zenith. Allocates one field on the level's horizontal grids given those interfaces in z and one 2D field. Call after define_level(); a later define_level() of the level (a regrid) drops the request. More...
 
bool supplies_canopy_forcing (int lev) const noexcept
 True when supply_canopy_forcing() was called for lev since its last define_level(). More...
 
TwoStreamCanopyView canopy_forcing (int lev, int nstep) const noexcept
 The beam and the cosine of the zenith of lev as the sweep of step nstep wrote them (the view's fluxes are left for the caller, who owns rad_fluxes), or an empty view when the level does not supply them or has not swept at that step: the faces must not take a previous step's radiation, nor the zeros of a level whose radiation came from its parent. 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, const amrex::Periodicity &period)
 Read it back when the checkpoint carries it; else keep defaults. More...
 

Private Attributes

const RadChoice * m_rad = nullptr
 
amrex::Real m_rdOcp = RdoCp
 
OrbitalCache m_orbit
 
amrex::Vector< std::unique_ptr< RadiationDiagnostics > > m_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< std::unique_ptr< amrex::MultiFab > > m_moisture
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_sw_dn_sfc
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_lw_dn_sfc
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_cos_zenith
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_canopy
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_canopy_cosz
 
amrex::Vector< int > m_canopy_m
 
amrex::Vector< int > m_canopy_step
 
amrex::Vector< FluxDiag > m_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, ba, domain, supply_land_forcing) 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_land_forcing(...) after a sweep, when a land-surface model integrates on the radiation this model computes (see supply_land_forcing);
  • supply_canopy_forcing(...) once, when the building faces of the immersed-boundary balance take their radiation from the sweep (erf.ibseb.radiation = two_stream), and canopy_forcing(...) after each sweep;
  • 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.

57  {
58  return (m_rad != nullptr) && m_rad->enabled;
59  }
const RadChoice * m_rad
Definition: ERF_TwoStreamRadiation.H:321
bool enabled
True when erf.radiation_model = TwoStream. Set by init_params from the model SolverChoice read; the e...
Definition: ERF_RadStruct.H:138

Referenced by seb_hfx(), seb_lh(), seb_prognostic_active(), seb_q_sfc(), and seb_t_sfc().

Here is the caller graph for this function:

◆ 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,
const amrex::Vector< const amrex::MultiFab * > &  radiation_inputs,
bool  noahmp_active,
amrex::MultiFab *  qheating,
amrex::MultiFab *  rad_fluxes,
const amrex::MultiFab *  t_surf,
const amrex::MultiFab *  sfc_sens_flux,
const amrex::MultiFab *  sfc_laten_flux,
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)
radiation_inputscanonical surface-property inputs, in fixed order
noahmp_activewhether Noah-MP diagnostics may be queried
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
sfc_sens_fluxthe conservative heat flux (rho w'theta', z faces) the zlo surface layer applies to this level, or nullptr when it applies none; the surface energy balance's H where no land-surface model supplies one (see ERF_SEBTurbulentFlux.H)
sfc_laten_fluxthe moisture flux (rho w'qv', z faces) likewise, for LE; nullptr without a moisture model
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
565 {
566  BL_PROFILE("TwoStreamRadiation::advance()");
567 
568  // Only proceed if TwoStream radiation is enabled
569  if (!active()) { return; }
570  const RadChoice& rad_choice = *m_rad;
571 
572  // A level on which no box holds a whole column has nothing for the sweep or the surface
573  // state to work on; ERF::advance_radiation interpolates its fields from the parent
574  // instead. Returning here covers the post-dycore call too, which comes from ERF::Advance
575  // and does not test for it. Per box, and identical to level_needs_interpolation() there:
576  // if the two tests disagreed, one of them would try to sweep a level the other had
577  // already interpolated.
578  if (lev > 0) {
579  const BoxArray& lev_ba = cons_old.boxArray();
580  const Box& dom = geom.Domain();
581  for (int ibox = 0; ibox < lev_ba.size(); ++ibox) {
582  const Box& b = lev_ba[ibox];
583  if (b.smallEnd(2) != dom.smallEnd(2) || b.bigEnd(2) != dom.bigEnd(2)) { return; }
584  }
585  }
586 
587  // ---- Contract checks. Each of these would otherwise surface as a wrong
588  // heating rate or an out-of-bounds read several routines away.
589  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(call_site == "pre_dycore" || call_site == "post_dycore",
590  "TwoStreamRadiation::advance: call_site must be pre_dycore or post_dycore");
591  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(lev < m_alb_sw.size() && m_alb_sw[lev] != nullptr,
592  "TwoStreamRadiation::advance called on a level define_level has not built");
593  // The sweep indexes the 2D surface fields with the MFIter of the 3D state,
594  // so both must have the same number of boxes on the same ranks.
596  m_alb_sw[lev]->boxArray().size() == cons_old.boxArray().size() &&
597  m_alb_sw[lev]->DistributionMap() == cons_old.DistributionMap(),
598  "TwoStreamRadiation: the 2D surface fields and the 3D state are laid out differently");
599  if (qheating != nullptr) {
600  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(qheating->nComp() == 2 &&
601  qheating->boxArray() == cons_old.boxArray() &&
602  qheating->DistributionMap() == cons_old.DistributionMap(),
603  "TwoStreamRadiation: qheating must be the 2-component (SW, LW) field on the state's grids");
604  }
605  if (rad_choice.seb_enable) {
607  m_lh_sfc[lev] && m_grdflx_sfc[lev] && m_q_sfc[lev] &&
608  m_t_deep[lev] && m_q_deep[lev],
609  "TwoStreamRadiation: seb_enable is set but the SEB fields were not allocated");
610  }
611  // The land-model forcing is read off the interface fluxes the sweep writes. Reached
612  // only on a level that will sweep: the per-box interpolation return above comes first,
613  // and a level whose radiation comes from its parent has no sweep and no rad_fluxes to
614  // read here. Keep that order -- TwoStreamRadiationDriver.LandForcingIsUndefinedUntilASweep
615  // advances such a level and expects it to return untouched.
616  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(!supplies_land_forcing(lev) || rad_fluxes != nullptr ||
617  call_site == "post_dycore",
618  "TwoStreamRadiation: supplying a land-surface model's forcing needs the rad_fluxes array");
619  if (call_site == "post_dycore" && rad_choice.seb_prognostic_enable) {
620  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(dt_step > 0.0 && std::isfinite(dt_step),
621  "TwoStreamRadiation: the force-restore update needs a positive, finite dt_step");
622  }
623 
624  // The surface energy balance runs on every level that sweeps, each evolving its
625  // own force-restore state.
626  //
627  // These fields are per level (see define_level), so two levels covering the same
628  // column are not advancing one shared state twice -- they are forming two
629  // independent estimates of one surface. Left alone those estimates drift apart,
630  // which would give the longwave boundary condition two different values for the
631  // same ground. ERF averages t_sfc and q_sfc down after the finer levels have
632  // advanced, so a coarse cell carries the mean of its fine children and the two
633  // agree where they overlap.
634  //
635  // A level that does not sweep has no fluxes of its own and returns earlier.
636  //
637  // NOTE for anyone resolving a merge here: the development side of this line still
638  // carries `&& (lev == 0)`, the single-level restriction. Taking that side back --
639  // which a "resolve by accepting theirs" does -- silently turns this whole branch
640  // into a no-op: the surface energy balance returns to level 0 only while the tests,
641  // docs and PR all still claim otherwise. Keep this form.
642  const bool seb_active = rad_choice.seb_enable;
643  // The force-restore state is only the longwave boundary when no external
644  // surface-temperature provider owns that boundary on this level. The
645  // canonical radiation input covers SurfaceModel providers such as SLM;
646  // Noah-MP can also supply t_sfc directly when no canonical input is set.
647  // Orthogonal to the level question above: this asks WHO owns the boundary,
648  // that one asks WHETHER this level sweeps at all.
649  const bool has_external_surface_temperature =
650  (radiation_inputs.size() > 0 && radiation_inputs[0] != nullptr) ||
651  (noahmp_active && lsm_has_field(lsm, lev, "t_sfc"));
652  // The column kernel would substitute placeholders (rho = 1, rho*theta of
653  // 288 K) for a non-finite or non-positive density or rho*theta and carry
654  // on, hiding a corrupt state behind plausible heating rates. Refuse such
655  // a state here instead.
656  //
657  // This runs every step, so it is one pass over the state and one
658  // collective, not the three that contains_nan() plus two MultiFab::min()
659  // calls would cost. A non-finite value is mapped to -infinity so that the
660  // same minimum answers both questions: -infinity means non-finite, and any
661  // other value <= 0 means non-positive. The reduction is collective, so
662  // every rank takes the same branch.
663  if (call_site == "pre_dycore") {
664  ReduceOps<ReduceOpMin, ReduceOpMin> state_ops;
665  ReduceData<amrex::Real, amrex::Real> state_data(state_ops);
666  using StateTuple = typename decltype(state_data)::Type;
667  for (MFIter mfi(cons_old, TilingIfNotGPU()); mfi.isValid(); ++mfi) {
668  const auto& arr = cons_old.const_array(mfi);
669  state_ops.eval(mfi.tilebox(), state_data,
670  [=] AMREX_GPU_DEVICE (int i, int j, int k) -> StateTuple {
671  constexpr amrex::Real neg_inf = -std::numeric_limits<amrex::Real>::infinity();
672  const amrex::Real rho = arr(i,j,k,Rho_comp);
673  const amrex::Real rth = arr(i,j,k,RhoTheta_comp);
674  return {amrex::Math::isfinite(rho) ? rho : neg_inf,
675  amrex::Math::isfinite(rth) ? rth : neg_inf};
676  });
677  }
678  auto state_tuple = state_data.value(state_ops);
679  amrex::Real mins[2] = {amrex::get<0>(state_tuple), amrex::get<1>(state_tuple)};
680  ParallelDescriptor::ReduceRealMin(mins, 2);
681  const amrex::Real rho_min = mins[0];
682  const amrex::Real rth_min = mins[1];
683  if (!std::isfinite(rho_min) || !std::isfinite(rth_min)) {
684  amrex::Abort("TwoStreamRadiation: the state handed to the column sweep at level " +
685  std::to_string(lev) + ", step " + std::to_string(nstep) +
686  " has a non-finite density or rho*theta");
687  }
688  if (!(rho_min > 0.0) || !(rth_min > 0.0)) {
689  amrex::Abort("TwoStreamRadiation: the state handed to the column sweep at level " +
690  std::to_string(lev) + ", step " + std::to_string(nstep) +
691  " has a non-positive density (min " + std::to_string(rho_min) +
692  ") or rho*theta (min " + std::to_string(rth_min) + ")");
693  }
694  }
695 
696  // The column sweep runs once per step, at the pre-dycore call. The
697  // post-dycore call would sweep the same old state (vars_old is not
698  // swapped until the next step), so it reuses the cached flux diagnostics
699  // and only advances the surface-energy-balance state.
700  const bool do_sweep = (call_site != "post_dycore");
701 
702  // One diagnostics writer per level for the life of the run, so the
703  // header-written flag and the (step, call_site, time) duplicate guard carry
704  // over between calls. Per level rather than one for the run: the levels of a
705  // hierarchy report the same step, call_site and time, so a single shared
706  // guard would drop every level but the first. All of them append to the one
707  // CSV, whose `level` column tells the rows apart.
708  if (!m_diag[lev]) {
709  m_diag[lev] = std::make_unique<RadiationDiagnostics>(
710  rad_choice.verbosity, rad_choice.diag_file, lev,
711  rad_choice.diag_enable, rad_choice.diag_stdout_enable,
712  rad_choice.diag_tagged_enable, rad_choice.diag_regtest_line_enable,
713  rad_choice.diag_csv_enable, rad_choice.diag_callsite_mode,
714  rad_choice.diag_dedup_tol);
715  }
716  RadiationDiagnostics& rad_diag = *m_diag[lev];
717 
718  // ========================================
719  // GPU-Safe ParallelFor Implementation with Cloud Fraction
720  // Blending, Diffuse (Scattering) SW Flux, and Per-Level Heating Rate
721  // Output (qheating_rates MultiFab)
722  // ========================================
723 
724  // Initialize global diagnostics
725  amrex::Real SW_surface = 0.0;
726  amrex::Real SW_TOA = 0.0;
727  amrex::Real SW_up_TOA = 0.0;
728  amrex::Real LW_net_surface = 0.0;
729  amrex::Real LW_up_TOA = 0.0;
730  amrex::Real heating_rate_max = 0.0;
731  amrex::Real seb_residual_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
732  amrex::Real seb_residual_max = std::numeric_limits<amrex::Real>::quiet_NaN();
733 
734  // Prognostic SEB surface temperature and moisture diagnostics
735  amrex::Real t_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
736  amrex::Real t_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
737  amrex::Real q_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
738  amrex::Real q_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
739 
740  // Get state at this level (conservative variables: density, RhoTheta, etc.)
741  const MultiFab& state_cons = cons_old;
742 
743  // Only compute radiation if we have valid state data
744  if (state_cons.nComp() > 0 ) {
745 
746  // Trivially copyable parameter set for the device lambdas below
747  // (RadChoice itself holds std::string members and cannot be captured),
748  // with the sun of this call from the inputs shared with RRTMGP. The
749  // incident SW at the top (SW_TOA) is a domain mean formed by the sweep,
750  // since with a calendar sun it varies across the columns.
751  TwoStreamParams ts_params = make_two_stream_params(rad_choice, m_rdOcp);
752  if (do_sweep) { set_solar_state(ts_params, rad_choice, m_orbit, epoch_time, have_datetime, lev, nstep); }
753 
754  // Host-side storage for reduction results (will be set by device-side reduction)
755  amrex::Real max_heating_global = 0.0;
756  amrex::Real sw_surface_sum = 0.0;
757  amrex::Real sw_up_toa_sum = 0.0;
758  amrex::Real lw_net_sum = 0.0;
759  amrex::Real lw_up_toa_sum = 0.0;
760  amrex::Real sw_toa_sum = 0.0;
761  amrex::Long n_columns_total = 0;
762 
763  // SEB residual diagnostics
764  amrex::Real seb_residual_sum = 0.0;
765  amrex::Long n_seb_columns = 0;
766 
767  // cloud fraction used to blend clear-sky and cloudy-column results.
768  // cloud_fraction == 0.0 (default) means only the clear-sky column is
769  // ever evaluated, and the blend below reduces to F = F_clear exactly.
770  amrex::Real cloud_fraction = rad_choice.cloud_fraction;
771 
772  // qheating is ERF's 2-component (SW, LW) heating-rate MultiFab of this
773  // level. If it is not allocated yet, the sweep still runs for the
774  // diagnostics and skips the per-level heating write.
775  MultiFab* qheating_mf = qheating;
776 
777  // Surface radiative fluxes for the SEB. Precedence: an LSM field when
778  // the LSM exposes one; otherwise, with seb_use_radiation_fluxes, the
779  // fluxes the column sweep below computes at the surface (written per
780  // column by the sweep and left in place for the post-dycore call);
781  // otherwise the scalar defaults.
782  const bool sw_flux_from_rad = seb_active &&
783  rad_choice.seb_use_radiation_fluxes &&
784  !(noahmp_active && lsm_has_field(lsm, lev, "sav"));
785  const bool lw_flux_from_rad = seb_active &&
786  rad_choice.seb_use_radiation_fluxes &&
787  !(noahmp_active && lsm_has_field(lsm, lev, "fira"));
788 
789  if (seb_active) {
790  fill_or_copy_seb_field(m_alb_sw[lev].get(), lsm, lev, lsm_broadband_albedo_name, rad_choice.surface_albedo_sw);
791  fill_or_copy_seb_field(m_emiss_lw[lev].get(), lsm, lev, "sfc_emis", rad_choice.surface_emissivity_lw);
792 
793  // In prognostic mode, do not seed this state from an LSM field.
794  // The column sweep applies external-provider precedence directly,
795  // and the post-dycore update advances this state only when
796  // TwoStream owns the surface-temperature boundary.
797  if (!rad_choice.seb_prognostic_enable) {
798  fill_or_copy_seb_field(m_t_sfc[lev].get(), lsm, lev, "t_sfc", rad_choice.rad_t_sfc);
799  }
800 
801  // Net absorbed shortwave: Noah-MP splits it into the canopy (sav)
802  // and ground (sag) parts. Net longwave: Noah-MP's fira is the
803  // net flux to the atmosphere (positive up); the SEB wants the
804  // absorbed flux, so the sign flips.
805  if (!sw_flux_from_rad) {
806  fill_or_copy_seb_field(m_sw_flux_sfc[lev].get(), lsm, lev, "sav",
807  rad_choice.seb_sw_flux_default, 1.0, "sag");
808  }
809  if (!lw_flux_from_rad) {
810  fill_or_copy_seb_field(m_lw_flux_sfc[lev].get(), lsm, lev, "fira",
811  rad_choice.seb_lw_flux_default, -1.0);
812  }
813  // H and LE: a land-surface field of that name, else the flux the
814  // surface layer applies to the air (so the ground loses what the air
815  // receives), else the scalar defaults; see ERF_SEBTurbulentFlux.H.
816  // No land model exposes "hfx" or "lh" today, so without one the
817  // surface layer is the source. G is Noah-MP's grdflx when present.
818  //
819  // At the post-dycore call, the one the force-restore update uses, the
820  // surface layer's flux is the one it applied during this step. At the
821  // pre-dycore call it is the previous step's, which only the residual
822  // diagnostic reads.
823  const int surface_k = geom.Domain().smallEnd(2);
825  lsm_field(lsm, lev, "hfx"), sfc_sens_flux, surface_k,
826  rad_choice.seb_turbulent_flux_source,
827  rad_choice.seb_hfx_default);
829  lsm_field(lsm, lev, "lh"), sfc_laten_flux, surface_k,
830  rad_choice.seb_turbulent_flux_source,
831  rad_choice.seb_lh_default);
832  fill_or_copy_seb_field(m_grdflx_sfc[lev].get(), lsm, lev, "grdflx", rad_choice.seb_grdflx_default);
833 
834  // Gate q_sfc fill on prognostic mode: same reasoning as t_sfc.
835  if (!rad_choice.seb_prognostic_enable) {
836  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);
837  }
838  // No LSM exposes a deep-soil temperature or moisture in kg/kg by
839  // name (Noah-MP's smstav / smstot are soil-moisture availability
840  // and total column water), so the reservoir values are the
841  // scalar defaults.
842  m_t_deep[lev]->setVal(rad_choice.seb_t_deep_default);
843  m_q_deep[lev]->setVal(rad_choice.seb_q_deep_default);
844  }
845 
846  // The column sweep integrates the whole atmospheric column in one
847  // kernel, so it needs every k of a box in a single pass. MFIter tiling
848  // would hand it partial columns (the default CPU tile size splits z),
849  // so this loop is deliberately untiled and works on valid boxes; the
850  // horizontal ParallelFor below still provides the parallelism.
851  const Box& rad_domain = geom.Domain();
852  if (do_sweep) {
853  for (MFIter mfi(state_cons, false); mfi.isValid(); ++mfi)
854  {
855  const Box& bx = mfi.validbox();
856 
857  // A box that does not span the domain vertically would give this
858  // column solver a truncated atmosphere: no beam from above, no
859  // cooling to space. ERF only decomposes in z when max_grid_size_z
860  // is smaller than the domain, so refuse that configuration rather
861  // than return heating rates that look plausible and are wrong.
863  bx.smallEnd(2) == rad_domain.smallEnd(2) &&
864  bx.bigEnd(2) == rad_domain.bigEnd(2),
865  "TwoStream radiation requires grids that span the domain in z; "
866  "set amr.max_grid_size_z to at least amr.n_cell in z");
867  const auto& state_arr = state_cons.const_array(mfi);
868  // Geometry::CellSize() is host-only; read it here and hand the
869  // value to the device sweep.
870  const amrex::Real dz_uniform_lev = geom.CellSize(2);
871 
872  // Nodal heights for the layer thicknesses on a stretched or
873  // terrain-following grid (nullptr on a uniform grid).
874  Array4<const amrex::Real> z_phys_nd_arr;
875  if (z_phys_nd != nullptr) {
876  z_phys_nd_arr = z_phys_nd->const_array(mfi);
877  }
878 
879  // Per-column scratch of the sweep (interfaces need nlev + 1 entries),
880  // shared by the clear and cloudy evaluations of the same column.
881  FArrayBox scratch_fab(two_stream_scratch_box(bx), TwoStreamScratch::NCOMP);
882  Array4<amrex::Real> scratch_arr = scratch_fab.array();
883 
884  // Wire LSM surface property fields or use standalone fallback MultiFabs
885  // Priority:
886  // 1. If LSM is active (lsm.Get_DataIdx() returns >=0), use real LSM fields
887  // 2. Otherwise, use standalone fallback MultiFabs (allocated and constant-filled from RadChoice scalars)
888  // The resolve_surface_*() helpers implement the full precedence chain with finite guards
889 
890  // SW albedo. With Noah-MP, its broadband albedo (see lsm_broadband_albedo_name)
891  // comes first: the canonical SurfaceModel input radiation_inputs[2] is the
892  // visible direct-beam band of RRTMGP's four (Noah-MP's sfc_alb_dir_vis), not a
893  // broadband value. Otherwise that canonical input (SLM), then the standalone
894  // field. resolve_surface_albedo_sw takes the value per column where it is in
895  // [0, 1] and otherwise the erf.radiation.surface_albedo_sw default, so the
896  // lsm_undefined placeholder over water, before the first land step and at
897  // night (Noah-MP has no albedo without sunlight) falls back to the default.
898  bool has_hetero_alb_sw = false;
899  Array4<const amrex::Real> hetero_alb_sw_arr;
900  {
901  const MultiFab* lsm_albedo = noahmp_active
902  ? lsm_field(lsm, lev, lsm_broadband_albedo_name)
903  : nullptr;
904  if (lsm_albedo != nullptr) {
905  hetero_alb_sw_arr = lsm_albedo->const_array(mfi);
906  has_hetero_alb_sw = true;
907  } else if (radiation_inputs.size() > 2 && radiation_inputs[2]) {
908  hetero_alb_sw_arr = radiation_inputs[2]->const_array(mfi);
909  has_hetero_alb_sw = true;
910  } else if (m_alb_sw[lev]) {
911  hetero_alb_sw_arr = m_alb_sw[lev]->const_array(mfi);
912  has_hetero_alb_sw = true;
913  }
914  }
915 
916  // LW emissivity: Try LSM field "sfc_emis"
917  bool has_hetero_emiss_lw = false;
918  Array4<const amrex::Real> hetero_emiss_lw_arr;
919  {
920  std::string varname_emiss = "sfc_emis";
921  int lsm_idx = noahmp_active ? lsm.Get_DataIdx(lev, varname_emiss) : -1;
922  if (radiation_inputs.size() > 1 && radiation_inputs[1]) {
923  hetero_emiss_lw_arr = radiation_inputs[1]->const_array(mfi);
924  has_hetero_emiss_lw = true;
925  } else if (lsm_idx >= 0) {
926  auto lsm_ptr = lsm.Get_Data_Ptr(lev, lsm_idx);
927  if (lsm_ptr) {
928  hetero_emiss_lw_arr = lsm_ptr->const_array(mfi);
929  has_hetero_emiss_lw = true;
930  }
931  } else if (m_emiss_lw[lev]) {
932  hetero_emiss_lw_arr = m_emiss_lw[lev]->const_array(mfi);
933  has_hetero_emiss_lw = true;
934  }
935  }
936 
937  // Keep the surface-temperature sources separate until the column
938  // kernel resolves them per cell. An LSM field can exist globally
939  // while carrying an undefined sentinel in an individual column;
940  // that column must still fall through to SurfaceLayer theta.
941  // The kernel's precedence is valid LSM absolute temperature,
942  // valid prognostic SEB absolute temperature, SurfaceLayer theta,
943  // then the scalar absolute-temperature fallback.
944  bool has_lsm_t_sfc = false;
945  Array4<const amrex::Real> lsm_t_sfc_arr;
946  bool has_seb_t_sfc = false;
947  Array4<const amrex::Real> seb_t_sfc_arr;
948  bool has_surface_layer = false;
949  Array4<const amrex::Real> surface_layer_theta_arr;
950  {
951  std::string varname_t_sfc = "t_sfc";
952  int lsm_idx = noahmp_active ? lsm.Get_DataIdx(lev, varname_t_sfc) : -1;
953  if (radiation_inputs.size() > 0 && radiation_inputs[0]) {
954  lsm_t_sfc_arr = radiation_inputs[0]->const_array(mfi);
955  has_lsm_t_sfc = true;
956  } else if (lsm_idx >= 0) {
957  auto lsm_ptr = lsm.Get_Data_Ptr(lev, lsm_idx);
958  if (lsm_ptr) {
959  lsm_t_sfc_arr = lsm_ptr->const_array(mfi);
960  has_lsm_t_sfc = true;
961  }
962  }
963  // Prognostic SEB is a level-wide alternative to an authoritative
964  // external surface-temperature provider. When one owns this
965  // level's boundary, m_t_sfc is not advanced and is not offered
966  // as a per-cell fallback.
967  if (!has_lsm_t_sfc && rad_choice.seb_prognostic_enable &&
968  seb_active && m_t_sfc[lev]) {
969  seb_t_sfc_arr = m_t_sfc[lev]->const_array(mfi);
970  has_seb_t_sfc = true;
971  }
972  if (t_surf != nullptr) {
973  surface_layer_theta_arr = t_surf->const_array(mfi);
974  has_surface_layer = true;
975  }
976  }
977 
978  // Per-column latitude and longitude for the calendar sun (only
979  // the WRF and metgrid initialisations fill them; otherwise the
980  // erf.rad_cons_lat/lon constants apply).
981  const bool has_latlon = (lat_m != nullptr) && (lon_m != nullptr);
982  Array4<const amrex::Real> lat_arr, lon_arr;
983  if (has_latlon) {
984  lat_arr = lat_m->const_array(mfi);
985  lon_arr = lon_m->const_array(mfi);
986  }
987 
988  // Interface fluxes for ERF's rad_fluxes (SW up, SW down, LW up,
989  // LW down at each layer's lower interface, plus the top of the
990  // atmosphere in the z-ghost cell above the column), the level
991  // layout RRTMGP writes. The cloudy evaluation goes to a scratch
992  // FArrayBox on the same grown box and is blended like the
993  // heating rates.
994  const bool write_fluxes = (rad_fluxes != nullptr);
995  Array4<amrex::Real> rad_flux_clear_arr;
996  FArrayBox rad_flux_cloudy_fab;
997  Array4<amrex::Real> rad_flux_cloudy_arr;
998  if (write_fluxes) {
999  rad_flux_clear_arr = rad_fluxes->array(mfi);
1000  if (cloud_fraction > 0.0) {
1001  rad_flux_cloudy_fab.resize(two_stream_scratch_box(bx), 4);
1002  rad_flux_cloudy_arr = rad_flux_cloudy_fab.array();
1003  }
1004  }
1005 
1006 
1007  // Surface flux arrays the sweep fills for the SEB when asked to.
1008  Array4<amrex::Real> sw_sfc_out;
1009  Array4<amrex::Real> lw_sfc_out;
1010  if (sw_flux_from_rad) sw_sfc_out = m_sw_flux_sfc[lev]->array(mfi);
1011  if (lw_flux_from_rad) lw_sfc_out = m_lw_flux_sfc[lev]->array(mfi);
1012 
1013  // What a land-surface model integrates on, written by the same kernel: the
1014  // surface-interface values of the (blended) fluxes, and the sun the sweep used.
1015  const bool supply_land = supplies_land_forcing(lev);
1016  Array4<amrex::Real> sw_dn_out, lw_dn_out, coszen_out;
1017  if (supply_land) {
1018  sw_dn_out = m_sw_dn_sfc[lev]->array(mfi);
1019  lw_dn_out = m_lw_dn_sfc[lev]->array(mfi);
1020  coszen_out = m_cos_zenith[lev]->array(mfi);
1021  }
1022 
1023  // What the building faces take (erf.ibseb.radiation = two_stream) besides the
1024  // interface fluxes, written by the same kernel: the direct beam at the canopy's
1025  // interfaces from the scratch of each evaluation, weighted as the blend weights
1026  // them, and the cosine of the zenith the sweep used.
1027  const bool supply_canopy = supplies_canopy_forcing(lev);
1028  const int m_canopy_top = supply_canopy ? m_canopy_m[lev] : 0;
1029  const bool sw_on = rad_choice.sw_enabled;
1030  Array4<amrex::Real> canopy_out, canopy_cosz_out;
1031  if (supply_canopy) {
1032  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(write_fluxes,
1033  "TwoStreamRadiation: the canopy forcing of the building faces needs the rad_fluxes array");
1034  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_canopy_top <= bx.length(2),
1035  "TwoStreamRadiation: the canopy reaches above the top of the column");
1036  canopy_out = m_canopy[lev]->array(mfi);
1037  canopy_cosz_out = m_canopy_cosz[lev]->array(mfi);
1038  }
1039 
1040  // Create a 2D box for (i,j) iteration over the horizontal extent
1041  // One GPU thread per (i,j) column; k-loop is sequential within each thread
1042  const auto& lo = bx.loVect();
1043  const auto& hi = bx.hiVect();
1044  Box xy_box(IntVect(lo[0], lo[1], 0), IntVect(hi[0], hi[1], 0));
1045 
1046  // Count columns in this box for later averaging
1047  amrex::Long n_cols = static_cast<amrex::Long>(bx.length(0)) *
1048  static_cast<amrex::Long>(bx.length(1));
1049  n_columns_total += n_cols;
1050 
1051  // Clear-sky-column heating rates are written directly
1052  // into the real qheating_rates MultiFab when available.
1053  // Fall back to a throwaway local FArrayBox otherwise (keeps the
1054  // kernel call GPU-safe even if qheating_rates isn't allocated).
1055  FArrayBox qheating_fallback_fab;
1056  Array4<amrex::Real> qheating_clear_arr;
1057  if (qheating_mf != nullptr) {
1058  qheating_clear_arr = qheating_mf->array(mfi);
1059  } else {
1060  qheating_fallback_fab.resize(bx, 2);
1061  qheating_clear_arr = qheating_fallback_fab.array();
1062  }
1063 
1064  // Cloudy-column heating rates always go into a scratch
1065  // FArrayBox; only used/blended when cloud_fraction > 0.
1066  FArrayBox qheating_cloudy_fab;
1067  Array4<amrex::Real> qheating_cloudy_arr;
1068  if (cloud_fraction > 0.0) {
1069  qheating_cloudy_fab.resize(bx, 2);
1070  qheating_cloudy_arr = qheating_cloudy_fab.array();
1071  }
1072 
1073  // GPU-safe reduction using ReduceOps (per-column results aggregated on device)
1074  amrex::Real max_heating_box = 0.0;
1075  amrex::Real sw_sum_box = 0.0;
1076  amrex::Real sw_up_sum_box = 0.0;
1077  amrex::Real lw_sum_box = 0.0;
1078  amrex::Real lw_up_sum_box = 0.0;
1079  amrex::Real sw_toa_sum_box = 0.0;
1080 
1081  // Device-side reduction: max heating, sums of the surface and
1082  // top-of-atmosphere fluxes
1083  ReduceOps<ReduceOpMax, ReduceOpSum, ReduceOpSum, ReduceOpSum, ReduceOpSum, ReduceOpSum> reduce_ops;
1084  ReduceData<amrex::Real, amrex::Real, amrex::Real, amrex::Real, amrex::Real, amrex::Real> reduce_data(reduce_ops);
1085 
1086  using ReduceTuple = typename decltype(reduce_data)::Type;
1087 
1088  // Launch parallel kernel over (i,j) columns
1089  reduce_ops.eval(xy_box, reduce_data,
1090  [=] AMREX_GPU_DEVICE (int i, int j, int /*k_unused*/) -> ReduceTuple
1091  {
1092  // Clear-sky column (always evaluated; this is the sole
1093  // contributor when cloud_fraction == 0.0, matching earlier behavior)
1094  amrex::Real max_heating_clear = 0.0;
1095  amrex::Real sw_flux_clear = 0.0;
1096  amrex::Real sw_up_clear = 0.0;
1097  amrex::Real lw_net_clear = 0.0;
1098  amrex::Real lw_up_clear = 0.0;
1099  amrex::Real sw_toa_clear = 0.0;
1100  amrex::Real cos_zenith_col = 0.0;
1102  i, j, bx, dz_uniform_lev, state_arr, ts_params, /*cloudy=*/false,
1103  qheating_clear_arr,
1104  max_heating_clear, sw_flux_clear, sw_up_clear, lw_net_clear, lw_up_clear,
1105  sw_toa_clear,
1106  z_phys_nd_arr, scratch_arr,
1107  has_hetero_alb_sw, &hetero_alb_sw_arr,
1108  has_hetero_emiss_lw, &hetero_emiss_lw_arr,
1109  has_lsm_t_sfc, &lsm_t_sfc_arr,
1110  has_seb_t_sfc, &seb_t_sfc_arr,
1111  has_surface_layer, &surface_layer_theta_arr,
1112  has_latlon, &lat_arr, &lon_arr,
1113  write_fluxes ? &rad_flux_clear_arr : nullptr,
1114  &cos_zenith_col);
1115 
1116  // The scratch holds this evaluation's beam until the cloudy one overwrites it.
1117  if (supply_canopy) {
1118  two_stream_canopy_beam(i, j, bx.smallEnd(2), m_canopy_top, scratch_arr,
1119  TwoStreamScratch::F_DIR, sw_on, cos_zenith_col,
1120  cloud_fraction, false, canopy_out);
1121  }
1122 
1123  amrex::Real max_heating_col = max_heating_clear;
1124  amrex::Real sw_flux_col = sw_flux_clear;
1125  amrex::Real sw_up_col = sw_up_clear;
1126  amrex::Real lw_net_col = lw_net_clear;
1127  amrex::Real lw_up_col = lw_up_clear;
1128 
1129  // Cloudy column only needs to be evaluated when there is a
1130  // nonzero cloud fraction; this keeps the cloud_fraction==0
1131  // path numerically and computationally identical.
1132  if (cloud_fraction > 0.0) {
1133  amrex::Real max_heating_cloudy = 0.0;
1134  amrex::Real sw_flux_cloudy = 0.0;
1135  amrex::Real sw_up_cloudy = 0.0;
1136  amrex::Real lw_net_cloudy = 0.0;
1137  amrex::Real lw_up_cloudy = 0.0;
1138  amrex::Real sw_toa_cloudy = 0.0;
1140  i, j, bx, dz_uniform_lev, state_arr, ts_params, /*cloudy=*/true,
1141  qheating_cloudy_arr,
1142  max_heating_cloudy, sw_flux_cloudy, sw_up_cloudy, lw_net_cloudy, lw_up_cloudy,
1143  sw_toa_cloudy,
1144  z_phys_nd_arr, scratch_arr,
1145  has_hetero_alb_sw, &hetero_alb_sw_arr,
1146  has_hetero_emiss_lw, &hetero_emiss_lw_arr,
1147  has_lsm_t_sfc, &lsm_t_sfc_arr,
1148  has_seb_t_sfc, &seb_t_sfc_arr,
1149  has_surface_layer, &surface_layer_theta_arr,
1150  has_latlon, &lat_arr, &lon_arr,
1151  write_fluxes ? &rad_flux_cloudy_arr : nullptr);
1152 
1153  // Blend clear-sky and cloudy-column results
1154  sw_flux_col = (1.0 - cloud_fraction) * sw_flux_clear +
1155  cloud_fraction * sw_flux_cloudy;
1156  sw_up_col = (1.0 - cloud_fraction) * sw_up_clear +
1157  cloud_fraction * sw_up_cloudy;
1158  lw_net_col = (1.0 - cloud_fraction) * lw_net_clear +
1159  cloud_fraction * lw_net_cloudy;
1160  lw_up_col = (1.0 - cloud_fraction) * lw_up_clear +
1161  cloud_fraction * lw_up_cloudy;
1162  max_heating_col = std::max(max_heating_clear, max_heating_cloudy);
1163  if (supply_canopy) {
1164  two_stream_canopy_beam(i, j, bx.smallEnd(2), m_canopy_top, scratch_arr,
1165  TwoStreamScratch::F_DIR, sw_on, cos_zenith_col,
1166  cloud_fraction, true, canopy_out);
1167  }
1168 
1169  // Blend per-level heating rates in place
1170  // into qheating_clear_arr (which is the real output
1171  // MultiFab when qheating_mf != nullptr).
1172  int kmin = bx.smallEnd(2);
1173  int kmax = bx.bigEnd(2);
1174  for (int k = kmin; k <= kmax; ++k) {
1175  for (int comp = 0; comp < 2; ++comp) {
1176  amrex::Real q_clear_val = qheating_clear_arr(i, j, k, comp);
1177  amrex::Real q_cloudy_val = qheating_cloudy_arr(i, j, k, comp);
1178  qheating_clear_arr(i, j, k, comp) =
1179  (1.0 - cloud_fraction) * q_clear_val +
1180  cloud_fraction * q_cloudy_val;
1181  }
1182  }
1183  if (write_fluxes) {
1184  // nlev + 1 levels: the top interface sits at kmax + 1
1185  for (int k = kmin; k <= kmax + 1; ++k) {
1186  for (int comp = 0; comp < 4; ++comp) {
1187  rad_flux_clear_arr(i, j, k, comp) =
1188  (1.0 - cloud_fraction) * rad_flux_clear_arr(i, j, k, comp) +
1189  cloud_fraction * rad_flux_cloudy_arr(i, j, k, comp);
1190  }
1191  }
1192  }
1193  }
1194 
1195  // Surface fluxes for the SEB: absorbed shortwave, and the
1196  // absorbed longwave, which is minus the net (up - down).
1197  if (sw_flux_from_rad) sw_sfc_out(i, j, 0) = sw_flux_col;
1198  if (lw_flux_from_rad) lw_sfc_out(i, j, 0) = -lw_net_col;
1199 
1200  // The land model's forcing, after the clear/cloudy blend above.
1201  if (supply_land) {
1202  two_stream_land_forcing(i, j, bx.smallEnd(2), rad_flux_clear_arr,
1203  cos_zenith_col, sw_dn_out(i, j, 0),
1204  lw_dn_out(i, j, 0), coszen_out(i, j, 0));
1205  }
1206 
1207  // The sun the building faces must share (their beam is in canopy_out).
1208  if (supply_canopy) {
1209  canopy_cosz_out(i, j, 0) = (cos_zenith_col > amrex::Real(0.0)) ? cos_zenith_col : amrex::Real(0.0);
1210  }
1211 
1212  // The incident TOA flux is the same for both evaluations.
1213  // Return tuple for reduction
1214  return {max_heating_col, sw_flux_col, sw_up_col, lw_net_col, lw_up_col, sw_toa_clear};
1215  }
1216  );
1217 
1218  // Copy results from device to host
1219  amrex::Gpu::synchronize();
1220  auto reduce_tuple = reduce_data.value(reduce_ops);
1221  max_heating_box = amrex::get<0>(reduce_tuple);
1222  sw_sum_box = amrex::get<1>(reduce_tuple);
1223  sw_up_sum_box = amrex::get<2>(reduce_tuple);
1224  lw_sum_box = amrex::get<3>(reduce_tuple);
1225  lw_up_sum_box = amrex::get<4>(reduce_tuple);
1226  sw_toa_sum_box = amrex::get<5>(reduce_tuple);
1227 
1228  // Accumulate box results into global results
1229  max_heating_global = std::max(max_heating_global, max_heating_box);
1230  sw_surface_sum += sw_sum_box;
1231  sw_up_toa_sum += sw_up_sum_box;
1232  lw_net_sum += lw_sum_box;
1233  lw_up_toa_sum += lw_up_sum_box;
1234  sw_toa_sum += sw_toa_sum_box;
1235 
1236  }
1237  // Every accumulator above is rank-local. Reduce before forming means
1238  // and maxima, so the diagnostics describe the whole domain and do
1239  // not change with the decomposition. These are collective calls; the
1240  // conditions around them are input-driven and identical on all ranks.
1241  if (do_sweep) {
1242  amrex::Real sums[5] = {sw_surface_sum, sw_up_toa_sum, lw_net_sum, lw_up_toa_sum, sw_toa_sum};
1243  ParallelDescriptor::ReduceRealSum(sums, 5);
1244  sw_surface_sum = sums[0]; sw_up_toa_sum = sums[1];
1245  lw_net_sum = sums[2]; lw_up_toa_sum = sums[3];
1246  sw_toa_sum = sums[4];
1247  ParallelDescriptor::ReduceLongSum(n_columns_total);
1248  ParallelDescriptor::ReduceRealMax(max_heating_global);
1249  // The faces may take this sweep's canopy forcing in this step, and only in it.
1250  if (supplies_canopy_forcing(lev)) { m_canopy_step[lev] = nstep; }
1251  }
1252  } // do_sweep
1253 
1254  // The heating rates go straight into the RhoTheta source term, so a
1255  // non-finite value here becomes a non-finite state one step later.
1256  if (do_sweep && qheating_mf != nullptr &&
1257  (qheating_mf->contains_nan(0, 2, 0) || qheating_mf->contains_inf(0, 2, 0))) {
1258  amrex::Abort("TwoStreamRadiation: non-finite heating rate after the column sweep at level " +
1259  std::to_string(lev) + ", step " + std::to_string(nstep));
1260  }
1261 
1262  // Warn if diagnostic is requested but SEB infrastructure isn't enabled
1263  if (rad_choice.seb_diagnostic_enable && !rad_choice.seb_enable && lev == 0) {
1264  static bool warned_seb_misconfig = false;
1265  if (!warned_seb_misconfig && ParallelDescriptor::IOProcessor()) {
1266  Print() << "WARNING: erf.radiation.seb_diagnostic_enable=true but "
1267  "seb_enable=false; SEB residual diagnostics will report NaN. "
1268  "Set erf.radiation.seb_enable=true to enable SEB field "
1269  "population.\n";
1270  warned_seb_misconfig = true;
1271  }
1272  }
1273  // Compute SEB residual diagnostics if enabled
1274  if (rad_choice.seb_diagnostic_enable && seb_active) {
1275  seb_residual_max = 0.0;
1276  // Second loop over boxes to compute SEB residual from populated SEB
1277  // MultiFabs. Untiled: the work below is per surface column, and a
1278  // tiled iteration would visit each (i,j) once per z tile.
1279  for (MFIter mfi(state_cons, false); mfi.isValid(); ++mfi) {
1280  const Box& bx = mfi.validbox();
1281  const auto& lo = bx.loVect();
1282  const auto& hi = bx.hiVect();
1283  Box xy_box(IntVect(lo[0], lo[1], 0), IntVect(hi[0], hi[1], 0));
1284 
1285  // Get SEB field arrays
1286  Array4<const amrex::Real> sw_flux_arr = m_sw_flux_sfc[lev]->const_array(mfi);
1287  Array4<const amrex::Real> lw_flux_arr = m_lw_flux_sfc[lev]->const_array(mfi);
1288  Array4<const amrex::Real> hfx_arr = m_hfx_sfc[lev]->const_array(mfi);
1289  Array4<const amrex::Real> lh_arr = m_lh_sfc[lev]->const_array(mfi);
1290  Array4<const amrex::Real> grdflx_arr = m_grdflx_sfc[lev]->const_array(mfi);
1291 
1292  // Count columns and compute residuals
1293  amrex::Long n_cols_box = static_cast<amrex::Long>(bx.length(0)) *
1294  static_cast<amrex::Long>(bx.length(1));
1295  amrex::Real residual_sum_box = 0.0;
1296  amrex::Real residual_max_box = 0.0;
1297 
1298  // GPU-safe reduction for SEB residuals
1299  ReduceOps<ReduceOpSum, ReduceOpMax> seb_reduce_ops;
1300  ReduceData<amrex::Real, amrex::Real> seb_reduce_data(seb_reduce_ops);
1301 
1302  using SEBReduceTuple = typename decltype(seb_reduce_data)::Type;
1303 
1304  seb_reduce_ops.eval(xy_box, seb_reduce_data,
1305  [=] AMREX_GPU_DEVICE (int i, int j, int /*k_unused*/) -> SEBReduceTuple {
1306  amrex::Real sw_net = sw_flux_arr(i, j, 0);
1307  amrex::Real lw_net = lw_flux_arr(i, j, 0);
1308  amrex::Real hfx = hfx_arr(i, j, 0);
1309  amrex::Real lh = lh_arr(i, j, 0);
1310  amrex::Real grdflx = grdflx_arr(i, j, 0);
1311 
1312  // Compute residual using helper function
1313  amrex::Real residual = diagnose_seb_residual(sw_net, lw_net, hfx, lh, grdflx);
1314 
1315  // Return sum and abs(max) of residual
1316  return {residual, std::abs(residual)};
1317  }
1318  );
1319 
1320  // Copy results from device to host
1321  amrex::Gpu::synchronize();
1322  auto seb_reduce_tuple = seb_reduce_data.value(seb_reduce_ops);
1323  residual_sum_box = amrex::get<0>(seb_reduce_tuple);
1324  residual_max_box = amrex::get<1>(seb_reduce_tuple);
1325 
1326  // Accumulate into global results
1327  seb_residual_sum += residual_sum_box;
1328  seb_residual_max = std::max(seb_residual_max, residual_max_box);
1329  n_seb_columns += n_cols_box;
1330  }
1331  ParallelDescriptor::ReduceRealSum(seb_residual_sum);
1332  ParallelDescriptor::ReduceRealMax(seb_residual_max);
1333  ParallelDescriptor::ReduceLongSum(n_seb_columns);
1334  }
1335 
1336  // Prognostic SEB surface temperature and moisture evolution
1337  // Only advance the force-restore state when TwoStream owns the
1338  // surface-temperature boundary at this level.
1339  //
1340  // With the surface radiation taken from the sweep, also wait for this level's
1341  // first sweep. Until then m_sw_flux_sfc / m_lw_flux_sfc hold what define_level
1342  // left, and advancing on them would apply the scalar defaults for that step. That
1343  // is the first step of a level built by interp_atmos_from_coarse, whose pre-dycore
1344  // sweep ERF::advance_radiation skips (the level's state is not yet consistent),
1345  // while this post-dycore call still arrives.
1346  const bool seb_radiation_ready = m_flux_diag[lev].valid ||
1347  !(sw_flux_from_rad || lw_flux_from_rad);
1348  if (rad_choice.seb_prognostic_enable && seb_active &&
1349  call_site == "post_dycore" && seb_radiation_ready) {
1350  if (!has_external_surface_temperature) {
1351  // No external provider owns the boundary; advance TwoStream's state.
1352 
1353  // Initialize diagnostics for T_s and q_s
1354  amrex::Real t_s_sum = 0.0;
1355  amrex::Real t_s_max_val = -std::numeric_limits<amrex::Real>::max();
1356  amrex::Real q_s_sum = 0.0;
1357  amrex::Real q_s_max_val = -std::numeric_limits<amrex::Real>::max();
1358  amrex::Long n_prog_columns = 0;
1359 
1360  // Third loop over boxes for prognostic SEB update. Untiled for
1361  // the same reason as above, and here it also matters for
1362  // correctness: the force-restore update is applied in place, so
1363  // visiting a column twice would advance it twice in one step.
1364  for (MFIter mfi(state_cons, false); mfi.isValid(); ++mfi) {
1365  const Box& bx = mfi.validbox();
1366  const auto& lo = bx.loVect();
1367  const auto& hi = bx.hiVect();
1368  Box xy_box(IntVect(lo[0], lo[1], 0), IntVect(hi[0], hi[1], 0));
1369 
1370  // Get SEB field arrays (read-only)
1371  Array4<const amrex::Real> sw_flux_arr = m_sw_flux_sfc[lev]->const_array(mfi);
1372  Array4<const amrex::Real> lw_flux_arr = m_lw_flux_sfc[lev]->const_array(mfi);
1373  Array4<const amrex::Real> hfx_arr = m_hfx_sfc[lev]->const_array(mfi);
1374  Array4<const amrex::Real> lh_arr = m_lh_sfc[lev]->const_array(mfi);
1375  Array4<const amrex::Real> grdflx_arr = m_grdflx_sfc[lev]->const_array(mfi);
1376  Array4<const amrex::Real> t_deep_arr = m_t_deep[lev]->const_array(mfi);
1377  Array4<const amrex::Real> q_deep_arr = m_q_deep[lev]->const_array(mfi);
1378 
1379  // Get SEB state arrays (read-write for prognostic update)
1380  Array4<amrex::Real> t_s_arr = m_t_sfc[lev]->array(mfi);
1381  Array4<amrex::Real> q_s_arr = m_q_sfc[lev]->array(mfi);
1382  // Count columns and prepare for reductions
1383  amrex::Long n_cols_box = static_cast<amrex::Long>(bx.length(0)) *
1384  static_cast<amrex::Long>(bx.length(1));
1385  amrex::Real t_s_sum_box = 0.0;
1386  amrex::Real t_s_max_box = -std::numeric_limits<amrex::Real>::max();
1387  amrex::Real q_s_sum_box = 0.0;
1388  amrex::Real q_s_max_box = -std::numeric_limits<amrex::Real>::max();
1389 
1390  // GPU-safe update for prognostic T_s and q_s with reductions
1391  ReduceOps<ReduceOpSum, ReduceOpMax, ReduceOpSum, ReduceOpMax> prog_reduce_ops;
1392  ReduceData<amrex::Real, amrex::Real, amrex::Real, amrex::Real> prog_reduce_data(prog_reduce_ops);
1393 
1394  using ProgReduceTuple = typename decltype(prog_reduce_data)::Type;
1395 
1396  prog_reduce_ops.eval(xy_box, prog_reduce_data,
1397  [=, C_s=rad_choice.seb_surface_heat_capacity,
1398  tau=rad_choice.seb_restore_timescale_s,
1399  d_s=rad_choice.seb_moisture_layer_depth_m,
1400  tau_q=rad_choice.seb_moisture_restore_timescale_s,
1401  t_min=rad_choice.seb_prognostic_t_min_k,
1402  t_max=rad_choice.seb_prognostic_t_max_k,
1403  q_min=rad_choice.seb_prognostic_q_min,
1404  q_max=rad_choice.seb_prognostic_q_max]
1405  AMREX_GPU_DEVICE (int i, int j, int /*k_unused*/) -> ProgReduceTuple {
1406  amrex::Real t_s_old = t_s_arr(i, j, 0);
1407  amrex::Real q_s_old = q_s_arr(i, j, 0);
1408 
1409  // Read forcing data
1410  amrex::Real sw_net = sw_flux_arr(i, j, 0);
1411  amrex::Real lw_net = lw_flux_arr(i, j, 0);
1412  amrex::Real hfx = hfx_arr(i, j, 0);
1413  amrex::Real lh = lh_arr(i, j, 0);
1414  amrex::Real grdflx = grdflx_arr(i, j, 0);
1415  amrex::Real t_deep_val = t_deep_arr(i, j, 0);
1416  amrex::Real q_deep_val = q_deep_arr(i, j, 0);
1417 
1418  // Compute SEB residual
1419  amrex::Real seb_res = diagnose_seb_residual(sw_net, lw_net, hfx, lh, grdflx);
1420 
1421  // Compute tendencies
1422  amrex::Real dT_s_dt = prognostic_dTs_dt(seb_res, t_s_old, t_deep_val,
1423  C_s, tau);
1424  amrex::Real dq_s_dt = prognostic_dqs_dt(lh, q_s_old, q_deep_val,
1425  d_s, tau_q);
1426 
1427  // Explicit Euler update over the step size
1428  amrex::Real t_s_new = t_s_old + dt_step * dT_s_dt;
1429  amrex::Real q_s_new = q_s_old + dt_step * dq_s_dt;
1430 
1431  // Clamp to valid ranges
1432  t_s_new = amrex::max(t_min, amrex::min(t_max, t_s_new));
1433  q_s_new = amrex::max(q_min, amrex::min(q_max, q_s_new));
1434 
1435  // Write back updated values (this modifies the device array)
1436  t_s_arr(i, j, 0) = t_s_new;
1437  q_s_arr(i, j, 0) = q_s_new;
1438 
1439  // Return for reduction: sum T_s, max T_s, sum q_s, max q_s
1440  return {t_s_new, std::abs(t_s_new), q_s_new, std::abs(q_s_new)};
1441  });
1442 
1443  // Copy results from device to host
1444  amrex::Gpu::synchronize();
1445  auto prog_reduce_tuple = prog_reduce_data.value(prog_reduce_ops);
1446  t_s_sum_box = amrex::get<0>(prog_reduce_tuple);
1447  t_s_max_box = amrex::get<1>(prog_reduce_tuple);
1448  q_s_sum_box = amrex::get<2>(prog_reduce_tuple);
1449  q_s_max_box = amrex::get<3>(prog_reduce_tuple);
1450 
1451  // Accumulate into global results
1452  t_s_sum += t_s_sum_box;
1453  t_s_max_val = std::max(t_s_max_val, t_s_max_box);
1454  q_s_sum += q_s_sum_box;
1455  q_s_max_val = std::max(q_s_max_val, q_s_max_box);
1456  n_prog_columns += n_cols_box;
1457  }
1458 
1459  // The force-restore kernels return a zero tendency on any
1460  // non-finite input, so a NaN here means the state itself was
1461  // corrupted (a bad restart file, or an overwrite elsewhere).
1462  if (m_t_sfc[lev]->contains_nan(0, 1, 0) || m_q_sfc[lev]->contains_nan(0, 1, 0)) {
1463  amrex::Abort("TwoStreamRadiation: non-finite surface temperature or moisture "
1464  "after the force-restore update at level " + std::to_string(lev) +
1465  ", step " + std::to_string(nstep));
1466  }
1467 
1468  {
1469  amrex::Real sums[2] = {t_s_sum, q_s_sum};
1470  ParallelDescriptor::ReduceRealSum(sums, 2);
1471  t_s_sum = sums[0]; q_s_sum = sums[1];
1472  ParallelDescriptor::ReduceRealMax(t_s_max_val);
1473  ParallelDescriptor::ReduceRealMax(q_s_max_val);
1474  ParallelDescriptor::ReduceLongSum(n_prog_columns);
1475  }
1476 
1477  // Compute mean values from sums
1478  if (n_prog_columns > 0) {
1479  t_s_mean = t_s_sum / static_cast<amrex::Real>(n_prog_columns);
1480  t_s_max = t_s_max_val;
1481  q_s_mean = q_s_sum / static_cast<amrex::Real>(n_prog_columns);
1482  q_s_max = q_s_max_val;
1483  } else {
1484  t_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
1485  t_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
1486  q_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
1487  q_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
1488  }
1489  } else {
1490  // An external provider owns the boundary; leave the unused
1491  // TwoStream state untouched and do not report it as active.
1492  t_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
1493  t_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
1494  q_s_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
1495  q_s_max = std::numeric_limits<amrex::Real>::quiet_NaN();
1496  }
1497  }
1498  // equivalent to a single-column value for spatially UNIFORM atmospheres
1499  // (as in the current SW_ClearSky_Analytical / LW_Isothermal RegTests).
1500  // Cloud layer and scattering tests are ALSO spatially uniform (identical
1501  // cloud/tau/scattering parameters applied to every column), so the
1502  // domain-averaged value still equals the true single-column flux there.
1503  // True horizontal heterogeneity (e.g., patchy clouds varying by column)
1504  // remains deferred to future work.
1505  if (do_sweep) {
1506  if (n_columns_total > 0) {
1507  const amrex::Real inv_n = 1.0 / static_cast<amrex::Real>(n_columns_total);
1508  SW_surface = sw_surface_sum * inv_n;
1509  SW_TOA = sw_toa_sum * inv_n;
1510  SW_up_TOA = sw_up_toa_sum * inv_n;
1511  LW_net_surface = lw_net_sum * inv_n;
1512  LW_up_TOA = lw_up_toa_sum * inv_n;
1513  }
1514  heating_rate_max = max_heating_global;
1515  m_flux_diag[lev] = FluxDiag{true, SW_surface, SW_TOA, SW_up_TOA,
1516  LW_net_surface, LW_up_TOA,
1517  heating_rate_max};
1518  } else {
1519  const FluxDiag& cached = m_flux_diag[lev];
1520  if (!cached.valid) {
1521  // No sweep has run on this level yet, so there is nothing to report. This
1522  // happens on the first step of a level built by interp_atmos_from_coarse:
1523  // ERF::advance_radiation interpolates that level's fields and returns
1524  // before the pre-dycore sweep, while this post-dycore call still arrives
1525  // from ERF::Advance. Emitting here would write a row of zeros that reads
1526  // exactly like a real zero-flux result. A nested patch already contributes
1527  // no rows for the same reason; this is the same rule for a level that
1528  // simply has not swept yet.
1529  return;
1530  }
1531  SW_surface = cached.SW_surface;
1532  SW_TOA = cached.SW_TOA;
1533  SW_up_TOA = cached.SW_up_TOA;
1534  LW_net_surface = cached.LW_net_surface;
1535  LW_up_TOA = cached.LW_up_TOA;
1536  heating_rate_max = cached.heating_rate_max;
1537  }
1538 
1539  // Compute SEB residual mean from sum
1540  if (rad_choice.seb_diagnostic_enable && seb_active && n_seb_columns > 0) {
1541  seb_residual_mean = seb_residual_sum / static_cast<amrex::Real>(n_seb_columns);
1542  } else {
1543  // When feature is disabled, use NaN for backward compatibility
1544  seb_residual_mean = std::numeric_limits<amrex::Real>::quiet_NaN();
1545  seb_residual_max = std::numeric_limits<amrex::Real>::quiet_NaN();
1546  }
1547 
1548  }
1549 
1550  // Logging output
1551  if (rad_choice.verbosity >= 1 && ParallelDescriptor::IOProcessor()) {
1552  Print() << "Radiation diagnostics at step " << nstep << ":\n"
1553  << " SW TOA = " << SW_TOA << " W/m^2\n"
1554  << " SW surface = " << SW_surface << " W/m^2\n"
1555  << " SW up (TOA) = " << SW_up_TOA << " W/m^2\n"
1556  << " LW net (surface) = " << LW_net_surface << " W/m^2\n"
1557  << " LW up (TOA) = " << LW_up_TOA << " W/m^2\n"
1558  << " Max heating rate = " << heating_rate_max << " K/s\n";
1559  if (rad_choice.seb_diagnostic_enable && std::isfinite(seb_residual_mean)) {
1560  Print() << " SEB residual (mean) = " << seb_residual_mean << " W/m^2\n"
1561  << " SEB residual (max) = " << seb_residual_max << " W/m^2\n";
1562  }
1563  if (rad_choice.seb_prognostic_enable && std::isfinite(t_s_mean)) {
1564  Print() << " Surface temperature (mean) = " << t_s_mean << " K\n"
1565  << " Surface temperature (max) = " << t_s_max << " K\n"
1566  << " Surface moisture (mean) = " << q_s_mean << " m^3/m^3\n"
1567  << " Surface moisture (max) = " << q_s_max << " m^3/m^3\n";
1568  }
1569  }
1570 
1571  rad_diag.append(nstep, time, call_site, SW_surface, SW_TOA,
1572  SW_up_TOA, LW_net_surface, LW_up_TOA, heating_rate_max,
1573  seb_residual_mean, seb_residual_max,
1574  t_s_mean, t_s_max, q_s_mean, q_s_max);
1575 }
#define Rho_comp
Definition: ERF_IndexDefines.H:53
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:54
pp_for_pert_vels get("prob_type", prob_type)
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")
SEBFluxOrigin fill_seb_turbulent_flux(MultiFab &seb_flux, SEBTurbulentFlux kind, const MultiFab *land_surface_field, const MultiFab *surface_layer_flux, int surface_k, SEBTurbulentFluxSource source, Real fallback)
Definition: ERF_SEBTurbulentFlux.cpp:63
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:68
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:169
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:114
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void two_stream_canopy_beam(int i, int j, int kmin, int m_top, const amrex::Array4< const amrex::Real > &scratch, int f_dir_comp, bool sw_on, amrex::Real cos_zenith, amrex::Real cloud_fraction, bool blend, const amrex::Array4< amrex::Real > &beam)
Write the direct beam on a horizontal surface at the interfaces 0 .. m_top of column (i,...
Definition: ERF_TwoStreamCanopyForcing.H:77
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_GPU_HOST_DEVICE AMREX_FORCE_INLINE void two_stream_land_forcing(int i, int j, int kmin, const amrex::Array4< const amrex::Real > &rad_flux, amrex::Real cos_zenith, amrex::Real &sw_dn, amrex::Real &lw_dn, amrex::Real &coszen)
The radiative forcing a land-surface model integrates on, for column (i,j).
Definition: ERF_TwoStreamColumn.H:858
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, amrex::Real *cos_zenith_out=nullptr)
GPU-safe per-column vertical integration kernel for two-stream radiation, computing either the clear-...
Definition: ERF_TwoStreamColumn.H:945
int Get_DataIdx(const int &lev, const std::string &varname)
Definition: ERF_LandSurface.H:186
amrex::MultiFab * Get_Data_Ptr(const int &lev, const int &varIdx)
Definition: ERF_LandSurface.H:142
CSV logger and debug-print manager for radiation module.
Definition: ERF_RadiationDiagnostics.H:75
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:79
bool supplies_canopy_forcing(int lev) const noexcept
True when supply_canopy_forcing() was called for lev since its last define_level().
Definition: ERF_TwoStreamRadiation.H:263
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_canopy_cosz
Definition: ERF_TwoStreamRadiation.H:360
bool supplies_land_forcing(int lev) const noexcept
True when define_level allocated the land-model forcing fields on lev.
Definition: ERF_TwoStreamRadiation.H:213
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_q_sfc
Definition: ERF_TwoStreamRadiation.H:343
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_canopy
Definition: ERF_TwoStreamRadiation.H:359
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_grdflx_sfc
Definition: ERF_TwoStreamRadiation.H:342
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_lw_flux_sfc
Definition: ERF_TwoStreamRadiation.H:339
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_q_deep
Definition: ERF_TwoStreamRadiation.H:345
amrex::Real m_rdOcp
Definition: ERF_TwoStreamRadiation.H:322
amrex::Vector< FluxDiag > m_flux_diag
Definition: ERF_TwoStreamRadiation.H:364
bool active() const
True once a level has been defined with the model selected.
Definition: ERF_TwoStreamRadiation.H:57
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_emiss_lw
Definition: ERF_TwoStreamRadiation.H:334
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_t_sfc
Definition: ERF_TwoStreamRadiation.H:335
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_lw_dn_sfc
Definition: ERF_TwoStreamRadiation.H:353
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_lh_sfc
Definition: ERF_TwoStreamRadiation.H:341
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_alb_sw
Definition: ERF_TwoStreamRadiation.H:333
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_sw_flux_sfc
Definition: ERF_TwoStreamRadiation.H:338
OrbitalCache m_orbit
Definition: ERF_TwoStreamRadiation.H:323
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_t_deep
Definition: ERF_TwoStreamRadiation.H:344
amrex::Vector< std::unique_ptr< RadiationDiagnostics > > m_diag
Definition: ERF_TwoStreamRadiation.H:330
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_sw_dn_sfc
Definition: ERF_TwoStreamRadiation.H:352
amrex::Vector< int > m_canopy_step
Definition: ERF_TwoStreamRadiation.H:362
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_hfx_sfc
Definition: ERF_TwoStreamRadiation.H:340
amrex::Vector< int > m_canopy_m
Definition: ERF_TwoStreamRadiation.H:361
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_cos_zenith
Definition: ERF_TwoStreamRadiation.H:354
@ rho
Definition: ERF_Kessler.H:25
constexpr int F_DIR
Definition: ERF_TwoStreamColumn.H:602
constexpr int NCOMP
Definition: ERF_TwoStreamColumn.H:608
Container holding radiation-related choices and parameters.
Definition: ERF_RadStruct.H:132
bool sw_enabled
Enable shortwave (solar) radiation computation. Only used by the two-stream model.
Definition: ERF_RadStruct.H:144
std::string diag_file
Output file path for radiation diagnostics CSV. Paths are relative to the simulation working director...
Definition: ERF_RadStruct.H:268
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 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 seb_lh_default
Fallback SEB latent heat flux [W/m^2].
Definition: ERF_RadStruct.H:551
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 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
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
amrex::Real seb_t_deep_default
Fallback SEB deep soil temperature [K].
Definition: ERF_RadStruct.H:571
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 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 seb_prognostic_q_max
Maximum clamping bound for prognostic surface moisture [kg/kg]. After each Euler update,...
Definition: ERF_RadStruct.H:664
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
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 seb_hfx_default
Fallback SEB sensible heat flux [W/m^2].
Definition: ERF_RadStruct.H:546
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 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
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
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
bool seb_diagnostic_enable
Enable diagnostic SEB residual computation and reporting. When true, computes and reports SEB residua...
Definition: ERF_RadStruct.H:587
Trivially copyable subset of RadChoice used by the column kernels.
Definition: ERF_TwoStreamColumn.H:57
Here is the call graph for this function:

◆ canopy_forcing()

TwoStreamCanopyView TwoStreamRadiation::canopy_forcing ( int  lev,
int  nstep 
) const
inlinenoexcept

The beam and the cosine of the zenith of lev as the sweep of step nstep wrote them (the view's fluxes are left for the caller, who owns rad_fluxes), or an empty view when the level does not supply them or has not swept at that step: the faces must not take a previous step's radiation, nor the zeros of a level whose radiation came from its parent.

274  {
276  if (supplies_canopy_forcing(lev) && m_canopy_step[lev] == nstep) {
277  v.beam = m_canopy[lev].get();
278  v.cos_zenith = m_canopy_cosz[lev].get();
279  }
280  return v;
281  }
The fields a level's faces read for one step: the beam and the cosine of the zenith the sweep of that...
Definition: ERF_TwoStreamCanopyForcing.H:45
const amrex::MultiFab * beam
Direct beam on a horizontal surface, interfaces 0 .. m_top [W/m^2].
Definition: ERF_TwoStreamCanopyForcing.H:46
const amrex::MultiFab * cos_zenith
Cosine of the zenith of the sweep, floored at zero (2D) [1].
Definition: ERF_TwoStreamCanopyForcing.H:47
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,
const amrex::BoxArray &  ba,
const amrex::Box &  domain,
bool  supply_land_forcing = false 
)

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.

Also enforces the model's one grid requirement: every box of ba must span domain in z, because the column sweep applies the top-of-atmosphere and surface boundary conditions at the ends of a box. Aborting here means a bad refinement patch is rejected when the level is built (including on regrid), not at the first radiation call.

Parameters
levlevel
rad_choicethe model's inputs
ba2dhorizontal BoxArray the 2D surface fields live on
dmdistribution mapping shared with the level's state
bathe level's 3D BoxArray, checked for the z span
domainthe level's domain Box
supply_land_forcingallocate the downwelling SW, downwelling LW and cos(zenith) fields the sweep fills for a land-surface model (SolverChoice::rad_feeds_lsm()); off, the sweep does no extra work and the fields do not exist
267 {
268  if (!rad_choice.enabled) { return; }
269  m_rad = &rad_choice;
270  m_rdOcp = rdOcp;
271 
272  // The sweep integrates a whole column in one pass inside a single box, applying the
273  // top-of-atmosphere boundary condition above the top layer and the surface condition
274  // below the bottom one. So the requirement is per box: every box must span the domain
275  // in z. Levels that do not meet it come in several shapes -- a shallow nest, a level
276  // tagged at different heights in different horizontal regions, grids decomposed in z --
277  // and they all have the same consequence, that no box holds a column to sweep.
278  //
279  // Above level 0 that is not an error: ERF::advance_radiation interpolates the level's
280  // heating rates and fluxes from its parent, the route RRTMGP takes for a nested patch,
281  // and level_needs_interpolation() there applies this same per-box test so the two agree.
282  //
283  // On level 0 there is no parent to interpolate from, so it is fatal. Level 0 always
284  // covers the domain, which makes a box that does not span z there exactly the
285  // decomposed-in-z case -- and the remedies below are the ones that apply to it.
286  if (lev == 0) {
287  for (int ibox = 0; ibox < ba.size(); ++ibox) {
288  const Box& b = ba[ibox];
289  if (b.smallEnd(2) != domain.smallEnd(2) || b.bigEnd(2) != domain.bigEnd(2)) {
290  amrex::Abort("erf.radiation_model = TwoStream cannot run on a level 0 whose "
291  "grids are decomposed in z: a box there holds only part of a "
292  "column, and level 0 has no parent to interpolate from. Set "
293  "amr.max_grid_size_z to at least the number of cells in z, or "
294  "leave amr.no_box_split_dir at its default of 2 so grids are "
295  "never split in z.");
296  }
297  }
298  }
299 
300  // 2D surface fields on the horizontal BoxArray, one ghost cell in x and y
301  const IntVect ng_sfc{1,1,0};
302  m_alb_sw[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
303  m_emiss_lw[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
304  m_t_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
305  m_alb_sw[lev]->setVal(rad_choice.surface_albedo_sw);
306  m_emiss_lw[lev]->setVal(rad_choice.surface_emissivity_lw);
307  m_t_sfc[lev]->setVal(rad_choice.rad_t_sfc);
308 
309  if (rad_choice.seb_enable) {
310  m_sw_flux_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
311  m_lw_flux_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
312  m_hfx_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
313  m_lh_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
314  m_grdflx_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
315  m_q_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
316  m_t_deep[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
317  m_q_deep[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, ng_sfc);
318  m_sw_flux_sfc[lev]->setVal(rad_choice.seb_sw_flux_default);
319  m_lw_flux_sfc[lev]->setVal(rad_choice.seb_lw_flux_default);
320  m_hfx_sfc[lev]->setVal(rad_choice.seb_hfx_default);
321  m_lh_sfc[lev]->setVal(rad_choice.seb_lh_default);
322  m_grdflx_sfc[lev]->setVal(rad_choice.seb_grdflx_default);
323  m_q_sfc[lev]->setVal(rad_choice.seb_q_sfc_default);
324  m_t_deep[lev]->setVal(rad_choice.seb_t_deep_default);
325  m_q_deep[lev]->setVal(rad_choice.seb_q_deep_default);
326  if (rad_choice.seb_surface_layer_uses_moisture) {
327  m_moisture[lev] = std::make_unique<MultiFab>(ba2d, dm,
329  m_moisture[lev]->setVal(0.0);
330  } else {
331  m_moisture[lev].reset();
332  }
333  }
334 
335  // The land-model forcing (see the members). It holds the lsm_undefined sentinel until a
336  // sweep on this level fills it. Zero would be a valid forcing (night, no sky), so a copy
337  // made before any sweep -- a level whose advance() returned early, or a caller that
338  // drifts out of step with the sweep -- would hand the land model a 0 K sky that its
339  // first-land-step check (NOAHMP::Advance_With_State) accepts. The sentinel is what
340  // that check looks for.
341  if (supply_land_forcing) {
342  m_sw_dn_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, 0);
343  m_lw_dn_sfc[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, 0);
344  m_cos_zenith[lev] = std::make_unique<MultiFab>(ba2d, dm, 1, 0);
345  m_sw_dn_sfc[lev]->setVal(lsm_undefined);
346  m_lw_dn_sfc[lev]->setVal(lsm_undefined);
347  m_cos_zenith[lev]->setVal(lsm_undefined);
348  } else {
349  m_sw_dn_sfc[lev].reset();
350  m_lw_dn_sfc[lev].reset();
351  m_cos_zenith[lev].reset();
352  }
353  // A rebuilt level has new grids: the canopy forcing, if the faces asked for it,
354  // must be asked for again on them (the faces do not regrid; ERF::init_ibseb()).
355  m_canopy[lev].reset();
356  m_canopy_cosz[lev].reset();
357  m_canopy_m[lev] = -1;
358  m_canopy_step[lev] = -1;
359  m_flux_diag[lev] = FluxDiag{};
360 }
constexpr amrex::Real lsm_undefined
Definition: ERF_Constants.H:28
const Real rdOcp
Definition: ERF_InitCustomPert_ABL.H:72
amrex::Vector< std::unique_ptr< amrex::MultiFab > > m_moisture
Definition: ERF_TwoStreamRadiation.H:346
@ NumComponents
Definition: ERF_SurfaceMoisture.H:37
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

◆ land_forcing_cos_zenith()

const amrex::MultiFab* TwoStreamRadiation::land_forcing_cos_zenith ( int  lev) const
inlinenoexcept
230  {
231  return supplies_land_forcing(lev) ? m_cos_zenith[lev].get() : nullptr;
232  }
Here is the call graph for this function:

◆ land_forcing_lw_dn()

const amrex::MultiFab* TwoStreamRadiation::land_forcing_lw_dn ( int  lev) const
inlinenoexcept
227  {
228  return supplies_land_forcing(lev) ? m_lw_dn_sfc[lev].get() : nullptr;
229  }
Here is the call graph for this function:

◆ land_forcing_sw_dn()

const amrex::MultiFab* TwoStreamRadiation::land_forcing_sw_dn ( int  lev) const
inlinenoexcept

The surface forcing of the last sweep on lev (2D: i,j), or nullptr when the level does not supply it: total downwelling shortwave [W/m^2], downwelling longwave [W/m^2] and the cosine of the solar zenith angle floored at zero [1]. lsm_undefined until a sweep has run on the level, so a copy made before one is caught by the land model's missing-input check instead of reading as zero.

224  {
225  return supplies_land_forcing(lev) ? m_sw_dn_sfc[lev].get() : nullptr;
226  }
Here is the call graph for this function:

◆ prognostic_surface_temperature_state()

const amrex::MultiFab* TwoStreamRadiation::prognostic_surface_temperature_state ( int  lev) const
inlinenoexcept

Read-only view of the per-level prognostic surface-temperature state.

62  {
63  return (lev >= 0 && lev < static_cast<int>(m_t_sfc.size()))
64  ? m_t_sfc[lev].get() : nullptr;
65  }

◆ read_checkpoint()

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

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

519 {
520  // Older checkpoints do not carry the state; then the defaults set by
521  // define_level stand. Every level, matching write_checkpoint. A checkpoint
522  // written before the SEB ran on fine levels simply has no file for them, which
523  // the FileExists test below already handles.
524  //
525  // Each field is read on the grids in the file and redistributed onto the grids the
526  // run uses, which differ when the restart re-made level 0 (issue 4225). The fields
527  // are written with their ghost cells and an ordinary restart has always restored
528  // those, so the halo is offered too.
529  if (!active() || !m_rad->seb_enable) { return; }
530  const std::string tsfc_name =
531  MultiFabFileFullPrefix(lev, restart_chkfile, "Level_", "TwoStream_TSfc");
532  if (m_t_sfc[lev] && amrex::FileExists(tsfc_name + "_H")) {
533  erf_checkpoint::read_checkpoint_multifab(*m_t_sfc[lev], tsfc_name, 0, 0, 1,
534  m_t_sfc[lev]->nGrowVect(), true, period);
535  }
536  const std::string qsfc_name =
537  MultiFabFileFullPrefix(lev, restart_chkfile, "Level_", "TwoStream_QSfc");
538  if (m_q_sfc[lev] && amrex::FileExists(qsfc_name + "_H")) {
539  erf_checkpoint::read_checkpoint_multifab(*m_q_sfc[lev], qsfc_name, 0, 0, 1,
540  m_q_sfc[lev]->nGrowVect(), true, period);
541  }
542 }
void read_checkpoint_multifab(amrex::MultiFab &dst, const std::string &mf_name, int scomp, int dcomp, int ncomp, const amrex::IntVect &dst_ghosts, bool src_ghosts, const amrex::Periodicity &period)
Definition: ERF_ReadCheckpointMF.H:52
Here is the call graph for this function:

◆ resize()

void TwoStreamRadiation::resize ( int  nlevs_max)

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

234 {
235  m_alb_sw.resize(nlevs_max);
236  m_emiss_lw.resize(nlevs_max);
237  m_t_sfc.resize(nlevs_max);
238  m_sw_flux_sfc.resize(nlevs_max);
239  m_lw_flux_sfc.resize(nlevs_max);
240  m_hfx_sfc.resize(nlevs_max);
241  m_lh_sfc.resize(nlevs_max);
242  m_grdflx_sfc.resize(nlevs_max);
243  m_sw_dn_sfc.resize(nlevs_max);
244  m_lw_dn_sfc.resize(nlevs_max);
245  m_cos_zenith.resize(nlevs_max);
246  m_q_sfc.resize(nlevs_max);
247  m_t_deep.resize(nlevs_max);
248  m_q_deep.resize(nlevs_max);
249  m_moisture.resize(nlevs_max);
250  m_canopy.resize(nlevs_max);
251  m_canopy_cosz.resize(nlevs_max);
252  m_canopy_m.resize(nlevs_max, -1);
253  m_canopy_step.resize(nlevs_max, -1);
254  m_flux_diag.resize(nlevs_max);
255  m_diag.resize(nlevs_max);
256 }

Referenced by ERF::ERF_shared().

Here is the caller graph for this function:

◆ seb_hfx()

const amrex::MultiFab* TwoStreamRadiation::seb_hfx ( int  lev) const
inline

The sensible and latent heat fluxes [W/m^2] the surface energy balance of one level last used, or nullptr when the SEB is off or the level has not been built. Read-only: for output.

184  {
185  return (active() && m_rad->seb_enable && lev < m_hfx_sfc.size()) ? m_hfx_sfc[lev].get() : nullptr;
186  }
Here is the call graph for this function:

◆ seb_lh()

const amrex::MultiFab* TwoStreamRadiation::seb_lh ( int  lev) const
inline
187  {
188  return (active() && m_rad->seb_enable && lev < m_lh_sfc.size()) ? m_lh_sfc[lev].get() : nullptr;
189  }
Here is the call graph for this function:

◆ seb_prognostic_active()

bool TwoStreamRadiation::seb_prognostic_active ( ) const
inline

True when the force-restore state is being evolved and so needs transferring.

208  {
210  }
Here is the call graph for this function:

◆ seb_q_sfc()

amrex::MultiFab* TwoStreamRadiation::seb_q_sfc ( int  lev)
inline
175  {
176  return (active() && m_rad->seb_enable && lev < m_q_sfc.size()) ? m_q_sfc[lev].get() : nullptr;
177  }
Here is the call graph for this function:

◆ seb_surface_moisture()

const MultiFab * TwoStreamRadiation::seb_surface_moisture ( int  lev)

What level lev's surface hands the surface layer for its surface mixing ratio (erf.radiation.seb_surface_layer_uses_moisture), per column, in the components of erf_surface_moisture::Component: the soil-water factor (seb_moisture_availability of the current q_s); with erf.radiation.seb_soil_type, the bare-soil resistance and the relative humidity of its pore air at the skin temperature; and with erf.radiation.seb_vegetation_type as well, the vegetated fraction and the canopy resistance without its vapour-deficit factor (from the net shortwave the balance holds divided by (1 - surface albedo) – the sweep's with seb_use_radiation_fluxes, seb_sw_flux_default otherwise – and the skin temperature). Absent pieces are a fraction of 0 and resistances of 1e6 s/m. nullptr when the option is off or the level has not been built. Recomputed on every call, on the valid cells; the halo is not filled.

407 {
409  lev >= static_cast<int>(m_moisture.size()) || !m_moisture[lev] || !m_q_sfc[lev]) {
410  return nullptr;
411  }
412  using namespace erf_surface_moisture;
413  const Real q_wilt = m_rad->seb_soil_moisture_wilt;
414  const Real q_fc = m_rad->seb_soil_moisture_fc;
415 
416  // With a soil type: the soil's parameters for the bare-soil resistance of the top
417  // seb_moisture_layer_depth_m, vegetation or not. With a vegetation type as well (which
418  // needs a soil type): Noah-MP's canopy parameters. RadChoice::init_params checked both
419  // categories.
422  const NoahMPSoilParams* soil = (m_rad->seb_soil_type != 0)
423  ? noahmp_soil_params(m_rad->seb_soil_type) : nullptr;
424  const bool has_soil = (soil != nullptr);
425  const bool vegetated = (veg != nullptr && has_soil);
426  const Real f_veg = vegetated ? m_rad->seb_vegetation_fraction : Real(0.0);
428  const Real rs_min = vegetated ? veg->rs_min : Real(0.0);
429  const Real rs_max = vegetated ? veg->rs_max : Real(0.0);
430  const Real rgl = vegetated ? veg->rgl : Real(0.0);
431  const Real t_opt = vegetated ? veg->t_opt : Real(0.0);
432  const Real smc_max = has_soil ? soil->smc_max : Real(0.0);
433  const Real bb = has_soil ? soil->bb : Real(0.0);
434  const Real psi_sat = has_soil ? soil->psi_sat : Real(0.0);
436  const Real resistance_exponent = noahmp_soil_resistance_exponent;
437 
438  MultiFab& out = *m_moisture[lev];
439  const MultiFab& q_s = *m_q_sfc[lev];
440  const MultiFab& t_s = *m_t_sfc[lev];
441  // The shortwave down at the surface, from the net the balance holds and the albedo.
442  const MultiFab* sw_net = m_sw_flux_sfc[lev].get();
443  const MultiFab* albedo = m_alb_sw[lev].get();
444 #ifdef _OPENMP
445 #pragma omp parallel if (Gpu::notInLaunchRegion())
446 #endif
447  for (MFIter mfi(out, TilingIfNotGPU()); mfi.isValid(); ++mfi) {
448  const Box& bx = mfi.tilebox();
449  const auto out_arr = out.array(mfi);
450  const auto q_arr = q_s.const_array(mfi);
451  const auto t_arr = t_s.const_array(mfi);
452  const auto sw_arr = (vegetated && sw_net) ? sw_net->const_array(mfi) : Array4<const Real>{};
453  const auto alb_arr = (vegetated && albedo) ? albedo->const_array(mfi) : Array4<const Real>{};
454  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
455  {
456  const Real soil_factor = seb_moisture_availability(q_arr(i, j, k), q_wilt, q_fc);
457  out_arr(i, j, k, SoilFactor) = soil_factor;
458  out_arr(i, j, k, VegetationFraction) = f_veg;
460  Real r_soil = no_flux_resistance;
461  if (vegetated) {
462  const Real a = (alb_arr) ? alb_arr(i, j, k) : Real(0.0);
463  const Real sw_down = (sw_arr && a < Real(1.0)) ? sw_arr(i, j, k) / (Real(1.0) - a) : Real(0.0);
464  r_c = seb_canopy_resistance_without_vpd(rs_min, rs_max, rgl, t_opt, lai, sw_down,
465  t_arr(i, j, k), soil_factor);
466  }
467  Real rh_ground = Real(1.0);
468  if (has_soil) {
469  r_soil = seb_soil_evaporation_resistance(q_arr(i, j, k), smc_max, q_wilt, bb, d1,
470  resistance_exponent);
471  rh_ground = seb_soil_surface_relative_humidity(q_arr(i, j, k), smc_max, psi_sat,
472  bb, t_arr(i, j, k));
473  }
474  out_arr(i, j, k, CanopyResistanceWithoutVPD) = r_c;
475  out_arr(i, j, k, SoilResistance) = r_soil;
476  out_arr(i, j, k, GroundRelativeHumidity) = rh_ground;
477  });
478  }
479  return m_moisture[lev].get();
480 }
Real r_c
Definition: ERF_InitCustomPert_SquallLine.H:53
ParallelFor(fab_box, [=] AMREX_GPU_DEVICE(int i, int j, int k) { qrcuten_arr(i, j, k)=Real(0);qscuten_arr(i, j, k)=Real(0);qicuten_arr(i, j, k)=Real(0);})
constexpr amrex::Real noahmp_soil_resistance_exponent
Definition: ERF_NoahMPSoilTable.H:39
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
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
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real seb_soil_evaporation_resistance(amrex::Real theta, amrex::Real theta_sat, amrex::Real theta_wilt, amrex::Real b, amrex::Real d1, amrex::Real p)
Resistance of bare soil to evaporation [s/m], Noah-MP's ground-evaporation option 1 (Sakaguchi and Ze...
Definition: ERF_SimplifiedSEB.H:288
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real seb_moisture_availability(amrex::Real q_s, amrex::Real q_wilt, amrex::Real q_fc)
Moisture availability beta of the force-restore surface, from its water content.
Definition: ERF_SimplifiedSEB.H:218
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real seb_soil_surface_relative_humidity(amrex::Real theta, amrex::Real theta_sat, amrex::Real psi_sat, amrex::Real b, amrex::Real t_ground)
Relative humidity of the air in the top soil's pores, Noah-MP's for bare ground (ResistanceGroundEvap...
Definition: ERF_SimplifiedSEB.H:318
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real seb_canopy_resistance_without_vpd(amrex::Real rs_min, amrex::Real rs_max, amrex::Real rgl, amrex::Real t_opt, amrex::Real lai, amrex::Real sw_down, amrex::Real t_canopy, amrex::Real soil_factor)
Canopy resistance to transpiration [s/m] without its vapour-deficit factor.
Definition: ERF_SimplifiedSEB.H:254
@ lai
Definition: ERF_SLM.H:76
Definition: ERF_SurfaceMoisture.H:21
constexpr amrex::Real no_flux_resistance
Definition: ERF_SurfaceMoisture.H:79
@ GroundRelativeHumidity
Definition: ERF_SurfaceMoisture.H:36
@ SoilResistance
Definition: ERF_SurfaceMoisture.H:35
@ VegetationFraction
Definition: ERF_SurfaceMoisture.H:33
@ CanopyResistanceWithoutVPD
Definition: ERF_SurfaceMoisture.H:34
@ SoilFactor
Definition: ERF_SurfaceMoisture.H:32
Definition: ERF_NoahMPSoilTable.H:20
amrex::Real bb
BB Clapp-Hornberger b [1].
Definition: ERF_NoahMPSoilTable.H:21
amrex::Real psi_sat
SATPSI saturated matric potential [m].
Definition: ERF_NoahMPSoilTable.H:25
amrex::Real smc_max
MAXSMC saturated soil moisture (porosity) [m^3/m^3].
Definition: ERF_NoahMPSoilTable.H:23
Definition: ERF_NoahMPVegetationTable.H:23
amrex::Real t_opt
TOPT optimum transpiration air temperature [K].
Definition: ERF_NoahMPVegetationTable.H:27
amrex::Real rs_min
RS minimum stomatal resistance [s/m].
Definition: ERF_NoahMPVegetationTable.H:24
amrex::Real rgl
RGL radiation stress parameter [W/m^2].
Definition: ERF_NoahMPVegetationTable.H:25
amrex::Real rs_max
RSMAX maximum stomatal resistance [s/m].
Definition: ERF_NoahMPVegetationTable.H:28
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
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
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 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
Here is the call graph for this function:

◆ seb_t_sfc()

amrex::MultiFab* TwoStreamRadiation::seb_t_sfc ( int  lev)
inline

The prognostic surface state of one level, or nullptr when the SEB is off or the level has not been built.

Exposed so ERF can average t_sfc and q_sfc down after the finer levels have advanced. Every level evolves its own copy, and without that transfer the two estimates of one surface drift apart – which would give the longwave boundary condition two different values for the same ground. ERF owns refRatio and the level geometries; this class does not, and should not grow them for one call.

172  {
173  return (active() && m_rad->seb_enable && lev < m_t_sfc.size()) ? m_t_sfc[lev].get() : nullptr;
174  }
Here is the call graph for this function:

◆ supplies_canopy_forcing()

bool TwoStreamRadiation::supplies_canopy_forcing ( int  lev) const
inlinenoexcept

True when supply_canopy_forcing() was called for lev since its last define_level().

263  {
264  return lev >= 0 && lev < static_cast<int>(m_canopy.size()) && m_canopy[lev] != nullptr;
265  }

Referenced by canopy_forcing().

Here is the caller graph for this function:

◆ supplies_land_forcing()

bool TwoStreamRadiation::supplies_land_forcing ( int  lev) const
inlinenoexcept

True when define_level allocated the land-model forcing fields on lev.

213  {
214  return lev >= 0 && lev < static_cast<int>(m_sw_dn_sfc.size()) && m_sw_dn_sfc[lev] != nullptr;
215  }

Referenced by land_forcing_cos_zenith(), land_forcing_lw_dn(), and land_forcing_sw_dn().

Here is the caller graph for this function:

◆ supply_canopy_forcing()

void TwoStreamRadiation::supply_canopy_forcing ( int  lev,
int  m_top 
)

Have every later sweep on lev write what the building faces need besides the interface fluxes (erf.ibseb.radiation = two_stream; ERF_TwoStreamCanopyForcing.H): the direct beam on a horizontal surface at the interfaces 0 .. m_top of each column, counted from the surface, and the cosine of the zenith. Allocates one field on the level's horizontal grids given those interfaces in z and one 2D field. Call after define_level(); a later define_level() of the level (a regrid) drops the request.

Parameters
levlevel, which must have been defined; its sweep asserts that the columns reach m_top
m_tophighest interface the faces read (IBFaceSet::top_sample_interface()), in [0, number of cells in z]
364 {
366  "TwoStreamRadiation::supply_canopy_forcing: erf.radiation_model is not TwoStream");
367  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(lev >= 0 && lev < static_cast<int>(m_alb_sw.size()) && m_alb_sw[lev],
368  "TwoStreamRadiation::supply_canopy_forcing: level " + std::to_string(lev) + " has not been defined");
370  "TwoStreamRadiation::supply_canopy_forcing: the top of the canopy must be at or above the surface");
371  // The level's grids flattened (the 2D fields' boxes, on their distribution), given the
372  // interfaces 0 .. m_top in z; the sweep asserts that the column reaches m_top.
373  BoxList canopy_boxes = m_alb_sw[lev]->boxArray().boxList();
374  for (Box& b : canopy_boxes) { b.setRange(2, 0, m_top + 1); }
375  // The beam alone: the diffuse light, the longwave and the ground's fluxes are the
376  // sweep's interface fluxes, which ERF keeps (rad_fluxes); only the beam and the
377  // cosine of the zenith are lost after the sweep.
378  m_canopy[lev] = std::make_unique<MultiFab>(BoxArray(std::move(canopy_boxes)), m_alb_sw[lev]->DistributionMap(), 1, 0);
379  m_canopy_cosz[lev] = std::make_unique<MultiFab>(m_alb_sw[lev]->boxArray(), m_alb_sw[lev]->DistributionMap(), 1, 0);
380  // Never read before a sweep has written them: canopy_forcing() returns null until then.
381  m_canopy[lev]->setVal(0.0);
382  m_canopy_cosz[lev]->setVal(0.0);
383  m_canopy_m[lev] = m_top;
384  m_canopy_step[lev] = -1;
385 }
Here is the call 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).

498 {
499  // The force-restore state is the only part of this model a restart must
500  // carry; without it T_s and q_s restart from the scalar defaults.
501  //
502  // Every level: each one evolves its own force-restore state now, so each has
503  // state worth restoring. A coarse level's values where a finer level covers it
504  // are the average of that finer level's, restored on both sides.
505  if (!active() || !m_rad->seb_enable) { return; }
506  if (m_t_sfc[lev]) {
507  VisMF::Write(*m_t_sfc[lev],
508  MultiFabFileFullPrefix(lev, checkpointname, "Level_", "TwoStream_TSfc"));
509  }
510  if (m_q_sfc[lev]) {
511  VisMF::Write(*m_q_sfc[lev],
512  MultiFabFileFullPrefix(lev, checkpointname, "Level_", "TwoStream_QSfc"));
513  }
514 }

◆ write_land_forcing()

void TwoStreamRadiation::write_land_forcing ( int  lev,
amrex::MultiFab *  sw_flux_dn,
amrex::MultiFab *  lw_flux_dn,
amrex::MultiFab *  cos_zenith_angle 
) const

Copy the surface forcing of the last sweep on lev into a land-surface model's radiation inputs (SurfaceModel's "sw_flux_dn", "lw_flux_dn" and "cos_zenith_angle" outputs). A null destination is skipped. Each destination must lie on this level's horizontal grids; see copy_surface_plane. Before the level's first sweep this copies the lsm_undefined sentinel, not zero.

487 {
489  "TwoStreamRadiation::write_land_forcing: level " + std::to_string(lev) +
490  " was not defined to supply a land-surface model's forcing");
491  if (sw_flux_dn != nullptr) { copy_surface_plane(*m_sw_dn_sfc[lev], *sw_flux_dn); }
492  if (lw_flux_dn != nullptr) { copy_surface_plane(*m_lw_dn_sfc[lev], *lw_flux_dn); }
493  if (cos_zenith_angle != nullptr) { copy_surface_plane(*m_cos_zenith[lev], *cos_zenith_angle); }
494 }
void copy_surface_plane(const MultiFab &src2d, MultiFab &dst)
Definition: ERF_TwoStreamRadiation.cpp:388
Here is the call graph for this function:

Member Data Documentation

◆ m_alb_sw

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

◆ m_canopy

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

◆ m_canopy_cosz

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

Referenced by canopy_forcing().

◆ m_canopy_m

amrex::Vector<int> TwoStreamRadiation::m_canopy_m
private

◆ m_canopy_step

amrex::Vector<int> TwoStreamRadiation::m_canopy_step
private

Referenced by canopy_forcing().

◆ m_cos_zenith

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

Referenced by land_forcing_cos_zenith().

◆ m_diag

amrex::Vector<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

Referenced by seb_hfx().

◆ m_lh_sfc

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

Referenced by seb_lh().

◆ m_lw_dn_sfc

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

Referenced by land_forcing_lw_dn().

◆ m_lw_flux_sfc

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

◆ m_moisture

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_moisture
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

Referenced by seb_q_sfc().

◆ m_rad

const RadChoice* TwoStreamRadiation::m_rad = nullptr
private

◆ m_rdOcp

amrex::Real TwoStreamRadiation::m_rdOcp = RdoCp
private

◆ m_sw_dn_sfc

amrex::Vector<std::unique_ptr<amrex::MultiFab> > TwoStreamRadiation::m_sw_dn_sfc
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: