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

Interface between ERF and land-surface and urban models. More...

#include <ERF_SurfaceModel.H>

Collaboration diagram for SurfaceModel:

Classes

struct  Field
 Describes a registered land/urban field mapping. More...
 
struct  RadiationField
 

Public Member Functions

 SurfaceModel (int nlevs, const amrex::Vector< amrex::BoxArray > &ba, const amrex::Vector< amrex::Geometry > &geom, const amrex::Vector< amrex::DistributionMapping > &dm, const SolverChoice &, amrex::Vector< amrex::Vector< std::unique_ptr< amrex::iMultiFab >>> &)
 Constructs a surface-model interface for the supplied AMR levels. More...
 
void initialize_for_level (int lev, const amrex::BoxArray &ba, const amrex::Geometry &geom, const amrex::DistributionMapping &dm, const amrex::Vector< std::unique_ptr< amrex::iMultiFab >> &lmask_lev, const amrex::Vector< amrex::BCRec > &domain_bcs_type, const amrex::Vector< amrex::IntVect > &refRatio)
 Initializes storage and boundary data for an AMR level. More...
 
void clear_level (int lev)
 Removes an AMR level, and every level above it, from the surface model. More...
 
void set_model_data (const int lev, const amrex::Vector< amrex::MultiFab * > model_data, const amrex::Vector< std::string > &data_names, SurfaceModelType type)
 Registers model variables used in weighted averaging. More...
 
void set_model_fluxes (const int lev, const amrex::Vector< amrex::MultiFab * > model_fluxes, const amrex::Vector< std::string > &flux_names, SurfaceModelType type)
 Registers model fluxes as additional weighted-average inputs. More...
 
void set_model_fields (SurfaceModelType type, const amrex::Vector< int > &field_indices, const bool use_fluxes=true)
 Selects the model fields that participate in weighted averaging. More...
 
void apply_weight_average (int lev, const amrex::MultiFab *lsm_data, amrex::MultiFab *lsm_weighted, const amrex::MultiFab *urban_data, amrex::MultiFab *urban_weighted)
 Applies land and urban weights into separate output fields. More...
 
void calculate_weight_average (int lev, amrex::MultiFab *const urban_frac, bool update_derived=true)
 Computes land and urban weights and applies them to registered fields. More...
 
bool are_fluxes ()
 Returns whether the interface exports fluxes rather than MOST variables. More...
 
void request_surface_outputs (const bool persistent=true)
 Enables and materializes the surface outputs consumed by ERF. More...
 
void request_surface_layer_outputs ()
 Enables blended surface outputs needed by the SurfaceLayer. More...
 
void mark_fields_valid ()
 Marks the registered surface fields as filled by an actual model advance. More...
 
bool fields_are_valid () const
 Returns whether the surface models have advanced at least once. More...
 
amrex::MultiFab * get_ustar (const int &lev)
 Returns the weighted horizontal momentum output for a level. More...
 
amrex::MultiFab * get_tstar (const int &lev)
 Returns the weighted thermal output for a level. More...
 
amrex::MultiFab * get_qstar (const int &lev)
 Returns the weighted moisture output for a level. More...
 
amrex::MultiFab * get_tsurf (const int &lev)
 Returns the weighted surface-temperature output for a level. More...
 
SurfaceFluxView get_surface_flux_view (const int lev, const amrex::MFIter &mfi) const
 Returns provider or blended surface-flux views for one tile. More...
 
const amrex::MultiFab * get_weighted_model_data (const int lev, SurfaceModelType type, const int field_idx) const
 Returns weighted data for a selected model field. More...
 
amrex::MultiFab * get_wavg_factors (const int &lev)
 Returns the land and urban weighting factors for a level. More...
 
void activate_all_field_maps (const bool persistent=true)
 Activates all registered mapped fields for an output consumer. More...
 
void write_output (const int &finest_lev, const amrex::Real &time, const std::string &plot_prefix, const amrex::Vector< int > &level_steps, const amrex::Vector< amrex::IntVect > &ref_ratio)
 Writes registered surface-model fields to plotfile output. More...
 
void register_field_map (std::string name, const std::pair< int, int > &lsm_urb_map, bool fill_boundary=false)
 Registers a field map using land and urban field indices. More...
 
void register_field_map (std::string name, amrex::Vector< amrex::MultiFab * > &lsm_lev_mf, amrex::Vector< amrex::MultiFab * > &urb_lev_mf, bool fill_boundary=false)
 Registers a field map using per-level land and urban pointers. More...
 
void set_field_map_pointers (const std::string &name, int lev, amrex::MultiFab *lsm_mf, amrex::MultiFab *urban_mf)
 Updates a pointer-based field mapping for one level. More...
 
void register_radiation_input (const std::string &name, const std::pair< int, int > &lsm_urb_map)
 Registers a canonical radiation input mapping. More...
 
void register_radiation_inputs (const std::unordered_map< std::string, std::pair< int, int >> &input_map)
 Registers canonical radiation input mappings. More...
 
void register_radiation_output (const std::string &name, const std::pair< int, int > &lsm_urb_map)
 Registers a canonical radiation output mapping. More...
 
void register_radiation_outputs (const std::unordered_map< std::string, std::pair< int, int >> &output_map)
 Registers canonical radiation output mappings. More...
 
amrex::MultiFab * get_field (const std::string &name, int lev=0)
 Retrieves a registered field by its common name. More...
 
const amrex::Vector< const amrex::MultiFab * > get_radiation_fields (int lev=0)
 Returns fields registered under the RRTMGP radiation names. More...
 
const amrex::Vector< amrex::MultiFab * > get_radiation_output_fields (int lev=0)
 Returns canonical radiation output destinations for one level. More...
 
amrex::MultiFab * get_radiation_output_field (int lev, const std::string &name)
 Returns the destination of one canonical radiation output by name. More...
 
void distribute_radiation_outputs (int lev)
 Distributes updated canonical radiation outputs to other providers. More...
 
void distribute_radiation_output (int lev, int output_index)
 Distributes one updated canonical radiation output to other providers. More...
 
void WriteCheckpoint (const std::string &checkpointname)
 Writes surface-model state to a checkpoint file. More...
 
void ReadCheckpoint (const std::string &checkpointname)
 Restores surface-model state from a checkpoint file. More...
 
void validate_radiation_output_layout (int lev, const amrex::MultiFab *mf) const
 Validates the layout contract for a canonical radiation output. More...
 
void calculate_simple_average (int lev, amrex::MultiFab *const urban_frac)
 Computes simple land and urban weights from the urban fraction. More...
 
void build_radiation_fieldlist (int lev)
 Builds the ordered radiation-field list for one AMR level. More...
 
void weight_average_fields (int lev, amrex::MultiFab *const)
 Applies the current land and urban weights to selected model fields. More...
 
void activate_field_map (const std::string &name, const bool persistent=true)
 Activates a mapped field and allocates its storage on all levels. More...
 
void deactivate_transient_field_maps ()
 Disables and releases mapped fields used only by the last output. More...
 
bool is_field_mapped (int lev, SurfaceModelType type, int field_idx, const amrex::MultiFab *mf) const
 Checks whether a model field participates in a registered mapping. More...
 
void weight_model_field (int lev, const amrex::MultiFab *source, amrex::MultiFab *weighted, SurfaceModelType type)
 Writes a weighted copy of a model field. More...
 

Static Public Member Functions

static void GotoNextLine (std::istream &is)
 Advances an input stream to the next line. More...
 

Private Member Functions

void resize_levels (int nlevs)
 Sets the number of AMR levels and resizes all per-level storage to match. More...
 
void update_provider_mode (const int lev)
 Updates the provider mode after model data are registered. More...
 
void ensure_weight_factors (const int lev)
 Creates weighting factors when an external caller requests them. More...
 
void ensure_surface_outputs (const int lev)
 Allocates surface-output fields for one level. More...
 
void release_transient_surface_outputs ()
 Releases surface outputs that have no persistent consumer. More...
 

Private Attributes

int m_nlevs
 
bool m_use_urban = false
 
bool m_use_land = false
 
bool m_export_fluxes = true
 
bool m_export_fluxes_set = false
 
amrex::GpuArray< bool, 2 > m_output_fields_registered {{false, false}}
 
bool m_fields_are_valid = false
 
bool m_weights_updated = false
 
amrex::Vector< SurfaceProviderMode > m_provider_mode
 
amrex::Vector< amrex::BoxArray > m_ba
 
amrex::Vector< amrex::BoxArray > m_ba2d
 
amrex::Vector< amrex::Geometry > m_geom
 
amrex::Vector< amrex::DistributionMapping > m_dmap
 
amrex::Vector< amrex::Geometry > m_geom2d
 
amrex::Vector< amrex::iMultiFab * > m_lmask
 
amrex::Vector< amrex::MultiFab * > urban_frac_lev
 
amrex::Vector< amrex::Vector< amrex::MultiFab * > > lsm_data_lev
 
amrex::Vector< amrex::Vector< amrex::MultiFab * > > urban_data_lev
 
amrex::Vector< amrex::Vector< std::unique_ptr< amrex::MultiFab > > > weighted_lsm_data_lev
 
amrex::Vector< amrex::Vector< std::unique_ptr< amrex::MultiFab > > > weighted_urban_data_lev
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > u_star
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_star
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > q_star
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_surf
 
amrex::Vector< int > m_surface_outputs_requested
 
bool m_surface_outputs_enabled = false
 
bool m_surface_layer_outputs_enabled = false
 
amrex::Vector< amrex::MultiFab * > m_last_urban_frac
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > wavg
 
amrex::Vector< int > lsm_fields
 
amrex::Vector< int > urban_fields
 
amrex::Vector< amrex::Vector< std::unique_ptr< amrex::MultiFab > > > fields
 
std::unordered_map< std::string, Field > fieldmap
 
amrex::Vector< std::string > m_lsm_names
 
amrex::Vector< std::string > m_urban_names
 
amrex::Vector< amrex::Vector< const amrex::MultiFab * > > rad_fields
 
amrex::Vector< amrex::Vector< amrex::MultiFab * > > rad_output_fields
 
std::unordered_map< std::string, RadiationField > radiation_input_map
 
std::unordered_map< std::string, RadiationField > radiation_output_map
 

Static Private Attributes

static const std::vector< std::string > radnames = {"tskin", "emiss", "albedo_vis", "albedo_nir", "albedo_vis_diff", "albedo_nir_diff"}
 
static const std::vector< std::string > rad_output_names
 
static const std::vector< std::string > field_names
 

Detailed Description

Interface between ERF and land-surface and urban models.

SurfaceModel collects data and fluxes from the LSM and urban models, performs the land-urban weight averaging and exposes the resulting fields for use by ERF boundary-condition and physics components.

Models may register data and mappings independently, without requiring a fixed ordering or one-to-one correspondence between their fields.

Model-specific field names can also be registered under a common name for consumers such as RRTMGP and MOST.

This class supports its own checkpoint state, ensuring the mappings are restored correctly on restart. This class also provides a surface plotfile for weight-averaged model data for the fields registered.

Constructor & Destructor Documentation

◆ SurfaceModel()

SurfaceModel::SurfaceModel ( int  nlevs,
const amrex::Vector< amrex::BoxArray > &  ba,
const amrex::Vector< amrex::Geometry > &  geom,
const amrex::Vector< amrex::DistributionMapping > &  dm,
const SolverChoice &  ,
amrex::Vector< amrex::Vector< std::unique_ptr< amrex::iMultiFab >>> &   
)
inlineexplicit

Constructs a surface-model interface for the supplied AMR levels.

Parameters
nlevsNumber of AMR levels initially available.
baCell-centered box arrays for each level.
geomGeometry for each level.
dmDistribution mappings for each level.
solverChoiceERF solver configuration.
lmask_levLand-mask data for each level.
66  : m_nlevs(nlevs), m_ba(ba), m_geom(geom), m_dmap(dm) {
67 
69  m_output_fields_registered = {false, false};
70 
71  // set initial fields for each model to none
72  lsm_fields = amrex::Vector<int>(5, -1);
73  urban_fields = amrex::Vector<int>(5, -1);
74  }
void resize_levels(int nlevs)
Sets the number of AMR levels and resizes all per-level storage to match.
Definition: ERF_SurfaceModel.H:897
int m_nlevs
Definition: ERF_SurfaceModel.H:1016
amrex::Vector< amrex::Geometry > m_geom
Definition: ERF_SurfaceModel.H:1036
amrex::Vector< amrex::BoxArray > m_ba
Definition: ERF_SurfaceModel.H:1034
amrex::Vector< int > lsm_fields
Definition: ERF_SurfaceModel.H:1067
amrex::GpuArray< bool, 2 > m_output_fields_registered
Definition: ERF_SurfaceModel.H:1023
amrex::Vector< int > urban_fields
Definition: ERF_SurfaceModel.H:1068
amrex::Vector< amrex::DistributionMapping > m_dmap
Definition: ERF_SurfaceModel.H:1037
Here is the call graph for this function:

Member Function Documentation

◆ activate_all_field_maps()

void SurfaceModel::activate_all_field_maps ( const bool  persistent = true)

Activates all registered mapped fields for an output consumer.

660 {
661  for (const auto& entry : fieldmap) {
662  activate_field_map(entry.first, persistent);
663  }
664 }
void activate_field_map(const std::string &name, const bool persistent=true)
Activates a mapped field and allocates its storage on all levels.
Definition: ERF_SurfaceModel.cpp:637
std::unordered_map< std::string, Field > fieldmap
Definition: ERF_SurfaceModel.H:1098

◆ activate_field_map()

void SurfaceModel::activate_field_map ( const std::string &  name,
const bool  persistent = true 
)

Activates a mapped field and allocates its storage on all levels.

Parameters
nameCommon mapped field name.
638 {
639  auto field_it = fieldmap.find(name);
640  if (field_it == fieldmap.end()) { return; }
641 
642  Field& field = field_it->second;
643  field.active = true;
644  field.persistent_consumer = field.persistent_consumer || persistent;
645  if (field.mf_ind == -1) {
646  field.mf_ind = static_cast<int>(fields.size());
647  fields.emplace_back(m_nlevs);
648  }
649 
650  for (int lev = 0; lev < m_nlevs; ++lev) {
651  if (fields[field.mf_ind][lev] == nullptr) {
652  fields[field.mf_ind][lev] = std::make_unique<amrex::MultiFab>(
653  m_ba2d[lev], m_dmap[lev], 1, IntVect(1,1,0));
654  fields[field.mf_ind][lev]->setVal(0.0);
655  }
656  }
657 }
std::string name
Definition: ERF_Plotfile2DCatalog.cpp:116
amrex::Vector< amrex::BoxArray > m_ba2d
Definition: ERF_SurfaceModel.H:1035
amrex::Vector< amrex::Vector< std::unique_ptr< amrex::MultiFab > > > fields
Definition: ERF_SurfaceModel.H:1097

Referenced by get_field(), and initialize_for_level().

Here is the caller graph for this function:

◆ apply_weight_average()

void SurfaceModel::apply_weight_average ( int  lev,
const amrex::MultiFab *  lsm_data,
amrex::MultiFab *  lsm_weighted,
const amrex::MultiFab *  urban_data,
amrex::MultiFab *  urban_weighted 
)

Applies land and urban weights into separate output fields.

A null land or urban source is skipped, but both sources cannot be null. The source fields are not modified and the fields may use different underlying grids.

Parameters
levAMR level whose weights are used.
lsm_dataLand-model source data to weight.
lsm_weightedDestination for weighted land data.
urban_dataUrban-model source data to weight.
urban_weightedDestination for weighted urban data.
16 {
17  bool valid_land = (lsm_data != nullptr);
18  bool valid_urban = (urban_data != nullptr);
19 
20  AMREX_ASSERT_WITH_MESSAGE(valid_land || valid_urban, "Need at least one pointer to apply weights");
21  AMREX_ASSERT_WITH_MESSAGE(!valid_land || lsm_weighted != nullptr,
22  "Need a destination for weighted land data");
23  AMREX_ASSERT_WITH_MESSAGE(!valid_urban || urban_weighted != nullptr,
24  "Need a destination for weighted urban data");
25  AMREX_ASSERT_WITH_MESSAGE(lsm_data == nullptr || lsm_data != lsm_weighted,
26  "Weighted land data must use separate storage");
27  AMREX_ASSERT_WITH_MESSAGE(urban_data == nullptr || urban_data != urban_weighted,
28  "Weighted urban data must use separate storage");
29 
30  // make sure the weights have been calculated before applying them
32 
33  // Apply weights separately to support different underlying grids between lsm_data and urban_data
34  if (valid_land) {
35  weight_model_field(lev, lsm_data, lsm_weighted, SurfaceModelType::LAND);
36  }
37 
38  if (valid_urban) {
39  weight_model_field(lev, urban_data, urban_weighted, SurfaceModelType::URBAN);
40  }
41 }
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
@ LAND
Definition: ERF_SurfaceModel.H:15
@ URBAN
Definition: ERF_SurfaceModel.H:15
AMREX_ASSERT_WITH_MESSAGE(wbar_cutoff_min > wbar_cutoff_max, "ERROR: wbar_cutoff_min < wbar_cutoff_max")
bool m_weights_updated
Definition: ERF_SurfaceModel.H:1030
void weight_model_field(int lev, const amrex::MultiFab *source, amrex::MultiFab *weighted, SurfaceModelType type)
Writes a weighted copy of a model field.
Definition: ERF_SurfaceModel.cpp:43
Here is the call graph for this function:

◆ are_fluxes()

bool SurfaceModel::are_fluxes ( )
inline

Returns whether the interface exports fluxes rather than MOST variables.

Returns
True when weighted outputs are fluxes.
432 { return m_export_fluxes; }
bool m_export_fluxes
Definition: ERF_SurfaceModel.H:1021

◆ build_radiation_fieldlist()

void SurfaceModel::build_radiation_fieldlist ( int  lev)
inline

Builds the ordered radiation-field list for one AMR level.

Parameters
levAMR level to update.
777  {
778  amrex::Print() << " --- building radiation field list" << std::endl;
779 
780  // loop over fields and check against names of radiation variables
781  for (int li = 0; li < static_cast<int>(radnames.size()); ++li) {
782  amrex::Print() << " - Checking for radiation variable '" << radnames[li] << "'" << std::endl;
783 
784  amrex::MultiFab* mf = nullptr;
785  auto rad_it = radiation_input_map.find(radnames[li]);
786  if (rad_it != radiation_input_map.end()) {
787  const int land_idx = rad_it->second.map.first;
788  const int urban_idx = rad_it->second.map.second;
789  const SurfaceProviderMode mode = m_provider_mode[lev];
790  const bool use_land = mode == SurfaceProviderMode::LandOnly ||
792  const bool use_urban = mode == SurfaceProviderMode::UrbanOnly ||
794  const bool valid_land = use_land && land_idx >= 0 &&
795  lev < static_cast<int>(lsm_data_lev.size()) &&
796  land_idx < static_cast<int>(lsm_data_lev[lev].size()) &&
797  lsm_data_lev[lev][land_idx] != nullptr;
798  const bool valid_urban = use_urban && urban_idx >= 0 &&
799  lev < static_cast<int>(urban_data_lev.size()) &&
800  urban_idx < static_cast<int>(urban_data_lev[lev].size()) &&
801  urban_data_lev[lev][urban_idx] != nullptr;
802  if (valid_land && valid_urban) {
803  if (!rad_it->second.weighted[lev]) {
804  rad_it->second.weighted[lev] = std::make_unique<amrex::MultiFab>(
805  m_ba2d[lev], m_dmap[lev], 1, amrex::IntVect(1,1,0));
806  }
807  auto& weighted = *rad_it->second.weighted[lev];
808  const auto& land = *lsm_data_lev[lev][land_idx];
809  const auto& urban = *urban_data_lev[lev][urban_idx];
810  for (amrex::MFIter mfi(weighted); mfi.isValid(); ++mfi) {
811  const amrex::Box bx = mfi.tilebox();
812  const auto weights = wavg[lev]->const_array(mfi);
813  const auto land_arr = land.const_array(mfi);
814  const auto urban_arr = urban.const_array(mfi);
815  // LSM surface fields may be stored at k <= 0; use the top valid LSM
816  // plane, which matches k=0 when the LSM keeps those fields synchronized
817  // at the surface (such as SLM). Urban surface fields are stored at k=0.
818  const int land_k = land.box(mfi.index()).bigEnd(2);
819  const int urban_k = urban.box(mfi.index()).smallEnd(2);
820  auto output = weighted.array(mfi);
821  amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) {
822  output(i,j,k) = land_arr(i,j,land_k) *
823  weights(i,j,0,SurfaceModelType::LAND) +
824  urban_arr(i,j,urban_k) *
825  weights(i,j,0,SurfaceModelType::URBAN);
826  });
827  }
828  weighted.FillBoundary(m_geom[lev].periodicity());
829  mf = rad_it->second.weighted[lev].get();
830  } else if (valid_land) {
831  mf = lsm_data_lev[lev][land_idx];
832  } else if (valid_urban) {
833  mf = urban_data_lev[lev][urban_idx];
834  }
835  }
836  if (mf) {
837  rad_fields[lev].push_back(mf);
838  } else {
839  amrex::Print() << " - WARNING: did not find radiation variable, setting to null to use default RRTMGP value!" << std::endl;
840  rad_fields[lev].push_back(nullptr);
841  }
842  }
844  static_cast<int>(rad_fields[lev].size()) == static_cast<int>(radnames.size()),
845  "Did not find all radiation fields");
846  }
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")
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);})
SurfaceProviderMode
Definition: ERF_SurfaceModel.H:17
std::unordered_map< std::string, RadiationField > radiation_input_map
Definition: ERF_SurfaceModel.H:1115
amrex::Vector< SurfaceProviderMode > m_provider_mode
Definition: ERF_SurfaceModel.H:1032
static const std::vector< std::string > radnames
Definition: ERF_SurfaceModel.H:1071
amrex::Vector< amrex::Vector< amrex::MultiFab * > > lsm_data_lev
Definition: ERF_SurfaceModel.H:1044
amrex::Vector< amrex::Vector< const amrex::MultiFab * > > rad_fields
Definition: ERF_SurfaceModel.H:1103
amrex::Vector< std::unique_ptr< amrex::MultiFab > > wavg
Definition: ERF_SurfaceModel.H:1062
amrex::Vector< amrex::Vector< amrex::MultiFab * > > urban_data_lev
Definition: ERF_SurfaceModel.H:1045

Referenced by get_radiation_fields().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ calculate_simple_average()

void SurfaceModel::calculate_simple_average ( int  lev,
amrex::MultiFab *const  urban_frac 
)

Computes simple land and urban weights from the urban fraction.

Parameters
levAMR level to update.
urban_fracUrban fraction used to compute the weights.
528 {
529  const SurfaceProviderMode mode = m_provider_mode[lev];
530 
531  // If no urban fraction is available, use the only enabled model as the
532  // complete surface. A missing fraction is ambiguous when both models
533  // are enabled, so fail explicitly rather than silently choosing a model.
534  if (urban_frac == nullptr) {
537  "Urban fraction is required when both land and urban models are enabled");
538  }
539 
540  if (mode != SurfaceProviderMode::Both) {
541  m_weights_updated = true;
542  return;
543  }
544 
545  for (MFIter mfi(*wavg[lev], TileNoZ()); mfi.isValid(); ++mfi)
546  {
547  Box tbx = mfi.tilebox();
548  // weights are defined only on k = 0
549  tbx = tbx.makeSlab(2, 0);
550 
551  auto urban_frac_arr = (urban_frac != nullptr) ? urban_frac->const_array(mfi) : Array4<const Real>{};
552  auto weights_arr = wavg[lev]->array(mfi);
553 
554  ParallelFor(tbx, [=] AMREX_GPU_DEVICE (int i, int j, int /*k*/)
555  {
556  // Weights are proportional to urban fraction coverage in the current cell.
557  weights_arr(i, j, 0, SurfaceModelType::URBAN) = urban_frac_arr(i, j, 0);
558  weights_arr(i, j, 0, SurfaceModelType::LAND) = 1.0 - urban_frac_arr(i, j, 0);
559  });
560  }
561 
562  m_weights_updated = true;
563 }
AMREX_FORCE_INLINE amrex::IntVect TileNoZ()
Definition: ERF_TileNoZ.H:11
Here is the call graph for this function:

◆ calculate_weight_average()

void SurfaceModel::calculate_weight_average ( int  lev,
amrex::MultiFab *const  urban_frac,
bool  update_derived = true 
)

Computes land and urban weights and applies them to registered fields.

Parameters
levAMR level to update.
urban_fracUrban fraction used to compute the weights.
73 {
74  m_last_urban_frac[lev] = urban_frac;
75  const SurfaceProviderMode mode = m_provider_mode[lev];
76  // An urban model can be enabled on only a subset of AMR levels. Those
77  // inactive levels have no surface-provider data to update.
78  if (mode == SurfaceProviderMode::None) {
79  return;
80  }
81 
82  // Calculate weighted averages between Land and Urban. Output consumers
83  // can reuse the weights computed during the timestep update.
84  if (update_derived || !m_weights_updated) {
85  calculate_simple_average(lev, urban_frac);
86  }
87 
88  const bool use_urban = mode == SurfaceProviderMode::UrbanOnly ||
90  const bool use_land = mode == SurfaceProviderMode::LandOnly ||
92  const bool use_weights = mode == SurfaceProviderMode::Both;
93  const bool use_fluxes = m_export_fluxes;
94  const bool output_fields_registered =
97 
98  amrex::MultiFab* const outputs[] = {u_star[lev].get(), t_star[lev].get(), q_star[lev].get(), t_surf[lev].get()};
99 
100  const int nfields = (m_export_fluxes) ? 5 : 4;
101 
102  if (m_surface_outputs_requested[lev] && output_fields_registered && !use_weights) {
103  const auto& provider_fields = use_land ? lsm_fields : urban_fields;
104  const auto& provider_data = use_land ? lsm_data_lev[lev] : urban_data_lev[lev];
105  auto source_for = [&] (const int field) -> const amrex::MultiFab* {
106  if (field >= static_cast<int>(provider_fields.size()) ||
107  provider_fields[field] < 0 ||
108  provider_fields[field] >= static_cast<int>(provider_data.size())) {
109  return nullptr;
110  }
111  return provider_data[provider_fields[field]];
112  };
113 
114  const amrex::MultiFab* source0 = source_for(0);
115  const amrex::MultiFab* source1 = source_for(1);
116  const amrex::MultiFab* source2 = source_for(2);
117  const amrex::MultiFab* source3 = source_for(3);
118  const amrex::MultiFab* source4 = source_for(4);
119 
120  for (MFIter mfi(*u_star[lev], TileNoZ()); mfi.isValid(); ++mfi)
121  {
122  const Box tbx = mfi.tilebox();
123  const auto source0_arr = source0 ? source0->const_array(mfi) : Array4<const Real>{};
124  const auto source1_arr = source1 ? source1->const_array(mfi) : Array4<const Real>{};
125  const auto source2_arr = source2 ? source2->const_array(mfi) : Array4<const Real>{};
126  const auto source3_arr = source3 ? source3->const_array(mfi) : Array4<const Real>{};
127  const auto source4_arr = source4 ? source4->const_array(mfi) : Array4<const Real>{};
128  const int source0_k = source0 ? (use_land ? source0->box(mfi.index()).bigEnd(2)
129  : source0->box(mfi.index()).smallEnd(2)) : 0;
130  const int source1_k = source1 ? (use_land ? source1->box(mfi.index()).bigEnd(2)
131  : source1->box(mfi.index()).smallEnd(2)) : 0;
132  const int source2_k = source2 ? (use_land ? source2->box(mfi.index()).bigEnd(2)
133  : source2->box(mfi.index()).smallEnd(2)) : 0;
134  const int source3_k = source3 ? (use_land ? source3->box(mfi.index()).bigEnd(2)
135  : source3->box(mfi.index()).smallEnd(2)) : 0;
136  const int source4_k = source4 ? (use_land ? source4->box(mfi.index()).bigEnd(2)
137  : source4->box(mfi.index()).smallEnd(2)) : 0;
138 
139  auto u_arr = u_star[lev]->array(mfi);
140  auto t_arr = t_star[lev]->array(mfi);
141  auto q_arr = q_star[lev]->array(mfi);
142  auto tsurf_arr = t_surf[lev]->array(mfi);
143  ParallelFor(tbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
144  {
145  if (use_fluxes) {
146  u_arr(i, j, k, 0) = source0_arr ? source0_arr(i, j, source0_k) : 0.0;
147  u_arr(i, j, k, 1) = source1_arr ? source1_arr(i, j, source1_k) : 0.0;
148  t_arr(i, j, k) = source2_arr ? source2_arr(i, j, source2_k) : 0.0;
149  q_arr(i, j, k) = source3_arr ? source3_arr(i, j, source3_k) : 0.0;
150  tsurf_arr(i, j, k) = source4_arr ? source4_arr(i, j, source4_k) : 0.0;
151  } else {
152  u_arr(i, j, k, 0) = source0_arr ? source0_arr(i, j, source0_k) : 0.0;
153  u_arr(i, j, k, 1) = 0.0;
154  t_arr(i, j, k) = source1_arr ? source1_arr(i, j, source1_k) : 0.0;
155  q_arr(i, j, k) = source2_arr ? source2_arr(i, j, source2_k) : 0.0;
156  tsurf_arr(i, j, k) = source3_arr ? source3_arr(i, j, source3_k) : 0.0;
157  }
158  });
159  }
160 
161  u_star[lev]->FillBoundary(m_geom[lev].periodicity());
162  t_star[lev]->FillBoundary(m_geom[lev].periodicity());
163  q_star[lev]->FillBoundary(m_geom[lev].periodicity());
164  t_surf[lev]->FillBoundary(m_geom[lev].periodicity());
165  }
166 
167  // Output weighted surface fluxes into ustar, tstar, qstar and surface temperature into tsurf
168  // TODO: make sure grids of urban and LSM inputs match
169  if (m_surface_outputs_requested[lev] && output_fields_registered && use_weights) {
170  if (!use_fluxes) {
171  u_star[lev]->setVal(0.0, 1, 1, 0);
172  }
173  for (int field = 0; field < nfields; ++field)
174  {
175  int output_field = (use_fluxes && field > 0) ? field - 1 : field;
176  int comp = (use_fluxes && field < 2) ? field : 0;
177 
178  // whether we have a valid LSM multifab
179  bool valid_land = (use_land &&
180  lsm_fields[field] != -1 &&
181  lsm_data_lev[lev][lsm_fields[field]]);
182 
183  // whether we have a valid urban multifab
184  bool valid_urban = (use_urban &&
185  urban_fields[field] != -1 &&
186  urban_data_lev[lev][urban_fields[field]]);
187 
188  for (MFIter mfi(*u_star[lev], TileNoZ()); mfi.isValid(); ++mfi)
189  {
190  Box tbx = mfi.tilebox();
191 
192  const auto weights_arr = use_weights
193  ? wavg[lev]->const_array(mfi) : Array4<const Real>{};
194 
195  // Outputs for surface boundary condition
196  auto output_arr = outputs[output_field]->array(mfi);
197  const amrex::MultiFab* lsm_mf = valid_land ? lsm_data_lev[lev][lsm_fields[field]] : nullptr;
198  const amrex::MultiFab* urban_mf = valid_urban ? urban_data_lev[lev][urban_fields[field]] : nullptr;
199  const int lsm_khi = valid_land ? lsm_mf->box(mfi.index()).bigEnd(2) : 0;
200  const int urban_klo = valid_urban ? urban_mf->box(mfi.index()).smallEnd(2) : 0;
201  auto lsm_data_arr = valid_land ? lsm_mf->const_array(mfi) : Array4<const Real>{};
202  auto urban_data_arr = valid_urban ? urban_mf->const_array(mfi) : Array4<const Real>{};
203 
204  ParallelFor(tbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
205  {
206  if (valid_land && use_land && !use_urban) {
207  output_arr(i, j, k, comp) = lsm_data_arr(i, j, lsm_khi);
208  } else if (valid_urban && use_urban && !use_land) {
209  output_arr(i, j, k, comp) = urban_data_arr(i, j, urban_klo);
210  } else {
211  Real land = (lsm_data_arr) ? lsm_data_arr(i, j, lsm_khi) * weights_arr(i, j, 0, SurfaceModelType::LAND) : 0.0;
212  Real urb = (urban_data_arr) ? urban_data_arr(i, j, urban_klo) * weights_arr(i, j, 0, SurfaceModelType::URBAN) : 0.0;
213  output_arr(i, j, k, comp) = land + urb;
214  }
215 
216  });
217  }
218 
219  outputs[output_field]->FillBoundary(comp, 1, m_geom[lev].periodicity());
220  }
221  }
222 
223  // Write weighted copies only when both providers contribute. With one
224  // provider, get_weighted_model_data() returns the provider-owned source.
225  if (update_derived && use_weights && use_land) {
226  for (int field = nfields; field < static_cast<int>(lsm_fields.size()); ++field) {
227  if (lsm_fields[field] == -1) continue;
229  lsm_data_lev[lev][lsm_fields[field]])) continue;
230  const int field_idx = lsm_fields[field];
231  if (static_cast<int>(weighted_lsm_data_lev[lev].size()) <= field_idx) {
232  weighted_lsm_data_lev[lev].resize(lsm_data_lev[lev].size());
233  }
234  if (weighted_lsm_data_lev[lev][field_idx] == nullptr) {
235  const auto* source = lsm_data_lev[lev][field_idx];
236  weighted_lsm_data_lev[lev][field_idx] = std::make_unique<MultiFab>(
237  source->boxArray(), source->DistributionMap(), source->nComp(),
238  source->nGrowVect());
239  }
240  weight_model_field(lev, lsm_data_lev[lev][field_idx],
241  weighted_lsm_data_lev[lev][field_idx].get(),
243  }
244  }
245 
246  if (update_derived && use_weights && use_urban) {
247  for (int field = nfields; field < static_cast<int>(urban_fields.size()); ++field) {
248  if (urban_fields[field] == -1) continue;
250  urban_data_lev[lev][urban_fields[field]])) continue;
251  const int field_idx = urban_fields[field];
252  if (static_cast<int>(weighted_urban_data_lev[lev].size()) <= field_idx) {
253  weighted_urban_data_lev[lev].resize(urban_data_lev[lev].size());
254  }
255  if (weighted_urban_data_lev[lev][field_idx] == nullptr) {
256  const auto* source = urban_data_lev[lev][field_idx];
257  weighted_urban_data_lev[lev][field_idx] = std::make_unique<MultiFab>(
258  source->boxArray(), source->DistributionMap(), source->nComp(),
259  source->nGrowVect());
260  }
261  weight_model_field(lev, urban_data_lev[lev][field_idx],
262  weighted_urban_data_lev[lev][field_idx].get(),
264  }
265  }
266 
267  weight_average_fields(lev, urban_frac);
268 
269  if (!update_derived) { return; }
270 
271  for (auto& entry : radiation_input_map) {
272  const int land_idx = entry.second.map.first;
273  const int urban_idx = entry.second.map.second;
274  const bool valid_land = use_land && land_idx >= 0 &&
275  land_idx < static_cast<int>(lsm_data_lev[lev].size()) &&
276  lsm_data_lev[lev][land_idx] != nullptr;
277  const bool valid_urban = use_urban && urban_idx >= 0 &&
278  urban_idx < static_cast<int>(urban_data_lev[lev].size()) &&
279  urban_data_lev[lev][urban_idx] != nullptr;
280  if (!(valid_land && valid_urban)) { continue; }
281 
282  if (entry.second.weighted[lev] == nullptr) {
283  entry.second.weighted[lev] = std::make_unique<MultiFab>(
284  m_ba2d[lev], m_dmap[lev], 1, IntVect(1,1,0));
285  }
286  MultiFab& output = *entry.second.weighted[lev];
287  const MultiFab& land = *lsm_data_lev[lev][land_idx];
288  const MultiFab& urban = *urban_data_lev[lev][urban_idx];
289  for (MFIter mfi(output, TileNoZ()); mfi.isValid(); ++mfi) {
290  const Box bx = mfi.tilebox();
291  const auto weights = wavg[lev]->const_array(mfi);
292  const auto land_arr = land.const_array(mfi);
293  const auto urban_arr = urban.const_array(mfi);
294  // LSM surface fields may be stored at k <= 0; use the top valid LSM
295  // plane, which matches k=0 when the LSM keeps those fields synchronized
296  // at the surface (such as SLM). Urban surface fields are stored at k=0.
297  const int land_k = land.box(mfi.index()).bigEnd(2);
298  const int urban_k = urban.box(mfi.index()).smallEnd(2);
299  auto out = output.array(mfi);
300  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) {
301  out(i,j,k) = land_arr(i,j,land_k) * weights(i,j,0,SurfaceModelType::LAND) +
302  urban_arr(i,j,urban_k) * weights(i,j,0,SurfaceModelType::URBAN);
303  });
304  }
305  output.FillBoundary(m_geom[lev].periodicity());
306  }
307 }
pp_for_pert_vels get("prob_type", prob_type)
amrex::Real Real
Definition: ERF_ShocInterface.H:19
bool is_field_mapped(int lev, SurfaceModelType type, int field_idx, const amrex::MultiFab *mf) const
Checks whether a model field participates in a registered mapping.
Definition: ERF_SurfaceModel.cpp:492
amrex::Vector< amrex::MultiFab * > m_last_urban_frac
Definition: ERF_SurfaceModel.H:1059
amrex::Vector< std::unique_ptr< amrex::MultiFab > > q_star
Definition: ERF_SurfaceModel.H:1053
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_surf
Definition: ERF_SurfaceModel.H:1054
amrex::Vector< amrex::Vector< std::unique_ptr< amrex::MultiFab > > > weighted_urban_data_lev
Definition: ERF_SurfaceModel.H:1047
amrex::Vector< amrex::Vector< std::unique_ptr< amrex::MultiFab > > > weighted_lsm_data_lev
Definition: ERF_SurfaceModel.H:1046
void calculate_simple_average(int lev, amrex::MultiFab *const urban_frac)
Computes simple land and urban weights from the urban fraction.
Definition: ERF_SurfaceModel.cpp:527
amrex::Vector< std::unique_ptr< amrex::MultiFab > > u_star
Definition: ERF_SurfaceModel.H:1051
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_star
Definition: ERF_SurfaceModel.H:1052
void weight_average_fields(int lev, amrex::MultiFab *const)
Applies the current land and urban weights to selected model fields.
Definition: ERF_SurfaceModel.cpp:679
amrex::Vector< int > m_surface_outputs_requested
Definition: ERF_SurfaceModel.H:1056
Here is the call graph for this function:

◆ clear_level()

void SurfaceModel::clear_level ( int  lev)
inline

Removes an AMR level, and every level above it, from the surface model.

Called when ERF destroys a level during regridding so that m_nlevs tracks the levels that actually exist. Otherwise the checkpoint and other level loops would still visit the destroyed levels and their stale provider pointers.

Parameters
levAMR level being destroyed.
235  {
236  if (lev < 0 || lev >= m_nlevs) { return; }
237  resize_levels(lev);
238  }
Here is the call graph for this function:

◆ deactivate_transient_field_maps()

void SurfaceModel::deactivate_transient_field_maps ( )

Disables and releases mapped fields used only by the last output.

667 {
668  for (auto& entry : fieldmap) {
669  Field& field = entry.second;
670  if (field.persistent_consumer) { continue; }
671  field.active = false;
672  if (field.mf_ind == -1) { continue; }
673  for (auto& mf : fields[field.mf_ind]) {
674  mf.reset();
675  }
676  }
677 }

◆ distribute_radiation_output()

void SurfaceModel::distribute_radiation_output ( int  lev,
int  output_index 
)

Distributes one updated canonical radiation output to other providers.

Parameters
levAMR level to update.
output_indexIndex in the canonical radiation output list.
447 {
448  AMREX_ALWAYS_ASSERT(output_index >= 0 &&
449  output_index < static_cast<int>(rad_output_names.size()));
450  auto it = radiation_output_map.find(rad_output_names[output_index]);
451  if (it == radiation_output_map.end()) { return; }
452 
453  const int land_idx = it->second.map.first;
454  const int urban_idx = it->second.map.second;
455  const SurfaceProviderMode mode = m_provider_mode[lev];
456  const bool use_land = mode == SurfaceProviderMode::LandOnly ||
458  const bool use_urban = mode == SurfaceProviderMode::UrbanOnly ||
460  MultiFab* lsm = (use_land && land_idx >= 0 && land_idx < static_cast<int>(lsm_data_lev[lev].size()))
461  ? lsm_data_lev[lev][land_idx] : nullptr;
462  MultiFab* urban = (use_urban && urban_idx >= 0 && urban_idx < static_cast<int>(urban_data_lev[lev].size()))
463  ? urban_data_lev[lev][urban_idx] : nullptr;
464 
465  // The LSM destination is always primary when it is available. Urban
466  // receives the updated surface plane only when both destinations exist.
467  if (lsm && urban && lsm != urban) {
468  for (MFIter mfi(*urban, TileNoZ()); mfi.isValid(); ++mfi) {
469  const Box& source_box = (*lsm)[mfi].box();
470  const Box& target_box = (*urban)[mfi].box();
472  source_box.smallEnd(2) <= 0 && source_box.bigEnd(2) >= 0 &&
473  target_box.smallEnd(2) <= 0 && target_box.bigEnd(2) >= 0,
474  "Radiation output destinations must contain the k=0 surface plane");
475  //
476  // These are the *grown* boxes, so the two destinations need not carry the same
477  // ghost vector. Loop over the intersection of the two surface planes rather than
478  // over the target alone: indexing the source at the target's indices would read
479  // out of bounds wherever the urban halo reaches past the LSM one.
480  //
481  const Box copy_slab = makeSlab(source_box, 2, 0) & makeSlab(target_box, 2, 0);
482  if (copy_slab.isEmpty()) { continue; }
483  const auto source = (*lsm)[mfi].array();
484  auto target = (*urban)[mfi].array();
485  amrex::ParallelFor(copy_slab, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
486  target(i, j, k) = source(i, j, k);
487  });
488  }
489  }
490 }
std::unordered_map< std::string, RadiationField > radiation_output_map
Definition: ERF_SurfaceModel.H:1116
static const std::vector< std::string > rad_output_names
Definition: ERF_SurfaceModel.H:1106
Here is the call graph for this function:

◆ distribute_radiation_outputs()

void SurfaceModel::distribute_radiation_outputs ( int  lev)

Distributes updated canonical radiation outputs to other providers.

Parameters
levAMR level to update.
440 {
441  for (int i = 0; i < static_cast<int>(rad_output_names.size()); ++i) {
443  }
444 }
void distribute_radiation_output(int lev, int output_index)
Distributes one updated canonical radiation output to other providers.
Definition: ERF_SurfaceModel.cpp:446

◆ ensure_surface_outputs()

void SurfaceModel::ensure_surface_outputs ( const int  lev)
inlineprivate

Allocates surface-output fields for one level.

Parameters
levAMR level whose outputs should be materialized.
983  {
984  if (u_star[lev] != nullptr) { return; }
985 
986  const amrex::IntVect ng(1,1,0);
987  u_star[lev] = std::make_unique<amrex::MultiFab>(m_ba2d[lev], m_dmap[lev], 2, ng);
988  t_star[lev] = std::make_unique<amrex::MultiFab>(m_ba2d[lev], m_dmap[lev], 1, ng);
989  q_star[lev] = std::make_unique<amrex::MultiFab>(m_ba2d[lev], m_dmap[lev], 1, ng);
990  t_surf[lev] = std::make_unique<amrex::MultiFab>(m_ba2d[lev], m_dmap[lev], 1, ng);
991  u_star[lev]->setVal(0.0);
992  t_star[lev]->setVal(0.0);
993  q_star[lev]->setVal(0.0);
994  t_surf[lev]->setVal(300.0);
995  }
@ ng
Definition: ERF_Morrison.H:51

Referenced by initialize_for_level(), request_surface_layer_outputs(), request_surface_outputs(), and update_provider_mode().

Here is the caller graph for this function:

◆ ensure_weight_factors()

void SurfaceModel::ensure_weight_factors ( const int  lev)
inlineprivate

Creates weighting factors when an external caller requests them.

Parameters
levAMR level whose weighting factors should be materialized.
966  {
967  if (wavg[lev] != nullptr) { return; }
968 
969  const amrex::IntVect ng(1,1,0);
970  wavg[lev] = std::make_unique<amrex::MultiFab>(m_ba2d[lev], m_dmap[lev], 2, ng);
971  const SurfaceProviderMode mode = m_provider_mode[lev];
972  wavg[lev]->setVal(mode != SurfaceProviderMode::UrbanOnly ? 1.0 : 0.0,
973  SurfaceModelType::LAND, 1, 0);
974  wavg[lev]->setVal(mode == SurfaceProviderMode::UrbanOnly ? 1.0 : 0.0,
976  }

Referenced by get_wavg_factors(), and update_provider_mode().

Here is the caller graph for this function:

◆ fields_are_valid()

bool SurfaceModel::fields_are_valid ( ) const
inline

Returns whether the surface models have advanced at least once.

Returns
False until the first advance, while the registered fields still hold their initial values rather than anything the models computed.
470 { return m_fields_are_valid; }
bool m_fields_are_valid
Definition: ERF_SurfaceModel.H:1027

◆ get_field()

amrex::MultiFab* SurfaceModel::get_field ( const std::string &  name,
int  lev = 0 
)
inline

Retrieves a registered field by its common name.

Parameters
nameRegistered field name.
levAMR level to query.
Returns
Pointer to the registered field, or nullptr if it is unknown.
683  {
684  if (auto field = fieldmap.find(name); field != fieldmap.end()) {
686  return fields[field->second.mf_ind][lev].get();
687  } else {
688  return nullptr;
689  }
690  }
Here is the call graph for this function:

◆ get_qstar()

amrex::MultiFab* SurfaceModel::get_qstar ( const int &  lev)
inline

Returns the weighted moisture output for a level.

Parameters
levAMR level to query.
Returns
Pointer to the weighted q-star or vapor-flux field.
495  {
496  return q_star[lev].get();
497  }

◆ get_radiation_fields()

const amrex::Vector<const amrex::MultiFab*> SurfaceModel::get_radiation_fields ( int  lev = 0)
inline

Returns fields registered under the RRTMGP radiation names.

The resulting map is cached after the first call for reuse on subsequent timesteps. When an expected field is missing, it is marked nullptr for the RRTMGP implementation to apply its fallback values.

Parameters
levAMR level to query.
Returns
Radiation fields in the order expected by RRTMGP.
702  {
703  // check if we need to build the radiation list (for first time step,)
704  if (rad_fields[lev].size() == 0) {
706  }
707  return rad_fields[lev];
708  }
void build_radiation_fieldlist(int lev)
Builds the ordered radiation-field list for one AMR level.
Definition: ERF_SurfaceModel.H:777
Here is the call graph for this function:

◆ get_radiation_output_field()

MultiFab * SurfaceModel::get_radiation_output_field ( int  lev,
const std::string &  name 
)

Returns the destination of one canonical radiation output by name.

Parameters
levAMR level to query.
nameCanonical radiation output name (aborts on an unknown name).
Returns
The registered destination, or nullptr when none is registered.
412 {
413  const auto it = std::find(rad_output_names.begin(), rad_output_names.end(), name);
415  "Unknown canonical radiation output: " + name);
416  return get_radiation_output_fields(lev)[static_cast<int>(it - rad_output_names.begin())];
417 }
const amrex::Vector< amrex::MultiFab * > get_radiation_output_fields(int lev=0)
Returns canonical radiation output destinations for one level.
Definition: ERF_SurfaceModel.cpp:378
Here is the call graph for this function:

◆ get_radiation_output_fields()

const Vector< MultiFab * > SurfaceModel::get_radiation_output_fields ( int  lev = 0)

Returns canonical radiation output destinations for one level.

Parameters
levAMR level to query.
Returns
One destination per canonical radiation output.
379 {
380  if (!rad_output_fields[lev].empty()) { return rad_output_fields[lev]; }
381  rad_output_fields[lev].resize(rad_output_names.size(), nullptr);
382  const SurfaceProviderMode mode = m_provider_mode[lev];
383  const bool use_land = mode == SurfaceProviderMode::LandOnly ||
385  const bool use_urban = mode == SurfaceProviderMode::UrbanOnly ||
387  for (int i = 0; i < static_cast<int>(rad_output_names.size()); ++i) {
388  auto it = radiation_output_map.find(rad_output_names[i]);
389  if (it == radiation_output_map.end()) { continue; }
390  const int land_idx = it->second.map.first;
391  const int urban_idx = it->second.map.second;
392  const bool valid_land = use_land && land_idx >= 0 &&
393  land_idx < static_cast<int>(lsm_data_lev[lev].size()) &&
394  lsm_data_lev[lev][land_idx];
395  const bool valid_urban = use_urban && urban_idx >= 0 &&
396  urban_idx < static_cast<int>(urban_data_lev[lev].size()) &&
397  urban_data_lev[lev][urban_idx];
398  if (valid_land) {
399  validate_radiation_output_layout(lev, lsm_data_lev[lev][land_idx]);
400  rad_output_fields[lev][i] = lsm_data_lev[lev][land_idx];
401  } else if (valid_urban) {
402  rad_output_fields[lev][i] = urban_data_lev[lev][urban_idx];
403  }
404  if (valid_urban) {
405  validate_radiation_output_layout(lev, urban_data_lev[lev][urban_idx]);
406  }
407  }
408  return rad_output_fields[lev];
409 }
amrex::Vector< amrex::Vector< amrex::MultiFab * > > rad_output_fields
Definition: ERF_SurfaceModel.H:1104
void validate_radiation_output_layout(int lev, const amrex::MultiFab *mf) const
Validates the layout contract for a canonical radiation output.
Definition: ERF_SurfaceModel.cpp:419

◆ get_surface_flux_view()

SurfaceFluxView SurfaceModel::get_surface_flux_view ( const int  lev,
const amrex::MFIter &  mfi 
) const
inline

Returns provider or blended surface-flux views for one tile.

Parameters
levAMR level to query.
mfiTile whose flux arrays are requested.
Returns
Read-only flux views selected for the level's provider mode.
515  {
517 
518  const SurfaceProviderMode mode = m_provider_mode[lev];
519  if (mode == SurfaceProviderMode::Both) {
520  AMREX_ALWAYS_ASSERT(u_star[lev] != nullptr);
521  AMREX_ALWAYS_ASSERT(t_star[lev] != nullptr);
522  AMREX_ALWAYS_ASSERT(q_star[lev] != nullptr);
523  return {u_star[lev]->const_array(mfi, 0),
524  u_star[lev]->const_array(mfi, 1),
525  t_star[lev]->const_array(mfi),
526  q_star[lev]->const_array(mfi)};
527  }
528 
529  const auto& provider_fields = (mode == SurfaceProviderMode::LandOnly)
531  const auto& provider_data = (mode == SurfaceProviderMode::LandOnly)
532  ? lsm_data_lev[lev] : urban_data_lev[lev];
533  auto get_provider_field = [&] (const int field) -> amrex::Array4<const amrex::Real> {
534  if (field >= static_cast<int>(provider_fields.size())) {
535  return {};
536  }
537  const int index = provider_fields[field];
538  if (index < 0 || index >= static_cast<int>(provider_data.size()) ||
539  provider_data[index] == nullptr) {
540  return {};
541  }
542  return provider_data[index]->const_array(mfi);
543  };
544 
545  return {get_provider_field(0), get_provider_field(1),
546  get_provider_field(2), get_provider_field(3)};
547  }
Here is the call graph for this function:

◆ get_tstar()

amrex::MultiFab* SurfaceModel::get_tstar ( const int &  lev)
inline

Returns the weighted thermal output for a level.

Parameters
levAMR level to query.
Returns
Pointer to the weighted t-star or heat-flux field.
486  {
487  return t_star[lev].get();
488  }

◆ get_tsurf()

amrex::MultiFab* SurfaceModel::get_tsurf ( const int &  lev)
inline

Returns the weighted surface-temperature output for a level.

Parameters
levAMR level to query.
Returns
Pointer to the weighted surface-temperature field.
504  {
505  return t_surf[lev].get();
506  }

◆ get_ustar()

amrex::MultiFab* SurfaceModel::get_ustar ( const int &  lev)
inline

Returns the weighted horizontal momentum output for a level.

Parameters
levAMR level to query.
Returns
Pointer to the weighted u-star or momentum-flux field.
477  {
478  return u_star[lev].get();
479  }

◆ get_wavg_factors()

amrex::MultiFab* SurfaceModel::get_wavg_factors ( const int &  lev)
inline

Returns the land and urban weighting factors for a level.

Parameters
levAMR level to query.
Returns
Pointer to the weighting-factor field.
600  {
602  return wavg[lev].get();
603  }
void ensure_weight_factors(const int lev)
Creates weighting factors when an external caller requests them.
Definition: ERF_SurfaceModel.H:965
Here is the call graph for this function:

◆ get_weighted_model_data()

const amrex::MultiFab* SurfaceModel::get_weighted_model_data ( const int  lev,
SurfaceModelType  type,
const int  field_idx 
) const
inline

Returns weighted data for a selected model field.

Parameters
levAMR level to query.
typeProvider type owning the field.
field_idxProvider field index.
Returns
Weighted field or the source field for a single provider, or nullptr if it has not been selected.
558  {
559  const SurfaceProviderMode mode = m_provider_mode[lev];
560  const bool single_provider =
563  if (single_provider) {
564  const auto& selected = (type == SurfaceModelType::LAND)
566  const int first_output_field = m_export_fluxes ? 5 : 4;
567  bool is_weighted_field = false;
568  for (int field = first_output_field;
569  field < static_cast<int>(selected.size()); ++field) {
570  if (selected[field] == field_idx) {
571  is_weighted_field = true;
572  break;
573  }
574  }
575  if (!is_weighted_field) {
576  return nullptr;
577  }
578 
579  const auto& source = (type == SurfaceModelType::LAND)
580  ? lsm_data_lev[lev] : urban_data_lev[lev];
581  if (field_idx < 0 || field_idx >= static_cast<int>(source.size())) {
582  return nullptr;
583  }
584  return source[field_idx];
585  }
586 
587  const auto& weighted = (type == SurfaceModelType::LAND)
589  if (field_idx < 0 || field_idx >= static_cast<int>(weighted.size())) {
590  return nullptr;
591  }
592  return weighted[field_idx].get();
593  }

◆ GotoNextLine()

void SurfaceModel::GotoNextLine ( std::istream &  is)
static

Advances an input stream to the next line.

taken from ERF_Checkpoint, reuse?

Parameters
isInput stream to advance.
825 {
826  constexpr std::streamsize bl_ignore_max { 100000 };
827  is.ignore(bl_ignore_max, '\n');
828 }

◆ initialize_for_level()

void SurfaceModel::initialize_for_level ( int  lev,
const amrex::BoxArray &  ba,
const amrex::Geometry &  geom,
const amrex::DistributionMapping &  dm,
const amrex::Vector< std::unique_ptr< amrex::iMultiFab >> &  lmask_lev,
const amrex::Vector< amrex::BCRec > &  domain_bcs_type,
const amrex::Vector< amrex::IntVect > &  refRatio 
)
inline

Initializes storage and boundary data for an AMR level.

Existing coarser-level data are interpolated when a finer level is initialized.

Parameters
levAMR level to initialize.
baCell-centered box array for the level.
geomGeometry for the level.
dmDistribution mapping for the level.
lmask_levLand-mask data used by the level.
domain_bcs_typeBoundary conditions for interpolation.
refRatioRefinement ratios between adjacent levels.
91  {
92  if (lev >= m_nlevs) { resize_levels(lev+1); }
93 
94  m_ba[lev] = ba;
95  m_geom[lev] = geom;
96  m_dmap[lev] = dm;
97 
98  // Create a 2D ba, dm, & ghost cells
99  amrex::BoxList bl2d = ba.boxList();
100  for (auto& b : bl2d) {
101  b.setRange(2, 0);
102  }
103  m_ba2d[lev] = amrex::BoxArray(std::move(bl2d));
104 
105  amrex::RealBox dom2d = geom.ProbDomain();
106  dom2d.setHi(2, geom.CellSize(2));
107  m_geom2d[lev].define(makeSlab(m_geom[lev].Domain(), 2, 0), dom2d, geom.Coord(), geom.isPeriodic());
108 
109  m_lmask[lev] = lmask_lev[0].get();
110 
111  weighted_lsm_data_lev[lev].clear();
112  weighted_urban_data_lev[lev].clear();
113 
114  u_star[lev].reset();
115  t_star[lev].reset();
116  q_star[lev].reset();
117  t_surf[lev].reset();
118  wavg[lev].reset();
119  if (m_surface_outputs_requested[lev]) {
121  }
123 
124  // Recreate registered fields on the current level layout. This is also
125  // needed when an existing AMR level is remade with a new grid layout.
126  for (int field = 0; field < static_cast<int>(fields.size()); ++field) {
127  fields[field][lev].reset();
128  }
129  // Keep each map's consumer kind: a map active only for one output must not
130  // become persistent because a regrid happened while it was active
131  for (const auto& entry : fieldmap) {
132  if (entry.second.active) {
133  activate_field_map(entry.first, entry.second.persistent_consumer);
134  }
135  }
136  for (auto& entry : radiation_input_map) {
137  entry.second.weighted[lev].reset();
138  }
139 
140  // Cached radiation fields point into the registered storage above.
141  // Rebuild the cache when this level's fields have been replaced.
142  rad_fields[lev].clear();
143  rad_output_fields[lev].clear();
144 
145  // Interpolate the new finer level using coarse data
146  if (lev > 0) {
147  amrex::Real time_for_fp = zero; // This is not actually used
148  amrex::Vector<amrex::Real> ftime = {time_for_fp, time_for_fp};
149  amrex::Vector<amrex::Real> ctime = {time_for_fp, time_for_fp};
150  amrex::IntVect ng_od(0, 0, 0); // ng outside domain
151  amrex::Interpolater* mapper = &amrex::cell_cons_interp;
152  amrex::Vector<amrex::MultiFab*> fmf;
153  amrex::Vector<amrex::MultiFab*> cmf;
154  if (u_star[lev] != nullptr && u_star[lev-1] != nullptr) {
155  amrex::InterpFromCoarseLevel(*u_star[lev], u_star[lev-1]->nGrowVect(), ng_od,
156  *u_star[lev-1], 0, 0, 2,
157  m_geom[lev-1], m_geom[lev],
158  refRatio[lev-1], &amrex::cell_cons_interp,
159  domain_bcs_type, BCVars::cons_bc);
160  amrex::InterpFromCoarseLevel(*t_star[lev], t_star[lev-1]->nGrowVect(), ng_od,
161  *t_star[lev-1], 0, 0, 1,
162  m_geom[lev-1], m_geom[lev],
163  refRatio[lev-1], &amrex::cell_cons_interp,
164  domain_bcs_type, BCVars::cons_bc);
165  amrex::InterpFromCoarseLevel(*q_star[lev], q_star[lev-1]->nGrowVect(), ng_od,
166  *q_star[lev-1], 0, 0, 1,
167  m_geom[lev-1], m_geom[lev],
168  refRatio[lev-1], &amrex::cell_cons_interp,
169  domain_bcs_type, BCVars::cons_bc);
170  amrex::InterpFromCoarseLevel(*t_surf[lev], t_surf[lev-1]->nGrowVect(), ng_od,
171  *t_surf[lev-1], 0, 0, 1,
172  m_geom[lev-1], m_geom[lev],
173  refRatio[lev-1], &amrex::cell_cons_interp,
174  domain_bcs_type, BCVars::cons_bc);
175 
176  fmf = {u_star[lev ].get(), u_star[lev ].get()};
177  cmf = {u_star[lev-1].get(), u_star[lev-1].get()};
178  amrex::FillPatchTwoLevels(*u_star[lev].get(), u_star[lev]->nGrowVect(), ng_od,
179  time_for_fp, cmf, ctime, fmf, ftime,
180  0, 0, 2, m_geom[lev-1], m_geom[lev],
181  refRatio[lev-1], mapper, domain_bcs_type,
183  fmf = {t_star[lev ].get(), t_star[lev ].get()};
184  cmf = {t_star[lev-1].get(), t_star[lev-1].get()};
185  amrex::FillPatchTwoLevels(*t_star[lev].get(), t_star[lev]->nGrowVect(), ng_od,
186  time_for_fp, cmf, ctime, fmf, ftime,
187  0, 0, 1, m_geom[lev-1], m_geom[lev],
188  refRatio[lev-1], mapper, domain_bcs_type,
190  fmf = {q_star[lev ].get(), q_star[lev ].get()};
191  cmf = {q_star[lev-1].get(), q_star[lev-1].get()};
192  amrex::FillPatchTwoLevels(*q_star[lev].get(), q_star[lev]->nGrowVect(), ng_od,
193  time_for_fp, cmf, ctime, fmf, ftime,
194  0, 0, 1, m_geom[lev-1], m_geom[lev],
195  refRatio[lev-1], mapper, domain_bcs_type,
197  fmf = {t_surf[lev ].get(), t_surf[lev ].get()};
198  cmf = {t_surf[lev-1].get(), t_surf[lev-1].get()};
199  amrex::FillPatchTwoLevels(*t_surf[lev].get(), t_surf[lev]->nGrowVect(), ng_od,
200  time_for_fp, cmf, ctime, fmf, ftime,
201  0, 0, 1, m_geom[lev-1], m_geom[lev],
202  refRatio[lev-1], mapper, domain_bcs_type,
204  }
205 
206  for (int field = 0; field < static_cast<int>(fields.size()); ++field) {
207  if (fields[field][lev] == nullptr || fields[field][lev-1] == nullptr) { continue; }
208  amrex::IntVect ngv = fields[field][lev-1]->nGrowVect(); ngv[2] = 0;
209  amrex::InterpFromCoarseLevel(*fields[field][lev], ngv, ng_od,
210  *fields[field][lev-1], 0, 0, 1,
211  m_geom[lev-1], m_geom[lev],
212  refRatio[lev-1], &amrex::cell_cons_interp,
213  domain_bcs_type, BCVars::cons_bc);
214  fmf = {fields[field][lev ].get(), fields[field][lev ].get()};
215  cmf = {fields[field][lev-1].get(), fields[field][lev-1].get()};
216  amrex::FillPatchTwoLevels(*fields[field][lev].get(), ngv, ng_od,
217  time_for_fp, cmf, ctime, fmf, ftime,
218  0, 0, 1, m_geom[lev-1], m_geom[lev],
219  refRatio[lev-1], mapper, domain_bcs_type,
221  }
222  }
223  }
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:36
void ensure_surface_outputs(const int lev)
Allocates surface-output fields for one level.
Definition: ERF_SurfaceModel.H:982
amrex::Vector< amrex::Geometry > m_geom2d
Definition: ERF_SurfaceModel.H:1039
amrex::Vector< amrex::iMultiFab * > m_lmask
Definition: ERF_SurfaceModel.H:1040
@ cons_bc
Definition: ERF_IndexDefines.H:110
Here is the call graph for this function:

◆ is_field_mapped()

bool SurfaceModel::is_field_mapped ( int  lev,
SurfaceModelType  type,
int  field_idx,
const amrex::MultiFab *  mf 
) const

Checks whether a model field participates in a registered mapping.

Parameters
levAMR level to query.
typeLand or urban model owning the field.
field_idxIndex of the model field.
mfField pointer to test.
Returns
True when the field is part of a registered mapping.
494 {
495  for (const auto& entry : fieldmap) {
496  const Field& field = entry.second;
497 
498  const int mapped_idx = (type == SurfaceModelType::LAND)
499  ? field.map.first : field.map.second;
500  if (mapped_idx == field_idx) {
501  return true;
502  }
503 
504  // Pointer-based mappings have no model-field index. Match the
505  // registered pointer for the current level instead.
506  if (field.map.first == -1 && field.map.second == -1 && mf != nullptr) {
507  const auto& ptrs = (type == SurfaceModelType::LAND)
508  ? field.lsm_ptr : field.urb_ptr;
509  if (lev < static_cast<int>(ptrs.size()) && ptrs[lev] == mf) {
510  return true;
511  }
512  }
513  }
514 
515  for (const auto& entry : radiation_input_map) {
516  const auto& map = entry.second.map;
517  const int mapped_idx = (type == SurfaceModelType::LAND)
518  ? map.first : map.second;
519  if (mapped_idx == field_idx) {
520  return true;
521  }
522  }
523 
524  return false;
525 }
if(KE_decay_height > 0 &&state_pert.nComp() > RhoKE_comp)
Definition: ERF_InitCustomPert_ABL.H:74
real(c_double), private map
Definition: ERF_module_mp_morr_two_moment.F90:222
Here is the call graph for this function:

◆ mark_fields_valid()

void SurfaceModel::mark_fields_valid ( )
inline

Marks the registered surface fields as filled by an actual model advance.

463 { m_fields_are_valid = true; }

◆ ReadCheckpoint()

void SurfaceModel::ReadCheckpoint ( const std::string &  checkpointname)

Restores surface-model state from a checkpoint file.

Parameters
checkpointnameCheckpoint file name.
948 {
951  auto check_start = amrex::second();
952 
953  amrex::Print() << " Reading SurfaceModel checkpoint " << std::endl;
954 
955  auto checkpoint_error = [&] (const std::string& message) {
956  amrex::Abort("SurfaceModel checkpoint '" + checkpointname + "': " + message);
957  };
958 
959  // Header
960  const std::string File(checkpointname + "/SurfaceModel_Header");
961  if (!amrex::FileExists(File)) {
962  checkpoint_error("missing SurfaceModel_Header");
963  }
964 
965  Vector<char> fileCharPtr;
966  ParallelDescriptor::ReadAndBcastFile(File, fileCharPtr);
967  std::string fileCharPtrString(fileCharPtr.dataPtr(), fileCharPtr.size());
968  std::istringstream is(fileCharPtrString, std::istringstream::in);
969 
970  std::string line;
971 
972  // read in title line
973  if (!std::getline(is, line) || line != "Checkpoint file for SurfaceModel") {
974  checkpoint_error("invalid or truncated header title");
975  }
976 
977  // read in number of levels
978  int chk_nlevs = -1;
979  if (!(is >> chk_nlevs) || chk_nlevs < 1) {
980  checkpoint_error("invalid number of levels");
981  }
982 
983  // read in flags
984  bool chk_use_urban = false;
985  bool chk_use_land = false;
986  bool chk_export_fluxes = false;
987  bool chk_weights_updated = false;
988  bool chk_fields_are_valid = false;
989  if (!(is >> chk_use_urban >> chk_use_land >> chk_export_fluxes >> chk_weights_updated
990  >> chk_fields_are_valid)) {
991  checkpoint_error("invalid flags in header");
992  }
993  GotoNextLine(is);
994 
995  if (chk_nlevs != m_nlevs) {
996  checkpoint_error("level count mismatch: checkpoint has " + std::to_string(chk_nlevs) +
997  ", current SurfaceModel has " + std::to_string(m_nlevs));
998  }
999  if (chk_use_urban != m_use_urban || chk_use_land != m_use_land ||
1000  chk_export_fluxes != m_export_fluxes) {
1001  checkpoint_error("model configuration flags do not match the current SurfaceModel");
1002  }
1003 
1004  // Read box arrays into temporaries so current runtime geometry is not
1005  // overwritten before it has been validated.
1006  amrex::Vector<amrex::BoxArray> chk_ba(chk_nlevs);
1007  amrex::Vector<amrex::BoxArray> chk_ba2d(chk_nlevs);
1008  for (int lev = 0; lev < chk_nlevs; ++lev) {
1009  chk_ba[lev].readFrom(is);
1010  if (!is) checkpoint_error("invalid 3D BoxArray for level " + std::to_string(lev));
1011  GotoNextLine(is);
1012  }
1013  GotoNextLine(is);
1014  for (int lev = 0; lev < chk_nlevs; ++lev) {
1015  chk_ba2d[lev].readFrom(is);
1016  if (!is) checkpoint_error("invalid 2D BoxArray for level " + std::to_string(lev));
1017  GotoNextLine(is);
1018  }
1019  GotoNextLine(is);
1020 
1021  // The checkpointed fields are read back on the decomposition they were written with and
1022  // then redistributed onto the live one, so the two BoxArrays need not match box for box.
1023  // They do have to cover the same index space: ERF turns regridding of level 0 on by itself
1024  // when a restart uses more ranks than level 0 has boxes (see ERF::restart), which changes
1025  // the boxes but not the region they tile.
1026  const std::string coverage_hint =
1027  "; the SurfaceModel checkpoint can be read back on a different decomposition, but not "
1028  "on a different domain -- check that the restart uses the same grid extents and "
1029  "refinement as the run that wrote the checkpoint";
1030  amrex::Vector<amrex::DistributionMapping> chk_dmap(chk_nlevs);
1031  for (int lev = 0; lev < chk_nlevs; ++lev) {
1032  if (chk_ba[lev].minimalBox() != m_ba[lev].minimalBox()) {
1033  checkpoint_error("3D domain coverage mismatch at level " + std::to_string(lev) +
1034  coverage_hint);
1035  }
1036  if (chk_ba2d[lev].minimalBox() != m_ba2d[lev].minimalBox()) {
1037  checkpoint_error("2D domain coverage mismatch at level " + std::to_string(lev) +
1038  coverage_hint);
1039  }
1040  chk_dmap[lev].define(chk_ba2d[lev]);
1041  }
1042 
1043  // Read in LSM fields
1044  if (!std::getline(is, line)) {
1045  checkpoint_error("missing LSM field list");
1046  }
1047  amrex::Vector<int> chk_lsm_fields;
1048  std::istringstream lsm_stream(line);
1049  int field_idx = -1;
1050  while (lsm_stream >> field_idx) chk_lsm_fields.push_back(field_idx);
1051 
1052  // Read in Urban fields
1053  if (!std::getline(is, line)) {
1054  checkpoint_error("missing urban field list");
1055  }
1056  amrex::Vector<int> chk_urban_fields;
1057  std::istringstream urban_stream(line);
1058  while (urban_stream >> field_idx) chk_urban_fields.push_back(field_idx);
1059  GotoNextLine(is);
1060 
1061  // The entries are indices into the provider's data list, with the fluxes appended after
1062  // the data fields, so they move whenever a model adds a data field (Noah-MP grew from 27
1063  // to 29 when snow and snowh were added). Nothing is restored from them -- the fields
1064  // themselves are read by name below -- so only require that the same slots are in use.
1065  auto same_slots = [] (const amrex::Vector<int>& a, const amrex::Vector<int>& b) {
1066  if (a.size() != b.size()) return false;
1067  for (int i = 0; i < static_cast<int>(a.size()); ++i) {
1068  if ((a[i] == -1) != (b[i] == -1)) return false;
1069  }
1070  return true;
1071  };
1072  if (!same_slots(chk_lsm_fields, lsm_fields) || !same_slots(chk_urban_fields, urban_fields)) {
1073  checkpoint_error("model field lists do not match the current SurfaceModel");
1074  }
1075 
1076  // Read number of mapped fields
1077  int nfields = 0;
1078  if (!(is >> nfields) || nfields < 0) {
1079  checkpoint_error("invalid mapped-field count");
1080  }
1081  GotoNextLine(is);
1082 
1083  // Read any mapped fields
1084  if (nfields != static_cast<int>(fieldmap.size())) {
1085  checkpoint_error("mapped-field count does not match the current SurfaceModel");
1086  }
1087 
1088  // The field mapping should already be created before the restart, but verify consistency with the file
1089  amrex::Vector<std::string> checkpoint_field_names;
1090  for (int i = 0; i < nfields; i++) {
1091  if (!std::getline(is, line)) {
1092  checkpoint_error("missing mapped-field entry " + std::to_string(i));
1093  }
1094  std::istringstream lis(line);
1095 
1096  std::string field_name;
1097  int chk_mf_ind = -1;
1098  int lsm_ind = -1;
1099  int urb_ind = -1;
1100  int fill_bound_int = -1;
1101  if (!(lis >> field_name >> chk_mf_ind >> lsm_ind >> urb_ind >> fill_bound_int) ||
1102  chk_mf_ind < 0 || lsm_ind < -1 || urb_ind < -1 ||
1103  (fill_bound_int != 0 && fill_bound_int != 1)) {
1104  checkpoint_error("invalid mapped-field entry for '" + field_name + "'");
1105  }
1106  const bool fill_bound = (fill_bound_int != 0);
1107 
1108  auto field_it = fieldmap.find(field_name);
1109  if (field_it == fieldmap.end()) {
1110  checkpoint_error("checkpoint mapping '" + field_name + "' is not registered");
1111  }
1112  for (const auto& seen_name : checkpoint_field_names) {
1113  if (seen_name == field_name) {
1114  checkpoint_error("duplicate mapped-field entry for '" + field_name + "'");
1115  }
1116  }
1117  checkpoint_field_names.push_back(field_name);
1118  const auto &field = field_it->second;
1119  if (field.mf_ind < 0 || field.mf_ind >= static_cast<int>(fields.size())) {
1120  checkpoint_error("current mapped-field storage is invalid for '" + field_name + "'");
1121  }
1122  if (static_cast<int>(fields[field.mf_ind].size()) != m_nlevs) {
1123  checkpoint_error("current mapped-field level storage is invalid for '" + field_name + "'");
1124  }
1125  // As above, the provider indices may have shifted; only whether the field maps to the
1126  // land and/or urban provider has to match.
1127  if ((field.map.first == -1) != (lsm_ind == -1) ||
1128  (field.map.second == -1) != (urb_ind == -1) ||
1129  field.fill_bound != fill_bound) {
1130  checkpoint_error("mapped-field metadata mismatch for '" + field_name + "'");
1131  }
1132  if (lsm_ind == -1 && urb_ind == -1) {
1133  if (static_cast<int>(field.lsm_ptr.size()) != m_nlevs ||
1134  static_cast<int>(field.urb_ptr.size()) != m_nlevs) {
1135  checkpoint_error("pointer mapping has the wrong number of levels for '" + field_name + "'");
1136  }
1137  for (int lev = 0; lev < m_nlevs; ++lev) {
1138  if (fields[field.mf_ind][lev] == nullptr) {
1139  checkpoint_error("mapped-field storage is missing at level " +
1140  std::to_string(lev) + " for '" + field_name + "'");
1141  }
1142  }
1143  } else {
1144  for (int lev = 0; lev < m_nlevs; ++lev) {
1145  const bool invalid_land_index =
1146  !lsm_data_lev[lev].empty() &&
1147  field.map.first >= static_cast<int>(lsm_data_lev[lev].size());
1148  const bool invalid_urban_index =
1149  !urban_data_lev[lev].empty() &&
1150  field.map.second >= static_cast<int>(urban_data_lev[lev].size());
1151  if (invalid_land_index || invalid_urban_index) {
1152  checkpoint_error("mapped-field index is out of range for '" + field_name + "'");
1153  }
1154  if (fields[field.mf_ind][lev] == nullptr) {
1155  checkpoint_error("mapped-field storage is missing at level " +
1156  std::to_string(lev) + " for '" + field_name + "'");
1157  }
1158  }
1159  }
1160  amrex::ignore_unused(chk_mf_ind);
1161  }
1162 
1163  const std::string prefix = "SurfaceModel_";
1164 
1165  IntVect ng = IntVect(1,1,0);
1166  auto validate_multifab_header = [&] (const std::string& name, int lev, int ncomp) {
1167  const std::string header_name = name + "_H";
1168  if (!amrex::FileExists(header_name)) {
1169  checkpoint_error("missing MultiFab header '" + header_name + "'");
1170  }
1171 
1172  Vector<char> header_data;
1173  ParallelDescriptor::ReadAndBcastFile(header_name, header_data);
1174  std::istringstream header_stream(
1175  std::string(header_data.dataPtr(), header_data.size()), std::istringstream::in);
1176  VisMF::Header header;
1177  if (!(header_stream >> header)) {
1178  checkpoint_error("invalid MultiFab header '" + header_name + "'");
1179  }
1180  if (header.m_ncomp != ncomp || header.m_ngrow != ng ||
1181  !(header.m_ba == chk_ba2d[lev])) {
1182  checkpoint_error("MultiFab layout mismatch for level " + std::to_string(lev) +
1183  " in '" + name + "'");
1184  }
1185  if (static_cast<amrex::Long>(header.m_fod.size()) != chk_ba2d[lev].size()) {
1186  checkpoint_error("MultiFab FAB count mismatch for level " + std::to_string(lev) +
1187  " in '" + name + "'");
1188  }
1189  const std::string data_dir = VisMF::DirName(name);
1190  for (const auto& fab_file : header.m_fod) {
1191  if (!amrex::FileExists(data_dir + fab_file.m_name)) {
1192  checkpoint_error("missing MultiFab data file '" + data_dir + fab_file.m_name + "'");
1193  }
1194  }
1195  };
1196 
1197  // Validate all required data headers before changing any runtime fields.
1198  for (int lev = 0; lev < m_nlevs; ++lev) {
1199  validate_multifab_header(MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "ustar"), lev, 2);
1200  validate_multifab_header(MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "wavg"), lev, 2);
1201  validate_multifab_header(MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "tstar"), lev, 1);
1202  validate_multifab_header(MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "qstar"), lev, 1);
1203  validate_multifab_header(MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "tsurf"), lev, 1);
1204  for (const auto &field : fieldmap) {
1205  validate_multifab_header(
1206  MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "_f_" + field.first), lev, 1);
1207  }
1208  }
1209 
1210  //
1211  // Move a field that was read on the checkpointed layout onto the live one. When the two
1212  // layouts are identical this is the same local copy it always was, so an unchanged restart
1213  // stays bitwise identical; otherwise the data is redistributed.
1214  //
1215  // The redistribution is done in two passes because the checkpoint carries ghost cells and
1216  // a single ParallelCopy over grown source boxes would leave the outcome up to the order in
1217  // which overlapping sources happen to be applied. The first pass seeds everything,
1218  // including the ghost cells outside a non-periodic physical boundary, which no amount of
1219  // valid-region copying can reach. The second pass then lays the valid data over the top,
1220  // so wherever a cell is covered by a valid source cell that value wins.
1221  //
1222  auto redistribute = [&] (MultiFab& dst, const MultiFab& src, int ncomp, int lev)
1223  {
1224  if (dst.boxArray() == src.boxArray() && dst.DistributionMap() == src.DistributionMap()) {
1225  MultiFab::Copy(dst, src, 0, 0, ncomp, ng);
1226  return;
1227  }
1228  dst.ParallelCopy(src, 0, 0, ncomp, ng, ng);
1229  dst.ParallelCopy(src, 0, 0, ncomp, IntVect(0), ng, m_geom2d[lev].periodicity());
1230  };
1231 
1232  for (int lev = 0; lev < m_nlevs; lev++) {
1233  {
1234  MultiFab mf(chk_ba2d[lev],chk_dmap[lev],2,ng);
1235  VisMF::Read(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "ustar"));
1236  redistribute(*(u_star[lev]),mf,2,lev);
1237 
1238  VisMF::Read(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "wavg"));
1239  ensure_weight_factors(lev);
1240  redistribute(*wavg[lev],mf,2,lev);
1241  }
1242 
1243  MultiFab mf(chk_ba2d[lev],chk_dmap[lev],1,ng);
1244 
1245  VisMF::Read(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "tstar"));
1246  redistribute(*t_star[lev],mf,1,lev);
1247 
1248  VisMF::Read(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "qstar"));
1249  redistribute(*q_star[lev],mf,1,lev);
1250 
1251  VisMF::Read(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "tsurf"));
1252  redistribute(*t_surf[lev],mf,1,lev);
1253 
1254  for (auto &field : fieldmap) {
1255  VisMF::Read(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "_f_" + field.first));
1256  redistribute(*(fields[field.second.mf_ind][lev]), mf, 1, lev);
1257  }
1258  }
1259 
1260  m_weights_updated = chk_weights_updated;
1261  // Restore whether the surface models had already integrated a step when the checkpoint
1262  // was written. Without this a restart would spend its first step treating the
1263  // checkpointed u*/t*/q* as unfilled and fall back to the MOST values instead.
1264  m_fields_are_valid = chk_fields_are_valid;
1265 
1268 
1269  auto check_end = amrex::second() - check_start;
1270  ParallelDescriptor::ReduceRealMax(check_end,ParallelDescriptor::IOProcessorNumber());
1271  amrex::Print() << " SurfaceModel Checkpoint load time = " << check_end << " seconds." << '\n';
1272 }
static void GotoNextLine(std::istream &is)
Advances an input stream to the next line.
Definition: ERF_SurfaceModel.cpp:824
void release_transient_surface_outputs()
Releases surface outputs that have no persistent consumer.
Definition: ERF_SurfaceModel.H:1000
bool m_use_urban
Definition: ERF_SurfaceModel.H:1018
void activate_all_field_maps(const bool persistent=true)
Activates all registered mapped fields for an output consumer.
Definition: ERF_SurfaceModel.cpp:659
void request_surface_outputs(const bool persistent=true)
Enables and materializes the surface outputs consumed by ERF.
Definition: ERF_SurfaceModel.H:437
void deactivate_transient_field_maps()
Disables and releases mapped fields used only by the last output.
Definition: ERF_SurfaceModel.cpp:666
bool m_use_land
Definition: ERF_SurfaceModel.H:1019

◆ register_field_map() [1/2]

void SurfaceModel::register_field_map ( std::string  name,
amrex::Vector< amrex::MultiFab * > &  lsm_lev_mf,
amrex::Vector< amrex::MultiFab * > &  urb_lev_mf,
bool  fill_boundary = false 
)

Registers a field map using per-level land and urban pointers.

Parameters
nameCommon name used to retrieve the mapped field.
lsm_lev_mfLand-model fields, indexed by AMR level.
urb_lev_mfUrban-model fields, indexed by AMR level.
fill_boundaryWhether to fill mapped-field boundaries.
591 {
592  // Register a field using explicit data pointers, rather than indices into lsm_data and urban_data
593  AMREX_ALWAYS_ASSERT(lsm_lev_mf.size() > 0);
594  amrex::Print() << " adding mapping between LSM<->Urban mfs <"<<lsm_lev_mf[0] << "," << urb_lev_mf[0] << "> to common surface name " << name << std::endl;
595 
596  AMREX_ALWAYS_ASSERT(lsm_lev_mf.size() == urb_lev_mf.size());
597  if (fieldmap.find(name) == fieldmap.end()) {
598 
599  Field field;
600  field.map = std::pair<int,int>(-1,-1);
601  field.mf_ind = -1;
602  field.fill_bound = fill_boundary;
603  field.lsm_ptr = lsm_lev_mf;
604  field.urb_ptr = urb_lev_mf;
605 
606  // Storage is allocated when a consumer activates this mapping.
607  amrex::Vector<std::unique_ptr<amrex::MultiFab>> mf_lev(m_nlevs);
608  fields.push_back(std::move(mf_lev));
609  field.mf_ind = fields.size() - 1;
610 
611  amrex::Print() << " -- created at ind = " << field.mf_ind << std::endl;
612 
613  fieldmap.insert({name, field});
614  } else {
615  return;
616  }
617 }
Here is the call graph for this function:

◆ register_field_map() [2/2]

void SurfaceModel::register_field_map ( std::string  name,
const std::pair< int, int > &  lsm_urb_map,
bool  fill_boundary = false 
)

Registers a field map using land and urban field indices.

Parameters
nameCommon name used to retrieve the mapped field.
lsm_urb_mapPair of land and urban field indices.
fill_boundaryWhether to fill mapped-field boundaries.
566 {
567  amrex::Print() << " adding mapping between LSM<->Urban fields <"<<lsm_urb_map.first << "," << lsm_urb_map.second << "> to common surface name " << name << std::endl;
568 
569  AMREX_ALWAYS_ASSERT(!(lsm_urb_map.first == -1 && lsm_urb_map.second == -1));
570  if (fieldmap.find(name) == fieldmap.end()) {
571 
572  Field field;
573  field.map = lsm_urb_map;
574  field.mf_ind = -1;
575  field.fill_bound = fill_boundary;
576 
577  // Storage is allocated when a consumer activates this mapping.
578  amrex::Vector<std::unique_ptr<amrex::MultiFab>> mf_lev(m_nlevs);
579  fields.push_back(std::move(mf_lev));
580  field.mf_ind = fields.size() - 1;
581 
582  amrex::Print() << " -- created at ind = " << field.mf_ind << std::endl;
583 
584  fieldmap.insert({name, field});
585  } else {
586  return;
587  }
588 }
Here is the call graph for this function:

◆ register_radiation_input()

void SurfaceModel::register_radiation_input ( const std::string &  name,
const std::pair< int, int > &  lsm_urb_map 
)

Registers a canonical radiation input mapping.

Parameters
nameCanonical radiation input name.
lsm_urb_mapPair of land and urban model field indices.
311 {
312  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(!(map.first == -1 && map.second == -1),
313  "A radiation input must have a provider mapping");
315  std::find(radnames.begin(), radnames.end(), name) != radnames.end(),
316  "Unknown canonical radiation input: " + name);
317  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(map.first >= -1 && map.second >= -1,
318  "Radiation input indices must be -1 or nonnegative");
319  if (map.first >= 0) {
320  AMREX_ALWAYS_ASSERT(map.first < static_cast<int>(m_lsm_names.size()));
321  }
322  if (map.second >= 0) {
323  AMREX_ALWAYS_ASSERT(map.second < static_cast<int>(m_urban_names.size()));
324  }
325  RadiationField field;
326  field.map = map;
327  field.weighted.resize(m_nlevs);
328  radiation_input_map[name] = std::move(field);
329  for (auto& fields_at_level : rad_fields) { fields_at_level.clear(); }
330 }
amrex::Vector< std::string > m_lsm_names
Definition: ERF_SurfaceModel.H:1100
amrex::Vector< std::string > m_urban_names
Definition: ERF_SurfaceModel.H:1101
Here is the call graph for this function:

◆ register_radiation_inputs()

void SurfaceModel::register_radiation_inputs ( const std::unordered_map< std::string, std::pair< int, int >> &  input_map)

Registers canonical radiation input mappings.

Parameters
input_mapCanonical names mapped to land and urban indices.
334 {
335  for (const auto& entry : input_map) {
336  register_radiation_input(entry.first, entry.second);
337  }
338 }
void register_radiation_input(const std::string &name, const std::pair< int, int > &lsm_urb_map)
Registers a canonical radiation input mapping.
Definition: ERF_SurfaceModel.cpp:309

◆ register_radiation_output()

void SurfaceModel::register_radiation_output ( const std::string &  name,
const std::pair< int, int > &  lsm_urb_map 
)

Registers a canonical radiation output mapping.

Parameters
nameCanonical radiation output name.
lsm_urb_mapPair of land and urban model field indices.
342 {
343  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(!(map.first == -1 && map.second == -1),
344  "A radiation output must have a provider mapping");
346  std::find(rad_output_names.begin(), rad_output_names.end(), name) != rad_output_names.end(),
347  "Unknown canonical radiation output: " + name);
348  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(map.first >= -1 && map.second >= -1,
349  "Radiation output indices must be -1 or nonnegative");
350  if (map.first >= 0) {
351  AMREX_ALWAYS_ASSERT(map.first < static_cast<int>(m_lsm_names.size()));
352  }
353  if (map.second >= 0) {
354  AMREX_ALWAYS_ASSERT(map.second < static_cast<int>(m_urban_names.size()));
355  }
356  for (int lev = 0; lev < m_nlevs; ++lev) {
357  if (map.first >= 0 && map.first < static_cast<int>(lsm_data_lev[lev].size())) {
359  }
360  if (map.second >= 0 && map.second < static_cast<int>(urban_data_lev[lev].size())) {
362  }
363  }
364  RadiationField field;
365  field.map = map;
366  radiation_output_map[name] = std::move(field);
367  for (auto& fields_at_level : rad_output_fields) { fields_at_level.clear(); }
368 }
Here is the call graph for this function:

◆ register_radiation_outputs()

void SurfaceModel::register_radiation_outputs ( const std::unordered_map< std::string, std::pair< int, int >> &  output_map)

Registers canonical radiation output mappings.

Parameters
output_mapCanonical names mapped to land and urban indices.
372 {
373  for (const auto& entry : output_map) {
374  register_radiation_output(entry.first, entry.second);
375  }
376 }
void register_radiation_output(const std::string &name, const std::pair< int, int > &lsm_urb_map)
Registers a canonical radiation output mapping.
Definition: ERF_SurfaceModel.cpp:340

◆ release_transient_surface_outputs()

void SurfaceModel::release_transient_surface_outputs ( )
inlineprivate

Releases surface outputs that have no persistent consumer.

1001  {
1002  if (m_surface_outputs_enabled) { return; }
1003  for (int lev = 0; lev < m_nlevs; ++lev) {
1006  continue;
1007  }
1008  m_surface_outputs_requested[lev] = false;
1009  u_star[lev].reset();
1010  t_star[lev].reset();
1011  q_star[lev].reset();
1012  t_surf[lev].reset();
1013  }
1014  }
bool m_surface_layer_outputs_enabled
Definition: ERF_SurfaceModel.H:1058
bool m_surface_outputs_enabled
Definition: ERF_SurfaceModel.H:1057

◆ request_surface_layer_outputs()

void SurfaceModel::request_surface_layer_outputs ( )
inline

Enables blended surface outputs needed by the SurfaceLayer.

450  {
452  for (int lev = 0; lev < m_nlevs; ++lev) {
454  m_surface_outputs_requested[lev] = true;
456  }
457  }
458  }
Here is the call graph for this function:

◆ request_surface_outputs()

void SurfaceModel::request_surface_outputs ( const bool  persistent = true)
inline

Enables and materializes the surface outputs consumed by ERF.

438  {
440  for (int lev = 0; lev < m_nlevs; ++lev) {
441  m_surface_outputs_requested[lev] = true;
443  }
444  }
Here is the call graph for this function:

◆ resize_levels()

void SurfaceModel::resize_levels ( int  nlevs)
inlineprivate

Sets the number of AMR levels and resizes all per-level storage to match.

Shared by the constructor, initialize_for_level and clear_level so that every per-level member is grown and shrunk together. New levels start empty.

Parameters
nlevsNew number of AMR levels.
898  {
899  m_nlevs = nlevs;
900 
901  u_star.resize(m_nlevs);
902  t_star.resize(m_nlevs);
903  q_star.resize(m_nlevs);
904  t_surf.resize(m_nlevs);
905 
906  wavg.resize(m_nlevs);
908 
909  m_dmap.resize(m_nlevs);
910  m_ba.resize(m_nlevs);
911  m_ba2d.resize(m_nlevs);
912  m_geom.resize(m_nlevs);
913  m_geom2d.resize(m_nlevs);
914  m_lmask.resize(m_nlevs);
915 
916  urban_frac_lev.resize(m_nlevs);
917  lsm_data_lev.resize(m_nlevs);
918  urban_data_lev.resize(m_nlevs);
921 
922  rad_fields.resize(m_nlevs);
923  rad_output_fields.resize(m_nlevs);
925  m_last_urban_frac.resize(m_nlevs, nullptr);
926 
927  for (auto& field_lev : fields) { field_lev.resize(m_nlevs); }
928  for (auto& entry : fieldmap) {
929  entry.second.lsm_ptr.resize(m_nlevs, nullptr);
930  entry.second.urb_ptr.resize(m_nlevs, nullptr);
931  }
932  for (auto& entry : radiation_input_map) { entry.second.weighted.resize(m_nlevs); }
933  }
amrex::Vector< amrex::MultiFab * > urban_frac_lev
Definition: ERF_SurfaceModel.H:1043

Referenced by clear_level(), initialize_for_level(), and SurfaceModel().

Here is the caller graph for this function:

◆ set_field_map_pointers()

void SurfaceModel::set_field_map_pointers ( const std::string &  name,
int  lev,
amrex::MultiFab *  lsm_mf,
amrex::MultiFab *  urban_mf 
)

Updates a pointer-based field mapping for one level.

Parameters
nameCommon name of the mapped field.
levAMR level to update.
lsm_mfLand-model field pointer, or nullptr.
urban_mfUrban-model field pointer, or nullptr.
622 {
623  auto field_it = fieldmap.find(name);
624  AMREX_ALWAYS_ASSERT(field_it != fieldmap.end());
625 
626  Field& field = field_it->second;
627  AMREX_ALWAYS_ASSERT(field.map.first == -1 && field.map.second == -1);
628  if (static_cast<int>(field.lsm_ptr.size()) < m_nlevs) {
629  field.lsm_ptr.resize(m_nlevs, nullptr);
630  field.urb_ptr.resize(m_nlevs, nullptr);
631  }
633  field.lsm_ptr[lev] = lsm_mf;
634  field.urb_ptr[lev] = urban_mf;
635 }
Here is the call graph for this function:

◆ set_model_data()

void SurfaceModel::set_model_data ( const int  lev,
const amrex::Vector< amrex::MultiFab * >  model_data,
const amrex::Vector< std::string > &  data_names,
SurfaceModelType  type 
)
inline

Registers model variables used in weighted averaging.

Parameters
levAMR level associated with the data.
model_dataPointers to the model variables.
data_namesNames corresponding to model_data.
typeWhether the data belong to the land or urban model.
249  {
250  if (type == SurfaceModelType::LAND) {
251  lsm_data_lev[lev].resize(model_data.size());
252  weighted_lsm_data_lev[lev].clear();
253  weighted_lsm_data_lev[lev].resize(model_data.size());
254  if (lev == 0) {
255  // only set names on first level, since they are the same for all levels
256  m_lsm_names.resize(model_data.size());
257  AMREX_ALWAYS_ASSERT(data_names.size() == model_data.size());
258  for (int i = 0; i < static_cast<int>(data_names.size()); ++i) {
259  m_lsm_names[i] = data_names[i];
260  }
261  }
262 
263  for (int i = 0; i < static_cast<int>(lsm_data_lev[lev].size()); ++i) {
264  AMREX_ALWAYS_ASSERT(model_data[i]);
265  lsm_data_lev[lev][i] = model_data[i];
266  }
267  m_use_land = true;
269  for (auto& fields_at_level : rad_fields) { fields_at_level.clear(); }
270  for (auto& fields_at_level : rad_output_fields) { fields_at_level.clear(); }
271  for (auto& entry : radiation_input_map) {
272  if (lev < static_cast<int>(entry.second.weighted.size())) {
273  entry.second.weighted[lev].reset();
274  }
275  }
276  } else if (type == SurfaceModelType::URBAN) {
277  urban_data_lev[lev].resize(model_data.size());
278  weighted_urban_data_lev[lev].clear();
279  weighted_urban_data_lev[lev].resize(model_data.size());
280  if (lev == 0) {
281  // only set names on first level, since they are the same for all levels
282  m_urban_names.resize(model_data.size());
283  AMREX_ALWAYS_ASSERT(data_names.size() == model_data.size());
284  for (int i = 0; i < static_cast<int>(data_names.size()); ++i) {
285  m_urban_names[i] = data_names[i];
286  }
287  }
288  for (int i = 0; i < static_cast<int>(urban_data_lev[lev].size()); ++i) {
289  AMREX_ALWAYS_ASSERT(model_data[i]);
290  urban_data_lev[lev][i] = model_data[i];
291  }
292  m_use_urban = true;
294  for (auto& fields_at_level : rad_fields) { fields_at_level.clear(); }
295  for (auto& fields_at_level : rad_output_fields) { fields_at_level.clear(); }
296  for (auto& entry : radiation_input_map) {
297  if (lev < static_cast<int>(entry.second.weighted.size())) {
298  entry.second.weighted[lev].reset();
299  }
300  }
301  }
302  }
void update_provider_mode(const int lev)
Updates the provider mode after model data are registered.
Definition: ERF_SurfaceModel.H:939
Here is the call graph for this function:

◆ set_model_fields()

void SurfaceModel::set_model_fields ( SurfaceModelType  type,
const amrex::Vector< int > &  field_indices,
const bool  use_fluxes = true 
)
inline

Selects the model fields that participate in weighted averaging.

When using fluxes, the first five elements map to u-stress, v-stress, heat flux, vapor flux, and surface temperature. Otherwise, the first four elements map to ustar, tstar, qstar, and surface temperature. A field index of -1 skips that field.

Parameters
typeWhether the fields belong to the land or urban model.
field_indicesIndices of the fields to average.
use_fluxesWhether the selected fields are fluxes.
358  {
359  const int nfields = static_cast<int>(field_indices.size());
360  if (!use_fluxes) {
361  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(nfields >= 4, "Must have at least four fields to average (ustar, tstar, qstar, tsurf)");
362  } else {
363  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(nfields >= 5, "Must have at least five fields to average (u-stress, v-stress, hfx, qfx, tsurf)");
364  }
365 
366  // Latch the export mode off the first model that registers, then make sure every
367  // subsequent model agrees: either all of them supply fluxes, or all of them supply
368  // u*, t*, q* directly. Taking the mode from the caller rather than assuming the
369  // default is what makes the non-flux path reachable at all.
370  if (!m_export_fluxes_set) {
371  m_export_fluxes = use_fluxes;
372  m_export_fluxes_set = true;
373  }
375  "All registered surface models must export either fluxes or u*/t*/q*, not a mix");
376 
377  const int nregistered = (type == SurfaceModelType::LAND)
378  ? static_cast<int>(m_lsm_names.size())
379  : static_cast<int>(m_urban_names.size());
380  for (const int field : field_indices) {
382  field == -1 || (field >= 0 && field < nregistered),
383  "Model field index must be -1 or within the registered provider data range");
384  }
385 
386  if (type == SurfaceModelType::LAND) {
387  lsm_fields = amrex::Vector<int>(field_indices);
389  for (auto& fields_at_level : weighted_lsm_data_lev) {
390  fields_at_level.clear();
391  }
392  } else if (type == SurfaceModelType::URBAN) {
393  urban_fields = amrex::Vector<int>(field_indices);
395  for (auto& fields_at_level : weighted_urban_data_lev) {
396  fields_at_level.clear();
397  }
398  }
399  }
bool m_export_fluxes_set
Definition: ERF_SurfaceModel.H:1022
Here is the call graph for this function:

◆ set_model_fluxes()

void SurfaceModel::set_model_fluxes ( const int  lev,
const amrex::Vector< amrex::MultiFab * >  model_fluxes,
const amrex::Vector< std::string > &  flux_names,
SurfaceModelType  type 
)
inline

Registers model fluxes as additional weighted-average inputs.

Parameters
levAMR level associated with the fluxes.
model_fluxesPointers to the model flux fields.
flux_namesNames corresponding to model_fluxes.
typeWhether the fluxes belong to the land or urban model.
314  {
315  amrex::Vector<amrex::MultiFab*>* model_data = nullptr;
316  amrex::Vector<std::string>* model_names = nullptr;
317 
318  if (type == SurfaceModelType::LAND) {
319  model_data = &lsm_data_lev[lev];
320  model_names = &m_lsm_names;
321  } else if (type == SurfaceModelType::URBAN) {
322  model_data = &urban_data_lev[lev];
323  model_names = &m_urban_names;
324  }
325 
326  AMREX_ALWAYS_ASSERT(model_data != nullptr);
327  AMREX_ALWAYS_ASSERT(model_names != nullptr);
328  AMREX_ALWAYS_ASSERT(flux_names.size() == model_fluxes.size());
329 
330  const int data_size = static_cast<int>(model_data->size());
331  model_data->resize(data_size + static_cast<int>(model_fluxes.size()));
332  if (lev == 0) {
333  model_names->resize(data_size + static_cast<int>(model_fluxes.size()));
334  }
335 
336  for (int i = 0; i < static_cast<int>(model_fluxes.size()); ++i) {
337  AMREX_ALWAYS_ASSERT(model_fluxes[i]);
338  (*model_data)[data_size + i] = model_fluxes[i];
339  if (lev == 0) {
340  (*model_names)[data_size + i] = flux_names[i];
341  }
342  }
343  }
Here is the call graph for this function:

◆ update_provider_mode()

void SurfaceModel::update_provider_mode ( const int  lev)
inlineprivate

Updates the provider mode after model data are registered.

Parameters
levAMR level whose provider mode should be updated.
940  {
941  const bool has_land = !lsm_data_lev[lev].empty();
942  const bool has_urban = !urban_data_lev[lev].empty();
943  if (has_land && has_urban) {
947  m_surface_outputs_requested[lev] = true;
949  }
950  } else if (has_land) {
952  wavg[lev].reset();
953  } else if (has_urban) {
955  wavg[lev].reset();
956  } else {
958  }
959  }

Referenced by set_model_data().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ validate_radiation_output_layout()

void SurfaceModel::validate_radiation_output_layout ( int  lev,
const amrex::MultiFab *  mf 
) const

Validates the layout contract for a canonical radiation output.

Parameters
levAMR level associated with the destination.
mfRadiation output destination to validate.
420 {
421  if (mf == nullptr) { return; }
422  BoxList horizontal_boxes = mf->boxArray().boxList();
423  // include the ghost cells here for SLM: surface values are exchanged at
424  // k = 0 which is a ghost cell for SLM.
425  const int k_grow = mf->nGrowVect()[2];
426  for (Box& box : horizontal_boxes) {
428  box.smallEnd(2) <= 0 && box.bigEnd(2) + k_grow >= 0,
429  "Radiation output destination must contain the k=0 surface plane "
430  "in its valid or ghost region");
431  box.setRange(2, 0);
432  }
433  const BoxArray horizontal_ba(std::move(horizontal_boxes));
435  horizontal_ba == m_ba2d[lev] && mf->DistributionMap() == m_dmap[lev],
436  "Radiation output destination must match the level horizontal layout");
437 }
Here is the call graph for this function:

◆ weight_average_fields()

void SurfaceModel::weight_average_fields ( int  lev,
amrex::MultiFab * const   
)

Applies the current land and urban weights to selected model fields.

Parameters
levAMR level to update.
urban_fracUrban fraction used to compute the weights.
680 {
681  const SurfaceProviderMode mode = m_provider_mode[lev];
682  const bool use_land = mode == SurfaceProviderMode::LandOnly ||
684  const bool use_urban = mode == SurfaceProviderMode::UrbanOnly ||
686 
687  for (auto &field : fieldmap)
688  {
689  if (!field.second.active) { continue; }
690  int mf_idx = field.second.mf_ind;
691  AMREX_ASSERT(mf_idx != -1);
692 
693  int lsm_idx = field.second.map.first;
694  int urb_idx = field.second.map.second;
695 
696  // whether we have a valid LSM multifab
697  bool valid_land = (use_land &&
698  lsm_idx != -1 &&
699  lsm_data_lev[lev][lsm_idx]);
700 
701  // whether we have a valid urban multifab
702  bool valid_urban = (use_urban &&
703  urb_idx != -1 &&
704  urban_data_lev[lev][urb_idx]);
705 
706  bool use_mf = false;
707  if (lsm_idx == -1 && urb_idx == -1) {
708  // use explicit MF ptrs rather than indices
709  use_mf = true;
710  valid_land = (use_land && field.second.lsm_ptr[lev]);
711  valid_urban = (use_urban && field.second.urb_ptr[lev]);
712  }
713 
714  for (MFIter mfi(*fields[mf_idx][lev], TileNoZ()); mfi.isValid(); ++mfi)
715  {
716  Box tbx = mfi.tilebox();
717  const auto weights_arr = (mode == SurfaceProviderMode::Both)
718  ? wavg[lev]->const_array(mfi) : Array4<const Real>{};
719 
720  // Calculate weight average into output
721  auto output_arr = fields[mf_idx][lev]->array(mfi);
722  const amrex::MultiFab *lsm_mf = (use_mf) ? field.second.lsm_ptr[lev] : (lsm_idx != -1 ? lsm_data_lev[lev][lsm_idx] : nullptr);
723  const amrex::MultiFab *urb_mf = (use_mf) ? field.second.urb_ptr[lev] : (urb_idx != -1 ? urban_data_lev[lev][urb_idx] : nullptr);
724 
725  auto lsm_data_arr = (valid_land) ? lsm_mf->const_array(mfi) : Array4<const Real>{};
726  auto urban_data_arr = (valid_urban) ? urb_mf->const_array(mfi) : Array4<const Real>{};
727 
728  if (valid_land && !valid_urban) {
729  // use solely land value
730  ParallelFor(tbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
731  {
732  output_arr(i, j, k) = lsm_data_arr(i, j, k);
733  });
734  } else if (!valid_land && valid_urban) {
735  // use solely urban value
736  ParallelFor(tbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
737  {
738  output_arr(i, j, k) = urban_data_arr(i, j, k);
739  });
740  } else {
741  ParallelFor(tbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
742  {
743  Real land = (lsm_data_arr) ? lsm_data_arr(i, j, k) * weights_arr(i, j, 0, SurfaceModelType::LAND) : 0.0;
744  Real urb = (urban_data_arr) ? urban_data_arr(i, j, k) * weights_arr(i, j, 0, SurfaceModelType::URBAN) : 0.0;
745 
746  output_arr(i, j, k) = land + urb;
747  });
748  }
749  }
750  if (field.second.fill_bound) {
751  fields[mf_idx][lev]->FillBoundary(m_geom[lev].periodicity());
752  }
753  //outputs[output_field]->FillBoundary(comp, 1, m_geom[lev].periodicity());
754  }
755 }
Here is the call graph for this function:

◆ weight_model_field()

void SurfaceModel::weight_model_field ( int  lev,
const amrex::MultiFab *  source,
amrex::MultiFab *  weighted,
SurfaceModelType  type 
)

Writes a weighted copy of a model field.

Parameters
levAMR level whose weights are used.
sourceModel-owned source field.
weightedSurfaceModel-owned destination field.
typeProvider type owning the source field.
46 {
47  AMREX_ASSERT(source != nullptr);
48  AMREX_ASSERT(weighted != nullptr);
49 
50  for (MFIter mfi(*source, TileNoZ()); mfi.isValid(); ++mfi)
51  {
52  Box tbx = mfi.tilebox();
53  const auto source_arr = source->const_array(mfi);
54  auto weighted_arr = weighted->array(mfi);
55  const bool use_weights = m_provider_mode[lev] == SurfaceProviderMode::Both;
56  const auto weights_arr = use_weights
57  ? wavg[lev]->const_array(mfi) : Array4<const Real>{};
58 
59  ParallelFor(tbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
60  {
61  weighted_arr(i, j, k) = use_weights
62  ? source_arr(i, j, k) * weights_arr(i, j, 0, type)
63  : source_arr(i, j, k);
64  });
65  }
66 
67  weighted->FillBoundary(m_geom[lev].periodicity());
68 }
Here is the call graph for this function:

◆ write_output()

void SurfaceModel::write_output ( const int &  finest_lev,
const amrex::Real &  time,
const std::string &  plot_prefix,
const amrex::Vector< int > &  level_steps,
const amrex::Vector< amrex::IntVect > &  ref_ratio 
)

Writes registered surface-model fields to plotfile output.

Parameters
finest_levFinest AMR level to write.
timeSimulation time associated with the output.
plot_prefixOutput plotfile prefix.
level_stepsNumber of steps represented at each level.
ref_ratioRefinement ratios between adjacent levels.
759 {
762  std::string plotfilename = amrex::Concatenate(plot_prefix + "2D_", level_steps[0], 5);
763 
764  const int nfields = (m_export_fluxes) ? 5 : 4;
765  // Surface outputs, land mask, urban fraction, and mapped fields.
766  const int noutput = nfields + 2 + fieldmap.size();
767  IntVect ng(0, 0, 0);
768 
769  amrex::Vector<std::string> varnames(nfields);
770 
771  amrex::Vector<amrex::MultiFab> fab(finest_lev+1);
772  for (int lev = 0; lev <= finest_lev; lev++) {
774  amrex::MultiFab* const outputs[] = {u_star[lev].get(), t_star[lev].get(), q_star[lev].get(), t_surf[lev].get()};
775  fab[lev].define(outputs[0]->boxArray(), m_dmap[lev], noutput, ng);
776 
777  //fab[lev].setVal(0.0);
778 
779  // Output weighted surface fluxes into ustar, tstar, qstar and surface temperature into tsurf
780  // TODO: make sure grids of urban and LSM inputs match
781  for (int field=0; field < nfields; field++)
782  {
783  int output_field = (m_export_fluxes && field > 0) ? field - 1 : field;
784  int comp = (m_export_fluxes && field < 2) ? field : 0;
785 
786  MultiFab::Copy(fab[lev], *(outputs[output_field]), comp, field, 1, ng);
787 
788  varnames[field] = field_names[field];
789  }
790 
791  int nout = nfields;
792 
793  MultiFab lmask_tmp = amrex::ToMultiFab(*m_lmask[lev]); // iMultiFab -> MultiFab
794  MultiFab::Copy(fab[lev], lmask_tmp, 0, nout, 1, ng);
795  nout++;
796  MultiFab::Copy(fab[lev], *get_wavg_factors(lev), SurfaceModelType::URBAN, nout, 1, ng);
797  nout++;
798 
799  if (lev == 0) {
800  varnames.push_back("lmask");
801  varnames.push_back("urb_frac");
802  }
803 
804  // Add any mapped fields
805  for (auto &field : fieldmap) {
806  MultiFab::Copy(fab[lev], *(fields[field.second.mf_ind][lev]), 0, nout, 1, ng);
807  if (lev == 0) {
808  varnames.push_back(field.first);
809  }
810  nout++;
811  }
812 
813  AMREX_ALWAYS_ASSERT(varnames.size() == noutput);
814  }
815 
816  amrex::WriteMultiLevelPlotfile(plotfilename, finest_lev+1, GetVecOfConstPtrs(fab), varnames, m_geom2d, time, level_steps, ref_ratio);
819 }
amrex::MultiFab * get_wavg_factors(const int &lev)
Returns the land and urban weighting factors for a level.
Definition: ERF_SurfaceModel.H:600
void calculate_weight_average(int lev, amrex::MultiFab *const urban_frac, bool update_derived=true)
Computes land and urban weights and applies them to registered fields.
Definition: ERF_SurfaceModel.cpp:71
static const std::vector< std::string > field_names
Definition: ERF_SurfaceModel.H:1119
Here is the call graph for this function:

◆ WriteCheckpoint()

void SurfaceModel::WriteCheckpoint ( const std::string &  checkpointname)

Writes surface-model state to a checkpoint file.

Parameters
checkpointnameCheckpoint file name.
831 {
834  auto check_start = amrex::second();
835 
836  // write header
837  if (ParallelDescriptor::IOProcessor()) {
838 
839  amrex::Print() << " Writing SurfaceModel checkpoint " << std::endl;
840 
841  std::string HeaderFileName(checkpointname + "/SurfaceModel_Header");
842  VisMF::IO_Buffer io_buffer(VisMF::IO_Buffer_Size);
843  std::ofstream HeaderFile;
844  HeaderFile.rdbuf()->pubsetbuf(io_buffer.dataPtr(), io_buffer.size());
845  HeaderFile.open(HeaderFileName.c_str(), std::ofstream::out |
846  std::ofstream::trunc |
847  std::ofstream::binary);
848  if(! HeaderFile.good()) {
849  FileOpenFailed(HeaderFileName);
850  }
851 
852  HeaderFile.precision(17);
853 
854  // write out title line
855  HeaderFile << "Checkpoint file for SurfaceModel\n";
856 
857  // write out number of levels
858  HeaderFile << m_nlevs << "\n";
859 
860  // write out flags
861  HeaderFile << m_use_urban << "\n";
862  HeaderFile << m_use_land << "\n";
863  HeaderFile << m_export_fluxes << "\n";
864  HeaderFile << m_weights_updated << "\n";
865  HeaderFile << m_fields_are_valid << "\n";
866  HeaderFile << "\n";
867 
868  // Write box arrays
869  for (int lev = 0; lev < m_nlevs; lev++) {
870  m_ba[lev].writeOn(HeaderFile);
871  HeaderFile << '\n';
872 
873  }
874  HeaderFile << '\n';
875  for (int lev = 0; lev < m_nlevs; lev++) {
876  m_ba2d[lev].writeOn(HeaderFile);
877  HeaderFile << '\n';
878  }
879  HeaderFile << '\n';
880 
881  // Write out fields
882  // LSM fields
883  for (int i = 0; i < static_cast<int>(lsm_fields.size()); ++i) {
884  HeaderFile << lsm_fields[i] << " ";
885  }
886  HeaderFile << '\n';
887 
888  // Urban fields
889  for (int i = 0; i < static_cast<int>(urban_fields.size()); ++i) {
890  HeaderFile << urban_fields[i] << " ";
891  }
892  HeaderFile << '\n';
893 
894  // Field mapping
895  HeaderFile << '\n';
896  HeaderFile << fieldmap.size() << "\n";
897  for (auto &field : fieldmap) {
898  // pointer fields are reconstructed at load
899  HeaderFile << field.first << " " << field.second.mf_ind << " " << field.second.map.first << " " << field.second.map.second << " " << field.second.fill_bound << '\n';
900  }
901  HeaderFile << '\n';
902  }
903 
904  amrex::ParallelDescriptor::Barrier();
905 
906  const std::string prefix = "SurfaceModel_";
907 
908  // Radiation input fields are derived from provider data and are intentionally
909  // rebuilt after restart rather than written to the checkpoint.
910  for (int lev = 0; lev < m_nlevs; lev++) {
911  IntVect ng(1,1,0);
913 
914  {
915  MultiFab mf(m_ba2d[lev],m_dmap[lev],2,ng);
916  MultiFab::Copy(mf,*u_star[lev],0,0,2,ng);
917  VisMF::Write(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "ustar"));
918 
919  MultiFab::Copy(mf,*get_wavg_factors(lev),0,0,2,ng);
920  VisMF::Write(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "wavg"));
921  }
922 
923  MultiFab mf(m_ba2d[lev],m_dmap[lev],1,ng);
924 
925  MultiFab::Copy(mf,*t_star[lev],0,0,1,ng);
926  VisMF::Write(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "tstar"));
927 
928  MultiFab::Copy(mf,*q_star[lev],0,0,1,ng);
929  VisMF::Write(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "qstar"));
930 
931  MultiFab::Copy(mf,*t_surf[lev],0,0,1,ng);
932  VisMF::Write(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "tsurf"));
933 
934  for (auto &field : fieldmap) {
935  MultiFab::Copy(mf, *(fields[field.second.mf_ind][lev]), 0, 0, 1, ng);
936  VisMF::Write(mf, MultiFabFileFullPrefix(lev, checkpointname, "Level_", prefix + "_f_" + field.first));
937  }
938  }
939 
940  auto check_end = amrex::second() - check_start;
943  ParallelDescriptor::ReduceRealMax(check_end,ParallelDescriptor::IOProcessorNumber());
944  amrex::Print() << " SurfaceModel Checkpoint write time = " << check_end << " seconds." << '\n';
945 }

Member Data Documentation

◆ field_names

const std::vector<std::string> SurfaceModel::field_names
inlinestaticprivate
Initial value:
= {
"uflux",
"vflux",
"hfx",
"qfx",
"tskin",
}

◆ fieldmap

std::unordered_map<std::string, Field> SurfaceModel::fieldmap
private

◆ fields

amrex::Vector<amrex::Vector<std::unique_ptr<amrex::MultiFab> > > SurfaceModel::fields
private

◆ lsm_data_lev

amrex::Vector<amrex::Vector<amrex::MultiFab*> > SurfaceModel::lsm_data_lev
private

◆ lsm_fields

amrex::Vector<int> SurfaceModel::lsm_fields
private

◆ m_ba

amrex::Vector<amrex::BoxArray> SurfaceModel::m_ba
private

◆ m_ba2d

amrex::Vector<amrex::BoxArray> SurfaceModel::m_ba2d
private

◆ m_dmap

amrex::Vector<amrex::DistributionMapping> SurfaceModel::m_dmap
private

◆ m_export_fluxes

bool SurfaceModel::m_export_fluxes = true
private

◆ m_export_fluxes_set

bool SurfaceModel::m_export_fluxes_set = false
private

Referenced by set_model_fields().

◆ m_fields_are_valid

bool SurfaceModel::m_fields_are_valid = false
private

◆ m_geom

amrex::Vector<amrex::Geometry> SurfaceModel::m_geom
private

◆ m_geom2d

amrex::Vector<amrex::Geometry> SurfaceModel::m_geom2d
private

◆ m_last_urban_frac

amrex::Vector<amrex::MultiFab*> SurfaceModel::m_last_urban_frac
private

Referenced by resize_levels().

◆ m_lmask

amrex::Vector<amrex::iMultiFab*> SurfaceModel::m_lmask
private

◆ m_lsm_names

amrex::Vector<std::string> SurfaceModel::m_lsm_names
private

◆ m_nlevs

◆ m_output_fields_registered

amrex::GpuArray<bool, 2> SurfaceModel::m_output_fields_registered {{false, false}}
private

Referenced by set_model_fields(), and SurfaceModel().

◆ m_provider_mode

◆ m_surface_layer_outputs_enabled

bool SurfaceModel::m_surface_layer_outputs_enabled = false
private

◆ m_surface_outputs_enabled

bool SurfaceModel::m_surface_outputs_enabled = false
private

◆ m_surface_outputs_requested

amrex::Vector<int> SurfaceModel::m_surface_outputs_requested
private

◆ m_urban_names

amrex::Vector<std::string> SurfaceModel::m_urban_names
private

◆ m_use_land

bool SurfaceModel::m_use_land = false
private

Referenced by set_model_data().

◆ m_use_urban

bool SurfaceModel::m_use_urban = false
private

Referenced by set_model_data().

◆ m_weights_updated

bool SurfaceModel::m_weights_updated = false
private

◆ q_star

amrex::Vector<std::unique_ptr<amrex::MultiFab> > SurfaceModel::q_star
private

◆ rad_fields

amrex::Vector<amrex::Vector<const amrex::MultiFab*> > SurfaceModel::rad_fields
private

◆ rad_output_fields

amrex::Vector<amrex::Vector<amrex::MultiFab*> > SurfaceModel::rad_output_fields
private

◆ rad_output_names

const std::vector<std::string> SurfaceModel::rad_output_names
inlinestaticprivate
Initial value:
= {
"cos_zenith_angle", "sw_flux_dn", "sw_flux_dn_dir_vis",
"sw_flux_dn_dir_nir", "sw_flux_dn_dif_vis", "sw_flux_dn_dif_nir",
"lw_flux_dn"}

◆ radiation_input_map

std::unordered_map<std::string, RadiationField> SurfaceModel::radiation_input_map
private

◆ radiation_output_map

std::unordered_map<std::string, RadiationField> SurfaceModel::radiation_output_map
private

◆ radnames

const std::vector<std::string> SurfaceModel::radnames = {"tskin", "emiss", "albedo_vis", "albedo_nir", "albedo_vis_diff", "albedo_nir_diff"}
inlinestaticprivate

◆ t_star

amrex::Vector<std::unique_ptr<amrex::MultiFab> > SurfaceModel::t_star
private

◆ t_surf

amrex::Vector<std::unique_ptr<amrex::MultiFab> > SurfaceModel::t_surf
private

◆ u_star

amrex::Vector<std::unique_ptr<amrex::MultiFab> > SurfaceModel::u_star
private

◆ urban_data_lev

amrex::Vector<amrex::Vector<amrex::MultiFab*> > SurfaceModel::urban_data_lev
private

◆ urban_fields

amrex::Vector<int> SurfaceModel::urban_fields
private

◆ urban_frac_lev

amrex::Vector<amrex::MultiFab*> SurfaceModel::urban_frac_lev
private

Referenced by resize_levels().

◆ wavg

amrex::Vector<std::unique_ptr<amrex::MultiFab> > SurfaceModel::wavg
private

◆ weighted_lsm_data_lev

amrex::Vector<amrex::Vector<std::unique_ptr<amrex::MultiFab> > > SurfaceModel::weighted_lsm_data_lev
private

◆ weighted_urban_data_lev

amrex::Vector<amrex::Vector<std::unique_ptr<amrex::MultiFab> > > SurfaceModel::weighted_urban_data_lev
private

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