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

#include <ERF_SurfaceLayer.H>

Collaboration diagram for SurfaceLayer:

Classes

struct  PBLHColumns
 

Public Types

enum class  FluxCalcType {
  MOENG = 0 , CUSTOM , BULK_COEFF , ROTATE ,
  RICO
}
 
enum class  ThetaCalcType { ADIABATIC = 0 , HEAT_FLUX , SURFACE_TEMPERATURE }
 
enum class  MoistCalcType { ADIABATIC = 0 , MOISTURE_FLUX , SURFACE_MOISTURE }
 
enum class  RoughCalcType {
  CONSTANT = 0 , CHARNOCK , MODIFIED_CHARNOCK , DONELAN ,
  WAVE_COUPLED
}
 
enum class  PBLHeightCalcType {
  None , MYNN25 , YSU , MRF ,
  YSUNew
}
 

Public Member Functions

 SurfaceLayer (amrex::Orientation face, const amrex::Vector< amrex::Geometry > &geom, bool &use_rot_surface_flux, std::string a_pp_prefix, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Qv_prim, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &z_phys_nd, const amrex::Vector< amrex::Vector< amrex::Real >> &zlevels_stag, const MeshType &a_mesh_type, const TerrainType &a_terrain_type, const TurbChoice &a_turb_choice, amrex::Real a_rdOcp, double start_low_time, double final_low_time, double low_time_interval=0.0, const amrex::Vector< const eb_ * > &eb_vec={}, SurfaceModel *surf_model=nullptr)
 
void make_SurfaceLayer_at_level (const int &lev, int nlevs, const amrex::Vector< amrex::MultiFab * > &mfv, std::unique_ptr< amrex::MultiFab > &Theta_prim, std::unique_ptr< amrex::MultiFab > &Qv_prim, std::unique_ptr< amrex::MultiFab > &Qr_prim, std::unique_ptr< amrex::MultiFab > &z_phys_nd, amrex::MultiFab *Hwave, amrex::MultiFab *Lwave, amrex::MultiFab *eddyDiffs, amrex::Vector< amrex::MultiFab * > lsm_data, amrex::Vector< std::string > lsm_data_name, amrex::Vector< amrex::MultiFab * > lsm_flux, amrex::Vector< std::string > lsm_flux_name, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &sst_lev, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &tsk_lev, amrex::Vector< std::unique_ptr< amrex::iMultiFab >> &lmask_lev)
 
void update_fluxes (const int &lev, const double &elapsed_time, const double &elapsed_time_since_start_low, amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd, const std::unique_ptr< amrex::MultiFab > &walldist, int max_iters=100)
 
template<typename FluxIter >
void compute_fluxes (const int &lev, const int &max_iters, amrex::MultiFab &cons_in, const FluxIter &most_flux, bool is_land)
 
void init_tke_from_ustar (const int &lev, amrex::MultiFab &cons, const std::unique_ptr< amrex::MultiFab > &z_phys_nd, const amrex::Real tkefac=one, const amrex::Real zscale=amrex::Real(700.0))
 
void fill_planar_boundary (const int &lev, amrex::MultiFab &mf)
 
amrex::Real surface_sum (const int &lev, const amrex::MultiFab &mf, int comp=0) const
 
void impose_SurfaceLayer_bcs (const int &lev, amrex::Vector< const amrex::MultiFab * > mfs, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Tau_lev, amrex::MultiFab *xheat_flux, amrex::MultiFab *yheat_flux, amrex::MultiFab *zheat_flux, amrex::MultiFab *xqv_flux, amrex::MultiFab *yqv_flux, amrex::MultiFab *zqv_flux, const amrex::MultiFab *z_phys)
 
void impose_SurfaceLayer_bcs_EB (const int &lev, amrex::Vector< const amrex::MultiFab * > mfs, amrex::Vector< amrex::Vector< std::unique_ptr< amrex::MultiFab >>> &Tau_lev, amrex::MultiFab *xheat_flux, amrex::MultiFab *yheat_flux, amrex::MultiFab *zheat_flux, amrex::MultiFab *xqv_flux, amrex::MultiFab *yqv_flux, amrex::MultiFab *zqv_flux)
 
template<typename FluxCalc >
void compute_SurfaceLayer_bcs (const int &lev, amrex::Vector< const amrex::MultiFab * > mfs, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Tau_lev, amrex::MultiFab *xheat_flux, amrex::MultiFab *yheat_flux, amrex::MultiFab *zheat_flux, amrex::MultiFab *xqv_flux, amrex::MultiFab *yqv_flux, amrex::MultiFab *zqv_flux, const amrex::MultiFab *z_phys, const FluxCalc &flux_comp)
 
template<typename FluxCalc >
void compute_SurfaceLayer_bcs_EB (const int &lev, amrex::Vector< const amrex::MultiFab * > mfs, amrex::Vector< amrex::Vector< std::unique_ptr< amrex::MultiFab >>> &Tau_lev, amrex::MultiFab *xheat_flux, amrex::MultiFab *yheat_flux, amrex::MultiFab *zheat_flux, amrex::MultiFab *xqv_flux, amrex::MultiFab *yqv_flux, amrex::MultiFab *zqv_flux, const FluxCalc &flux_comp)
 
void compute_sfc_params_from_lsm_fluxes (const int &lev, amrex::MultiFab &cons_in)
 
void fill_tsurf_with_sst_and_tsk (const int &lev, const double &time)
 
void fill_tsurf_with_sfc_sst (const int &lev, const double &time, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
 
void fill_tsurf_with_coupled_sst (const int &lev, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
 
void fill_tsurf_with_skin_temperature (const int &lev, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
 
void fill_qsurf_with_qsat (const int &lev, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
 
void set_pblh (const int &lev, const amrex::MultiFab &pblh_in)
 
void update_sfc_time_index (const amrex::Real &time)
 
amrex::Real interpolate_sfc_column (const amrex::Real &time, int col) const
 
void get_lsm_tsurf (const int &lev)
 
void update_pblh (const int &lev, amrex::Vector< amrex::Vector< amrex::MultiFab >> &vars, amrex::MultiFab *z_phys_cc, const MoistureComponentIndices &moisture_indices)
 
template<typename PBLHeightEstimator >
void compute_pblh (const int &lev, amrex::Vector< amrex::Vector< amrex::MultiFab >> &vars, amrex::MultiFab *z_phys_cc, const PBLHeightEstimator &est, const MoistureComponentIndices &moisture_indice)
 
void read_custom_roughness (const int &lev, const std::string &fname)
 
void update_surf_temp (const double &time)
 
void update_mac_ptrs (const int &lev, amrex::Vector< amrex::Vector< amrex::MultiFab >> &vars_old, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Theta_prim, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Qv_prim, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Qr_prim)
 
amrex::MultiFab * get_u_star (const int &lev)
 
amrex::MultiFab * get_w_star (const int &lev)
 
bool computes_w_star () const
 
bool computes_pblh () const
 
amrex::MultiFab * get_t_star (const int &lev)
 
amrex::MultiFab * get_q_star (const int &lev)
 
amrex::MultiFab * get_olen (const int &lev)
 
amrex::MultiFab * get_pblh (const int &lev)
 
const amrex::MultiFab * get_mac_avg (const int &lev, int comp)
 
bool mac_avg_is_time_averaged () const
 
int get_num_mac_avg () const
 
bool mac_avg_is_initialized (const int &lev) const
 
void set_mac_avg_initialized (const int &lev)
 
amrex::MultiFab * get_mac_avg_ptr (const int &lev, int comp)
 
amrex::Vector< amrex::Real > get_mac_plane_avg (const int &lev) const
 
bool set_mac_plane_avg (const int &lev, const amrex::Vector< amrex::Real > &pavg)
 
amrex::MultiFab * get_t_surf (const int &lev)
 
void set_skin_temperature (const int &lev, const amrex::MultiFab *t_skin)
 
std::string skin_temperature_conflict () const
 
bool use_sfc_fluxes () const
 
bool rotates_surface_fluxes () const
 
void set_t_surf (const int &lev, const amrex::Real tsurf)
 
amrex::MultiFab * get_q_surf (const int &lev)
 
void set_q_surf (const int &lev, const amrex::Real qsurf)
 
amrex::MultiFab * get_surface_diagnostic_source (const int &lev)
 
amrex::MultiFab * get_zref (const int &lev)
 
amrex::Real get_zref_min (const int &lev)
 
amrex::MultiFab * get_z0 (const int &lev)
 
bool have_variable_sea_roughness ()
 
amrex::iMultiFab * get_lmask (const int &lev)
 
int lmask_min_reduce (amrex::iMultiFab &lmask, const int &nghost)
 
void update_sst_ptr (const int lev, const int itime, amrex::MultiFab *sst_ptr)
 
void update_tsk_ptr (const int lev, const int itime, amrex::MultiFab *tsk_ptr)
 
void update_coupled_sst_ptr (const int lev, amrex::MultiFab *sst_ptr, amrex::iMultiFab *valid_ptr)
 
void set_coupled_sst_active (const bool active)
 
void set_surface_layer_faces (const amrex::GpuArray< int, AMREX_SPACEDIM *2 > &active_faces)
 
void fill_lateral_surface_parameter_ghosts (const int &lev, amrex::MultiFab *selected_field=nullptr)
 
template<typename FluxIter >
void compute_fluxes (const int &lev, const int &max_iters, MultiFab &, const FluxIter &most_flux, bool is_land)
 
template<typename FluxCalc >
void compute_SurfaceLayer_bcs (const int &lev, Vector< const MultiFab * > mfs, Vector< std::unique_ptr< MultiFab >> &Tau_lev, MultiFab *xheat_flux, MultiFab *yheat_flux, MultiFab *zheat_flux, MultiFab *xqv_flux, MultiFab *yqv_flux, MultiFab *zqv_flux, const MultiFab *z_phys, const FluxCalc &flux_comp)
 
template<typename FluxCalc >
void compute_SurfaceLayer_bcs_EB (const int &lev, Vector< const MultiFab * > mfs, Vector< Vector< std::unique_ptr< MultiFab >>> &Tau_EB, [[maybe_unused]] MultiFab *xheat_flux, [[maybe_unused]] MultiFab *yheat_flux, MultiFab *Hfx3_EB, [[maybe_unused]] MultiFab *xqv_flux, [[maybe_unused]] MultiFab *yqv_flux, [[maybe_unused]] MultiFab *zqv_flux, const FluxCalc &flux_comp)
 
template<typename PBLHeightEstimator >
void compute_pblh (const int &lev, Vector< Vector< MultiFab >> &vars, MultiFab *z_phys_cc, const PBLHeightEstimator &est, const MoistureComponentIndices &moisture_indices)
 

Static Public Member Functions

static amrex::Vector< amrex::Vector< amrex::Real > > read_cols (const std::string &fname, const int skip_nlines=1)
 

Public Attributes

FluxCalcType flux_type {FluxCalcType::MOENG}
 
ThetaCalcType theta_type {ThetaCalcType::ADIABATIC}
 
MoistCalcType moist_type {MoistCalcType::ADIABATIC}
 
RoughCalcType rough_type_land {RoughCalcType::CONSTANT}
 
RoughCalcType rough_type_sea {RoughCalcType::CHARNOCK}
 
PBLHeightCalcType pblh_type {PBLHeightCalcType::None}
 

Private Member Functions

void define_pblh_columns (const int &lev, const amrex::BoxArray &ba, const amrex::DistributionMapping &dm)
 

Private Attributes

amrex::Orientation m_face
 
std::string m_pp_prefix
 
amrex::Vector< amrex::Geometry > m_geom
 
bool m_rotate = false
 
amrex::GpuArray< int, AMREX_SPACEDIM *2 > m_surface_layer_faces {}
 
double m_start_low_time
 
double m_final_low_time
 
double m_low_time_interval
 
bool m_include_wstar = false
 
amrex::Real z0_const {amrex::Real(0.1)}
 
amrex::Real default_land_surf_temp {amrex::Real(300.)}
 
amrex::Real surf_temp {amrex::Real(-1.)}
 
amrex::Real surf_heating_rate {0}
 
amrex::Real surf_temp_flux {0}
 
amrex::Real default_land_surf_moist {zero}
 
amrex::Real surf_moist {amrex::Real(-1.)}
 
amrex::Real surf_moist_flux {0}
 
amrex::Real custom_ustar {0}
 
amrex::Real custom_tstar {0}
 
amrex::Real custom_qstar {0}
 
amrex::Real custom_rhosurf {0}
 
bool specified_rho_surf {false}
 
amrex::Real cnk_a {amrex::Real(0.0185)}
 
bool smooth_flow_visc {true}
 
amrex::Real depth {amrex::Real(30.0)}
 
amrex::Vector< amrex::MultiFab > z_0
 
bool m_var_z0 {false}
 
amrex::Real rico_theta_z0 {amrex::Real(298.0)}
 
amrex::Real rico_qsat_z0 {amrex::Real(0.001)}
 
bool use_moisture
 
bool m_has_lsm_fluxes = false
 
bool m_has_lsm_tsurf = false
 
int m_lsm_tsurf_indx = -1
 
bool m_use_sfc_fluxes = false
 
bool m_use_sfc_sst = false
 
int sfc_time_ind = 0
 
amrex::Vector< amrex::Vector< amrex::Real > > sfc
 
amrex::Real sfc_qflux = zero
 
amrex::Real sfc_tflux = zero
 
amrex::Real sfc_ustar = zero
 
bool m_use_coupled_sst = false
 
amrex::Real m_Cd = zero
 
amrex::Real m_Ch = zero
 
amrex::Real m_Cq = zero
 
bool m_ignore_sst = false
 
amrex::Vector< const eb_ * > m_eb_vec
 
TerrainType m_terrain_type
 
amrex::Real m_rdOcp = RdoCp
 
MOSTAverage m_ma
 
SurfaceModel * m_surf_model = nullptr
 
bool use_surface_model = false
 
bool surf_model_fluxes = false
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > u_star
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > w_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 > > olen
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > pblh
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_surf
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > q_surf
 
amrex::Vector< PlanarBoundary > m_planar_bndry
 
amrex::Vector< PBLHColumns > m_pblh_columns
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > surface_diagnostic_source
 
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_sst_lev
 
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_tsk_lev
 
amrex::Vector< amrex::Vector< amrex::iMultiFab * > > m_lmask_lev
 
amrex::Vector< amrex::MultiFab * > m_coupled_sst_lev
 
amrex::Vector< amrex::iMultiFab * > m_coupled_sst_valid_lev
 
amrex::Vector< const amrex::MultiFab * > m_skin_tsurf_lev
 
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_lsm_data_lev
 
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_lsm_flux_lev
 
amrex::Vector< std::string > m_lsm_data_name
 
amrex::Vector< std::string > m_lsm_flux_name
 
amrex::Vector< amrex::MultiFab * > m_Hwave_lev
 
amrex::Vector< amrex::MultiFab * > m_Lwave_lev
 
amrex::Vector< amrex::MultiFab * > m_eddyDiffs_lev
 
bool m_update_k_rans = false
 
amrex::Real inv_Cmu2 = zero
 
amrex::Real theta_ref = zero
 

Detailed Description

Abstraction layer for different surface layer schemes (e.g. MOST, Cd)

van der Laan, P., Kelly, M. C., & Sørensen, N. N. (2017). A new k-epsilon model consistent with Monin-Obukhov similarity theory. Wind Energy, 20(3), 479–amrex::Real(489.) https://doi.org/amrex::Real(10.1002)/we.2017

Consistent with Dyer (1974) formulation from page 57, Chapter 2, Modeling the vertical ABL structure in Modelling of Atmospheric Flow Fields, Demetri P Lalas and Corrado F Ratto, January 1996, https://doi.org/amrex::Real(10.1142)/amrex::Real(2975.)

Member Enumeration Documentation

◆ FluxCalcType

Enumerator
MOENG 

Moeng functional form.

CUSTOM 

Custom constant flux functional form.

BULK_COEFF 

Bulk transfer coefficient functional form.

ROTATE 

Terrain rotation flux functional form.

RICO 
1507  {
1508  MOENG = 0, ///< Moeng functional form
1509  CUSTOM, ///< Custom constant flux functional form
1510  BULK_COEFF, ///< Bulk transfer coefficient functional form
1511  ROTATE, ///< Terrain rotation flux functional form
1512  RICO
1513  };

◆ MoistCalcType

Enumerator
ADIABATIC 
MOISTURE_FLUX 

Qv-flux specified.

SURFACE_MOISTURE 

Surface Qv specified.

1521  {
1522  ADIABATIC = 0,
1523  MOISTURE_FLUX, ///< Qv-flux specified
1524  SURFACE_MOISTURE ///< Surface Qv specified
1525  };

◆ PBLHeightCalcType

Enumerator
None 
MYNN25 
YSU 
MRF 
YSUNew 
1535 { None, MYNN25, YSU, MRF, YSUNew };
@ None
Definition: ERF_LSM_Types.H:6

◆ RoughCalcType

Enumerator
CONSTANT 

Constant z0.

CHARNOCK 
MODIFIED_CHARNOCK 
DONELAN 
WAVE_COUPLED 
1527  {
1528  CONSTANT = 0, ///< Constant z0
1529  CHARNOCK,
1530  MODIFIED_CHARNOCK,
1531  DONELAN,
1532  WAVE_COUPLED
1533  };

◆ ThetaCalcType

Enumerator
ADIABATIC 
HEAT_FLUX 

Heat-flux specified.

SURFACE_TEMPERATURE 

Surface temperature specified.

1515  {
1516  ADIABATIC = 0,
1517  HEAT_FLUX, ///< Heat-flux specified
1518  SURFACE_TEMPERATURE ///< Surface temperature specified
1519  };

Constructor & Destructor Documentation

◆ SurfaceLayer()

SurfaceLayer::SurfaceLayer ( amrex::Orientation  face,
const amrex::Vector< amrex::Geometry > &  geom,
bool &  use_rot_surface_flux,
std::string  a_pp_prefix,
amrex::Vector< std::unique_ptr< amrex::MultiFab >> &  Qv_prim,
amrex::Vector< std::unique_ptr< amrex::MultiFab >> &  z_phys_nd,
const amrex::Vector< amrex::Vector< amrex::Real >> &  zlevels_stag,
const MeshType &  a_mesh_type,
const TerrainType &  a_terrain_type,
const TurbChoice &  a_turb_choice,
amrex::Real  a_rdOcp,
double  start_low_time,
double  final_low_time,
double  low_time_interval = 0.0,
const amrex::Vector< const eb_ * > &  eb_vec = {},
SurfaceModel *  surf_model = nullptr 
)
inlineexplicit

Construct the surface-layer interface.

Parameters
[in]faceorientation of face (for wall geometries)
[in]geomgeometry for all AMR levels
[in,out]use_rot_surface_fluxwhether to use rotated surface fluxes
[in]a_pp_prefixParmParse prefix used by MOST averages
[in]Qv_primprimitive water-vapor fields by level
[in]z_phys_ndnodal physical-height fields by level
[in]zlevels_stagnominal staggered z levels by level
[in]a_mesh_typemesh type
[in]a_terrain_typeterrain representation
[in]a_turb_choiceturbulence-model options
[in]a_rdOcpconfigured Rd/cp exponent for T-theta conversions
[in]start_low_timefirst available low-boundary-data time
[in]final_low_timefinal available low-boundary-data time
[in]low_time_intervallow-boundary-data time interval
[in]eb_vecoptional embedded-boundary geometry data
[in]surf_modeloptional weighted surface-model interface
97  {},
98  SurfaceModel* surf_model = nullptr)
99  : m_face(face),
100  m_pp_prefix(a_pp_prefix),
101  m_geom(geom),
102  m_rotate(use_rot_surface_flux),
103  m_start_low_time(start_low_time),
104  m_final_low_time(final_low_time),
105  m_low_time_interval(low_time_interval),
106  m_eb_vec(eb_vec),
107  m_terrain_type(a_terrain_type),
108  m_rdOcp(a_rdOcp),
109  m_ma(face, geom, (z_phys_nd[0] != nullptr), a_pp_prefix, a_mesh_type, a_terrain_type,
110  zlevels_stag, eb_vec),
111  m_surf_model(surf_model)
112  {
113  // We have a moisture model if Qv_prim is a valid pointer
114  use_moisture = (Qv_prim[0].get());
115 
116  // Keep standalone SurfaceLayer instances compatible with the historical
117  // single-face behavior. ERF replaces this with the complete active-face
118  // set after constructing all SurfaceLayer objects.
119  m_surface_layer_faces[static_cast<int>(face)] = 1;
120 
121  // Get roughness
122  amrex::ParmParse pp(a_pp_prefix);
123  pp.queryAdd("most.z0", z0_const);
124 
125  // Specify how to compute the flux
126  if (use_rot_surface_flux) {
128  } else {
129  std::string flux_string_in;
130  std::string flux_string{"moeng"};
131  auto read_flux = pp.queryAdd("surface_layer.flux_type", flux_string_in);
132  if (read_flux) {
133  flux_string = amrex::toLower(flux_string_in);
134  }
135  if (flux_string == "moeng") {
137  } else if (flux_string == "rico") {
139  } else if (flux_string == "bulk_coeff") {
141  } else if (flux_string == "custom") {
143  } else {
144  amrex::Abort("Undefined MOST flux type!");
145  }
146  }
147 
152  m_face.coordDir() == 2 && m_face.isLow(),
153  "BULK_COEFF, CUSTOM, and RICO surface-layer fluxes are supported only on the z-low face.");
154  }
155 
156  // Include w* to handle free convection (Beljaars 1995, QJRMS)
157  pp.queryAdd("most.include_wstar", m_include_wstar);
158 
159  std::string pblh_string_in;
160  std::string pblh_string{"none"};
161  auto read_pblh = pp.queryAdd("most.pblh_calc", pblh_string_in);
162  if (read_pblh) {
163  pblh_string = amrex::toLower(pblh_string_in);
164  }
165  if (pblh_string == "none") {
167  } else if (pblh_string == "mynn25") {
169  } else if (pblh_string == "mynnedmf") {
171  } else if (pblh_string == "ysu") {
173  } else if (pblh_string == "mrf") {
175  } else {
176  amrex::Abort("Undefined PBLH calc type!");
177  }
178 
181  m_face.coordDir() == 2 && m_face.isLow(),
182  "MOST PBL-height calculation and wstar correction are supported only on the z-low face.");
183  }
184 
185  // The w* correction is computed from the PBL height, so it needs a scheme that
186  // actually diagnoses one. With pblh_calc = "none" the pblh MultiFab keeps the
187  // bogus_large_value it was initialized with and calc_wstar turns that into a
188  // convective velocity scale of ~1e50, which destroys the surface fluxes.
190  amrex::Abort("erf.most.include_wstar requires a PBL height: set "
191  "erf.most.pblh_calc (MYNN25 is the only scheme implemented)");
192  }
193 
194  if (m_surf_model) {
195  use_surface_model = true;
196  amrex::Print() << " Using fluxes from surface model!" << std::endl;
198  }
199 
200  // Get surface temperature. surf_temp and surf_moist are declared with negative
201  // sentinels (see below) so that "did the user set this" is a property of the
202  // value rather than of the queryAdd return value, which only reports whether the
203  // key existed before the call and so stops being meaningful once anything has
204  // parsed the key. Both most.surf_temp and most.surf_moist are also parsed by
205  // ERF_InputSoundingData.H, so the two sites would poison each other otherwise.
206  pp.queryAdd("most.surf_temp", surf_temp);
207  const bool erf_st = (surf_temp > amrex::Real(0));
208  if (erf_st) { default_land_surf_temp = surf_temp; }
209 
210  // Get surface moisture
211  bool erf_sq = false;
212  if (use_moisture) {
213  pp.queryAdd("most.surf_moist", surf_moist);
214  erf_sq = (surf_moist >= amrex::Real(0));
215  }
216  if (erf_sq) { default_land_surf_moist = surf_moist; }
217 
218  // Custom type user must specify the fluxes
223  pp.get("most.ustar", custom_ustar);
224  pp.get("most.tstar", custom_tstar);
225  pp.get("most.qstar", custom_qstar);
226  pp.queryAdd("most.rhosurf", custom_rhosurf);
227  if (custom_qstar != 0) {
229  "Specified custom MOST qv flux without moisture model!");
230  }
231  amrex::Print() << "Using specified ustar, tstar, qstar for MOST = "
232  << custom_ustar << " " << custom_tstar << " "
233  << custom_qstar << std::endl;
234 
235  // Bulk transfer coefficient (must specify coeffs and surface values)
236  } else if (flux_type == FluxCalcType::BULK_COEFF) {
237  pp.get("most.Cd", m_Cd);
238  pp.get("most.Ch", m_Ch);
239  pp.get("most.Cq", m_Cq);
240  pp.get("most.surf_temp", default_land_surf_temp);
241  pp.get("most.surf_moist", default_land_surf_moist);
242  amrex::Print() << "Using specified Cd, Ch, Cq for MOST = "
243  << m_Cd << " " << m_Ch << " "
244  << m_Cq << std::endl;
245 
246  // Specify surface temperature/moisture or surface flux
247  } else {
248  if (erf_st) {
250  pp.queryAdd("most.surf_heating_rate", surf_heating_rate); // [K/h]
251 
252  // Modify rate to be in units of K / s rather than K / hr
253  surf_heating_rate /= amrex::Real(3600.0); // [K/s]
254 
255  if (pp.query("most.surf_temp_flux", surf_temp_flux)) {
256  amrex::Abort("Can only specify one of surf_temp_flux or surf_heating_rate");
257  }
258  } else {
259  pp.queryAdd("most.surf_temp_flux", surf_temp_flux);
260 
261  if (pp.query("most.surf_heating_rate", surf_heating_rate)) {
262  amrex::Abort("Can only specify one of surf_temp_flux or surf_heating_rate");
263  }
264  if (std::abs(surf_temp_flux) >
267  } else {
269  }
270  }
271 
272  if (erf_sq) {
274  } else {
275  pp.queryAdd("most.surf_moist_flux", surf_moist_flux);
276  if (std::abs(surf_moist_flux) >
279  } else {
281  }
282  }
283  }
284 
289  m_face.coordDir() == 2 && m_face.isLow(),
290  "HEAT_FLUX and ADIABATIC surface-layer fluxes are supported only on the z-low face.");
291  }
292 
294  {
295  pp.queryAdd("most.rico.theta_z0", rico_theta_z0);
296  pp.queryAdd("most.rico.qsat_z0", rico_qsat_z0);
297  }
298 
299  // Make sure the inputs file doesn't try to use most.roughness_type
300  std::string bogus_input;
301  if (pp.queryAdd("most.roughness_type", bogus_input) > 0) {
302  amrex::Abort("most.roughness_type is deprecated; use "
303  "most.roughness_type_land and/or most.roughness_type_sea");
304  }
305 
306  // Specify how to compute the surface flux over land (if there is any)
307  std::string rough_land_string_in;
308  std::string rough_land_string{"constant"};
309  auto read_rough_land =
310  pp.queryAdd("most.roughness_type_land", rough_land_string_in);
311  if (read_rough_land) {
312  rough_land_string = amrex::toLower(rough_land_string_in);
313  }
314  if (rough_land_string == "constant") {
316  } else {
317  amrex::Abort("Undefined MOST roughness type for land!");
318  }
319 
320  // Allow for smooth-flow limit?
321  pp.queryAdd("most.smooth_flow_viscosity", smooth_flow_visc);
322  amrex::Print() << "The smooth-flow limit will be included for variable roughness models over sea: " << smooth_flow_visc << "\n";
323 
324  // Specify how to compute the surface flux over sea (if there is any)
325  std::string rough_sea_string_in;
326  std::string rough_sea_string{"charnock"};
327  auto read_rough_sea = pp.queryAdd("most.roughness_type_sea", rough_sea_string_in);
328  if (read_rough_sea) {
329  rough_sea_string = amrex::toLower(rough_sea_string_in);
330  }
331  if (rough_sea_string == "charnock") {
333  pp.queryAdd("most.charnock_constant", cnk_a);
334  if (cnk_a > 0) {
335  amrex::Print() << "If there is water, Charnock relation with C_a="
336  << cnk_a << " will be used" << std::endl;
337  } else {
338  amrex::Print() << "If there is water, Charnock relation with variable "
339  "Charnock parameter (COARE3.0) will be used" << std::endl;
340  }
341  } else if (rough_sea_string == "coare3.0") {
343  amrex::Print() << "If there is water, Charnock relation with variable "
344  "Charnock parameter (COARE3.0) will be used" << std::endl;
345  cnk_a = -1;
346  } else if (rough_sea_string == "donelan") {
348  } else if (rough_sea_string == "modified_charnock") {
350  pp.queryAdd("most.modified_charnock_depth", depth);
351  if (depth < amrex::Real(10.0) || depth > amrex::Real(100.0) ) {
352  amrex::Print() << "Specified depth of " << depth
353  << " is outside the valid range of [10 100], resetting to bounds now."
354  << std::endl;
355  }
356  // Limiter based upon the fit range in Jiménez & Dudhia
357  depth = amrex::min(amrex::max(depth,amrex::Real(10.0)),amrex::Real(100.0));
358  } else if (rough_sea_string == "wave_coupled") {
360  } else if (rough_sea_string == "constant") {
362  } else {
363  amrex::Abort("Undefined MOST roughness type for sea!");
364  }
365 
366  // use skin temperature instead of sea-surface temperature
367  // (wrfinput data may have lower resolution SST data)
368  pp.queryAdd("most.ignore_sst", m_ignore_sst);
369 
370  // If we're using the RANS k model, then we need to update the dirichlet
371  // wall value of k (written into the first cell above the wall and held
372  // there through the step) based on the instantaneous u* and θ*; the turbulence modeling
373  // choices can vary per level but for now, assume that if specified then
374  // all levels are using the same RANS model.
375  m_update_k_rans = (a_turb_choice.rans_type == RANSType::kEqn &&
376  a_turb_choice.dirichlet_k == true);
377  if (m_update_k_rans) {
379  m_face.coordDir() == 2 && m_face.isLow(),
380  "RANS surface-layer k updates are supported only on the z-low face.");
381  }
382  if (m_update_k_rans) {
383  inv_Cmu2 = one / (a_turb_choice.Cmu0 * a_turb_choice.Cmu0);
384  theta_ref = a_turb_choice.theta_ref;
385  }
386 
387  } // constructor
ParmParse pp("prob")
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_cloud_chamber_config.active, "Cloud Chamber: initializer reached without a parsed configuration")
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:37
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_ASSERT_WITH_MESSAGE(wbar_cutoff_min > wbar_cutoff_max, "ERROR: wbar_cutoff_min < wbar_cutoff_max")
ThetaCalcType theta_type
Definition: ERF_SurfaceLayer.H:1538
bool m_include_wstar
Definition: ERF_SurfaceLayer.H:1555
bool m_rotate
Definition: ERF_SurfaceLayer.H:1549
PBLHeightCalcType pblh_type
Definition: ERF_SurfaceLayer.H:1542
double m_final_low_time
Definition: ERF_SurfaceLayer.H:1552
bool use_moisture
Definition: ERF_SurfaceLayer.H:1583
amrex::Real m_Cq
Definition: ERF_SurfaceLayer.H:1604
amrex::Vector< const eb_ * > m_eb_vec
Definition: ERF_SurfaceLayer.H:1607
RoughCalcType rough_type_land
Definition: ERF_SurfaceLayer.H:1540
SurfaceModel * m_surf_model
Definition: ERF_SurfaceLayer.H:1611
amrex::Real z0_const
Definition: ERF_SurfaceLayer.H:1556
amrex::Real cnk_a
Definition: ERF_SurfaceLayer.H:1574
amrex::Real m_Ch
Definition: ERF_SurfaceLayer.H:1603
amrex::Real surf_temp
Definition: ERF_SurfaceLayer.H:1563
double m_start_low_time
Definition: ERF_SurfaceLayer.H:1551
amrex::Real rico_qsat_z0
Definition: ERF_SurfaceLayer.H:1581
bool m_update_k_rans
Definition: ERF_SurfaceLayer.H:1676
amrex::Real surf_moist_flux
Definition: ERF_SurfaceLayer.H:1568
bool smooth_flow_visc
Definition: ERF_SurfaceLayer.H:1575
RoughCalcType rough_type_sea
Definition: ERF_SurfaceLayer.H:1541
std::string m_pp_prefix
Definition: ERF_SurfaceLayer.H:1547
amrex::Real surf_moist
Definition: ERF_SurfaceLayer.H:1567
bool m_ignore_sst
Definition: ERF_SurfaceLayer.H:1605
double m_low_time_interval
Definition: ERF_SurfaceLayer.H:1553
amrex::GpuArray< int, AMREX_SPACEDIM *2 > m_surface_layer_faces
Definition: ERF_SurfaceLayer.H:1550
amrex::Real custom_qstar
Definition: ERF_SurfaceLayer.H:1571
bool surf_model_fluxes
Definition: ERF_SurfaceLayer.H:1613
amrex::Real custom_rhosurf
Definition: ERF_SurfaceLayer.H:1572
@ MOENG
Moeng functional form.
@ BULK_COEFF
Bulk transfer coefficient functional form.
@ CUSTOM
Custom constant flux functional form.
@ ROTATE
Terrain rotation flux functional form.
amrex::Real m_rdOcp
Definition: ERF_SurfaceLayer.H:1609
@ SURFACE_MOISTURE
Surface Qv specified.
@ MOISTURE_FLUX
Qv-flux specified.
amrex::Real depth
Definition: ERF_SurfaceLayer.H:1576
amrex::Real default_land_surf_moist
Definition: ERF_SurfaceLayer.H:1566
bool use_surface_model
Definition: ERF_SurfaceLayer.H:1612
amrex::Real rico_theta_z0
Definition: ERF_SurfaceLayer.H:1580
amrex::Real surf_temp_flux
Definition: ERF_SurfaceLayer.H:1565
amrex::Vector< amrex::Geometry > m_geom
Definition: ERF_SurfaceLayer.H:1548
amrex::Real theta_ref
Definition: ERF_SurfaceLayer.H:1678
amrex::Real custom_tstar
Definition: ERF_SurfaceLayer.H:1570
amrex::Real surf_heating_rate
Definition: ERF_SurfaceLayer.H:1564
FluxCalcType flux_type
Definition: ERF_SurfaceLayer.H:1537
MoistCalcType moist_type
Definition: ERF_SurfaceLayer.H:1539
amrex::Real inv_Cmu2
Definition: ERF_SurfaceLayer.H:1677
amrex::Real custom_ustar
Definition: ERF_SurfaceLayer.H:1569
amrex::Orientation m_face
Definition: ERF_SurfaceLayer.H:1546
amrex::Real m_Cd
Definition: ERF_SurfaceLayer.H:1602
amrex::Real default_land_surf_temp
Definition: ERF_SurfaceLayer.H:1557
@ SURFACE_TEMPERATURE
Surface temperature specified.
@ HEAT_FLUX
Heat-flux specified.
TerrainType m_terrain_type
Definition: ERF_SurfaceLayer.H:1608
MOSTAverage m_ma
Definition: ERF_SurfaceLayer.H:1610
Interface between ERF and land-surface and urban models.
Definition: ERF_SurfaceModel.H:47
bool are_fluxes()
Returns whether the interface exports fluxes rather than MOST variables.
Definition: ERF_SurfaceModel.H:471
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12
RANSType rans_type
Selected RANS closure.
Definition: ERF_TurbStruct.H:754
amrex::Real theta_ref
Reference potential temperature for stable stratification.
Definition: ERF_TurbStruct.H:743
bool dirichlet_k
Whether TKE uses Dirichlet boundary treatment.
Definition: ERF_TurbStruct.H:756
amrex::Real Cmu0
One-equation RANS Cmu0 coefficient.
Definition: ERF_TurbStruct.H:732

Member Function Documentation

◆ compute_fluxes() [1/2]

template<typename FluxIter >
void SurfaceLayer::compute_fluxes ( const int &  lev,
const int &  max_iters,
amrex::MultiFab &  cons_in,
const FluxIter &  most_flux,
bool  is_land 
)

Compute MOST fluxes with a selected flux-iteration functor.

Parameters
[in]levlevel index
[in]max_itersmaximum MOST iteration count
[in,out]cons_inconserved state used by the flux computation
[in]most_fluxflux-iteration functor
[in]is_landwhether the land-surface branch is active

◆ compute_fluxes() [2/2]

template<typename FluxIter >
void SurfaceLayer::compute_fluxes ( const int &  lev,
const int &  max_iters,
MultiFab &  ,
const FluxIter &  most_flux,
bool  is_land 
)

Function to compute the fluxes (u^star and t^star) for Monin Obukhov similarity theory

Parameters
[in]levCurrent level
[in]max_itersMaximum iterations to use
[in]cons_inConserved state whose grids define the surface iteration
[in]most_fluxFlux-iteration functor used to compute ustar, tstar, qstar, and related fields
[in]is_landSelects whether land or sea cells are updated
494 {
495  // Pointers to the computed averages
496  const auto *const tm_ptr = m_ma.get_average(lev,3); // potential temperature
497  const auto *const qvm_ptr = m_ma.get_average(lev,4); // water vapor mixing ratio
498  const auto *const tvm_ptr = m_ma.get_average(lev,5); // virtual potential temperature
499  const auto *const umm_ptr = m_ma.get_average(lev,6); // horizontal velocity magnitude
500  const auto *const uw_mag_mean = m_ma.get_average(lev,7); // x/z velocity magnitude
501  const auto *const vw_mag_mean = m_ma.get_average(lev,8); // y/z velocity magnitude
502  const auto *const zref_ptr = m_ma.get_zref(lev); // reference height
503  const bool l_use_eb = (m_terrain_type == TerrainType::EB);
504 
505  const int dir = m_face.coordDir();
506  // The source fields and Taus use the same BoxArray ordering and
507  // DistributionMapping. Full-state fields follow cons_in, while the
508  // collapsed SurfaceLayer/MOSTAverage fields follow u_star.
509  int sm_index = 0;
510  if (m_face.isLow()) {
511  sm_index = m_geom[lev].Domain().smallEnd(dir);
512  } else {
513  sm_index = m_geom[lev].Domain().bigEnd(dir);
514  }
515 
516  // Use the 2-D surface mask as the iterator so ranks participate only when
517  // their grids coincide with the selected face.
518  for (MFIter mfi(*m_lmask_lev[lev][0]); mfi.isValid(); ++mfi)
519  {
520  // Face ownership is a property of the full grid, not an individual
521  // tile. Keep the tile box for the kernel below, but test the valid
522  // box so all tiles of a face-owning grid are processed.
523  Box vbx = mfi.validbox();
524  const Box tbx = mfi.tilebox();
525  Box gtbx = mfi.growntilebox();
526 
527  // Since lmask is used in the MFIter, its z extent is collapsed. The
528  // lateral kernels need the full z column; z-face ownership is instead
529  // determined from the original 3-D surface-copy mapping.
530  if (dir != 2 || l_use_eb) {
531  gtbx.setSmall(2, m_geom[lev].Domain().smallEnd(2));
532  gtbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
533  }
534 
535  const bool owns_surface = l_use_eb || dir != 2 ||
536  m_planar_bndry[lev].is_surface_copy(mfi.index());
537  if (!owns_surface) {
538  continue;
539  }
540 
541  if (dir != 2) {
542  if (m_face.isLow()) {
543  if (vbx.smallEnd(dir) != sm_index ||
544  tbx.smallEnd(dir) != sm_index) {
545  continue;
546  }
547  } else {
548  if (vbx.bigEnd(dir) != sm_index ||
549  tbx.bigEnd(dir) != sm_index) {
550  continue;
551  }
552  }
553  }
554 
555  if (m_face.isLow()) {
556  gtbx.setSmall(dir, sm_index);
557  gtbx.setBig(dir, sm_index);
558  } else {
559  gtbx.setSmall(dir, sm_index);
560  gtbx.setBig(dir, sm_index);
561  }
562 
563  // X/Y faces still need the full valid z column
564  if (!l_use_eb && dir == 2) {
565  gtbx.makeSlab(2, sm_index);
566  } else {
567  gtbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
568  }
569 
570  // The mask iterator can have more lateral ghost cells than the
571  // surface-layer fields, and an EB FAB may be decomposed in z. Keep
572  // the face selection above, but never launch outside the target FAB.
573  gtbx &= u_star[lev]->fabbox(mfi.index());
574  if (gtbx.isEmpty()) { continue; }
575 
576  auto u_star_arr = u_star[lev]->array(mfi);
577  auto t_star_arr = t_star[lev]->array(mfi);
578  auto q_star_arr = q_star[lev]->array(mfi);
579  auto t_surf_arr = t_surf[lev]->array(mfi);
580  auto q_surf_arr = q_surf[lev]->array(mfi);
581  auto olen_arr = olen[lev]->array(mfi);
582 
583  const auto tm_arr = tm_ptr->array(mfi);
584  const auto tvm_arr = tvm_ptr->array(mfi);
585  const auto qvm_arr = qvm_ptr->array(mfi);
586  const auto umm_arr = umm_ptr->array(mfi);
587  const auto vwmm_arr = (dir == 0) ? vw_mag_mean->array(mfi) : Array4<Real>{};
588  const auto uwmm_arr = (dir == 1) ? uw_mag_mean->array(mfi) : Array4<Real>{};
589  const auto zref_arr = zref_ptr->array(mfi);
590 
591  // umm depending on face direction (YZ, XZ, XY)
592  const auto dir_umm_arr = ((dir == 0) ? vwmm_arr : ((dir == 1) ? uwmm_arr : umm_arr));
593  const auto z0_arr = z_0[lev].array(mfi);
594 
595  // PBL height if we need to calculate wstar for the Beljaars correction
596  // TODO: can/should we apply this in LES mode?
597  const auto w_star_arr = (m_include_wstar) ? w_star[lev].get()->array(mfi) : Array4<Real> {};
598  const auto pblh_arr = (m_include_wstar) ? pblh[lev].get()->array(mfi) : Array4<Real> {};
599 
600  // Wave properties if they exist
601  const auto Hwave_arr = (m_Hwave_lev[lev]) ? m_Hwave_lev[lev]->array(mfi) : Array4<Real> {};
602  const auto Lwave_arr = (m_Lwave_lev[lev]) ? m_Lwave_lev[lev]->array(mfi) : Array4<Real> {};
603  const auto eta_arr = (m_eddyDiffs_lev[lev]) ? m_eddyDiffs_lev[lev]->array(mfi) : Array4<Real> {};
604 
605  // Land mask array if it exists
606  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
607  Array4<int> {};
608 
609  // Get EB flags if needed
610  const auto flag_arr = (l_use_eb) ? m_eb_vec[lev]->get_const_factory()->getMultiEBCellFlagFab()[mfi].const_array() : Array4<const EBCellFlag>{};
611 
612  if (!l_use_eb) {
613  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
614  {
615  // always check for land mask at k=0 even if on other faces
616  if (( is_land && lmask_arr(i,j,0) == 1) ||
617  (!is_land && lmask_arr(i,j,0) == 0))
618  {
619  // NOTE: All 2D MFs so k index is always 0 from ba2d definition
620  most_flux.iterate_flux(i, j, k, max_iters,
621  zref_arr, // set in most average
622  z0_arr, // updated if(!is_land)
623  dir_umm_arr, tm_arr, tvm_arr, qvm_arr,
624  u_star_arr, // updated
625  w_star_arr, // updated if(m_include_wstar)
626  t_star_arr, q_star_arr, // updated
627  t_surf_arr, q_surf_arr, olen_arr, // updated
628  pblh_arr, // updated if(m_include_wstar)
629  Hwave_arr, Lwave_arr, eta_arr);
630  }
631  });
632  // EB
633  } else {
634  if (std::is_same<FluxIter, adiabatic_eb>::value ||
635  std::is_same<FluxIter, surface_temp_eb>::value ||
636  std::is_same<FluxIter, surface_flux_eb>::value) {
637  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
638  {
639  if (( is_land && lmask_arr(i,j,0) == 1) ||
640  (!is_land && lmask_arr(i,j,0) == 0))
641  {
642  if (flag_arr(i,j,k).isSingleValued()) {
643  most_flux.iterate_flux(i, j, k, max_iters,
644  zref_arr, // set in most average
645  z0_arr, // updated if(!is_land)
646  umm_arr, tm_arr, tvm_arr, qvm_arr,
647  u_star_arr, // updated
648  w_star_arr, // updated if(m_include_wstar)
649  t_star_arr, q_star_arr, // updated
650  t_surf_arr, q_surf_arr, olen_arr, // updated
651  pblh_arr, // updated if(m_include_wstar)
652  Hwave_arr, Lwave_arr, eta_arr);
653  }
654  }
655  });
656  } else {
657  amrex::Abort("FluxIter type not supported for EB");
658  }
659  }
660  }
661 }
pp get("wavelength", wavelength)
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);})
amrex::MultiFab * get_zref(const int &lev) const
Definition: ERF_MOSTAverage.H:329
const amrex::MultiFab * get_average(const int &lev, const int &comp) const
Definition: ERF_MOSTAverage.H:251
amrex::Vector< amrex::Vector< amrex::iMultiFab * > > m_lmask_lev
Definition: ERF_SurfaceLayer.H:1655
amrex::Vector< PlanarBoundary > m_planar_bndry
Definition: ERF_SurfaceLayer.H:1626
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_surf
Definition: ERF_SurfaceLayer.H:1620
amrex::Vector< std::unique_ptr< amrex::MultiFab > > q_star
Definition: ERF_SurfaceLayer.H:1617
amrex::Vector< amrex::MultiFab * > m_Lwave_lev
Definition: ERF_SurfaceLayer.H:1673
amrex::Vector< amrex::MultiFab > z_0
Definition: ERF_SurfaceLayer.H:1577
amrex::Vector< std::unique_ptr< amrex::MultiFab > > w_star
Definition: ERF_SurfaceLayer.H:1615
amrex::Vector< std::unique_ptr< amrex::MultiFab > > u_star
Definition: ERF_SurfaceLayer.H:1614
amrex::Vector< std::unique_ptr< amrex::MultiFab > > q_surf
Definition: ERF_SurfaceLayer.H:1621
amrex::Vector< amrex::MultiFab * > m_Hwave_lev
Definition: ERF_SurfaceLayer.H:1672
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_star
Definition: ERF_SurfaceLayer.H:1616
amrex::Vector< amrex::MultiFab * > m_eddyDiffs_lev
Definition: ERF_SurfaceLayer.H:1674
amrex::Vector< std::unique_ptr< amrex::MultiFab > > olen
Definition: ERF_SurfaceLayer.H:1618
amrex::Vector< std::unique_ptr< amrex::MultiFab > > pblh
Definition: ERF_SurfaceLayer.H:1619
Here is the call graph for this function:

◆ compute_pblh() [1/2]

template<typename PBLHeightEstimator >
void SurfaceLayer::compute_pblh ( const int &  lev,
amrex::Vector< amrex::Vector< amrex::MultiFab >> &  vars,
amrex::MultiFab *  z_phys_cc,
const PBLHeightEstimator &  est,
const MoistureComponentIndices &  moisture_indice 
)

Compute planetary-boundary-layer height with the selected estimator.

Parameters
[in]levlevel index
[in,out]varsstate variables used by the PBL-height calculation
[in]z_phys_cccell-centered physical-height field
[in]estPBL-height estimator functor
[in]moisture_indiceindices for moisture components

◆ compute_pblh() [2/2]

template<typename PBLHeightEstimator >
void SurfaceLayer::compute_pblh ( const int &  lev,
Vector< Vector< MultiFab >> &  vars,
MultiFab *  z_phys_cc,
const PBLHeightEstimator &  est,
const MoistureComponentIndices &  moisture_indices 
)

Compute PBL height with the supplied estimator.

Parameters
[in]levCurrent level
[in]varsLevel-indexed state MultiFabs passed to the estimator
[in]z_phys_ccCell-centered physical height used by the estimator
[in]estPBL height estimator functor
[in]moisture_indicesMoisture component indices used by the estimator
2452 {
2453  const MultiFab& cons = vars[lev][Vars::cons];
2454  const iMultiFab* lmask = m_lmask_lev[lev][0];
2455 
2456  // The estimator scans each box from its lowest cell to its highest and writes the planar
2457  // pblh of that box, so every box it is given must start at the ground. Grids that hold
2458  // such boxes only -- full height or not -- go straight to it. Any other grids (boxes
2459  // stacked in z, or boxes aloft) go through columns: the runs of cells that start at the
2460  // ground, each as one box (see define_pblh_columns).
2461  if (static_cast<int>(m_pblh_columns.size()) <= lev) { m_pblh_columns.resize(lev+1); }
2462  if (m_pblh_columns[lev].ba != cons.boxArray() ||
2463  m_pblh_columns[lev].dm != cons.DistributionMap()) {
2464  define_pblh_columns(lev, cons.boxArray(), cons.DistributionMap());
2465  }
2466  const PBLHColumns& cols = m_pblh_columns[lev];
2467 
2468  if (!cols.needed) {
2469  est.compute_pblh(m_geom[lev], z_phys_cc, pblh[lev].get(), cons, lmask, moisture_indices);
2470  return;
2471  }
2472 
2473  // Zero is the estimator's own value for a height it did not find. It stays on the planar
2474  // boxes over which no box of this level reaches the ground: all of them on a level that
2475  // lies entirely aloft.
2476  pblh[lev]->setVal(zero);
2477  if (cols.ba_col.empty()) { return; }
2478 
2479  const Periodicity period = m_geom[lev].periodicity();
2480 
2481  // The estimator reads the density, the potential temperature, the TKE and the moisture
2482  // species that enter theta_v. Those live in [0, RhoKE_comp] and in the moist window, so
2483  // the columns carry the state up to the highest of them and no further: the species above
2484  // it (the number concentrations of a two-moment scheme, the non-water species) are the
2485  // bulk of a moist state and are never read here. The span is contiguous rather than the
2486  // two pieces it is made of, so that every component of cons_col is filled by the copies
2487  // below -- a component the estimator reads must never be one this routine left unset --
2488  // and the components keep their place, since the estimator indexes the state by
2489  // component number.
2490  int q_hi = -1;
2491  for (const int q : {moisture_indices.qv, moisture_indices.qc, moisture_indices.qi,
2492  moisture_indices.qr, moisture_indices.qs, moisture_indices.qg}) {
2493  AMREX_ALWAYS_ASSERT((q < 0) || ((q > RhoKE_comp) && (q < cons.nComp())));
2494  q_hi = std::max(q_hi, q);
2495  }
2496  const int ncomp_col = std::max(RhoKE_comp+1, q_hi+1);
2497 
2498  // The halo the columns need: in x and y the ghost cells of pblh, which the estimator
2499  // writes and so reads the state over, and in z the one cell above the top of each column
2500  // that the scan's k+1 reads end in. The state must hold that halo for the copies below
2501  // to have anything to take it from.
2502  const IntVect ng_pblh = pblh[lev]->nGrowVect();
2503  const IntVect ng_col = elemwiseMax(ng_pblh, IntVect(AMREX_D_DECL(0,0,1)));
2504  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(cons.nGrowVect().allGE(ng_col),
2505  "erf.most.pblh_calc = MYNN25 on grids that do not all start at the ground needs the "
2506  "state to carry at least the ghost cells of the surface-layer fields, and one in z.");
2507 
2508  // Every valid cell of a column is a valid cell of this level, and every ghost cell of a
2509  // column is a valid or a ghost cell of the box that holds the cell next to it, so the two
2510  // passes below leave no cell of the columns unset. Ghost cells go first (they hold the
2511  // physical boundary values and, next to a coarser level, the values interpolated from
2512  // it), then the valid cells, so that every cell this level owns comes from the box that
2513  // owns it and not from a neighbour's ghost cell.
2514  MultiFab cons_col(cols.ba_col, cols.dm_col, ncomp_col, ng_col);
2515  for (const IntVect& ng_src : {ng_col, IntVect(0)}) {
2516  cons_col.ParallelCopy(cons, 0, 0, ncomp_col, ng_src, ng_col, period);
2517  }
2518 
2519  std::unique_ptr<MultiFab> zcc_col;
2520  if (z_phys_cc) {
2521  const IntVect ng_z = z_phys_cc->nGrowVect();
2522  zcc_col = std::make_unique<MultiFab>(cols.ba_col, cols.dm_col, 1, ng_z);
2523  zcc_col->ParallelCopy(*z_phys_cc, 0, 0, 1, ng_z, ng_z, period);
2524  zcc_col->ParallelCopy(*z_phys_cc, 0, 0, 1, IntVect(0), ng_z, period);
2525  }
2526 
2527  std::unique_ptr<iMultiFab> lmask_col;
2528  if (lmask) {
2529  lmask_col = std::make_unique<iMultiFab>(cols.ba_col2d, cols.dm_col, 1, ng_pblh);
2530  lmask_col->setVal(1);
2531  lmask_col->ParallelCopy(*lmask, 0, 0, 1, elemwiseMin(lmask->nGrowVect(), ng_pblh), ng_pblh, period);
2532  lmask_col->ParallelCopy(*lmask, 0, 0, 1, IntVect(0), ng_pblh, period);
2533  }
2534 
2535  MultiFab pblh_col(cols.ba_col2d, cols.dm_col, 1, ng_pblh);
2536  est.compute_pblh(m_geom[lev], zcc_col.get(), &pblh_col, cons_col, lmask_col.get(), moisture_indices);
2537 
2538  // Onto every planar box. The ghost cells of the columns go first, for the ghost cells of
2539  // pblh outside the domain. They also reach valid cells of pblh over which no box of this
2540  // level starts at the ground (the estimator fills the ghost cells of a column next to such
2541  // a gap from ghost data), so those are set back to zero before the valid cells of the
2542  // columns are copied. Every planar copy of a cell then holds the same value, which makes
2543  // the FillBoundary that ends this well defined despite the duplicate boxes.
2544  pblh[lev]->ParallelCopy(pblh_col, 0, 0, 1, ng_pblh, ng_pblh, period);
2545  pblh[lev]->setVal(zero, 0, 1, 0);
2546  pblh[lev]->ParallelCopy(pblh_col, 0, 0, 1, IntVect(0), IntVect(0), period);
2547  pblh[lev]->FillBoundary(period);
2548 }
#define RhoKE_comp
Definition: ERF_IndexDefines.H:44
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:36
amrex::Vector< PBLHColumns > m_pblh_columns
Definition: ERF_SurfaceLayer.H:1644
void define_pblh_columns(const int &lev, const amrex::BoxArray &ba, const amrex::DistributionMapping &dm)
Definition: ERF_SurfaceLayer.cpp:2559
@ cons
Definition: ERF_IndexDefines.H:217
@ q
Definition: ERF_WSM6.H:273
int qs
snow
Definition: ERF_DataStruct.H:253
int qr
rain
Definition: ERF_DataStruct.H:252
int qi
cloud ice
Definition: ERF_DataStruct.H:251
int qv
water vapor
Definition: ERF_DataStruct.H:249
int qc
cloud liquid water
Definition: ERF_DataStruct.H:250
int qg
graupel
Definition: ERF_DataStruct.H:254
Here is the call graph for this function:

◆ compute_sfc_params_from_lsm_fluxes()

void SurfaceLayer::compute_sfc_params_from_lsm_fluxes ( const int &  lev,
amrex::MultiFab &  cons_in 
)

Derive MOST surface parameters from LSM fluxes.

Parameters
[in]levlevel index
[in,out]cons_inconserved state used by the surface-parameter computation

Compute surface-layer parameters from land-surface-model fluxes.

Parameters
[in]levCurrent level
[in]cons_inConserved state used to derive density, theta, and moisture at the surface
1667 {
1669  static_cast<int>(m_face) == Orientation::zlo(),
1670  "LSM surface-layer parameters are supported only on the z-low face.");
1671 
1673  bool has_moisture = use_moisture;
1674  const bool use_surface_model_fluxes = use_surface_model && surf_model_fluxes;
1675  const int klo = m_geom[lev].Domain().smallEnd(2);
1676  const auto *const umm_ptr = m_ma.get_average(lev,6); // horizontal velocity magnitude
1677  const auto *const zref_ptr = m_ma.get_zref(lev); // reference height
1678  for (MFIter mfi(cons_in); mfi.isValid(); ++mfi) {
1679 
1680  Box vbx = mfi.validbox();
1681  if (vbx.smallEnd(2) != klo) { continue; }
1682  vbx.makeSlab(2,0);
1683 
1684  // Get CC state
1685  const Array4<const Real> cons_arr = cons_in.const_array(mfi);
1686 
1687  // Get SL params
1688  const auto u_star_arr = u_star[lev]->array(mfi);
1689  const auto t_star_arr = t_star[lev]->array(mfi);
1690  const auto q_star_arr = q_star[lev]->array(mfi);
1691  const auto olen_arr = olen[lev]->array(mfi);
1692 
1693  const auto umm_arr = umm_ptr->array(mfi);
1694  const auto zref_arr = zref_ptr->array(mfi);
1695  const auto z0_arr = z_0[lev].array(mfi);
1696 
1697  // Get LSM fluxes
1698  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
1699  Array4<int> {};
1700  auto lsm_t_flux_arr = Array4<const Real> {};
1701  auto lsm_q_flux_arr = Array4<const Real> {};
1702  auto lsm_tau13_arr = Array4<const Real> {};
1703  auto lsm_tau23_arr = Array4<const Real> {};
1704  // compute_sfc_params_from_lsm_fluxes consumes signed kinematic stress
1705  // components; their vector magnitude determines u_star^2.
1706  if (use_surface_model_fluxes) {
1707  const SurfaceFluxView surf_flux = m_surf_model->get_surface_flux_view(lev, mfi);
1708  lsm_t_flux_arr = surf_flux.t_flux;
1709  lsm_q_flux_arr = surf_flux.q_flux;
1710  lsm_tau13_arr = surf_flux.tau13;
1711  lsm_tau23_arr = surf_flux.tau23;
1712  } else {
1713  for (int n(0); n<m_lsm_flux_lev[lev].size(); ++n) {
1714  if (toLower(m_lsm_flux_name[n]) == "t_flux") { lsm_t_flux_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1715  if (toLower(m_lsm_flux_name[n]) == "q_flux") { lsm_q_flux_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1716  if (toLower(m_lsm_flux_name[n]) == "tau13") { lsm_tau13_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1717  if (toLower(m_lsm_flux_name[n]) == "tau23") { lsm_tau23_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1718  }
1719  }
1720 
1721  ParallelFor(vbx, [=] AMREX_GPU_DEVICE(int i, int j, int /*k*/) noexcept
1722  {
1723  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
1724  // Skip cells the LSM did not have a valid flux (lsm_undefined).
1725  if (is_land && lsm_t_flux_arr && lsm_t_flux_arr(i,j,0) < lsm_undefined) {
1726  Real rho = cons_arr(i,j,klo,Rho_comp);
1727  Real Thd = cons_arr(i,j,klo,RhoTheta_comp) / rho;
1728  Real qv = (has_moisture) ? cons_arr(i,j,klo,RhoQ1_comp) / rho : zero;
1729  Real Thv = Thd * (one + epsv*qv);
1730  Real tau = std::sqrt( lsm_tau13_arr(i,j,0)*lsm_tau13_arr(i,j,0)
1731  + lsm_tau23_arr(i,j,0)*lsm_tau23_arr(i,j,0) );
1732  u_star_arr(i,j,0) = amrex::max(std::sqrt(tau),eps);
1733  if (lsm_t_flux_arr(i,j,0)>=zero) {
1734  t_star_arr(i,j,0) = amrex::min(-lsm_t_flux_arr(i,j,0) / u_star_arr(i,j,0),-eps);
1735  } else {
1736  t_star_arr(i,j,0) = amrex::max(-lsm_t_flux_arr(i,j,0) / u_star_arr(i,j,0),eps);
1737  }
1738  if (lsm_q_flux_arr(i,j,0)>=zero) {
1739  q_star_arr(i,j,0) = amrex::min(-lsm_q_flux_arr(i,j,0) / u_star_arr(i,j,0),-eps);
1740  } else {
1741  q_star_arr(i,j,0) = amrex::max(-lsm_q_flux_arr(i,j,0) / u_star_arr(i,j,0),eps);
1742  }
1743  Real tstv = t_star_arr(i,j,0)*(one + epsv*qv) + epsv*Thd*q_star_arr(i,j,0);
1744  tstv = (tstv >= zero) ? amrex::max(tstv, eps) : amrex::min(tstv, -eps);
1745  olen_arr(i,j,0) = ( u_star_arr(i,j,0) * u_star_arr(i,j,0) * Thv ) /
1746  ( KAPPA * CONST_GRAV * tstv );
1747  z0_arr(i,j,0) = Compute_roughness(zref_arr(i,j,0), olen_arr(i,j,0),
1748  umm_arr(i,j,0), u_star_arr(i,j,0));
1749  }
1750  });
1751  } // mfi
1752 }
constexpr amrex::Real epsv
Definition: ERF_Constants.H:52
constexpr amrex::Real KAPPA
Definition: ERF_Constants.H:67
constexpr amrex::Real CONST_GRAV
Definition: ERF_Constants.H:68
constexpr amrex::Real lsm_undefined
Definition: ERF_Constants.H:28
#define Rho_comp
Definition: ERF_IndexDefines.H:42
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:43
#define RhoQ1_comp
Definition: ERF_IndexDefines.H:48
const int klo
Definition: ERF_InitCustomPert_ABL.H:75
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real Compute_roughness(amrex::Real zref, amrex::Real Olen, amrex::Real umm, amrex::Real ustar)
Definition: ERF_MOSTUtils.H:302
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_lsm_flux_lev
Definition: ERF_SurfaceLayer.H:1669
amrex::Vector< std::string > m_lsm_flux_name
Definition: ERF_SurfaceLayer.H:1671
SurfaceFluxView get_surface_flux_view(const int lev, const amrex::MFIter &mfi) const
Returns provider or blended surface-flux views for one tile.
Definition: ERF_SurfaceModel.H:553
@ rho
Definition: ERF_Kessler.H:25
@ qv
Definition: ERF_Kessler.H:31
Non-owning views of surface fluxes for one tile.
Definition: ERF_SurfaceModel.H:22
amrex::Array4< const amrex::Real > q_flux
Definition: ERF_SurfaceModel.H:26
amrex::Array4< const amrex::Real > tau23
Definition: ERF_SurfaceModel.H:24
amrex::Array4< const amrex::Real > tau13
Definition: ERF_SurfaceModel.H:23
amrex::Array4< const amrex::Real > t_flux
Definition: ERF_SurfaceModel.H:25
Here is the call graph for this function:

◆ compute_SurfaceLayer_bcs() [1/2]

template<typename FluxCalc >
void SurfaceLayer::compute_SurfaceLayer_bcs ( const int &  lev,
amrex::Vector< const amrex::MultiFab * >  mfs,
amrex::Vector< std::unique_ptr< amrex::MultiFab >> &  Tau_lev,
amrex::MultiFab *  xheat_flux,
amrex::MultiFab *  yheat_flux,
amrex::MultiFab *  zheat_flux,
amrex::MultiFab *  xqv_flux,
amrex::MultiFab *  yqv_flux,
amrex::MultiFab *  zqv_flux,
const amrex::MultiFab *  z_phys,
const FluxCalc &  flux_comp 
)

Compute planar-terrain surface-layer flux boundary conditions.

Parameters
[in]levlevel index
[in]mfsstate and velocity fields used by the BC computation
[in,out]Tau_levstress fields to fill
[in,out]xheat_fluxx-face heat flux field
[in,out]yheat_fluxy-face heat flux field
[in,out]zheat_fluxz-face heat flux field
[in,out]xqv_fluxx-face moisture flux field
[in,out]yqv_fluxy-face moisture flux field
[in,out]zqv_fluxz-face moisture flux field
[in]z_physphysical-height field
[in]flux_compflux-computation functor

◆ compute_SurfaceLayer_bcs() [2/2]

template<typename FluxCalc >
void SurfaceLayer::compute_SurfaceLayer_bcs ( const int &  lev,
Vector< const MultiFab * >  mfs,
Vector< std::unique_ptr< MultiFab >> &  Tau_lev,
MultiFab *  xheat_flux,
MultiFab *  yheat_flux,
MultiFab *  zheat_flux,
MultiFab *  xqv_flux,
MultiFab *  yqv_flux,
MultiFab *  zqv_flux,
const MultiFab *  z_phys,
const FluxCalc &  flux_comp 
)

Function to calculate MOST fluxes for populating ghost cells.

Parameters
[in]levCurrent level
[in]mfsState MultiFabs used to compute the boundary fluxes
[in,out]Tau_levDiffusive stress MultiFabs populated with surface stresses
[in,out]xheat_fluxx-face heat-flux MultiFab, used when rotated fluxes are enabled
[in,out]yheat_fluxy-face heat-flux MultiFab, used when rotated fluxes are enabled
[in,out]zheat_fluxz-face heat-flux MultiFab populated with vertical surface heat flux
[in,out]xqv_fluxx-face moisture-flux MultiFab, used when rotated fluxes and moisture are enabled
[in,out]yqv_fluxy-face moisture-flux MultiFab, used when rotated fluxes and moisture are enabled
[in,out]zqv_fluxz-face moisture-flux MultiFab populated when moisture is enabled
[in]z_physNodal physical height used to rotate terrain-following fluxes
[in]flux_compFlux-calculation functor used to compute scalar and momentum fluxes
896 {
897  bool rotate = m_rotate;
898 
899  const int dir = m_face.coordDir();
901  !mfs.empty() && mfs[0] != nullptr,
902  "Surface-layer stress computation requires a conserved-state MultiFab.");
904  Tau_lev[TauType::tau13] != nullptr && Tau_lev[TauType::tau23] != nullptr,
905  "tau13 and tau23 are required by surface-layer stress computation.");
906  int sm_index = 0;
907  if (m_face.isLow()) {
908  sm_index = m_geom[lev].Domain().smallEnd(dir);
909  } else {
910  sm_index = m_geom[lev].Domain().bigEnd(dir);
911  }
912 
913  // These requirements are invariant over the loop below. Check them once
914  // before acquiring per-tile array views.
915  if (dir == 0) {
917  Tau_lev[TauType::tau31] != nullptr,
918  "tau31 is required when imposing an x-face surface layer.");
920  Tau_lev[TauType::tau21] != nullptr,
921  "tau21 is required when imposing an x-face surface layer.");
922  } else if (dir == 1) {
924  Tau_lev[TauType::tau32] != nullptr,
925  "tau32 is required when imposing a y-face surface layer.");
927  Tau_lev[TauType::tau12] != nullptr,
928  "tau12 is required when imposing a y-face surface layer.");
929  }
930 
931  const int klo = sm_index;
932  const auto& dxInv = m_geom[lev].InvCellSizeArray();
933  const Box domain = m_geom[lev].Domain();
934  const int xlo_node = domain.smallEnd(0);
935  const int xhi_node = domain.bigEnd(0) + 1;
936  const int ylo_node = domain.smallEnd(1);
937  const int yhi_node = domain.bigEnd(1) + 1;
938  const int zlo_node = domain.smallEnd(2);
939  const int zhi_node = domain.bigEnd(2) + 1;
940  const bool xlo_surface = m_surface_layer_faces[Orientation::xlo()];
941  const bool xhi_surface = m_surface_layer_faces[Orientation::xhi()];
942  const bool ylo_surface = m_surface_layer_faces[Orientation::ylo()];
943  const bool yhi_surface = m_surface_layer_faces[Orientation::yhi()];
944  const bool zlo_surface = m_surface_layer_faces[Orientation::zlo()];
945  const bool zhi_surface = m_surface_layer_faces[Orientation::zhi()];
946 
947  for (MFIter mfi(*mfs[0]); mfi.isValid(); ++mfi)
948  {
949  // Skip boxes that do not own the selected face before acquiring any
950  // arrays from collapsed or staggered layouts. Interior boxes may
951  // share the same collapsed coordinates but do not contain surface
952  // data for this boundary.
953  Box bx = mfi.tilebox();
954  const Box valid_bx = mfi.validbox();
955  if (m_face.isLow()) {
956  if (valid_bx.smallEnd(dir) != klo ||
957  bx.smallEnd(dir) != klo) {
958  continue;
959  }
960  bx.setBig(dir, bx.smallEnd(dir));
961  } else {
962  if (valid_bx.bigEnd(dir) != klo ||
963  bx.bigEnd(dir) != klo) {
964  continue;
965  }
966  bx.setSmall(dir, bx.bigEnd(dir));
967  }
968 
969  // Get field arrays
970  const auto cons_arr = mfs[Vars::cons]->array(mfi);
971  const auto velx_arr = mfs[Vars::xvel]->array(mfi);
972  const auto vely_arr = mfs[Vars::yvel]->array(mfi);
973  const auto velz_arr = mfs[Vars::zvel]->array(mfi);
974 
975  // Output stresses:
976  // T Q U V
977  // X-faces: hfx1 qfx1 t21 t31
978  // Y-faces: hfx2 qfx2 t12 t32
979  // Z-faces: hfx3 qfx3 t13 t23
980 
981  // Output stresses nodal locations:
982  // T Q
983  // X-faces: hfx1 qfx1(1,0,0) t21(V)(1,1,0) t31(W)(1,0,1)
984  // Y-faces: hfx2 qfx2(0,1,0) t12(U)(1,1,0) t32(W)(0,1,1)
985  // Z-faces: hfx3 qfx3(0,0,1) t13(U)(1,0,1) t23(V)(0,1,1)
986 
987  // Diffusive stress vars
988  auto t13_arr = Tau_lev[TauType::tau13]->array(mfi);
989  auto t31_arr = Tau_lev[TauType::tau31]
990  ? Tau_lev[TauType::tau31]->array(mfi) : Array4<Real>{};
991 
992  auto t23_arr = Tau_lev[TauType::tau23]->array(mfi);
993  auto t32_arr = Tau_lev[TauType::tau32]
994  ? Tau_lev[TauType::tau32]->array(mfi) : Array4<Real>{};
995 
996 
997  auto hfx3_arr = zheat_flux->array(mfi);
998  auto qfx3_arr = (zqv_flux) ? zqv_flux->array(mfi) : Array4<Real>{};
999 
1000  auto olen_arr = olen[lev]->array(mfi);
1001 
1002  // Rotated stress vars
1003  auto t11_arr = (m_rotate) ? Tau_lev[TauType::tau11]->array(mfi) : Array4<Real>{};
1004  auto t22_arr = (m_rotate) ? Tau_lev[TauType::tau22]->array(mfi) : Array4<Real>{};
1005  auto t33_arr = (m_rotate) ? Tau_lev[TauType::tau33]->array(mfi) : Array4<Real>{};
1006  auto t12_arr = Tau_lev[TauType::tau12]
1007  ? Tau_lev[TauType::tau12]->array(mfi) : Array4<Real>{};
1008  auto t21_arr = Tau_lev[TauType::tau21]
1009  ? Tau_lev[TauType::tau21]->array(mfi) : Array4<Real>{};
1010 
1011  auto hfx1_arr = (m_rotate || dir == 0) ? xheat_flux->array(mfi) : Array4<Real>{};
1012  auto hfx2_arr = (m_rotate || dir == 1) ? yheat_flux->array(mfi) : Array4<Real>{};
1013  auto qfx1_arr = (xqv_flux && (m_rotate || dir == 0)) ? xqv_flux->array(mfi) : Array4<Real>{};
1014  auto qfx2_arr = (yqv_flux && (m_rotate || dir == 1)) ? yqv_flux->array(mfi) : Array4<Real>{};
1015 
1016  // Terrain
1017  const auto zphys_arr = (z_phys) ? z_phys->const_array(mfi) : Array4<const Real>{};
1018 
1019  // Get average arrays
1020  const auto *const u_mean = m_ma.get_average(lev,0);
1021  const auto *const v_mean = m_ma.get_average(lev,1);
1022  const auto *const w_mean = m_ma.get_average(lev,2);
1023  const auto *const t_mean = m_ma.get_average(lev,3);
1024  const auto *const q_mean = m_ma.get_average(lev,4);
1025  const auto *const u_mag_mean = m_ma.get_average(lev,6);
1026  const auto *const uw_mag_mean = m_ma.get_average(lev,7);
1027  const auto *const vw_mag_mean = m_ma.get_average(lev,8);
1028  const auto *const zref_ptr = m_ma.get_zref(lev);
1029 
1030  const auto um_arr = u_mean->array(mfi);
1031  const auto vm_arr = v_mean->array(mfi);
1032  const auto wm_arr = w_mean->array(mfi);
1033  const auto tm_arr = t_mean->array(mfi);
1034  const auto qm_arr = q_mean->array(mfi);
1035  const auto umm_arr = u_mag_mean->array(mfi);
1036  const auto vwmm_arr = (dir == 0) ? vw_mag_mean->array(mfi) : Array4<Real>{};
1037  const auto uwmm_arr = (dir == 1) ? uw_mag_mean->array(mfi) : Array4<Real>{};
1038 
1039  // umm depending on face direction (YZ, XZ, XY)
1040  const auto dir_umm_arr = ((dir == 0) ? vwmm_arr : ((dir == 1) ? uwmm_arr : umm_arr));
1041 
1042  const auto zref_arr = zref_ptr->array(mfi);
1043  const auto z0_arr = z_0[lev].array(mfi);
1044 
1045  // Get derived arrays
1046  const auto u_star_arr = u_star[lev]->array(mfi);
1047  const auto t_star_arr = t_star[lev]->array(mfi);
1048  const auto q_star_arr = q_star[lev]->array(mfi);
1049  const auto t_surf_arr = t_surf[lev]->array(mfi);
1050  const auto q_surf_arr = q_surf[lev]->array(mfi);
1051  auto surface_source_arr = surface_diagnostic_source[lev]->array(mfi);
1052 
1053  // Get LSM fluxes
1054  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
1055  Array4<int> {};
1056  auto lsm_t_flux_arr = Array4<const Real> {};
1057  auto soil_t_flux_arr = Array4<Real> {};
1058  auto lsm_q_flux_arr = Array4<const Real> {};
1059  auto lsm_tau13_arr = Array4<const Real> {};
1060  auto lsm_tau23_arr = Array4<const Real> {};
1061  // LSM tau fields are cell-centered kinematic stresses [m2 s-2].
1062  // Tau_lev tau13/tau23 are face-centered conservative stresses [N m-2].
1063  for (int n(0); n<m_lsm_flux_lev[lev].size(); ++n) {
1064  if (toLower(m_lsm_flux_name[n]) == "t_flux") { lsm_t_flux_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1065  if (toLower(m_lsm_flux_name[n]) == "soil_t_flux") { soil_t_flux_arr = m_lsm_flux_lev[lev][n]->array(mfi); }
1066  if (toLower(m_lsm_flux_name[n]) == "q_flux") { lsm_q_flux_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1067  if (toLower(m_lsm_flux_name[n]) == "tau13") { lsm_tau13_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1068  if (toLower(m_lsm_flux_name[n]) == "tau23") { lsm_tau23_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1069  }
1070 
1071  // Get provider or weight-averaged LSM+Urban fluxes through SurfaceModel.
1072  const SurfaceFluxView surf_flux = (use_surface_model && surf_model_fluxes)
1074  const auto surf_tflux_arr = surf_flux.t_flux;
1075  const auto surf_qflux_arr = surf_flux.q_flux;
1076  const auto surf_uflux_arr = surf_flux.tau13;
1077  const auto surf_vflux_arr = surf_flux.tau23;
1078  const bool use_surface_model_fluxes = use_surface_model && surf_model_fluxes;
1079  const auto t_flux_arr = use_surface_model_fluxes ? surf_tflux_arr : lsm_t_flux_arr;
1080  const auto q_flux_arr = use_surface_model_fluxes ? surf_qflux_arr : lsm_q_flux_arr;
1081  const auto surface_tau13_arr = use_surface_model_fluxes ? surf_uflux_arr : lsm_tau13_arr;
1082  const auto surface_tau23_arr = use_surface_model_fluxes ? surf_vflux_arr : lsm_tau23_arr;
1083  const bool has_lsm_t_flux = static_cast<bool>(t_flux_arr);
1084  const bool is_custom = (flux_type == FluxCalcType::CUSTOM);
1085  const bool is_rico = (flux_type == FluxCalcType::RICO);
1086 
1087 
1088  // Rho*Theta flux
1089  //============================================================================
1090  const bool is_low_face = m_face.isLow();
1091 
1092  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1093  {
1094  // Valid theta flux from LSM and over land. The LSM writes the
1095  // lsm_undefined sentinel for cells it did not process (sea-ice /
1096  // open water); fall back to MOST there instead of applying garbage.
1097  Real Tflux;
1098  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
1099  const bool lsm_flux_is_valid = (t_flux_arr) ? (t_flux_arr(i,j,0) < lsm_undefined) :
1100  false;
1101  const bool has_land_and_flux = (is_land == 1 && lsm_flux_is_valid);
1102  if (t_flux_arr && has_land_and_flux) {
1103  // LSM flux MultiFabs store kinematic fluxes for MOST parameter
1104  // updates. The applied hfx array stores the conservative RHS flux.
1105  Tflux = cons_arr(i,j,k,Rho_comp) * t_flux_arr(i,j,0);
1106  } else if (is_land == 2) { // no temperature flux within buildings
1107  Tflux = zero;
1108  } else {
1109  Tflux = flux_comp.compute_t_flux(i, j, k, dir,
1110  cons_arr, velx_arr, vely_arr, velz_arr,
1111  dir_umm_arr, tm_arr, u_star_arr,
1112  t_star_arr, t_surf_arr);
1113  // NOTE: do NOT write the MOST-fallback flux back into lsm_t_flux_arr.
1114  // Doing so flips a sentinel (water/unprocessed) cell to "valid LSM"
1115  // on the next step, so a MOST-derived value is re-read as an LSM flux
1116  // Only Noah-MP should populate the LSM cache.
1117  }
1118 
1119  if (soil_t_flux_arr && is_land == 1) {
1120  soil_t_flux_arr(i,j,k) = Tflux / cons_arr(i,j,k,Rho_comp);
1121  }
1122 
1123  surface_source_arr(i,j,k) = surface_diagnostics::to_plot_value(
1125  is_custom, is_rico, is_land, has_lsm_t_flux, lsm_flux_is_valid));
1126 
1127  // Do scalar flux rotations?
1128  if (rotate) {
1129  rotate_scalar_flux(i, j, k, dir, Tflux, dxInv, zphys_arr,
1130  hfx1_arr, hfx2_arr, hfx3_arr);
1131  } else {
1132  // swap sign for upward faces
1133  if (!is_low_face && dir != 2) {
1134  Tflux = -Tflux;
1135  }
1136 
1137  // write out to corresponding face
1138  if (dir == 0) {
1139  if (!is_low_face) {
1140  hfx1_arr(i+1,j,k) = Tflux;
1141  } else {
1142  hfx1_arr(i,j,k) = Tflux;
1143  }
1144  } else if (dir == 1) {
1145  if (!is_low_face) {
1146  hfx2_arr(i,j+1,k) = Tflux;
1147  } else {
1148  hfx2_arr(i,j,k) = Tflux;
1149  }
1150  } else {
1151  if (!is_low_face) {
1152  hfx3_arr(i,j,k+1) = Tflux;
1153  } else {
1154  hfx3_arr(i,j,k) = Tflux;
1155  }
1156  }
1157  }
1158  });
1159 
1160  // Rho*Qv flux
1161  //============================================================================
1162  if (use_moisture) {
1163  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1164  {
1165  // Valid qv flux from LSM and over land (sentinel -> fall back to MOST)
1166  Real Qflux;
1167  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
1168  const bool lsm_flux_is_valid = (q_flux_arr) ? (q_flux_arr(i,j,0) < lsm_undefined) :
1169  false;
1170  const bool has_land_and_flux = (is_land == 1 && lsm_flux_is_valid);
1171  if (q_flux_arr && has_land_and_flux) {
1172  // LSM flux MultiFabs store kinematic fluxes for MOST parameter
1173  // updates. The applied qfx array stores the conservative RHS flux.
1174  Qflux = cons_arr(i,j,k,Rho_comp) * q_flux_arr(i,j,0);
1175  } else if (is_land == 2) { // no moisture flux within buildings
1176  Qflux = zero;
1177  } else {
1178  Qflux = flux_comp.compute_q_flux(i, j, k, dir,
1179  cons_arr, velx_arr, vely_arr, velz_arr,
1180  dir_umm_arr, qm_arr, u_star_arr,
1181  q_star_arr, q_surf_arr);
1182  // NOTE: no writeback into lsm_q_flux_arr -- see the matching
1183  // t_flux note above.
1184  }
1185 
1186  // Do scalar flux rotations?
1187  if (rotate) {
1188  rotate_scalar_flux(i, j, k, dir, Qflux, dxInv, zphys_arr,
1189  qfx1_arr, qfx2_arr, qfx3_arr);
1190  } else {
1191  // swap sign for upward faces
1192  if (!is_low_face && dir != 2) {
1193  Qflux = -Qflux;
1194  }
1195 
1196  // write out to corresponding face
1197  if (dir == 0) {
1198  if (!is_low_face) {
1199  qfx1_arr(i+1,j,k) = Qflux;
1200  } else {
1201  qfx1_arr(i,j,k) = Qflux;
1202  }
1203  } else if (dir == 1) {
1204  if (!is_low_face) {
1205  qfx2_arr(i,j+1,k) = Qflux;
1206  } else {
1207  qfx2_arr(i,j,k) = Qflux;
1208  }
1209  } else {
1210  if (!is_low_face) {
1211  qfx3_arr(i,j,k+1) = Qflux;
1212  } else {
1213  qfx3_arr(i,j,k) = Qflux;
1214  }
1215  }
1216  }
1217  });
1218  } // custom
1219 
1220  if (!rotate) {
1221  // Rho*u flux
1222  //============================================================================
1223  const IntVect stressx_nodal = (dir == 2) ? IntVect(1,0,1) : IntVect(1,1,0);
1224  Box bxx = convert(bx, stressx_nodal);
1225  const int stressx_face_index = is_low_face
1226  ? m_geom[lev].Domain().smallEnd(dir)
1227  : m_geom[lev].Domain().bigEnd(dir) + 1;
1228  bxx.setRange(dir, stressx_face_index);
1229  ParallelFor(bxx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1230  {
1231  // Valid tau13 from LSM and over land. A side that is land but
1232  // whose LSM flux is the sentinel (sea-ice / open water) is treated
1233  // as non-LSM so that side uses the MOST stress instead.
1234  Real stressx;
1235  int is_land_hi = (lmask_arr) ? lmask_arr(i ,j,0) : 1;
1236  int is_land_lo = (lmask_arr) ? lmask_arr(i-1,j,0) : 1;
1237  const bool lsm_hi_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1238  static_cast<bool>(surface_tau13_arr), is_land_hi == 1,
1239  surface_tau13_arr ? surface_tau13_arr(i ,j,0) : zero, lsm_undefined);
1240  const bool lsm_lo_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1241  static_cast<bool>(surface_tau13_arr), is_land_lo == 1,
1242  surface_tau13_arr ? surface_tau13_arr(i-1,j,0) : zero, lsm_undefined);
1243  const bool has_land_and_flux_hi = (is_land_hi == 1 && lsm_hi_flux_is_valid);
1244  const bool has_land_and_flux_lo = (is_land_lo == 1 && lsm_lo_flux_is_valid);
1245  if (surface_tau13_arr && (has_land_and_flux_hi || has_land_and_flux_lo)) {
1246  const Real rho_hi = cons_arr(i ,j,k,Rho_comp);
1247  const Real rho_lo = cons_arr(i-1,j,k,Rho_comp);
1248  const Real most_stress = (!has_land_and_flux_hi || !has_land_and_flux_lo) ?
1249  flux_comp.compute_u_flux(i, j, k, dir,
1250  cons_arr, velx_arr, vely_arr, velz_arr,
1251  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr) : zero;
1253  rho_lo, rho_hi, surface_tau13_arr(i-1,j,0), surface_tau13_arr(i,j,0),
1254  has_land_and_flux_lo, has_land_and_flux_hi, most_stress);
1255  stressx = result.face_stress;
1256  // NOTE: do NOT write the MOST-fallback stress back into the
1257  // cell-centered lsm_tau13_arr. This face-indexed ParallelFor
1258  // touches cells (i) and (i-1), so each cell is written by two
1259  // adjacent face threads in the same launch -> nondeterministic
1260  // write-write race on GPU (ERF #3446). It also spuriously flips
1261  // a sentinel (water/unprocessed) cell to "valid LSM" for the
1262  // next step. The face stress is fully determined here; the LSM
1263  // cache is (re)filled only by Noah-MP. Matches baseline 3ab899d3.
1264  } else if (is_land_hi == 2 || is_land_lo == 2) { // no stress within buildings
1265  stressx = zero;
1266  } else {
1267  stressx = flux_comp.compute_u_flux(i, j, k, dir,
1268  cons_arr, velx_arr, vely_arr, velz_arr,
1269  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr);
1270  }
1271 
1272  // write out to corresponding face
1273  if (dir == 0) {
1274  t21_arr(i,j,k) = stressx;
1275  if (t12_arr &&
1276  (!ylo_surface || j != ylo_node) &&
1277  (!yhi_surface || j != yhi_node)) {
1278  t12_arr(i,j,k) = stressx;
1279  }
1280  } else if (dir == 1) {
1281  t12_arr(i,j,k) = stressx;
1282  if (t21_arr &&
1283  (!xlo_surface || i != xlo_node) &&
1284  (!xhi_surface || i != xhi_node)) {
1285  t21_arr(i,j,k) = stressx;
1286  }
1287  } else {
1288  t13_arr(i,j,k) = stressx;
1289  if (t31_arr &&
1290  (!xlo_surface || i != xlo_node) &&
1291  (!xhi_surface || i != xhi_node)) {
1292  t31_arr(i,j,k) = stressx;
1293  }
1294  }
1295  });
1296 
1297  // Rho*v flux
1298  //============================================================================
1299  const IntVect stressy_nodal = (dir == 0) ? IntVect(1,0,1) : IntVect(0,1,1);
1300  Box bxy = convert(bx, stressy_nodal);
1301  const int stressy_face_index = is_low_face
1302  ? m_geom[lev].Domain().smallEnd(dir)
1303  : m_geom[lev].Domain().bigEnd(dir) + 1;
1304  bxy.setRange(dir, stressy_face_index);
1305  ParallelFor(bxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1306  {
1307  // Valid tau23 from LSM and over land (sentinel side -> MOST stress)
1308  Real stressy;
1309  int is_land_hi = (lmask_arr) ? lmask_arr(i,j ,0) : 1;
1310  int is_land_lo = (lmask_arr) ? lmask_arr(i,j-1,0) : 1;
1311  const bool lsm_hi_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1312  static_cast<bool>(surface_tau23_arr), is_land_hi == 1,
1313  surface_tau23_arr ? surface_tau23_arr(i,j ,0) : zero, lsm_undefined);
1314  const bool lsm_lo_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1315  static_cast<bool>(surface_tau23_arr), is_land_lo == 1,
1316  surface_tau23_arr ? surface_tau23_arr(i,j-1,0) : zero, lsm_undefined);
1317  const bool has_land_and_flux_hi = (is_land_hi == 1 && lsm_hi_flux_is_valid);
1318  const bool has_land_and_flux_lo = (is_land_lo == 1 && lsm_lo_flux_is_valid);
1319  if (surface_tau23_arr && (has_land_and_flux_hi || has_land_and_flux_lo)) {
1320  const Real rho_hi = cons_arr(i,j ,k,Rho_comp);
1321  const Real rho_lo = cons_arr(i,j-1,k,Rho_comp);
1322  const Real most_stress = (!has_land_and_flux_hi || !has_land_and_flux_lo) ?
1323  flux_comp.compute_v_flux(i, j, k, dir,
1324  cons_arr, velx_arr, vely_arr, velz_arr,
1325  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr) : zero;
1327  rho_lo, rho_hi, surface_tau23_arr(i,j-1,0), surface_tau23_arr(i,j,0),
1328  has_land_and_flux_lo, has_land_and_flux_hi, most_stress);
1329  stressy = result.face_stress;
1330  // NOTE: no writeback into cell-centered lsm_tau23_arr -- see the
1331  // matching tau13 note above (ERF #3446 write-write race + stale
1332  // sentinel-becomes-valid). Face stress is complete here.
1333  } else if (is_land_hi == 2 || is_land_lo == 2) { // no stress within buildings
1334  stressy = zero;
1335  } else {
1336  stressy = flux_comp.compute_v_flux(i, j, k, dir,
1337  cons_arr, velx_arr, vely_arr, velz_arr,
1338  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr);
1339  }
1340 
1341  // write out to corresponding face
1342  if (dir == 0) {
1343  t31_arr(i,j,k) = stressy;
1344  if (t13_arr &&
1345  (!zlo_surface || k != zlo_node) &&
1346  (!zhi_surface || k != zhi_node)) {
1347  t13_arr(i,j,k) = stressy;
1348  }
1349  } else if (dir == 1) {
1350  t32_arr(i,j,k) = stressy;
1351  if (t23_arr &&
1352  (!zlo_surface || k != zlo_node) &&
1353  (!zhi_surface || k != zhi_node)) {
1354  t23_arr(i,j,k) = stressy;
1355  }
1356  } else {
1357  t23_arr(i,j,k) = stressy;
1358  if (t32_arr &&
1359  (!ylo_surface || j != ylo_node) &&
1360  (!yhi_surface || j != yhi_node)) {
1361  t32_arr(i,j,k) = stressy;
1362  }
1363  }
1364  });
1365  } else {
1366  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(dir == 2 && m_face.isLow(), "Stress rotation only supported for zlo face");
1367  // All fluxes with rotation
1368  //============================================================================
1369  Box bxxy = convert(bx, IntVect(1,1,0));
1370  ParallelFor(bxxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1371  {
1372  Real stresst = flux_comp.compute_u_flux(i, j, k, dir,
1373  cons_arr, velx_arr, vely_arr, velz_arr,
1374  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr);
1375  rotate_stress_tensor(i, j, k, dir, stresst, dxInv, zphys_arr,
1376  velx_arr, vely_arr, velz_arr,
1377  t11_arr, t22_arr, t33_arr,
1378  t12_arr, t21_arr,
1379  t13_arr, t31_arr,
1380  t23_arr, t32_arr);
1381  });
1382  }
1383 
1384  // For models that do not do iterations to yield u*/T*/q*,
1385  // fill these values from the fluxes that were computed.
1386 
1387  // NOTE: For LSM, this has been handled in "compute_sfc_params_from_lsm_fluxes"
1388  // NOTE: Fluxes here are for conserved quantities, we divide by rho
1390  constexpr Real eps = std::numeric_limits<Real>::epsilon();
1391  bool l_use_moisture = use_moisture;
1392  ParallelFor(bx, [=] AMREX_GPU_DEVICE(int i, int j, int /*k*/)
1393  {
1394  Real rho = cons_arr(i,j,klo,Rho_comp);
1395  Real Thd = cons_arr(i,j,klo,RhoTheta_comp) / rho;
1396  Real qv = (l_use_moisture) ? cons_arr(i,j,klo,RhoQ1_comp) / rho : zero;
1397  Real Thv = Thd * (one + epsv*qv);
1398 
1399  Real tau = std::sqrt( t13_arr(i,j,klo)/rho * t13_arr(i,j,klo)/rho
1400  + t23_arr(i,j,klo)/rho * t23_arr(i,j,klo)/rho );
1401  u_star_arr(i,j,0) = amrex::max(std::sqrt(tau),eps);
1402 
1403  if (hfx3_arr(i,j,klo)>=zero) {
1404  t_star_arr(i,j,0) = amrex::min(-hfx3_arr(i,j,klo) / (rho * u_star_arr(i,j,0)),-eps);
1405  } else {
1406  t_star_arr(i,j,0) = amrex::max(-hfx3_arr(i,j,klo) / (rho * u_star_arr(i,j,0)),eps);
1407  }
1408  if (!l_use_moisture) {
1409  q_star_arr(i,j,0) = zero;
1410  } else if (qfx3_arr(i,j,klo)>=zero) {
1411  q_star_arr(i,j,0) = amrex::min(-qfx3_arr(i,j,klo) / (rho * u_star_arr(i,j,0)),-eps);
1412  } else {
1413  q_star_arr(i,j,0) = amrex::max(-qfx3_arr(i,j,klo) / ( rho * u_star_arr(i,j,0)),eps);
1414  }
1415  Real tstv = t_star_arr(i,j,0)*(one + epsv*qv) + epsv*Thd*q_star_arr(i,j,0);
1416  tstv = (tstv >= zero) ? amrex::max(tstv, eps) : amrex::min(tstv, -eps);
1417  olen_arr(i,j,0) = ( u_star_arr(i,j,0) * u_star_arr(i,j,0) * Thv ) /
1418  ( KAPPA * CONST_GRAV * tstv );
1419  z0_arr(i,j,0) = Compute_roughness(zref_arr(i,j,0), olen_arr(i,j,0),
1420  umm_arr(i,j,0), u_star_arr(i,j,0));
1421  });
1422  }
1423 
1424  } // mfiter
1425 
1427 }
@ tau12
Definition: ERF_DataStruct.H:48
@ tau23
Definition: ERF_DataStruct.H:48
@ tau33
Definition: ERF_DataStruct.H:48
@ tau22
Definition: ERF_DataStruct.H:48
@ tau11
Definition: ERF_DataStruct.H:48
@ tau32
Definition: ERF_DataStruct.H:48
@ tau31
Definition: ERF_DataStruct.H:48
@ tau21
Definition: ERF_DataStruct.H:48
@ tau13
Definition: ERF_DataStruct.H:48
amrex::GpuArray< Real, AMREX_SPACEDIM > dxInv
Definition: ERF_InitCustomPertVels_ParticleTests.H:17
AMREX_GPU_DEVICE AMREX_FORCE_INLINE void rotate_scalar_flux(const int &i, const int &j, const int &klo, const int &, const amrex::Real &flux, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &dxInv, const amrex::Array4< const amrex::Real > &zphys_arr, const amrex::Array4< amrex::Real > &phi1_arr, const amrex::Array4< amrex::Real > &phi2_arr, const amrex::Array4< amrex::Real > &phi3_arr)
Definition: ERF_TerrainMetrics.H:1052
AMREX_GPU_DEVICE AMREX_FORCE_INLINE void rotate_stress_tensor(const int &i, const int &j, const int &klo, const int &, const amrex::Real &flux, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &dxInv, const amrex::Array4< const amrex::Real > &zphys_arr, const amrex::Array4< const amrex::Real > &u_arr, const amrex::Array4< const amrex::Real > &v_arr, const amrex::Array4< const amrex::Real > &w_arr, const amrex::Array4< amrex::Real > &, const amrex::Array4< amrex::Real > &, const amrex::Array4< amrex::Real > &, const amrex::Array4< amrex::Real > &, const amrex::Array4< amrex::Real > &, const amrex::Array4< amrex::Real > &tau13_arr, const amrex::Array4< amrex::Real > &tau31_arr, const amrex::Array4< amrex::Real > &tau23_arr, const amrex::Array4< amrex::Real > &tau32_arr)
Definition: ERF_TerrainMetrics.H:1096
amrex::Vector< std::unique_ptr< amrex::MultiFab > > surface_diagnostic_source
Definition: ERF_SurfaceLayer.H:1651
void fill_planar_boundary(const int &lev, amrex::MultiFab &mf)
Definition: ERF_SurfaceLayer.cpp:402
@ xvel
Definition: ERF_IndexDefines.H:218
@ zvel
Definition: ERF_IndexDefines.H:220
@ yvel
Definition: ERF_IndexDefines.H:219
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real to_plot_value(SurfaceDiagnosticSource source) noexcept
Definition: ERF_SurfaceDiagnosticSource.H:45
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE SurfaceDiagnosticSource classify_scalar_source(bool is_custom, bool is_rico, bool is_land, bool has_lsm_flux, bool lsm_flux_is_valid) noexcept
Definition: ERF_SurfaceDiagnosticSource.H:61
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool lsm_flux_is_valid(bool has_flux, bool is_land, amrex::Real flux, amrex::Real undefined)
Definition: ERF_SurfaceLayerStress.H:35
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE FaceStressResult combine_lsm_and_most_stress(amrex::Real rho_low, amrex::Real rho_high, amrex::Real kinematic_low, amrex::Real kinematic_high, bool low_valid, bool high_valid, amrex::Real most_face_stress)
Definition: ERF_SurfaceLayerStress.H:64
Here is the call graph for this function:

◆ compute_SurfaceLayer_bcs_EB() [1/2]

template<typename FluxCalc >
void SurfaceLayer::compute_SurfaceLayer_bcs_EB ( const int &  lev,
amrex::Vector< const amrex::MultiFab * >  mfs,
amrex::Vector< amrex::Vector< std::unique_ptr< amrex::MultiFab >>> &  Tau_lev,
amrex::MultiFab *  xheat_flux,
amrex::MultiFab *  yheat_flux,
amrex::MultiFab *  zheat_flux,
amrex::MultiFab *  xqv_flux,
amrex::MultiFab *  yqv_flux,
amrex::MultiFab *  zqv_flux,
const FluxCalc &  flux_comp 
)

Compute embedded-boundary surface-layer flux boundary conditions.

Parameters
[in]levlevel index
[in]mfsstate and velocity fields used by the BC computation
[in,out]Tau_levEB stress fields to fill
[in,out]xheat_fluxx-face heat flux field
[in,out]yheat_fluxy-face heat flux field
[in,out]zheat_fluxz-face heat flux field
[in,out]xqv_fluxx-face moisture flux field
[in,out]yqv_fluxy-face moisture flux field
[in,out]zqv_fluxz-face moisture flux field
[in]flux_compflux-computation functor

◆ compute_SurfaceLayer_bcs_EB() [2/2]

template<typename FluxCalc >
void SurfaceLayer::compute_SurfaceLayer_bcs_EB ( const int &  lev,
Vector< const MultiFab * >  mfs,
Vector< Vector< std::unique_ptr< MultiFab >>> &  Tau_EB,
[[maybe_unused] ] MultiFab *  xheat_flux,
[[maybe_unused] ] MultiFab *  yheat_flux,
MultiFab *  Hfx3_EB,
[[maybe_unused] ] MultiFab *  xqv_flux,
[[maybe_unused] ] MultiFab *  yqv_flux,
[[maybe_unused] ] MultiFab *  zqv_flux,
const FluxCalc &  flux_comp 
)

Function to calculate MOST fluxes for EB.

Parameters
[in]levCurrent level
[in]mfsState MultiFabs used to compute the EB boundary fluxes
[in,out]Tau_EBEB diffusive stress MultiFabs populated with surface stresses
[in,out]xheat_fluxx-face EB heat-flux MultiFab, currently unused
[in,out]yheat_fluxy-face EB heat-flux MultiFab, currently unused
[in,out]Hfx3_EBEB heat-flux MultiFab populated with scalar surface flux
[in,out]xqv_fluxx-face EB moisture-flux MultiFab, currently unused
[in,out]yqv_fluxy-face EB moisture-flux MultiFab, currently unused
[in,out]zqv_fluxz-face EB moisture-flux MultiFab, currently unused
[in]flux_compEB flux-calculation functor used to compute scalar and momentum fluxes
1455 {
1456  const int dir = m_face.coordDir();
1457  // Get EB flags for all centerings
1458  const auto& cc_factory = m_eb_vec[lev]->get_const_factory();
1459  const auto& cc_flags = cc_factory->getMultiEBCellFlagFab();
1460  const auto& cc_vfrac = cc_factory->getVolFrac();
1461 
1462  const auto& u_factory = m_eb_vec[lev]->get_u_const_factory();
1463  const auto& u_flags = u_factory->getMultiEBCellFlagFab();
1464  const auto& u_vfrac = u_factory->getVolFrac();
1465 
1466  const auto& v_factory = m_eb_vec[lev]->get_v_const_factory();
1467  const auto& v_flags = v_factory->getMultiEBCellFlagFab();
1468  const auto& v_vfrac = v_factory->getVolFrac();
1469 
1470  const auto& w_factory = m_eb_vec[lev]->get_w_const_factory();
1471  const auto& w_flags = w_factory->getMultiEBCellFlagFab();
1472  const auto& w_vfrac = w_factory->getVolFrac();
1473 
1474  // EB does not currently have a cell-centered scalar-source classification.
1475  // Keep the provenance mask missing rather than inventing face-aware
1476  // semantics for the staggered stress path.
1477  surface_diagnostic_source[lev]->setVal(
1479 
1480  for (MFIter mfi(*mfs[0]); mfi.isValid(); ++mfi)
1481  {
1482  // Get flags for this box (all centerings)
1483  const auto& cc_flag = cc_flags[mfi];
1484  const auto& u_flag = u_flags[mfi];
1485  const auto& v_flag = v_flags[mfi];
1486  const auto& w_flag = w_flags[mfi];
1487 
1488  // Skip boxes that have no cut cells at any centering
1489  if (cc_flag.getType() != FabType::singlevalued &&
1490  u_flag.getType() != FabType::singlevalued &&
1491  v_flag.getType() != FabType::singlevalued &&
1492  w_flag.getType() != FabType::singlevalued
1493  ) continue;
1494 
1495  // Get EB flag and volfrac arrays
1496  auto const cc_flag_arr = cc_flag.const_array();
1497  auto const u_flag_arr = u_flag.const_array();
1498  auto const v_flag_arr = v_flag.const_array();
1499  auto const w_flag_arr = w_flag.const_array();
1500 
1501  auto const cc_vfrac_arr = cc_vfrac.const_array(mfi);
1502  auto const u_vfrac_arr = u_vfrac.const_array(mfi);
1503  auto const v_vfrac_arr = v_vfrac.const_array(mfi);
1504  auto const w_vfrac_arr = w_vfrac.const_array(mfi);
1505 
1506  // Get boundary normals only if cut cells exist
1507  auto const bnorm_arr = (cc_flag.getType() == FabType::singlevalued) ?
1508  cc_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1509  auto const u_bnorm_arr = (u_flag.getType() == FabType::singlevalued) ?
1510  u_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1511  auto const v_bnorm_arr = (v_flag.getType() == FabType::singlevalued) ?
1512  v_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1513  auto const w_bnorm_arr = (w_flag.getType() == FabType::singlevalued) ?
1514  w_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1515 
1516  // Get field arrays
1517  const auto cons_arr = mfs[Vars::cons]->array(mfi);
1518  const auto velx_arr = mfs[Vars::xvel]->array(mfi);
1519  const auto vely_arr = mfs[Vars::yvel]->array(mfi);
1520  const auto velz_arr = mfs[Vars::zvel]->array(mfi);
1521 
1522  // Diffusive stress vars - t13 and t23 components for all grid types
1523  auto u_t13_arr = Tau_EB[EBTauType::tau_eb13][EBGridType::xface]->array(mfi);
1524  auto v_t13_arr = Tau_EB[EBTauType::tau_eb13][EBGridType::yface]->array(mfi);
1525  auto w_t13_arr = Tau_EB[EBTauType::tau_eb13][EBGridType::zface]->array(mfi);
1526 
1527  auto u_t23_arr = Tau_EB[EBTauType::tau_eb23][EBGridType::xface]->array(mfi);
1528  auto v_t23_arr = Tau_EB[EBTauType::tau_eb23][EBGridType::yface]->array(mfi);
1529  auto w_t23_arr = Tau_EB[EBTauType::tau_eb23][EBGridType::zface]->array(mfi);
1530 
1531  auto hfx3_arr = Hfx3_EB->array(mfi);
1532 
1533  // Get average arrays
1534  const auto *const u_mean = m_ma.get_average(lev,0);
1535  const auto *const v_mean = m_ma.get_average(lev,1);
1536  // const auto *const w_mean = m_ma.get_average(lev,2);
1537 
1538  const auto *const t_mean = m_ma.get_average(lev,3);
1539  // const auto *const q_mean = m_ma.get_average(lev,4);
1540  const auto *const u_mag_mean = m_ma.get_average(lev,6);
1541  const auto *const uw_mag_mean = m_ma.get_average(lev,7);
1542  const auto *const vw_mag_mean = m_ma.get_average(lev,8);
1543 
1544  const auto um_arr = u_mean->array(mfi);
1545  const auto vm_arr = v_mean->array(mfi);
1546  // const auto wm_arr = w_mean->array(mfi);
1547  const auto tm_arr = t_mean->array(mfi);
1548  // const auto qm_arr = q_mean->array(mfi);
1549  const auto umm_arr = u_mag_mean->array(mfi);
1550  const auto vwmm_arr = (dir == 0) ? vw_mag_mean->array(mfi) : Array4<Real>{};
1551  const auto uwmm_arr = (dir == 1) ? uw_mag_mean->array(mfi) : Array4<Real>{};
1552 
1553  // umm depending on face direction (YZ, XZ, XY)
1554  const auto dir_umm_arr = ((dir == 0) ? vwmm_arr : ((dir == 1) ? uwmm_arr : umm_arr));
1555 
1556  // Get derived arrays
1557  const auto u_star_arr = u_star[lev]->array(mfi);
1558  const auto t_star_arr = t_star[lev]->array(mfi);
1559  const auto t_surf_arr = t_surf[lev]->array(mfi);
1560 
1561  // Rho*Theta flux
1562  //============================================================================
1563  Box bx = mfi.tilebox();
1564  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1565  {
1566  if (cc_flag_arr(i,j,k).isSingleValued()) {
1567  Real Tflux = flux_comp.compute_t_flux(i, j, k,
1568  cons_arr, velx_arr, vely_arr, velz_arr,
1569  dir_umm_arr, tm_arr, u_star_arr,
1570  t_star_arr, t_surf_arr,
1571  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1572  bnorm_arr);
1573  hfx3_arr(i,j,k) = Tflux;
1574  }
1575  });
1576 
1577  // Rho*u flux
1578  //============================================================================
1579  Box bxx = surroundingNodes(bx,0);
1580  Box bxy = surroundingNodes(bx,1);
1581  Box bxz = surroundingNodes(bx,2);
1582  ParallelFor(bxx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1583  {
1584  if (u_flag_arr(i,j,k).isSingleValued()) {
1585  Real stressx = flux_comp.compute_u_flux(i, j, k,
1586  cons_arr, velx_arr, vely_arr, velz_arr,
1587  dir_umm_arr, um_arr, u_star_arr,
1588  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1589  cc_vfrac_arr, cc_flag_arr,
1590  u_bnorm_arr, 0);
1591  u_t13_arr(i,j,k) = stressx;
1592  }
1593  });
1594  ParallelFor(bxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1595  {
1596  if (v_flag_arr(i,j,k).isSingleValued()) {
1597  Real stressx = flux_comp.compute_u_flux(i, j, k,
1598  cons_arr, velx_arr, vely_arr, velz_arr,
1599  dir_umm_arr, um_arr, u_star_arr,
1600  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1601  cc_vfrac_arr, cc_flag_arr,
1602  v_bnorm_arr, 1);
1603  v_t13_arr(i,j,k) = stressx;
1604  }
1605  });
1606  ParallelFor(bxz, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1607  {
1608  if (w_flag_arr(i,j,k).isSingleValued()) {
1609  Real stressx = flux_comp.compute_u_flux(i, j, k,
1610  cons_arr, velx_arr, vely_arr, velz_arr,
1611  dir_umm_arr, um_arr, u_star_arr,
1612  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1613  cc_vfrac_arr, cc_flag_arr,
1614  w_bnorm_arr, 2);
1615  w_t13_arr(i,j,k) = stressx;
1616  }
1617  });
1618 
1619  // Rho*v flux
1620  //============================================================================
1621  ParallelFor(bxx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1622  {
1623  if (u_flag_arr(i,j,k).isSingleValued()) {
1624  Real stressy = flux_comp.compute_v_flux(i, j, k,
1625  cons_arr, velx_arr, vely_arr, velz_arr,
1626  dir_umm_arr, vm_arr, u_star_arr,
1627  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1628  cc_vfrac_arr, cc_flag_arr, u_bnorm_arr, 0);
1629  u_t23_arr(i,j,k) = stressy;
1630  }
1631  });
1632  ParallelFor(bxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1633  {
1634  if (v_flag_arr(i,j,k).isSingleValued()) {
1635  Real stressy = flux_comp.compute_v_flux(i, j, k,
1636  cons_arr, velx_arr, vely_arr, velz_arr,
1637  dir_umm_arr, vm_arr, u_star_arr,
1638  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1639  cc_vfrac_arr, cc_flag_arr, v_bnorm_arr, 1);
1640  v_t23_arr(i,j,k) = stressy;
1641  }
1642  });
1643  ParallelFor(bxz, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1644  {
1645  if (w_flag_arr(i,j,k).isSingleValued()) {
1646  Real stressy = flux_comp.compute_v_flux(i, j, k,
1647  cons_arr, velx_arr, vely_arr, velz_arr,
1648  dir_umm_arr, vm_arr, u_star_arr,
1649  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1650  cc_vfrac_arr, cc_flag_arr, w_bnorm_arr, 2);
1651  w_t23_arr(i,j,k) = stressy;
1652  }
1653  });
1654  } // mfiter
1655 
1656 }
@ tau_eb23
Definition: ERF_EBStruct.H:22
@ tau_eb13
Definition: ERF_EBStruct.H:22
@ yface
Definition: ERF_EBStruct.H:29
@ zface
Definition: ERF_EBStruct.H:29
@ xface
Definition: ERF_EBStruct.H:29
Here is the call graph for this function:

◆ computes_pblh()

bool SurfaceLayer::computes_pblh ( ) const
inline

Do we actually compute the PBL height? If not then the field returned by get_pblh holds only the value it was initialized with, and must not be reported as a diagnostic.

1177 { return (pblh_type != PBLHeightCalcType::None); }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ computes_w_star()

bool SurfaceLayer::computes_w_star ( ) const
inline

Do we actually compute w*? If not then the field returned by get_w_star holds only the value it was initialized with, and must not be reported as a diagnostic.

1171 { return m_include_wstar; }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ define_pblh_columns()

void SurfaceLayer::define_pblh_columns ( const int &  lev,
const amrex::BoxArray &  ba,
const amrex::DistributionMapping &  dm 
)
private

Build the columns on which compute_pblh runs the PBL-height estimator when the grids of a level do not all start at the ground (see PBLHColumns). Called when the grids change.

Parameters
[in]levCurrent level
[in]baBoxArray of the state at this level
[in]dmDistributionMapping of the state at this level
2562 {
2563  PBLHColumns& cols = m_pblh_columns[lev];
2564  cols = PBLHColumns{};
2565  cols.ba = ba;
2566  cols.dm = dm;
2567 
2568  const int k_ground = m_geom[lev].Domain().smallEnd(2);
2569  for (int ib = 0; ib < ba.size(); ++ib) {
2570  if (ba[ib].smallEnd(2) != k_ground) { cols.needed = true; }
2571  }
2572  if (!cols.needed) { return; }
2573 
2574  // With EB the surface-layer fields live on the 3D grids and the estimator writes their
2575  // k = 0 plane, which a box that does not start at the ground does not hold
2576  if (m_terrain_type == TerrainType::EB) {
2577  Abort("erf.most.pblh_calc = MYNN25 with EB needs every grid at level " + std::to_string(lev) +
2578  " to start at the bottom of the domain: the PBL height is written into the lowest "
2579  "plane of each grid. Choose grids that are not decomposed in z "
2580  "(amr.max_grid_size_z) and refined regions that reach the ground.");
2581  }
2582 
2583  // The runs of cells in z, each as one box; those that start at the ground are the columns.
2584  // A column goes to the rank that owns its lowest corner cell, which keeps most of the
2585  // copies to and from the columns on the rank.
2586  const BoxArray ba_joined = join_boxes_stacked_in_z(ba);
2587  BoxList bl_col(IndexType::TheCellType());
2588  BoxList bl_col2d(IndexType::TheCellType());
2589  Vector<int> pmap;
2590  for (int ib = 0; ib < ba_joined.size(); ++ib) {
2591  const Box& b = ba_joined[ib];
2592  if (b.smallEnd(2) != k_ground) { continue; }
2593  const auto& owners = ba.intersections(Box(b.smallEnd(), b.smallEnd()));
2594  AMREX_ALWAYS_ASSERT(!owners.empty());
2595  Box b2d(b);
2596  b2d.setRange(2, k_ground);
2597  bl_col.push_back(b);
2598  bl_col2d.push_back(b2d);
2599  pmap.push_back(dm[owners[0].first]);
2600  }
2601  if (bl_col.isEmpty()) { return; }
2602 
2603  cols.ba_col = BoxArray(std::move(bl_col));
2604  cols.ba_col2d = BoxArray(std::move(bl_col2d));
2605  cols.dm_col = DistributionMapping(std::move(pmap));
2606 
2607  // These hold no data. AMReX drops the communication metadata of a BoxArray and
2608  // DistributionMapping pair with the last FabArray built on it, and the MultiFabs that
2609  // compute_pblh builds on the columns are temporaries.
2610  cols.hold_col.define(cols.ba_col, cols.dm_col, 1, 0, MFInfo().SetAlloc(false));
2611  cols.hold_col2d.define(cols.ba_col2d, cols.dm_col, 1, 0, MFInfo().SetAlloc(false));
2612 }
BoxArray join_boxes_stacked_in_z(const BoxArray &ba)
Definition: ERF_TerrainMetrics.cpp:100
Here is the call graph for this function:

◆ fill_lateral_surface_parameter_ghosts()

void SurfaceLayer::fill_lateral_surface_parameter_ghosts ( const int &  lev,
amrex::MultiFab *  selected_field = nullptr 
)

Fills surface layer data for lateral faces. A normal FillBoundary on lateral faces would bring in data from other ranks or interior grids that do not own data on the lateral face.

Parameters
[in]levlevel index
[in,out]selected_fieldif non-null, fill only this field; otherwise fill all lateral surface fields
666 {
667  const int dir = m_face.coordDir();
668  AMREX_ALWAYS_ASSERT(dir < 2);
669 
670  const iMultiFab& surface_mask = *m_lmask_lev[lev][0];
671  const int face_index = m_face.isLow()
672  ? m_geom[lev].Domain().smallEnd(dir)
673  : m_geom[lev].Domain().bigEnd(dir);
674  const auto& mask_ba = surface_mask.boxArray();
675 
676  IntVect period = m_geom[lev].periodicity().intVect();
677  period[dir] = 0; // The selected wall is not a periodic source plane.
678  const Periodicity tangential_periodicity(period);
679 
680  Vector<MultiFab*> fields;
681  if (selected_field) {
682  fields.push_back(selected_field);
683  } else {
684  const Vector<MultiFab*> all_fields{
685  u_star[lev].get(), t_star[lev].get(), q_star[lev].get(), olen[lev].get(),
686  t_surf[lev].get(), q_surf[lev].get(), pblh[lev].get(),
687  surface_diagnostic_source[lev].get()};
688  for (MultiFab* field : all_fields) {
689  if (field) { fields.push_back(field); }
690  }
691  if (m_include_wstar && w_star[lev]) { fields.push_back(w_star[lev].get()); }
692  }
693 
694  for (MultiFab* field : fields) {
695  // Each parameter field owns its own index type and BoxArray. Build
696  // the compact layout from that field rather than reusing u_star's
697  // layout for all parameters.
698  BoxList face_boxes;
699  Vector<int> face_pmap;
700  Vector<int> source_indices;
701  const auto& source_ba = field->boxArray();
702  const auto& source_pmap = field->DistributionMap().ProcessorMap();
703  for (int ibox = 0; ibox < mask_ba.size(); ++ibox) {
704  const Box& mask_box = mask_ba[ibox];
705  const bool owns_face = m_face.isLow()
706  ? mask_box.smallEnd(dir) == face_index
707  : mask_box.bigEnd(dir) == face_index;
708  if (!owns_face) { continue; }
709 
710  face_boxes.push_back(source_ba[ibox]);
711  face_pmap.push_back(source_pmap[ibox]);
712  source_indices.push_back(ibox);
713  }
714  if (face_boxes.isEmpty()) { continue; }
715 
716  BoxArray face_ba(std::move(face_boxes));
717  DistributionMapping face_dm(std::move(face_pmap));
718  MultiFab face_values(face_ba, face_dm, 1, field->nGrowVect());
719 
720  // Selectively gather only face-owned FABs. A direct FillBoundary on
721  // field would also treat interior-grid FABs as valid sources because
722  // all lateral FABs collapse onto the same wall plane.
723  for (MFIter fmfi(face_values, false); fmfi.isValid(); ++fmfi) {
724  const int compact_index = fmfi.index();
725  const int source_index = source_indices[compact_index];
726  const auto src = field->const_array(source_index);
727  const auto dst = face_values.array(fmfi);
728  // Preserve the source FAB's existing ghosts. A nonperiodic
729  // physical ghost has no FillBoundary source, so leaving the
730  // temporary FAB uninitialized would replace a valid local value
731  // with garbage at mixed-face corners.
732  Box source_fab = field->boxArray()[source_index];
733  source_fab.grow(field->nGrowVect());
734  const Box copy_box = fmfi.fabbox() & source_fab;
736  !copy_box.isEmpty(),
737  "Temporary surface-layer source copy has no valid overlap.");
738  ParallelFor(copy_box, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
739  {
740  dst(i,j,k) = src(i,j,k);
741  });
742  }
743  Gpu::streamSynchronize();
744 
745  face_values.FillBoundary(tangential_periodicity);
746 
747  for (MFIter fmfi(face_values, false); fmfi.isValid(); ++fmfi) {
748  const int compact_index = fmfi.index();
749  const int source_index = source_indices[compact_index];
750  const auto src = face_values.const_array(fmfi);
751  const auto dst = field->array(source_index);
752  Box source_fab = field->boxArray()[source_index];
753  source_fab.grow(field->nGrowVect());
754  const Box copy_box = fmfi.fabbox() & source_fab;
756  !copy_box.isEmpty(),
757  "Mapped surface-layer ghost copy has no valid overlap with its source FAB.");
758  ParallelFor(copy_box, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
759  {
760  dst(i,j,k) = src(i,j,k);
761  });
762  }
763  }
764  Gpu::streamSynchronize();
765 }
Here is the call graph for this function:

◆ fill_planar_boundary()

void SurfaceLayer::fill_planar_boundary ( const int &  lev,
amrex::MultiFab &  mf 
)

Fill the ghost cells of a planar surface-layer MultiFab, and the valid region of its uncomputed copies when the 3D BoxArray is split in z (see PlanarBoundary).

Parameters
[in]levlevel index
[in,out]mfplanar MultiFab to fill

Fill the ghost cells of a planar surface-layer MultiFab.

The planar MultiFabs hold one box per 3D box, so a 3D BoxArray split in z gives duplicate planar boxes of which only the surface copy is computed; a FillBoundary could then fill a ghost cell from an uncomputed copy (see PlanarBoundary). With the split, the valid region of the uncomputed copies is filled as well. With EB terrain the fields are 3D and FillBoundary is well defined.

Parameters
[in]levCurrent level
[in,out]mfPlanar MultiFab to fill
403 {
404  // PlanarBoundary handles both z-low and z-high z-collapsed arrays split
405  // in z. EB fields use ordinary FillBoundary because their 3-D layout has
406  // no duplicate collapsed surface boxes. Lateral planar fields need a
407  // selective exchange so an interior grid cannot provide wall data.
408  if (m_terrain_type == TerrainType::EB) {
409  mf.FillBoundary(m_geom[lev].periodicity());
410  } else if (m_face.coordDir() != 2) {
412  } else {
413  m_planar_bndry[lev].fill(mf, m_geom[lev].periodicity());
414  }
415 }
void fill_lateral_surface_parameter_ghosts(const int &lev, amrex::MultiFab *selected_field=nullptr)
Definition: ERF_SurfaceLayer.cpp:664

◆ fill_qsurf_with_qsat()

void SurfaceLayer::fill_qsurf_with_qsat ( const int &  lev,
const amrex::MultiFab &  cons_in,
const std::unique_ptr< amrex::MultiFab > &  z_phys_nd 
)

Fill surface moisture from saturation specific humidity.

Parameters
[in]levlevel index
[in]cons_inconserved state used to evaluate surface pressure
[in]z_phys_ndnodal physical-height field

Fill sea-surface moisture with saturation specific humidity.

Parameters
[in]levCurrent level
[in]cons_inConserved state used to derive pressure at the surface
[in]z_phys_ndNodal physical height used to compute terrain-relative surface height
1965 {
1966  const int dir = m_face.coordDir();
1967  int sm_index;
1968  if (m_face.isLow()) {
1969  sm_index = m_geom[lev].Domain().smallEnd(dir);
1970  } else {
1971  sm_index = m_geom[lev].Domain().bigEnd(dir);
1972  }
1973 
1974  // Populate q_surf with qsat over water. The selected face is the only
1975  // authoritative slab; state/terrain ghosts are useful when valid, but a
1976  // bad halo must not turn into a collective conversion failure.
1977  const Real dz = m_geom[lev].CellSize(2);
1978  const int ng_z = amrex::min(cons_in.nGrowVect()[2],
1979  amrex::min(t_surf[lev]->nGrowVect()[2],
1980  q_surf[lev]->nGrowVect()[2]));
1981  const bool moist = use_moisture;
1982  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
1983  const Real rdOcp = m_rdOcp;
1984  amrex::Gpu::DeviceScalar<int> d_conversion_failed(0);
1985  int* conversion_failed = d_conversion_failed.dataPtr();
1986  // Use the 2-D surface mask as the iterator so ranks participate only when
1987  // their grids coincide with the selected face.
1988  for (MFIter mfi(*m_lmask_lev[lev][0]); mfi.isValid(); ++mfi)
1989  {
1990  Box gtbx = mfi.growntilebox();
1991  Box tbx = mfi.validbox();
1992  const Box tilebx = mfi.tilebox();
1993 
1994  // Since lmask is used in the MFIter, its Z dimension is 0. These are
1995  // temporary geometry boxes, so use the physical domain's Z range for
1996  // indexing the 3-D surface/state arrays.
1997  tbx.setSmall(2, m_geom[lev].Domain().smallEnd(2));
1998  tbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
1999  gtbx.setSmall(2, m_geom[lev].Domain().smallEnd(2));
2000  gtbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
2001 
2002  if (dir == 2) {
2003  gtbx.makeSlab(2, sm_index);
2004  } else {
2005  gtbx.grow(2, ng_z);
2006  gtbx.setSmall(dir, sm_index);
2007  gtbx.setBig(dir, sm_index);
2008  }
2009 
2010  if (dir == 2) {
2011  if (m_terrain_type != TerrainType::EB &&
2012  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
2013  continue;
2014  }
2015  } else {
2016  if (tbx[m_face] != sm_index || tilebx[m_face] != sm_index) {
2017  continue;
2018  }
2019  }
2020 
2021  // The mask iterator can have more ghosts than the surface fields.
2022  // Limit every array accessed by the kernel to its corresponding FAB.
2023  gtbx &= t_surf[lev]->fabbox(mfi.index());
2024  gtbx &= q_surf[lev]->fabbox(mfi.index());
2025  gtbx &= cons_in.fabbox(mfi.index());
2026  if (z_phys_nd) {
2027  // z_phys_nd is nodal; the cell box whose corner nodes all lie in its FAB
2028  // is the nodal box converted to cells (one fewer cell on the high side).
2029  gtbx &= amrex::convert(z_phys_nd->fabbox(mfi.index()), IntVect::TheCellVector());
2030  }
2031  if (gtbx.isEmpty()) { continue; }
2032 
2033  auto t_surf_arr = t_surf[lev]->array(mfi);
2034  auto q_surf_arr = q_surf[lev]->array(mfi);
2035  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2036  Array4<int> {};
2037  const auto cons_arr = cons_in.const_array(mfi);
2038  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
2039  Array4<const Real> {};
2040  const Box source_box = cons_in.boxArray()[mfi.index()];
2041  const int src_i_lo = source_box.smallEnd(0);
2042  const int src_i_hi = source_box.bigEnd(0);
2043  const int src_j_lo = source_box.smallEnd(1);
2044  const int src_j_hi = source_box.bigEnd(1);
2045  const int src_k_lo = source_box.smallEnd(2);
2046  const int src_k_hi = source_box.bigEnd(2);
2047  const bool z_face = (dir == 2);
2048  const bool low_face = m_face.isLow();
2049 
2050  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2051  {
2052  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
2053  if (!is_land) {
2054  const bool authoritative = i >= src_i_lo && i <= src_i_hi &&
2055  j >= src_j_lo && j <= src_j_hi &&
2056  k >= src_k_lo && k <= src_k_hi;
2057  const Real rho = cons_arr(i,j,k,Rho_comp);
2058  const Real rho_theta = cons_arr(i,j,k,RhoTheta_comp);
2059  if (!amrex::Math::isfinite(rho) || rho <= Real(0.0) ||
2060  !amrex::Math::isfinite(rho_theta)) {
2061  if (authoritative) {
2062  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2063  }
2064  return;
2065  }
2066  Real qv = Real(0.0);
2067  if (moist) {
2068  if (!have_rho_qv) {
2069  if (authoritative) {
2070  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2071  }
2072  return;
2073  }
2074  const Real rho_qv = cons_arr(i,j,k,RhoQ1_comp);
2075  if (!amrex::Math::isfinite(rho_qv)) {
2076  if (authoritative) {
2077  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2078  }
2079  return;
2080  }
2081  qv = rho_qv / rho;
2082  if (!amrex::Math::isfinite(qv)) {
2083  if (authoritative) {
2084  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2085  }
2086  return;
2087  }
2088  }
2089  Real delta_z = Real(0.0);
2090  if (z_face) {
2091  if (z_arr) {
2092  const Real z_cc = Compute_Z_AtCellCenter(i,j,k,z_arr);
2093  const Real z_face_local = low_face
2094  ? Compute_Z_AtWFace(i,j,k,z_arr)
2095  : Compute_Z_AtWFace(i,j,k+1,z_arr);
2096  delta_z = z_cc - z_face_local;
2097  } else {
2098  delta_z = low_face ? myhalf*dz : -myhalf*dz;
2099  }
2100  }
2102  rho, rho_theta, qv, delta_z);
2103  Real t_surface = t_surf_arr(i,j,k);
2105  t_surface, pressure, rdOcp, t_surface)) {
2106  erf_qsatw(t_surface, pressure * Real(0.01), q_surf_arr(i,j,k));
2107  } else if (authoritative) {
2108  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2109  }
2110  }
2111  });
2112  }
2113  amrex::Gpu::streamSynchronize();
2114  int conversion_failed_host = d_conversion_failed.dataValue();
2115  amrex::ParallelDescriptor::ReduceIntMax(conversion_failed_host);
2116  if (conversion_failed_host != 0) {
2117  amrex::Abort("SurfaceLayer fill_qsurf_with_qsat: failed to convert the authoritative "
2118  "surface potential temperature to absolute temperature.");
2119  }
2120  fill_planar_boundary(lev, *q_surf[lev]);
2121 }
const Real rdOcp
Definition: ERF_InitCustomPert_ABL.H:72
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void erf_qsatw(amrex::Real t, amrex::Real p, amrex::Real &qsatw)
Definition: ERF_MicrophysicsUtils.H:264
constexpr amrex::Real myhalf
Definition: ERF_NumericalConstants.H:41
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real Compute_Z_AtCellCenter(const int &i, const int &j, const int &k, const amrex::Array4< const amrex::Real > &z_nd)
Definition: ERF_TerrainMetrics.H:711
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real Compute_Z_AtWFace(const int &i, const int &j, const int &k, const amrex::Array4< const amrex::Real > &z_nd)
Definition: ERF_TerrainMetrics.H:735
@ dz
Definition: ERF_AdvanceWDM6.cpp:272
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real pressure_at_boundary_from_cell(const amrex::Real rho, const amrex::Real rho_theta, const amrex::Real qv, const amrex::Real delta_z)
Definition: ERF_SurfaceTemperature.H:20
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool theta_to_temperature(const amrex::Real theta, const amrex::Real pressure, const amrex::Real rdOcp, amrex::Real &temperature)
Definition: ERF_SurfaceTemperature.H:54
Here is the call graph for this function:

◆ fill_tsurf_with_coupled_sst()

void SurfaceLayer::fill_tsurf_with_coupled_sst ( const int &  lev,
const amrex::MultiFab &  cons_in,
const std::unique_ptr< amrex::MultiFab > &  z_phys_nd 
)

Overwrite surface temperature with coupled ocean SST where the coupler covers the cell.

Runs after fill_tsurf_with_sst_and_tsk so that the lower-boundary data is the base layer: land cells, and water cells with no ocean donor, keep the value written there.

Parameters
[in]levlevel index

Overwrite surface temperature with coupled ocean SST where covered.

Parameters
[in]levCurrent level
2181 {
2182  // No coupler has handed us anything yet. Whatever fill_tsurf_with_sst_and_tsk
2183  // wrote stands, which is the correct answer for one-way and uncoupled runs.
2184  if (m_coupled_sst_lev.empty() || !m_coupled_sst_lev[lev]) { return; }
2185 
2186  // The loop below iterates t_surf and indexes the coupled arrays with the
2187  // same MFIter, so the layouts must agree. They do for planar terrain, where
2188  // t_surf is grids[lev] flattened with setRange(2,0) -- the same construction
2189  // GetOceanToAtmosSurfaceLayout reports. Under EB terrain t_surf keeps the
2190  // full 3D BoxArray and they would not, so fail loudly rather than read the
2191  // wrong fab.
2193  m_coupled_sst_lev[lev]->boxArray() == t_surf[lev]->boxArray() &&
2194  m_coupled_sst_lev[lev]->DistributionMap() == t_surf[lev]->DistributionMap(),
2195  "Coupled SST layout does not match the surface-layer layout.");
2196 
2197  const int klo = m_geom[lev].Domain().smallEnd(2);
2198  const Real dz = m_geom[lev].CellSize(2);
2199  const bool moist = use_moisture;
2200  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
2201  const Real rdOcp = m_rdOcp;
2202  amrex::Gpu::DeviceScalar<int> d_conversion_failed(0);
2203  int* conversion_failed = d_conversion_failed.dataPtr();
2204 
2205  // Absent coverage information we must assume nothing is covered: silently
2206  // treating the whole field as valid is how an uncovered cell ends up holding
2207  // the remap's zero fill.
2208  const bool has_valid = (m_coupled_sst_valid_lev[lev] != nullptr);
2209 
2210  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
2211  {
2212  Box gtbx = mfi.growntilebox();
2213 
2214  if (gtbx.smallEnd(2) != klo ||
2215  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
2216  continue;
2217  }
2218 
2219  // NOTE: the coupled lane does not carry lateral ghost cells, so clamp
2220  // into the physical source box exactly as the fallback path does.
2221  // FillBoundary in update_fluxes picks up the interior and
2222  // periodic directions.
2223  const Box source_box = cons_in.boxArray()[mfi.index()];
2224  const Box donor_box = m_coupled_sst_lev[lev]->boxArray()[mfi.index()];
2225  const int source_i_lo = source_box.smallEnd(0);
2226  const int source_i_hi = source_box.bigEnd(0);
2227  const int source_j_lo = source_box.smallEnd(1);
2228  const int source_j_hi = source_box.bigEnd(1);
2229  const int donor_i_lo = donor_box.smallEnd(0);
2230  const int donor_i_hi = donor_box.bigEnd(0);
2231  const int donor_j_lo = donor_box.smallEnd(1);
2232  const int donor_j_hi = donor_box.bigEnd(1);
2233  const int donor_k = donor_box.smallEnd(2);
2234  gtbx &= t_surf[lev]->fabbox(mfi.index());
2235  if (gtbx.isEmpty()) { continue; }
2236 
2237  auto t_surf_arr = t_surf[lev]->array(mfi);
2238  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2239  Array4<int> {};
2240  const auto coupled_sst_arr = m_coupled_sst_lev[lev]->const_array(mfi);
2241  auto const& valid_arr = has_valid ? m_coupled_sst_valid_lev[lev]->const_array(mfi)
2242  : Array4<const int>{};
2243  const auto cons_arr = cons_in.const_array(mfi);
2244  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
2245  Array4<const Real> {};
2246 
2247  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2248  {
2249  const int li = amrex::min(amrex::max(i, source_i_lo), source_i_hi);
2250  const int lj = amrex::min(amrex::max(j, source_j_lo), source_j_hi);
2251  const int si = amrex::min(amrex::max(li, donor_i_lo), donor_i_hi);
2252  const int sj = amrex::min(amrex::max(lj, donor_j_lo), donor_j_hi);
2253  int is_land = (lmask_arr) ? lmask_arr(li,lj,0) : 1;
2254  if (is_land) { return; }
2255 
2256  if (!has_valid || valid_arr(si,sj,donor_k) == 0) { return; }
2257 
2258  const Real rho = cons_arr(li,lj,klo,Rho_comp);
2259  const Real rho_theta = cons_arr(li,lj,klo,RhoTheta_comp);
2260  if (!amrex::Math::isfinite(rho) || rho <= Real(0.0) ||
2261  !amrex::Math::isfinite(rho_theta)) {
2262  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2263  return;
2264  }
2265  Real qv = Real(0.0);
2266  if (moist) {
2267  if (!have_rho_qv) {
2268  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2269  return;
2270  }
2271  const Real rho_qv = cons_arr(li,lj,klo,RhoQ1_comp);
2272  if (!amrex::Math::isfinite(rho_qv)) {
2273  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2274  return;
2275  }
2276  qv = rho_qv / rho;
2277  if (!amrex::Math::isfinite(qv)) {
2278  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2279  return;
2280  }
2281  }
2282  const Real delta_z = z_arr
2283  ? Compute_Z_AtCellCenter(li,lj,klo,z_arr) -
2284  Compute_Z_AtWFace(li,lj,klo,z_arr)
2285  : myhalf*dz;
2287  rho, rho_theta, qv, delta_z);
2288  Real theta = t_surf_arr(i,j,k);
2290  coupled_sst_arr(si,sj,donor_k), pressure, rdOcp, theta)) {
2291  t_surf_arr(i,j,k) = theta;
2292  } else {
2293  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2294  }
2295  });
2296  }
2297  amrex::Gpu::streamSynchronize();
2298  int conversion_failed_host = d_conversion_failed.dataValue();
2299  amrex::ParallelDescriptor::ReduceIntMax(conversion_failed_host);
2300  if (conversion_failed_host != 0) {
2301  amrex::Abort("SurfaceLayer fill_tsurf_with_coupled_sst: failed to convert the authoritative "
2302  "coupled sea-surface temperature to potential temperature.");
2303  }
2304 }
amrex::Vector< amrex::MultiFab * > m_coupled_sst_lev
Definition: ERF_SurfaceLayer.H:1663
amrex::Vector< amrex::iMultiFab * > m_coupled_sst_valid_lev
Definition: ERF_SurfaceLayer.H:1664
@ theta
Definition: ERF_Kessler.H:26
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool temperature_to_theta(const amrex::Real temperature, const amrex::Real pressure, const amrex::Real rdOcp, amrex::Real &theta)
Definition: ERF_SurfaceTemperature.H:73
Here is the call graph for this function:

◆ fill_tsurf_with_sfc_sst()

void SurfaceLayer::fill_tsurf_with_sfc_sst ( const int &  lev,
const double &  time,
const amrex::MultiFab &  cons_in,
const std::unique_ptr< amrex::MultiFab > &  z_phys_nd 
)

Fill surface temperature interpolated from time varying SST file

Parameters
[in]levlevel index
[in]timeinterpolation time
1852 {
1853  update_sfc_time_index(elapsed_time);
1854  const Real sfc_sst = interpolate_sfc_column(elapsed_time, 1);
1855  const int klo = m_geom[lev].Domain().smallEnd(2);
1856  const Real dz = m_geom[lev].CellSize(2);
1857  const bool moist = use_moisture;
1858  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
1859  const Real rdOcp = m_rdOcp;
1860  amrex::Gpu::DeviceScalar<int> d_conversion_failed(0);
1861  int* conversion_failed = d_conversion_failed.dataPtr();
1862 
1863  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
1864  {
1865  Box gtbx = mfi.growntilebox();
1866 
1867  // A z-split planar field has one copy for every stacked 3-D box. Only
1868  // the copy whose source box touches the physical z-low face owns the
1869  // text-SST conversion and may read the 3-D state or terrain.
1870  if (gtbx.smallEnd(2) != klo ||
1871  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
1872  continue;
1873  }
1874 
1875  const Box source_box = cons_in.boxArray()[mfi.index()];
1876  gtbx &= t_surf[lev]->fabbox(mfi.index());
1877  gtbx &= cons_in.fabbox(mfi.index());
1878  if (z_phys_nd) {
1879  gtbx &= amrex::convert(z_phys_nd->fabbox(mfi.index()), IntVect::TheCellVector());
1880  }
1881  if (m_lmask_lev[lev][0]) {
1882  gtbx &= m_lmask_lev[lev][0]->fabbox(mfi.index());
1883  }
1884  if (gtbx.isEmpty()) { continue; }
1885 
1886  auto t_surf_arr = t_surf[lev]->array(mfi);
1887  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
1888  Array4<int> {};
1889  const auto cons_arr = cons_in.const_array(mfi);
1890  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
1891  Array4<const Real> {};
1892  const int i_lo = source_box.smallEnd(0);
1893  const int i_hi = source_box.bigEnd(0);
1894  const int j_lo = source_box.smallEnd(1);
1895  const int j_hi = source_box.bigEnd(1);
1896 
1897  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
1898  {
1899  const int li = amrex::min(amrex::max(i, i_lo), i_hi);
1900  const int lj = amrex::min(amrex::max(j, j_lo), j_hi);
1901  int is_land = (lmask_arr) ? lmask_arr(li,lj,0) : 0;
1902  if (!is_land) {
1903  const Real rho = cons_arr(li,lj,klo,Rho_comp);
1904  const Real rho_theta = cons_arr(li,lj,klo,RhoTheta_comp);
1905  if (!amrex::Math::isfinite(rho) || rho <= Real(0.0) ||
1906  !amrex::Math::isfinite(rho_theta)) {
1907  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1908  return;
1909  }
1910  Real qv = Real(0.0);
1911  if (moist) {
1912  if (!have_rho_qv) {
1913  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1914  return;
1915  }
1916  const Real rho_qv = cons_arr(li,lj,klo,RhoQ1_comp);
1917  if (!amrex::Math::isfinite(rho_qv)) {
1918  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1919  return;
1920  }
1921  qv = rho_qv / rho;
1922  if (!amrex::Math::isfinite(qv)) {
1923  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1924  return;
1925  }
1926  }
1927  const Real delta_z = z_arr
1928  ? Compute_Z_AtCellCenter(li,lj,klo,z_arr) -
1929  Compute_Z_AtWFace(li,lj,klo,z_arr)
1930  : myhalf*dz;
1932  rho, rho_theta, qv, delta_z);
1933  Real theta = t_surf_arr(i,j,k);
1934  if (erf_surface_temperature::temperature_to_theta(sfc_sst, pressure, rdOcp, theta)) {
1935  t_surf_arr(i,j,k) = theta;
1936  } else {
1937  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1938  }
1939  }
1940  });
1941  }
1942 
1943  amrex::Gpu::streamSynchronize();
1944  int conversion_failed_host = d_conversion_failed.dataValue();
1945  amrex::ParallelDescriptor::ReduceIntMax(conversion_failed_host);
1946  if (conversion_failed_host != 0) {
1947  amrex::Abort("SurfaceLayer fill_tsurf_with_sfc_sst: failed to convert the authoritative "
1948  "sea-surface temperature to potential temperature.");
1949  }
1950 
1951  fill_planar_boundary(lev, *t_surf[lev]);
1952 }
amrex::Real interpolate_sfc_column(const amrex::Real &time, int col) const
Definition: ERF_SurfaceLayer.cpp:455
void update_sfc_time_index(const amrex::Real &time)
Definition: ERF_SurfaceLayer.cpp:438
Here is the call graph for this function:

◆ fill_tsurf_with_skin_temperature()

void SurfaceLayer::fill_tsurf_with_skin_temperature ( const int &  lev,
const amrex::MultiFab &  cons_in,
const std::unique_ptr< amrex::MultiFab > &  z_phys_nd 
)

Overwrite the land surface temperature with an external skin temperature.

The skin is an absolute temperature [K]; it is converted to the potential temperature the surface layer works in with the pressure at the surface diagnosed from the lowest cell, as for coupled SST. Runs last among the surface-temperature writers, so on land it replaces what they wrote.

Parameters
[in]levlevel index
[in]cons_inconserved state used to evaluate surface pressure
[in]z_phys_ndnodal physical-height field

Overwrite the land surface temperature with an external absolute skin temperature, converted to potential temperature at the surface pressure.

Parameters
[in]levCurrent level
[in]cons_inConserved state (surface pressure from the lowest cell)
[in]z_phys_ndNodal heights, or nullptr on a flat mesh
2318 {
2319  const MultiFab& skin = *m_skin_tsurf_lev[lev];
2320  // Indexed with the MFIter of t_surf, so the layouts must agree: both are the
2321  // level's grids flattened to the surface for planar terrain.
2323  skin.boxArray() == t_surf[lev]->boxArray() &&
2324  skin.DistributionMap() == t_surf[lev]->DistributionMap(),
2325  "Skin temperature layout does not match the surface-layer layout.");
2326 
2327  const int klo = m_geom[lev].Domain().smallEnd(2);
2328  const Real dz = m_geom[lev].CellSize(2);
2329  const bool moist = use_moisture;
2330  const Real rdOcp = m_rdOcp;
2331  // As in fill_tsurf_with_coupled_sst: a moist run whose state lacks the vapour
2332  // component aborts below rather than reading past the components.
2333  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
2334  amrex::Gpu::DeviceScalar<int> d_conversion_failed(0);
2335  int* conversion_failed = d_conversion_failed.dataPtr();
2336 
2337  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
2338  {
2339  Box gtbx = mfi.growntilebox();
2340  if (gtbx.smallEnd(2) != klo ||
2341  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
2342  continue;
2343  }
2344  gtbx &= t_surf[lev]->fabbox(mfi.index());
2345  if (gtbx.isEmpty()) { continue; }
2346 
2347  // The skin is written on valid cells only, so the halo takes the nearest
2348  // valid column; fill_planar_boundary in update_fluxes then fills the
2349  // interior and periodic ghosts.
2350  const Box vbx = mfi.validbox();
2351  const int i_lo = vbx.smallEnd(0); const int i_hi = vbx.bigEnd(0);
2352  const int j_lo = vbx.smallEnd(1); const int j_hi = vbx.bigEnd(1);
2353 
2354  auto t_surf_arr = t_surf[lev]->array(mfi);
2355  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2356  Array4<int> {};
2357  const auto skin_arr = skin.const_array(mfi);
2358  const auto cons_arr = cons_in.const_array(mfi);
2359  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
2360  Array4<const Real> {};
2361 
2362  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2363  {
2364  const int li = amrex::min(amrex::max(i, i_lo), i_hi);
2365  const int lj = amrex::min(amrex::max(j, j_lo), j_hi);
2366  const int is_land = (lmask_arr) ? lmask_arr(li,lj,0) : 1;
2367  if (!is_land) { return; }
2368 
2369  const Real rho = cons_arr(li,lj,klo,Rho_comp);
2370  const Real rho_theta = cons_arr(li,lj,klo,RhoTheta_comp);
2371  if (moist && !have_rho_qv) {
2372  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2373  return;
2374  }
2375  const Real qv = moist ? cons_arr(li,lj,klo,RhoQ1_comp) / rho : Real(0.0);
2376  const Real delta_z = z_arr
2377  ? Compute_Z_AtCellCenter(li,lj,klo,z_arr) -
2378  Compute_Z_AtWFace(li,lj,klo,z_arr)
2379  : myhalf*dz;
2381  rho, rho_theta, qv, delta_z);
2382  Real theta = t_surf_arr(i,j,k);
2383  if (amrex::Math::isfinite(qv) &&
2385  skin_arr(li,lj,k), pressure, rdOcp, theta)) {
2386  t_surf_arr(i,j,k) = theta;
2387  } else {
2388  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2389  }
2390  });
2391  }
2392  amrex::Gpu::streamSynchronize();
2393  int conversion_failed_host = d_conversion_failed.dataValue();
2394  amrex::ParallelDescriptor::ReduceIntMax(conversion_failed_host);
2395  if (conversion_failed_host != 0) {
2396  amrex::Abort("SurfaceLayer fill_tsurf_with_skin_temperature: failed to convert the "
2397  "skin temperature to potential temperature (non-finite or non-positive "
2398  "skin temperature, density or pressure, or a moist state without "
2399  "water vapour).");
2400  }
2401 }
amrex::Vector< const amrex::MultiFab * > m_skin_tsurf_lev
Definition: ERF_SurfaceLayer.H:1667
Here is the call graph for this function:

◆ fill_tsurf_with_sst_and_tsk()

void SurfaceLayer::fill_tsurf_with_sst_and_tsk ( const int &  lev,
const double &  elapsed_time_since_start_low 
)

Fill surface temperature from available SST and TSK data.

Parameters
[in]levlevel index
[in]timeinterpolation time

Fill surface temperature from SST/TSK lower-boundary data.

Parameters
[in]levCurrent level
[in]elapsed_time_since_start_lowTime since the start of the lower-boundary data
1763 {
1764  int n_times_in_sst = static_cast<int>(m_sst_lev[lev].size());
1765 
1766  double dT = m_low_time_interval;
1767 
1768  int n_time_lo, n_time_hi;
1769  Real alpha;
1770 
1771  if (n_times_in_sst > 1) {
1772  n_time_lo = static_cast<int>( elapsed_time_since_start_low / dT);
1773  alpha = static_cast<Real>((elapsed_time_since_start_low - n_time_lo * dT) / dT);
1774 
1775  AMREX_ALWAYS_ASSERT( alpha >= zero && alpha <= one);
1776 
1777  n_time_hi = n_time_lo + 1;
1778 
1779  // Do not over run the last sst file
1780  if (m_start_low_time + elapsed_time_since_start_low >= m_final_low_time) {
1781  n_time_lo = static_cast<int>(m_sst_lev[lev].size())-1;
1782  n_time_hi = n_time_lo;
1783  alpha = zero;
1784  }
1785 
1786  AMREX_ALWAYS_ASSERT( (n_time_lo >= 0) && (n_time_hi < m_sst_lev[lev].size()));
1787  } else {
1788  n_time_lo = 0;
1789  n_time_hi = 0;
1790  alpha = one;
1791  }
1792  AMREX_ALWAYS_ASSERT( alpha >= zero && alpha <= one);
1793 
1794  Real oma = one - alpha;
1795 
1796  // Define a default land surface temperature if we don't read in tsk
1798 
1799  bool use_tsk = (m_tsk_lev[lev][0]);
1800  bool ignore_sst = m_ignore_sst;
1801 
1802  const int klo = m_geom[lev].Domain().smallEnd(2);
1803 
1804  // Populate t_surf
1805  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
1806  {
1807  Box gtbx = mfi.growntilebox();
1808 
1809  if (gtbx.smallEnd(2) != klo) { continue; }
1810 
1811  auto t_surf_arr = t_surf[lev]->array(mfi);
1812 
1813  const auto sst_lo_arr = m_sst_lev[lev][n_time_lo]->const_array(mfi);
1814  const auto sst_hi_arr = m_sst_lev[lev][n_time_hi]->const_array(mfi);
1815 
1816  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
1817  Array4<int> {};
1818 
1819  if (use_tsk) {
1820  const auto tsk_arr = m_tsk_lev[lev][n_time_lo]->const_array(mfi);
1821  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
1822  {
1823  int is_land = (lmask_arr) ? lmask_arr(i,j,k) : 1;
1824  if (!is_land && !ignore_sst) {
1825  t_surf_arr(i,j,k) = oma * sst_lo_arr(i,j,k)
1826  + alpha * sst_hi_arr(i,j,k);
1827  } else {
1828  t_surf_arr(i,j,k) = tsk_arr(i,j,k);
1829  }
1830  });
1831  } else {
1832  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
1833  {
1834  int is_land = (lmask_arr) ? lmask_arr(i,j,k) : 1;
1835  if (!is_land) {
1836  t_surf_arr(i,j,k) = oma * sst_lo_arr(i,j,k)
1837  + alpha * sst_hi_arr(i,j,k);
1838  } else {
1839  t_surf_arr(i,j,k) = lst;
1840  }
1841  });
1842  }
1843  }
1844  fill_planar_boundary(lev, *t_surf[lev]);
1845 }
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_sst_lev
Definition: ERF_SurfaceLayer.H:1653
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_tsk_lev
Definition: ERF_SurfaceLayer.H:1654
@ alpha
Definition: ERF_SLM.H:151
Here is the call graph for this function:

◆ get_lmask()

amrex::iMultiFab* SurfaceLayer::get_lmask ( const int &  lev)
inline

Return the land-mask field.

Parameters
[in]levlevel index
1393 { return m_lmask_lev[lev][0]; }

◆ get_lsm_tsurf()

void SurfaceLayer::get_lsm_tsurf ( const int &  lev)

Fill surface temperature from the LSM surface-temperature field.

Parameters
[in]levlevel index

Fill surface temperature from land-surface-model data.

Parameters
[in]levCurrent level
2130 {
2131  const int klo = m_geom[lev].Domain().smallEnd(2);
2132  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
2133  {
2134  Box gtbx = mfi.growntilebox();
2135 
2136  if (gtbx.smallEnd(2) != klo) { continue; }
2137 
2138  // NOTE: LSM does not carry lateral ghost cells.
2139  // This copies the valid box into the ghost cells.
2140  // Fillboundary is called after this to pick up the
2141  // interior ghost and periodic directions.
2142  Box vbx = mfi.validbox();
2143  int i_lo = vbx.smallEnd(0); int i_hi = vbx.bigEnd(0);
2144  int j_lo = vbx.smallEnd(1); int j_hi = vbx.bigEnd(1);
2145 
2146  auto t_surf_arr = t_surf[lev]->array(mfi);
2147  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2148  Array4<int> {};
2149  const auto& lsm_tsurf = *m_lsm_data_lev[lev][m_lsm_tsurf_indx];
2150  // get the top-most index of the LSM to use as the surface temperature
2151  // this is -1 for SLM, but could be different for other models?
2152  const auto& lsm_box = lsm_tsurf.box(mfi.index());
2153  const int lsm_khi = lsm_box.bigEnd(2);
2154  const auto lsm_arr = lsm_tsurf.const_array(mfi);
2155 
2156  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2157  {
2158  int is_land = (lmask_arr) ? lmask_arr(i,j,k) : 1;
2159  if (is_land) {
2160  int li = amrex::min(amrex::max(i, i_lo), i_hi);
2161  int lj = amrex::min(amrex::max(j, j_lo), j_hi);
2162  const Real lsm_value = lsm_arr(li,lj,lsm_khi);
2163  if (amrex::Math::isfinite(lsm_value) && lsm_value > Real(0.0) &&
2164  lsm_value < lsm_undefined) {
2165  t_surf_arr(i,j,k) = lsm_value;
2166  }
2167  }
2168  });
2169  }
2170 }
int m_lsm_tsurf_indx
Definition: ERF_SurfaceLayer.H:1586
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_lsm_data_lev
Definition: ERF_SurfaceLayer.H:1668
Here is the call graph for this function:

◆ get_mac_avg()

const amrex::MultiFab* SurfaceLayer::get_mac_avg ( const int &  lev,
int  comp 
)
inline

Return a MOST-average field.

Parameters
[in]levlevel index
[in]compcomponent index
1214  {
1215  return m_ma.get_average(lev, comp);
1216  }
Here is the call graph for this function:

◆ get_mac_avg_ptr()

amrex::MultiFab* SurfaceLayer::get_mac_avg_ptr ( const int &  lev,
int  comp 
)
inline

Return a MOST-average field for modification (restart).

Parameters
[in]levlevel index
[in]compcomponent index
1248 { return m_ma.get_average(lev, comp); }
Here is the call graph for this function:

◆ get_mac_plane_avg()

amrex::Vector<amrex::Real> SurfaceLayer::get_mac_plane_avg ( const int &  lev) const
inline

Return the filtered plane averages, which hold the filter state for the plane and EB averaging policies.

Parameters
[in]levlevel index
1256 { return m_ma.get_plane_average(lev); }
amrex::Vector< amrex::Real > get_plane_average(const int &lev) const
Definition: ERF_MOSTAverage.H:301
Here is the call graph for this function:

◆ get_num_mac_avg()

int SurfaceLayer::get_num_mac_avg ( ) const
inline

Return the number of MOST-average components.

1226 { return m_ma.get_navg(); }
int get_navg() const
Definition: ERF_MOSTAverage.H:270
Here is the call graph for this function:

◆ get_olen()

amrex::MultiFab* SurfaceLayer::get_olen ( const int &  lev)
inline

Return the Obukhov length field.

Parameters
[in]levlevel index
1198 { return olen[lev].get(); }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_pblh()

amrex::MultiFab* SurfaceLayer::get_pblh ( const int &  lev)
inline

Return the planetary-boundary-layer-height field.

Parameters
[in]levlevel index
1205 { return pblh[lev].get(); }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_q_star()

amrex::MultiFab* SurfaceLayer::get_q_star ( const int &  lev)
inline

Return the moisture scale field.

Parameters
[in]levlevel index
1191 { return q_star[lev].get(); }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_q_surf()

amrex::MultiFab* SurfaceLayer::get_q_surf ( const int &  lev)
inline

Return the surface-moisture field.

Parameters
[in]levlevel index
1345 { return q_surf[lev].get(); }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_surface_diagnostic_source()

amrex::MultiFab* SurfaceLayer::get_surface_diagnostic_source ( const int &  lev)
inline

Return the surface-diagnostic provenance field.

Parameters
[in]levlevel index
1360 { return surface_diagnostic_source[lev].get(); }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_t_star()

amrex::MultiFab* SurfaceLayer::get_t_star ( const int &  lev)
inline

Return the temperature scale field.

Parameters
[in]levlevel index
1184 { return t_star[lev].get(); }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_t_surf()

amrex::MultiFab* SurfaceLayer::get_t_surf ( const int &  lev)
inline

Return the surface-temperature field.

Parameters
[in]levlevel index
1272 { return t_surf[lev].get(); }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_u_star()

amrex::MultiFab* SurfaceLayer::get_u_star ( const int &  lev)
inline

Return the friction-velocity field.

Parameters
[in]levlevel index
1158 { return u_star[lev].get(); }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_w_star()

amrex::MultiFab* SurfaceLayer::get_w_star ( const int &  lev)
inline

Return the convective velocity scale field.

Parameters
[in]levlevel index
1165 { return w_star[lev].get(); }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_z0()

amrex::MultiFab* SurfaceLayer::get_z0 ( const int &  lev)
inline

Return the roughness-height field.

Parameters
[in]levlevel index
1381 { return &z_0[lev]; }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_zref()

amrex::MultiFab* SurfaceLayer::get_zref ( const int &  lev)
inline

Return the 2D MF of zref heights.

Parameters
[in]levlevel index
1367 { return m_ma.get_zref(lev); }
Here is the call graph for this function:

◆ get_zref_min()

amrex::Real SurfaceLayer::get_zref_min ( const int &  lev)
inline

Return the minimum reference height for one level.

Parameters
[in]levlevel index
1374 { return (m_ma.get_zref(lev))->min(0); }
Here is the call graph for this function:

◆ have_variable_sea_roughness()

bool SurfaceLayer::have_variable_sea_roughness ( )
inline

Return whether variable sea roughness is active.

1386 { return m_var_z0; }
bool m_var_z0
Definition: ERF_SurfaceLayer.H:1578

◆ impose_SurfaceLayer_bcs()

void SurfaceLayer::impose_SurfaceLayer_bcs ( const int &  lev,
amrex::Vector< const amrex::MultiFab * >  mfs,
amrex::Vector< std::unique_ptr< amrex::MultiFab >> &  Tau_lev,
amrex::MultiFab *  xheat_flux,
amrex::MultiFab *  yheat_flux,
amrex::MultiFab *  zheat_flux,
amrex::MultiFab *  xqv_flux,
amrex::MultiFab *  yqv_flux,
amrex::MultiFab *  zqv_flux,
const amrex::MultiFab *  z_phys 
)

Impose surface-layer boundary conditions for planar terrain.

Parameters
[in]levlevel index
[in]mfsstate and velocity fields used by the BC computation
[in,out]Tau_levstress fields to fill
[in,out]xheat_fluxx-face heat flux field
[in,out]yheat_fluxy-face heat flux field
[in,out]zheat_fluxz-face heat flux field
[in,out]xqv_fluxx-face moisture flux field
[in,out]yqv_fluxy-face moisture flux field
[in,out]zqv_fluxz-face moisture flux field
[in]z_physphysical-height field

Wrapper to impose Monin Obukhov similarity theory fluxes by populating ghost cells.

Parameters
[in]levCurrent level
[in]mfsState MultiFabs used to compute the boundary fluxes
[in,out]Tau_levDiffusive stress MultiFabs populated with surface stresses
[in,out]xheat_fluxx-face heat-flux MultiFab, used when rotated fluxes are enabled
[in,out]yheat_fluxy-face heat-flux MultiFab, used when rotated fluxes are enabled
[in,out]zheat_fluxz-face heat-flux MultiFab populated with vertical surface heat flux
[in,out]xqv_fluxx-face moisture-flux MultiFab, used when rotated fluxes and moisture are enabled
[in,out]yqv_fluxy-face moisture-flux MultiFab, used when rotated fluxes and moisture are enabled
[in,out]zqv_fluxz-face moisture-flux MultiFab populated when moisture is enabled
[in]z_physNodal physical height used to rotate terrain-following fluxes
793 {
795  amrex::Real wsmin = 0.1; // TODO: change for different faces
796  const Box& domain = m_geom[lev].Domain();
797  moeng_flux flux_comp(wsmin, m_face.isLow(),
798  domain.smallEnd(2), domain.bigEnd(2));
799  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
800  xheat_flux, yheat_flux, zheat_flux,
801  xqv_flux, yqv_flux, zqv_flux,
802  z_phys, flux_comp);
803  } else if (flux_type == FluxCalcType::ROTATE) {
804  rotate_flux flux_comp;
805  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
806  xheat_flux, yheat_flux, zheat_flux,
807  xqv_flux, yqv_flux, zqv_flux,
808  z_phys, flux_comp);
809  } else if (flux_type == FluxCalcType::RICO) {
811  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
812  xheat_flux, yheat_flux, zheat_flux,
813  xqv_flux, yqv_flux, zqv_flux,
814  z_phys, flux_comp);
815  } else if (flux_type == FluxCalcType::BULK_COEFF) {
816  bulk_coeff_flux flux_comp(m_Cd, m_Ch, m_Cq);
817  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
818  xheat_flux, yheat_flux, zheat_flux,
819  xqv_flux, yqv_flux, zqv_flux,
820  z_phys, flux_comp);
821  } else if (flux_type == FluxCalcType::CUSTOM) {
822  const bool fluxes_include_rho = specified_rho_surf || m_use_sfc_fluxes;
823  custom_flux flux_comp(fluxes_include_rho);
824  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
825  xheat_flux, yheat_flux, zheat_flux,
826  xqv_flux, yqv_flux, zqv_flux,
827  z_phys, flux_comp);
828  } else {
829  amrex::Abort("Unknown surface layer flux calculation type");
830  }
831 }
bool specified_rho_surf
Definition: ERF_SurfaceLayer.H:1573
bool m_use_sfc_fluxes
Definition: ERF_SurfaceLayer.H:1589
void compute_SurfaceLayer_bcs(const int &lev, amrex::Vector< const amrex::MultiFab * > mfs, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Tau_lev, amrex::MultiFab *xheat_flux, amrex::MultiFab *yheat_flux, amrex::MultiFab *zheat_flux, amrex::MultiFab *xqv_flux, amrex::MultiFab *yqv_flux, amrex::MultiFab *zqv_flux, const amrex::MultiFab *z_phys, const FluxCalc &flux_comp)
Definition: ERF_MOSTStress.H:2582
Definition: ERF_MOSTStress.H:2416
Definition: ERF_MOSTStress.H:2092
Definition: ERF_MOSTStress.H:2756
Definition: ERF_MOSTStress.H:2934

◆ impose_SurfaceLayer_bcs_EB()

void SurfaceLayer::impose_SurfaceLayer_bcs_EB ( const int &  lev,
amrex::Vector< const amrex::MultiFab * >  mfs,
amrex::Vector< amrex::Vector< std::unique_ptr< amrex::MultiFab >>> &  Tau_lev,
amrex::MultiFab *  xheat_flux,
amrex::MultiFab *  yheat_flux,
amrex::MultiFab *  zheat_flux,
amrex::MultiFab *  xqv_flux,
amrex::MultiFab *  yqv_flux,
amrex::MultiFab *  zqv_flux 
)

Impose surface-layer boundary conditions for embedded-boundary terrain.

Parameters
[in]levlevel index
[in]mfsstate and velocity fields used by the BC computation
[in,out]Tau_levEB stress fields to fill
[in,out]xheat_fluxx-face heat flux field
[in,out]yheat_fluxy-face heat flux field
[in,out]zheat_fluxz-face heat flux field
[in,out]xqv_fluxx-face moisture flux field
[in,out]yqv_fluxy-face moisture flux field
[in,out]zqv_fluxz-face moisture flux field

Wrapper to impose Monin Obukhov similarity theory fluxes by populating ghost cells.

Parameters
[in]levCurrent level
[in]mfsState MultiFabs used to compute the EB boundary fluxes
[in,out]Tau_EBEB diffusive stress MultiFabs populated with surface stresses
[in,out]xheat_fluxx-face EB heat-flux MultiFab, currently unused
[in,out]yheat_fluxy-face EB heat-flux MultiFab, currently unused
[in,out]Hfx3_EBEB heat-flux MultiFab populated with scalar surface flux
[in,out]xqv_fluxx-face EB moisture-flux MultiFab, currently unused
[in,out]yqv_fluxy-face EB moisture-flux MultiFab, currently unused
[in,out]zqv_fluxz-face EB moisture-flux MultiFab, currently unused
856 {
858  moeng_flux_eb flux_comp;
859  compute_SurfaceLayer_bcs_EB(lev, mfs, Tau_EB,
860  xheat_flux, yheat_flux, Hfx3_EB,
861  xqv_flux, yqv_flux, zqv_flux,
862  flux_comp);
863  } else {
864  amrex::Abort("Not implemented surface layer flux calculation type for EB");
865  }
866 }
void compute_SurfaceLayer_bcs_EB(const int &lev, amrex::Vector< const amrex::MultiFab * > mfs, amrex::Vector< amrex::Vector< std::unique_ptr< amrex::MultiFab >>> &Tau_lev, amrex::MultiFab *xheat_flux, amrex::MultiFab *yheat_flux, amrex::MultiFab *zheat_flux, amrex::MultiFab *xqv_flux, amrex::MultiFab *yqv_flux, amrex::MultiFab *zqv_flux, const FluxCalc &flux_comp)
EB implementation of the Moeng surface-flux formulation.
Definition: ERF_EBMOSTStress.H:316

◆ init_tke_from_ustar()

void SurfaceLayer::init_tke_from_ustar ( const int &  lev,
amrex::MultiFab &  cons,
const std::unique_ptr< amrex::MultiFab > &  z_phys_nd,
const amrex::Real  tkefac = one,
const amrex::Real  zscale = amrex::Real(700.0) 
)

Initialize TKE from the current surface friction velocity.

Parameters
[in]levlevel index
[in,out]consconserved state whose TKE component is initialized
[in]z_phys_ndnodal physical-height field
[in]tkefacscale factor applied to the initialized TKE
[in]zscalevertical decay scale

Initialize TKE from surface-layer friction velocity.

Parameters
[in]levCurrent level
[in,out]consConserved state whose RhoKE component is initialized
[in]z_phys_ndNodal physical height used to compute height above ground
[in]tkefacFactor multiplying ustar squared for the surface TKE value
[in]zscaleScale factor used to taper TKE with height
2629 {
2631  static_cast<int>(m_face) == Orientation::zlo(),
2632  "TKE initialization from surface-layer ustar is supported only on the z-low face.");
2633 
2634  Print() << "Initializing TKE from surface layer ustar on level " << lev << std::endl;
2635 
2636  // Handle vertical decomposition by selectively copying into
2637  // a FArrayBox section on each rank. Then doing a reduce real sum
2638  // and broadcasting to each rank. No mask since all CC data
2639  //
2640  // Pinned so the reduction below can read them on the host; the device
2641  // still reaches pinned memory, so the loops here are unaffected.
2642  const int klo = m_geom[lev].Domain().smallEnd(2);
2643  Box bx_lo = u_star[lev]->boxArray().minimalBox();
2644  FArrayBox u_star_lo(bx_lo, 1, The_Pinned_Arena()); u_star_lo.setVal<RunOn::Host>(0);
2645  FArrayBox z_surf_lo(bx_lo, 1, The_Pinned_Arena()); z_surf_lo.setVal<RunOn::Host>(0);
2646  Real* ustar_ptr = u_star_lo.dataPtr();
2647  Real* zsurf_ptr = z_surf_lo.dataPtr();
2648  for (MFIter mfi(cons); mfi.isValid(); ++mfi)
2649  {
2650  Box vbx = mfi.validbox();
2651  if (vbx.smallEnd(2) != klo) { continue; }
2652  vbx.makeSlab(2,0);
2653 
2654  auto const& u_star_arr = u_star[lev]->const_array(mfi);
2655  auto u_star_all = u_star_lo.array();
2656 
2657  auto const& z_phys_arr = z_phys_nd->const_array(mfi);
2658  auto z_surf_all = z_surf_lo.array();
2659 
2660  ParallelFor(vbx, [=] AMREX_GPU_DEVICE(int i, int j, int ) noexcept
2661  {
2662  u_star_all(i,j,0) = u_star_arr(i,j,0);
2663  z_surf_all(i,j,0) = fourth * ( z_phys_arr(i ,j ,klo) + z_phys_arr(i+1,j ,klo)
2664  + z_phys_arr(i ,j+1,klo) + z_phys_arr(i+1,j+1,klo) );
2665  });
2666  }
2667  Gpu::streamSynchronize(); // the fills above are async, the reduction is not
2668  ParallelDescriptor::ReduceRealSum(ustar_ptr, static_cast<int>(bx_lo.numPts()));
2669  ParallelDescriptor::ReduceRealSum(zsurf_ptr, static_cast<int>(bx_lo.numPts()));
2670 
2671  // Now work on all boxes (ustar has been filled above)
2672  constexpr Real small = Real(0.01);
2673  for (MFIter mfi(cons); mfi.isValid(); ++mfi)
2674  {
2675  Box vbx = mfi.validbox();
2676 
2677  auto const& u_star_arr = u_star_lo.const_array();
2678  auto const& z_surf_arr = z_surf_lo.const_array();
2679  auto const& z_phys_arr = z_phys_nd->const_array(mfi);
2680 
2681  auto const& cons_arr = cons.array(mfi);
2682 
2683  ParallelFor(vbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2684  {
2685  Real rho = cons_arr(i, j, k, Rho_comp);
2686  Real ust = u_star_arr(i, j, 0);
2687  Real tke0 = tkefac * ust * ust; // surface value
2688  Real zagl = Compute_Z_AtCellCenter(i, j, k, z_phys_arr) - z_surf_arr(i,j,0);
2689 
2690  // linearly tapering profile -- following WRF, approximate top of
2691  // PBL as ustar * zscale
2692  cons_arr(i, j, k, RhoKE_comp) = rho * tke0 * std::max(
2693  (ust * zscale - zagl) / (std::max(ust, small) * zscale),
2694  small);
2695  });
2696  }
2697 }
constexpr amrex::Real fourth
Definition: ERF_NumericalConstants.H:42
Here is the call graph for this function:

◆ interpolate_sfc_column()

Real SurfaceLayer::interpolate_sfc_column ( const amrex::Real &  time,
int  col 
) const

Interpolates the SFC/SST data at the given column and time

Parameters
[in]timeelapsed time
[in]colcolumn index of file data
457 {
458  if (sfc.empty() || sfc[0].empty()) { return zero; }
459  if (sfc[0].size() == 1) { return sfc[col][0]; }
460 
461  const Real t0 = sfc[0][sfc_time_ind];
462  const Real t1 = sfc[0][sfc_time_ind+1];
463  const Real x0 = sfc[col][sfc_time_ind];
464  const Real x1 = sfc[col][sfc_time_ind+1];
465 
466  if (elapsed_time < t0) {
467  return x0;
468  }
469 
470  if (t0 == t1 || elapsed_time > t1) {
471  return x1;
472  }
473 
474  const Real dt = (elapsed_time - t0) / (t1 - t0);
475  return x0 + (x1 - x0) * dt;
476 }
amrex::Vector< amrex::Vector< amrex::Real > > sfc
Definition: ERF_SurfaceLayer.H:1592
int sfc_time_ind
Definition: ERF_SurfaceLayer.H:1591
real(c_double), parameter t0
Definition: ERF_module_model_constants.F90:39

◆ lmask_min_reduce()

int SurfaceLayer::lmask_min_reduce ( amrex::iMultiFab &  lmask,
const int &  nghost 
)
inline

Compute the minimum land-mask value over valid and optional ghost cells.

Parameters
[in]lmaskland-mask field
[in]nghostnumber of ghost cells included in the reduction
1403  {
1404  int lmask_min = amrex::ReduceMin(lmask, nghost, [=] AMREX_GPU_HOST_DEVICE(
1405  amrex::Box const& bx, amrex::Array4<int const> const& lm_arr) -> int
1406  {
1407  int locmin = std::numeric_limits<int>::max();
1408  const auto lo = lbound(bx);
1409  const auto hi = ubound(bx);
1410  for (int j = lo.y; j <= hi.y; ++j) {
1411  for (int i = lo.x; i <= hi.x; ++i) {
1412  locmin = std::min(locmin, lm_arr(i, j, 0));
1413  }
1414  }
1415  return locmin;
1416  });
1417 
1418  return lmask_min;
1419  }

Referenced by make_SurfaceLayer_at_level().

Here is the caller graph for this function:

◆ mac_avg_is_initialized()

bool SurfaceLayer::mac_avg_is_initialized ( const int &  lev) const
inline

Return whether the time filter at this level holds meaningful history.

Parameters
[in]levlevel index
1233 { return m_ma.time_avg_is_initialized(lev); }
bool time_avg_is_initialized(const int &lev) const
Definition: ERF_MOSTAverage.H:278
Here is the call graph for this function:

◆ mac_avg_is_time_averaged()

bool SurfaceLayer::mac_avg_is_time_averaged ( ) const
inline

Return whether the MOST averages are filtered in time.

1221 { return m_ma.do_time_averaging(); }
bool do_time_averaging() const
Definition: ERF_MOSTAverage.H:265
Here is the call graph for this function:

◆ make_SurfaceLayer_at_level()

void SurfaceLayer::make_SurfaceLayer_at_level ( const int &  lev,
int  nlevs,
const amrex::Vector< amrex::MultiFab * > &  mfv,
std::unique_ptr< amrex::MultiFab > &  Theta_prim,
std::unique_ptr< amrex::MultiFab > &  Qv_prim,
std::unique_ptr< amrex::MultiFab > &  Qr_prim,
std::unique_ptr< amrex::MultiFab > &  z_phys_nd,
amrex::MultiFab *  Hwave,
amrex::MultiFab *  Lwave,
amrex::MultiFab *  eddyDiffs,
amrex::Vector< amrex::MultiFab * >  lsm_data,
amrex::Vector< std::string >  lsm_data_name,
amrex::Vector< amrex::MultiFab * >  lsm_flux,
amrex::Vector< std::string >  lsm_flux_name,
amrex::Vector< std::unique_ptr< amrex::MultiFab >> &  sst_lev,
amrex::Vector< std::unique_ptr< amrex::MultiFab >> &  tsk_lev,
amrex::Vector< std::unique_ptr< amrex::iMultiFab >> &  lmask_lev 
)
inline

Allocate and initialize surface-layer data for one AMR level.

Parameters
[in]levlevel index
[in]nlevsnumber of AMR levels
[in]mfvconserved and velocity MultiFabs for this level
[in]Theta_primprimitive potential-temperature field
[in]Qv_primprimitive water-vapor field
[in]Qr_primprimitive rain-water field
[in]z_phys_ndnodal physical-height field
[in]Hwavewave-height field
[in]Lwavewavelength field
[in]eddyDiffseddy-diffusivity field
[in]lsm_dataland-surface-model data fields
[in]lsm_data_namenames for lsm_data entries
[in]lsm_fluxland-surface-model flux fields
[in]lsm_flux_namenames for lsm_flux entries
[in]sst_levsea-surface-temperature data by time
[in]tsk_levskin-temperature data by time
[in]lmask_levland-mask data by time
427  {
428  // Update MOST Average
430  Theta_prim, Qv_prim, Qr_prim,
431  z_phys_nd);
432 
433  // Get CC vars
434  amrex::MultiFab& mf = *(mfv[0]);
435 
436  amrex::ParmParse pp(m_pp_prefix);
437 
438  // Do we have a time-varying surface roughness that needs to be saved?
439  if (lev == 0) {
440  const int nghost = 0; // ghost cells not included
441  int lmask_min = lmask_min_reduce(*lmask_lev[0].get(), nghost);
442  amrex::ParallelDescriptor::ReduceIntMin(lmask_min);
443 
444  m_var_z0 = (lmask_min < 1) & (rough_type_sea != RoughCalcType::CONSTANT);
445  if (m_var_z0) {
447  m_face.coordDir() == 2 && m_face.isLow(),
448  "Variable sea roughness surface-layer fluxes are supported only on the z-low face.");
449 
450  std::string rough_sea_string{"charnock"};
451  pp.queryAdd("most.roughness_type_sea", rough_sea_string);
452  amrex::Print() << "Variable sea roughness (type " << rough_sea_string
453  << ")" << std::endl;
454  }
455  }
456 
457  if (m_eddyDiffs_lev.size() < lev+1) {
458  m_Hwave_lev.resize(nlevs);
459  m_Lwave_lev.resize(nlevs);
460  m_eddyDiffs_lev.resize(nlevs);
461 
462  m_lsm_data_lev.resize(nlevs);
463  m_lsm_flux_lev.resize(nlevs);
464 
465  m_sst_lev.resize(nlevs);
466  m_tsk_lev.resize(nlevs);
467  m_lmask_lev.resize(nlevs);
468 
469  m_coupled_sst_lev.resize(nlevs, nullptr);
470  m_coupled_sst_valid_lev.resize(nlevs, nullptr);
471 
472  // Size the MOST params for all levels
473  z_0.resize(nlevs);
474  u_star.resize(nlevs);
475  w_star.resize(nlevs);
476  t_star.resize(nlevs);
477  q_star.resize(nlevs);
478  t_surf.resize(nlevs);
479  q_surf.resize(nlevs);
480  surface_diagnostic_source.resize(nlevs);
481  olen.resize(nlevs);
482  pblh.resize(nlevs);
483  m_planar_bndry.resize(nlevs);
484  }
485 
486  // Get pointers to SST,TSK and LANDMASK data
487  int nt_tot_sst = sst_lev.size();
488  m_sst_lev[lev].resize(nt_tot_sst);
489  for (int nt(0); nt < nt_tot_sst; ++nt) {
490  m_sst_lev[lev][nt] = sst_lev[nt].get();
491  }
492  int nt_tot_tsk = static_cast<int>(tsk_lev.size());
493  m_tsk_lev[lev].resize(nt_tot_tsk);
494  for (int nt(0); nt < nt_tot_tsk; ++nt) {
495  m_tsk_lev[lev][nt] = tsk_lev[nt].get();
496  }
497  int nt_tot_lmask = static_cast<int>(lmask_lev.size());
498  m_lmask_lev[lev].resize(nt_tot_lmask);
499  for (int nt(0); nt < nt_tot_lmask; ++nt) {
500  m_lmask_lev[lev][nt] = lmask_lev[nt].get();
501  }
502 
503  // Get pointers to wave data
504  m_Hwave_lev[lev] = Hwave;
505  m_Lwave_lev[lev] = Lwave;
506  m_eddyDiffs_lev[lev] = eddyDiffs;
507 
508  // Text-file driven surface forcing modes. The file always contains at
509  // least time(day) and absolute sst(K); the value is normalized to the
510  // canonical SurfaceLayer theta field at the z-low surface.
511  pp.queryAdd("most.use_sfc_fluxes", m_use_sfc_fluxes);
512  pp.queryAdd("most.use_sfc_sst", m_use_sfc_sst);
514  amrex::Abort("Only one of most.use_sfc_fluxes and most.use_sfc_sst may be enabled");
515  }
517  if (m_use_sfc_fluxes) {
518  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(!use_surface_model, "Cannot use surface flux forcing with LSM/Urban model!");
519  }
520  if (m_terrain_type == TerrainType::EB) {
521  amrex::Abort("Text-file surface forcing is not supported with EB terrain");
522  }
524  m_face.coordDir() == 2 && m_face.isLow(),
525  "most.use_sfc_fluxes and most.use_sfc_sst are supported only on the z-low face.");
526 
527  // load sensible and latent heat fluxes from sfc to prescribe
528  std::string sfc_file = "";
529  pp.queryAdd("most.sfc_file", sfc_file);
530  if (sfc_file.empty()) {
531  amrex::Abort("most.sfc_file must be set when using text-file surface forcing");
532  }
533 
534  // sfc contains: time(day) sst(K) H(W/m2) LE(W/m2) TAU(m2/s2)
535  sfc = read_cols(sfc_file, 1);
536 
537  const int min_cols = m_use_sfc_fluxes ? 5 : 2;
538  if (static_cast<int>(sfc.size()) < min_cols) {
539  amrex::Abort("Surface forcing file does not contain the required number of columns");
540  }
541 
542  // shift time column in days to be relative to current elapsed time
543  const amrex::Real start_day = sfc[0][0];
544  for (int i = 0; i < static_cast<int>(sfc[0].size()); ++i) {
545  sfc[0][i] = 86400.0 * (sfc[0][i] - start_day);
546  }
547 
548  if (m_use_sfc_sst) {
550  amrex::Abort("most.use_sfc_sst cannot be combined with prescribed heat flux or surf_heating_rate");
551  }
553  amrex::Abort("most.use_sfc_sst cannot be combined with prescribed moisture flux");
554  }
556  amrex::Print() << "Using MOST with prescribed SST from most.sfc_file '" << sfc_file << "' over sea" << std::endl;
557  }
558 
559  if (m_use_sfc_fluxes) {
561  amrex::Print() << "Using MOST with prescribed time-varying surface fluxes from '" << sfc_file << "'" << std::endl;
562  }
563  }
564 
565  // Get pointers to LSM data and Fluxes
566  int ndata = static_cast<int>(lsm_data.size());
567  int nflux = static_cast<int>(lsm_flux.size());
568  m_lsm_data_name.resize(ndata);
569  m_lsm_data_lev[lev].resize(ndata);
570  m_lsm_flux_name.resize(nflux);
571  m_lsm_flux_lev[lev].resize(nflux);
572  for (int n(0); n < ndata; ++n) {
573  m_lsm_data_name[n] = lsm_data_name[n];
574  m_lsm_data_lev[lev][n] = lsm_data[n];
575  const std::string lc_name = amrex::toLower(lsm_data_name[n]);
576  if (lc_name == "tsurf" || lc_name == "theta" || lc_name == "t_surf") {
577  m_has_lsm_tsurf = true;
578  m_lsm_tsurf_indx = n;
579  }
580  }
582  amrex::Abort("most.use_sfc_sst cannot be combined with an ocean LSM t_surf input");
583  }
584  int n_valid_lsm_flux = 0;
585  bool has_soil_t_flux = false;
586  for (int n(0); n < nflux; ++n) {
587  m_lsm_flux_name[n] = lsm_flux_name[n];
588  m_lsm_flux_lev[lev][n] = lsm_flux[n];
589  if (m_lsm_flux_lev[lev][n]) { ++n_valid_lsm_flux; }
590  if (amrex::toLower(m_lsm_flux_name[n]) == "soil_t_flux") {
591  has_soil_t_flux = true;
592  }
593  }
594  AMREX_ALWAYS_ASSERT((n_valid_lsm_flux==0 || n_valid_lsm_flux>=4 ||
595  (n_valid_lsm_flux==1 && has_soil_t_flux)));
596  if (n_valid_lsm_flux>=4) { m_has_lsm_fluxes = true; }
597 
598  const bool use_sst = (!m_sst_lev[lev].empty() && m_sst_lev[lev][0]);
599  const bool use_tsk = (!m_tsk_lev[lev].empty() && m_tsk_lev[lev][0]);
600 
601  // Check if there is a user-specified roughness file to be read
602  std::string fname;
603  bool read_z0 = false;
604  if ( (flux_type == FluxCalcType::MOENG) ||
606  int count = pp.countval("most.roughness_file_name");
607  if (count > 1) {
608  AMREX_ALWAYS_ASSERT(count >= lev+1);
609  pp.query("most.roughness_file_name", fname, lev);
610  read_z0 = true;
611  } else if (count == 1) {
612  if (lev == 0) {
613  pp.queryAdd("most.roughness_file_name", fname);
614  } else {
615  // we will interpolate from the coarsest level
616  fname = "";
617  }
618  read_z0 = true;
619  }
620  // else use z0_const
621  }
622  if (read_z0) {
624  m_face.coordDir() == 2 && m_face.isLow(),
625  "Custom MOST roughness is supported only on the z-low face.");
626  }
627 
628  // LSM flux arrays are planar and compute_sfc_params_from_lsm_fluxes only
629  // writes the k=0 slab, while EB MOST consumes surface parameters at
630  // arbitrary cut-cell k; no valid planar-to-cut-cell mapping exists yet.
632  m_terrain_type, use_sst, use_tsk, m_use_coupled_sst,
633  m_has_lsm_tsurf, m_has_lsm_fluxes, read_z0)) {
634  amrex::Abort(
635  "EB SurfaceLayer does not support planar SST/TSK, coupled SST, LSM surface "
636  "temperature or fluxes, or custom/file-driven roughness; no mapping exists "
637  "from those planar inputs to arbitrary EB cut cells.");
638  }
639 
640  // Attributes for MFs and FABs
641  //--------------------------------------------------------
642  // Create a 2D ba for planar terrain, 3D for EB terrain
643  const int dir = m_face.coordDir();
644  int sm_index;
645  if (m_face.isLow()) {
646  sm_index = m_geom[lev].Domain().smallEnd(dir);
647  } else {
648  sm_index = m_geom[lev].Domain().bigEnd(dir);
649  }
650 
651  amrex::BoxArray ba = mf.boxArray();
652  amrex::BoxArray ba_flux;
653  amrex::IntVect ng{1,1,0};
654 
655  // The lateral-wall implementation uses the two-dimensional land-mask
656  // layout to identify grids on the requested face. Lateral walls are
657  // intentionally restricted to grids spanning the complete z domain;
658  // z-decomposed and partial-height grids are not supported.
659  if (dir != 2) {
660  const int dom_lo_z = m_geom[lev].Domain().smallEnd(2);
661  const int dom_hi_z = m_geom[lev].Domain().bigEnd(2);
662  for (int ibox = 0; ibox < ba.size(); ++ibox) {
664  ba[ibox].smallEnd(2) == dom_lo_z && ba[ibox].bigEnd(2) == dom_hi_z,
665  "Surface layer boundaries on x/y faces require Cartesian grids that "
666  "span the full level z domain; partial-height refined grids and grids "
667  "decomposed in z are not supported. Set erf.max_grid_size_z accordingly.");
668  }
669  }
670 
671  if (m_terrain_type == TerrainType::EB) {
672  // Use full 3D BoxArray for EB terrain
673  ba_flux = ba;
674  ng = amrex::IntVect{1,1,1}; // Include z ghost cells
675  } else {
676  // Collapse to 2D for planar terrain
677  amrex::BoxList bl2d = ba.boxList();
678  for (auto& b : bl2d) {
679  b.setRange(dir,sm_index);
680  }
681  ba_flux = amrex::BoxArray(std::move(bl2d));
682  // Lateral faces need state-width ghosts in the tangential z
683  // direction. A z face only needs the original one-cell x/y halo;
684  // kernels using the wider state/mask boxes clip to the FAB bounds.
685  if (dir == 2) {
686  ng = amrex::IntVect{1,1,0};
687  } else {
688  ng = mf.nGrowVect();
689  ng[dir] = 0;
690  }
691  }
692 
693  const amrex::DistributionMapping& dm = mf.DistributionMap();
694  const int ncomp = 1;
695 
696  // Surface copies of the planar boxes (see fill_planar_boundary)
697  // PlanarBoundary handles duplicate boxes created by a z-split. The x/y
698  // layouts use selective lateral exchange instead.
699  if (m_terrain_type != TerrainType::EB && dir == 2) {
700  const int ksurface = m_face.isLow()
701  ? m_geom[lev].Domain().smallEnd(2)
702  : m_geom[lev].Domain().bigEnd(2);
703  m_planar_bndry[lev].define(ba, ba_flux, dm, ksurface, m_face.isLow());
704  }
705 
706  // Z0 heights FAB
707  //--------------------------------------------------------
708  z_0[lev].define(ba_flux, dm, ncomp, ng);
709  z_0[lev].setVal(z0_const);
710  if (read_z0) {
711  read_custom_roughness(lev, fname);
712  }
713 
714  // 2D MFs for U*, T*, T_surf
715  //--------------------------------------------------------
716  u_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
717  u_star[lev]->setVal(bogus_large_value);
718 
719  w_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
720  w_star[lev]->setVal(bogus_large_value);
721 
722  t_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
723  t_star[lev]->setVal(zero); // default to neutral
724 
725  q_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
726  q_star[lev]->setVal(zero); // default to dry
727 
728  olen[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
729  olen[lev]->setVal(bogus_large_value);
730 
731  pblh[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
732  pblh[lev]->setVal(bogus_large_value);
733 
734  t_surf[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
735  t_surf[lev]->setVal(default_land_surf_temp);
736 
737  q_surf[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
738  q_surf[lev]->setVal(default_land_surf_moist);
739 
740  const amrex::iMultiFab& surface_mask = *m_lmask_lev[lev][0];
742  surface_mask.boxArray().size() == mf.boxArray().size(),
743  "Surface-layer mask and state must have the same number of boxes.");
745  surface_mask.DistributionMap() == mf.DistributionMap(),
746  "Surface-layer mask and state must have identical ownership.");
748  surface_mask.boxArray().size() == u_star[lev]->boxArray().size(),
749  "Surface-layer mask and parameters must have the same number of boxes.");
751  surface_mask.DistributionMap() == u_star[lev]->DistributionMap(),
752  "Surface-layer mask and parameters must have identical ownership.");
753 
754  surface_diagnostic_source[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
755  surface_diagnostic_source[lev]->setVal(
757 
758  // TODO: Do we want an enum struct for indexing?
759 
760  if (use_sst || use_tsk || m_has_lsm_tsurf || m_use_coupled_sst || use_surface_model) {
761  // Valid SST, TSK, LSM or coupled-ocean data; t_surf set before computing
762  // fluxes (avoids extended lambda capture) Note that land temp will be set
763  // from m_tsk_lev while sea temp will be set from m_sst_lev
765 
766  // Pathways in fill_tsurf_with_sst_and_tsk
767  amrex::Print() << "Using MOST with specified surface temperature ";
768  if (m_has_lsm_tsurf && !use_sst && !use_tsk) {
769  amrex::Print() << "(LSM: " << m_lsm_data_name[m_lsm_tsurf_indx] << ")";
770  } else if (!use_sst && !use_tsk) {
771  amrex::Print() << "(land: T0, sea: none)";
772  } else {
773  // NOTE: SST from the LOW file populates TSK in update_sst_tsk.
774  // So if we have TSK, it contains everything and has been
775  // sanity checked for valid SST values.
776  if (use_tsk) { m_ignore_sst = true; }
777  if (use_tsk) {
778  amrex::Print() << "(land: TSK, ";
779  } else {
780  amrex::Print() << "(land: T0, ";
781  }
782  if (use_tsk && !use_sst) {
783  amrex::Print() << "sea: TSK)";
784  } else {
785  amrex::Print() << "sea: SST)";
787  }
788  }
789  // The coupler is layered on top of whatever the above selected: it
790  // overwrites only the water cells it actually covers, so the pathway
791  // named above remains the value for land and for uncovered water.
792  if (m_use_coupled_sst) {
793  amrex::Print() << " + coupled ocean SST where covered";
794  }
795  amrex::Print() << std::endl;
796  }
797  }
constexpr amrex::Real bogus_large_value
Definition: ERF_Constants.H:19
void make_MOSTAverage_at_level(const int &lev, const amrex::Vector< amrex::MultiFab * > &vars_old, std::unique_ptr< amrex::MultiFab > &Theta_prim, std::unique_ptr< amrex::MultiFab > &Qv_prim, std::unique_ptr< amrex::MultiFab > &Qr_prim, std::unique_ptr< amrex::MultiFab > &z_phys_nd)
Definition: ERF_MOSTAverage.cpp:171
int lmask_min_reduce(amrex::iMultiFab &lmask, const int &nghost)
Definition: ERF_SurfaceLayer.H:1401
amrex::Vector< std::string > m_lsm_data_name
Definition: ERF_SurfaceLayer.H:1670
bool m_has_lsm_tsurf
Definition: ERF_SurfaceLayer.H:1585
bool m_has_lsm_fluxes
Definition: ERF_SurfaceLayer.H:1584
static amrex::Vector< amrex::Vector< amrex::Real > > read_cols(const std::string &fname, const int skip_nlines=1)
Definition: ERF_SurfaceLayer.cpp:2835
bool m_use_sfc_sst
Definition: ERF_SurfaceLayer.H:1590
bool m_use_coupled_sst
Definition: ERF_SurfaceLayer.H:1600
void read_custom_roughness(const int &lev, const std::string &fname)
Definition: ERF_SurfaceLayer.cpp:2707
@ ng
Definition: ERF_Morrison.H:50
bool planar_sources_supported_for_terrain(TerrainType terrain_type, bool use_sst, bool use_tsk, bool use_coupled_sst, bool has_lsm_tsurf, bool has_lsm_fluxes, bool has_custom_roughness)
Definition: ERF_SurfaceLayer.H:32
Here is the call graph for this function:

◆ read_cols()

amrex::Vector< amrex::Vector< amrex::Real > > SurfaceLayer::read_cols ( const std::string &  fname,
const int  skip_nlines = 1 
)
static

Reads columns of data from a text file, returning each column in a vector.

Parameters
[in]fnamepath to text file
[in]skip_nlinesnumber of lines to skip before reading data (e.g, header lines)
Returns
Vector containing each column in the file as a vector
2836 {
2837  std::ifstream ifs(fname);
2838  if (!ifs.is_open())
2839  {
2840  amrex::Error("Error opening input file " + fname);
2841  }
2842 
2843  amrex::Vector<amrex::Vector<amrex::Real>> col_data;
2844  std::string line;
2845  int nlines = 0;
2846  int ncols = -1;
2847 
2848  const auto print_err = [](const std::string &err_fname, int lineno, int cols, int expected_cols) {
2849  amrex::Error("Error reading file '" + err_fname + "': expected line " +
2850  std::to_string(lineno) + " to have " + std::to_string(expected_cols) +
2851  " columns, but got " + std::to_string(cols));
2852  };
2853 
2854  while (std::getline(ifs, line))
2855  {
2856  nlines++;
2857  if (nlines <= skip_nlines) continue;
2858  if (line.empty()) continue;
2859 
2860  std::istringstream iss(line);
2861 
2862  amrex::Real tmp;
2863  // Get the number of columns in the file
2864  if (ncols == -1) {
2865  int j = 0;
2866  while (iss >> tmp) {
2867  col_data.push_back(amrex::Vector<amrex::Real>());
2868  j+= 1;
2869  }
2870 
2871  ncols = j;
2872  iss = std::istringstream(line);
2873  }
2874 
2875  int j = 0;
2876  while (iss >> tmp) {
2877  // verify each line has the same number of columns
2878  if (j >= ncols) {
2879  print_err(fname, nlines, j+1, ncols);
2880  }
2881  col_data[j].push_back(tmp);
2882  j+= 1;
2883  }
2884 
2885  // throw error if there are fewer columns in the line than expected
2886  if (j != ncols) {
2887  print_err(fname, nlines, j, ncols);
2888  }
2889  }
2890 
2891  ifs.close();
2892 
2893  return col_data;
2894 }
@ tmp
Definition: ERF_AdvanceWSM6.cpp:116

Referenced by make_SurfaceLayer_at_level().

Here is the caller graph for this function:

◆ read_custom_roughness()

void SurfaceLayer::read_custom_roughness ( const int &  lev,
const std::string &  fname 
)

Read custom roughness data for one level.

Parameters
[in]levlevel index
[in]fnameroughness-data file name

Read or interpolate custom roughness length data.

Parameters
[in]levCurrent level
[in]fnameRoughness file name; an empty name interpolates from level 0
2709 {
2710  // Read the file if we have it
2711  if (!fname.empty()) {
2712  // Only the ioproc reads the file
2713  Gpu::HostVector<Real> m_x,m_y,m_z0;
2714  if (ParallelDescriptor::IOProcessor()) {
2715  Print()<<"Reading MOST roughness file at level " << lev << " : " << fname << std::endl;
2716  std::ifstream file(fname);
2717  Real value1,value2,value3;
2718  while(file>>value1>>value2>>value3){
2719  m_x.push_back(value1);
2720  m_y.push_back(value2);
2721  m_z0.push_back(value3);
2722  }
2723  file.close();
2724 
2725  AMREX_ALWAYS_ASSERT(m_x.size() == m_y.size());
2726  AMREX_ALWAYS_ASSERT(m_x.size() == m_z0.size());
2727  }
2728 
2729  // Broadcast the whole domain to every rank
2730  int ioproc = ParallelDescriptor::IOProcessorNumber();
2731  int nnode = static_cast<int>(m_x.size());
2732  ParallelDescriptor::Bcast(&nnode, 1, ioproc);
2733 
2734  if (!ParallelDescriptor::IOProcessor()) {
2735  m_x.resize(nnode);
2736  m_y.resize(nnode);
2737  m_z0.resize(nnode);
2738  }
2739  ParallelDescriptor::Bcast(m_x.data() , nnode, ioproc);
2740  ParallelDescriptor::Bcast(m_y.data() , nnode, ioproc);
2741  ParallelDescriptor::Bcast(m_z0.data(), nnode, ioproc);
2742 
2743  // Copy data to the GPU
2744  Gpu::DeviceVector<Real> d_x(nnode),d_y(nnode),d_z0(nnode);
2745  Gpu::copy(Gpu::hostToDevice, m_x.begin(), m_x.end(), d_x.begin());
2746  Gpu::copy(Gpu::hostToDevice, m_y.begin(), m_y.end(), d_y.begin());
2747  Gpu::copy(Gpu::hostToDevice, m_z0.begin(), m_z0.end(), d_z0.begin());
2748  Real* xp = d_x.data();
2749  Real* yp = d_y.data();
2750  Real* z0p = d_z0.data();
2751 
2752  // Each rank populates it's z_0[lev] MultiFab
2753  const int klo = m_geom[lev].Domain().smallEnd(2);
2754  for (MFIter mfi(z_0[lev]); mfi.isValid(); ++mfi)
2755  {
2756  Box gtbx = mfi.growntilebox();
2757 
2758  if (gtbx.smallEnd(2) != klo) { continue; }
2759 
2760  // Populate z_phys data
2761  Real tol = Real(1.0e-4);
2762  auto dx = m_geom[lev].CellSizeArray();
2763  auto ProbLoArr = m_geom[lev].ProbLoArray();
2764  int ilo = m_geom[lev].Domain().smallEnd(0);
2765  int jlo = m_geom[lev].Domain().smallEnd(1);
2766  int ihi = m_geom[lev].Domain().bigEnd(0);
2767  int jhi = m_geom[lev].Domain().bigEnd(1);
2768 
2769  Array4<Real> const& z0_arr = z_0[lev].array(mfi);
2770  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE (int i, int j, int /*k*/)
2771  {
2772  // Clip indices for ghost-cells
2773  int ii = amrex::min(amrex::max(i,ilo),ihi);
2774  int jj = amrex::min(amrex::max(j,jlo),jhi);
2775 
2776  // Location of nodes
2777  Real x = ProbLoArr[0] + ii * dx[0];
2778  Real y = ProbLoArr[1] + jj * dx[1];
2779  int inode = ii + jj * (ihi-ilo+2); // stride is Nx+1
2780  if (std::sqrt(amrex::Math::powi<2>(x-xp[inode])+amrex::Math::powi<2>(y-yp[inode])) < tol) {
2781  z0_arr(i,j,klo) = z0p[inode];
2782  } else {
2783  // Unexpected list order, do brute force search
2784  Real z0loc = zero;
2785  bool found = false;
2786  for (int n=0; n<nnode; ++n) {
2787  Real delta=std::sqrt(amrex::Math::powi<2>(x-xp[n])+amrex::Math::powi<2>(y-yp[n]));
2788  if (delta < tol) {
2789  found = true;
2790  z0loc = z0p[n];
2791  break;
2792  }
2793  }
2794  AMREX_ASSERT_WITH_MESSAGE(found, "Location read from terrain file does not match the grid!");
2795  amrex::ignore_unused(found);
2796  z0_arr(i,j,klo) = z0loc;
2797  }
2798  });
2799  } // mfi
2800  } else {
2801  AMREX_ALWAYS_ASSERT(lev > 0);
2802 
2803  Print()<<"Interpolating MOST roughness at level " << lev << std::endl;
2804 
2805  // Create a BC mapper that uses FOEXTRAP at domain bndry
2806  Vector<int> bc_lo(3,ERFBCType::foextrap);
2807  Vector<int> bc_hi(3,ERFBCType::foextrap);
2808  Vector<BCRec> bcr; bcr.push_back(BCRec(bc_lo.data(),bc_hi.data()));
2809 
2810  // Create ref ratio
2811  IntVect ratio;
2812  for (int idim = 0; idim < AMREX_SPACEDIM; ++idim) {
2813  ratio[idim] = m_geom[lev].Domain().length(idim) / m_geom[0].Domain().length(idim);
2814  }
2815 
2816  // Create interp object and interpolate from the coarsest grid
2817  MFInterpolater* interp = &mf_cell_cons_interp;
2818  interp->interp(z_0[0] , 0,
2819  z_0[lev], 0,
2820  1, z_0[lev].nGrowVect(),
2821  m_geom[0], m_geom[lev],
2822  m_geom[lev].Domain(),ratio,
2823  bcr, 0);
2824  }
2825 }
@ m_y
Definition: ERF_DataStruct.H:38
@ m_x
Definition: ERF_DataStruct.H:37
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
@ foextrap
Definition: ERF_IndexDefines.H:299

Referenced by make_SurfaceLayer_at_level().

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

◆ rotates_surface_fluxes()

bool SurfaceLayer::rotates_surface_fluxes ( ) const
inline

Return whether the surface fluxes are rotated with the terrain slope (erf.use_rotate_surface_flux), splitting them over the x, y and z faces.

1330 { return m_rotate; }

◆ set_coupled_sst_active()

void SurfaceLayer::set_coupled_sst_active ( const bool  active)
inline

Declare that an ocean coupler will supply SST for this run.

Must be called before make_SurfaceLayer_at_level, which uses it to select ThetaCalcType::SURFACE_TEMPERATURE.

Parameters
[in]activewhether coupled SST is configured
1470 { m_use_coupled_sst = active; }

◆ set_mac_avg_initialized()

void SurfaceLayer::set_mac_avg_initialized ( const int &  lev)
inline

Declare the time filter at this level to hold meaningful history (restart).

Parameters
[in]levlevel index
void set_time_avg_initialized(const int &lev)
Definition: ERF_MOSTAverage.H:289
Here is the call graph for this function:

◆ set_mac_plane_avg()

bool SurfaceLayer::set_mac_plane_avg ( const int &  lev,
const amrex::Vector< amrex::Real > &  pavg 
)
inline

Restore the filtered plane averages from a checkpoint; returns false if the checkpoint does not hold what this run expects.

Parameters
[in]levlevel index
[in]pavgfiltered plane averages, one per average component
1265 { return m_ma.set_plane_average(lev, pavg); }
bool set_plane_average(const int &lev, const amrex::Vector< amrex::Real > &pavg)
Definition: ERF_MOSTAverage.H:315
Here is the call graph for this function:

◆ set_pblh()

void SurfaceLayer::set_pblh ( const int &  lev,
const amrex::MultiFab &  pblh_in 
)
2430 {
2431  AMREX_ASSERT(pblh[lev]);
2432  amrex::MultiFab::Copy(*pblh[lev], pblh_in, 0, 0, 1, 0);
2433  fill_planar_boundary(lev, *pblh[lev]);
2434 }

◆ set_q_surf()

void SurfaceLayer::set_q_surf ( const int &  lev,
const amrex::Real  qsurf 
)
inline

Set the surface-moisture field to a constant value.

Parameters
[in]levlevel index
[in]qsurfsurface moisture
1353 { q_surf[lev]->setVal(qsurf); }

◆ set_skin_temperature()

void SurfaceLayer::set_skin_temperature ( const int &  lev,
const amrex::MultiFab *  t_skin 
)
inline

Take the land surface temperature of level lev from an external absolute skin temperature [K] on the surface-layer layout, or stop doing so (nullptr). update_fluxes reads it; the owner keeps it alive and resets the pointer whenever it reallocates the field.

Parameters
[in]levlevel index
[in]t_skinabsolute skin temperature, or nullptr
1284  {
1285  if (lev >= static_cast<int>(m_skin_tsurf_lev.size())) {
1286  m_skin_tsurf_lev.resize(lev + 1, nullptr);
1287  }
1288  m_skin_tsurf_lev[lev] = t_skin;
1289  }

◆ set_surface_layer_faces()

void SurfaceLayer::set_surface_layer_faces ( const amrex::GpuArray< int, AMREX_SPACEDIM *2 > &  active_faces)
inline

Set the domain faces that use the surface-layer boundary condition.

The face set is used to suppress transpose stress writes at shared edges; the face-normal component remains owned by its corresponding face path.

Parameters
[in]active_faceswhether a specific x/y/z lo/hi face is enabled
1481  {
1482  m_surface_layer_faces = active_faces;
1483  }

◆ set_t_surf()

void SurfaceLayer::set_t_surf ( const int &  lev,
const amrex::Real  tsurf 
)
inline

Set the surface-temperature field to a constant value.

Parameters
[in]levlevel index
[in]tsurfsurface temperature
1338 { t_surf[lev]->setVal(tsurf); }
@ tsurf
Definition: ERF_SLM.H:27

◆ skin_temperature_conflict()

std::string SurfaceLayer::skin_temperature_conflict ( ) const
inline

Why this surface layer cannot take its surface temperature from an external skin temperature, or an empty string when it can: the skin sets t_surf, so the surface layer must compute its heat flux from t_surf (erf.most.surf_temp given, no heating rate) on a non-EB lower surface without rotated surface fluxes, and no land model may own t_surf.

1299  {
1300  if (m_terrain_type == TerrainType::EB) {
1301  return "the surface layer is on EB terrain";
1302  }
1303  // Rotated, the flux is split over hfx1/hfx2/hfx3 and the balance, which removes
1304  // the vertical-face flux hfx3 only, would lose cos(slope) of what the air gains.
1305  if (m_rotate) {
1306  return "erf.use_rotate_surface_flux is set";
1307  }
1309  return "the surface layer does not compute its heat flux from a surface "
1310  "temperature (set erf.most.surf_temp, not a flux or custom/bulk fluxes)";
1311  }
1312  if (surf_heating_rate != amrex::Real(0)) {
1313  return "erf.most.surf_heating_rate is set";
1314  }
1316  return "a land-surface or surface model supplies the surface temperature";
1317  }
1318  return "";
1319  }

◆ surface_sum()

Real SurfaceLayer::surface_sum ( const int &  lev,
const amrex::MultiFab &  mf,
int  comp = 0 
) const

Sum a planar field over the valid cells of the surface, counting each surface cell once.

On a level whose grids are split in the surface-normal direction the planar BoxArray holds one duplicate box per stacked 3D box (see PlanarBoundary), so a plain sum over the planar MultiFab counts every surface cell once per stacked box. Only the computed surface copies are read here, so the duplicates need not have been filled by fill_planar_boundary, and the gather needs no communication. On EB terrain the fields are 3D without duplicates, and the lowest plane of the domain is summed. Only for a surface layer on a z face.

Parameters
[in]levlevel index
[in]mfcell-centered planar MultiFab on this surface's planar BoxArray
[in]compcomponent to sum
419 {
421  "SurfaceLayer::surface_sum is only defined for a surface layer on a z face");
422  if (m_terrain_type == TerrainType::EB) {
423  // EB fields live on the full 3D BoxArray without duplicates: sum the lowest plane
424  return sumToLine(mf, comp, 1, m_geom[lev].Domain(), 2)[0];
425  }
426  // A face-centered planar field (the MOST velocity averages) shares the planar BoxArray
427  // but not its index type, and neighbouring surface boxes would then share a face, which
428  // this sum would count twice
429  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(mf.boxArray().ixType().cellCentered(),
430  "SurfaceLayer::surface_sum is only defined for a cell-centered planar MultiFab");
431  const PlanarBoundary& pb = m_planar_bndry[lev];
432  MultiFab surface_copy(pb.surface_boxes(), pb.surface_dm(), 1, 0);
433  pb.gather_surface(mf, surface_copy, comp, 0, 1);
434  return surface_copy.sum(0);
435 }
Definition: ERF_PlanarBoundary.H:43
const amrex::DistributionMapping & surface_dm() const
Ranks owning the surface copies: each is on the rank of the planar box it is a copy of.
Definition: ERF_PlanarBoundary.H:94
void gather_surface(const amrex::MultiFab &mf, amrex::MultiFab &dst, int scomp, int dcomp, int ncomp) const
Definition: ERF_PlanarBoundary.cpp:109
const amrex::BoxArray & surface_boxes() const
Cell-centered surface copies of the planar boxes, without duplicates.
Definition: ERF_PlanarBoundary.H:91
Here is the call graph for this function:

◆ update_coupled_sst_ptr()

void SurfaceLayer::update_coupled_sst_ptr ( const int  lev,
amrex::MultiFab *  sst_ptr,
amrex::iMultiFab *  valid_ptr 
)
inline

Hand over the coupled sea-surface temperature and its per-cell coverage flag.

Both pointers stay owned by the caller and must remain valid, and on the same layout, until replaced. Passing a null sst_ptr retracts the coupled lane, so the lower-boundary SST/TSK value stands everywhere again.

Parameters
[in]levlevel index
[in]sst_ptrcoupled sea-surface temperature [K]
[in]valid_ptrper-cell coverage flag; nonzero where the coupler supplied a value. Null means no cell is covered.
1457  {
1458  m_coupled_sst_lev[lev] = sst_ptr;
1459  m_coupled_sst_valid_lev[lev] = valid_ptr;
1460  }

◆ update_fluxes()

void SurfaceLayer::update_fluxes ( const int &  lev,
const double &  elapsed_time,
const double &  elapsed_time_since_start_low,
amrex::MultiFab &  cons_in,
const std::unique_ptr< amrex::MultiFab > &  z_phys_nd,
const std::unique_ptr< amrex::MultiFab > &  walldist,
int  max_iters = 100 
)

Update surface fluxes and related surface-layer state.

Parameters
[in]levlevel index
[in]elapsed_timecurrent elapsed simulation time
[in]elapsed_time_since_start_lowelapsed time relative to low-data start
[in,out]cons_inconserved state used by the flux update
[in]z_phys_ndnodal physical-height field
[in]walldistwall-distance field
[in]max_itersmaximum MOST iteration count

Wrapper to update ustar and tstar for Monin Obukhov similarity theory.

Parameters
[in]levCurrent level
[in]elapsed_timeCurrent simulation time
[in]elapsed_time_since_start_lowTime since the start of the lower-boundary data
[in,out]cons_inConserved state, updated when RANS TKE is initialized from surface-layer data
[in]z_phys_ndNodal physical height used by terrain-aware surface calculations
[in]walldistWall distance used when updating boundary TKE
[in]max_itersMaximum iterations to use in the MOST flux solve
30 {
31  bool zlo = (int) m_face == Orientation::zlo();
32  // Update with SST/TSK data if we have a valid pointer.
33  //
34  // This runs even when an ocean coupler is active: it is the only writer of
35  // t_surf over land, and it is the fallback for the water cells the coupler
36  // does not cover. Coupled SST is applied below and only where the coupler
37  // actually supplied a value, so the lower-boundary data is the base layer
38  // rather than an alternative to it.
39  if (zlo && !m_sst_lev[lev].empty() && m_sst_lev[lev][0]) {
40  fill_tsurf_with_sst_and_tsk(lev, elapsed_time_since_start_low);
41  }
42  if (zlo && m_use_sfc_sst) {
43  // Set tsurf to time varying SST from sfc file
44  fill_tsurf_with_sfc_sst(lev, elapsed_time, cons_in, z_phys_nd);
45  }
46 
47  // Apply heating rate if needed
49  update_surf_temp(elapsed_time_since_start_low);
50  }
51 
52  // Overwrite the covered water cells with coupled ocean SST. This must come
53  // after update_surf_temp, which is a whole-domain setVal, and before
54  // fill_qsurf_with_qsat, which derives sea-surface humidity from t_surf.
55  if (zlo) {
56  fill_tsurf_with_coupled_sst(lev, cons_in, z_phys_nd);
57  }
58 
59  // Update land surface temp if we have a valid pointer
60  if (zlo && m_has_lsm_tsurf) {
61  get_lsm_tsurf(lev);
62  }
63 
64  // An external skin temperature (the two-stream surface energy balance's) owns
65  // the land surface temperature; last, so it replaces what was written above.
66  if (zlo && lev < static_cast<int>(m_skin_tsurf_lev.size()) && m_skin_tsurf_lev[lev]) {
67  fill_tsurf_with_skin_temperature(lev, cons_in, z_phys_nd);
68  }
69 
70  // Update qsurf with qsat over sea
71  if (use_moisture) {
72  fill_qsurf_with_qsat(lev, cons_in, z_phys_nd);
73  }
74 
75  // Fill interior ghost cells
76  fill_planar_boundary(lev, *t_surf[lev]);
77 
78  // Compute plane averages for all vars (regardless of flux type)
80 
81  // NOTE: Do iterations to seed variables on the first step (LSM called post step)
82  // as well as compute values where invalid LSM fluxes may reside
83  //*******************************************************************************
84  // ***************************************************************
85  // Iterate the fluxes if moeng type
86  // First iterate over land -- the only model for surface roughness
87  // over land is RoughCalcType::CONSTANT
88  // ***************************************************************
91  bool is_land = true;
92  // Do we have a constant flux for moisture over land?
93  bool cons_qflux = ( (moist_type == MoistCalcType::MOISTURE_FLUX) ||
95  if (m_terrain_type != TerrainType::EB) {
98  surface_flux most_flux(surf_temp_flux, surf_moist_flux, cons_qflux);
99  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
100  } else {
101  amrex::Abort("Unknown value for rough_type_land");
102  }
105  surface_temp most_flux(surf_temp_flux, surf_moist_flux, cons_qflux,
106  m_face.coordDir(), m_face.isLow());
107  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
108  } else {
109  amrex::Abort("Unknown value for rough_type_land");
110  }
111  } else if ((theta_type == ThetaCalcType::ADIABATIC) &&
115  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
116  } else {
117  amrex::Abort("Unknown value for rough_type_land");
118  }
119  } else {
120  amrex::Abort("Unknown value for theta_type");
121  }
122  // EB
123  } else {
126  surface_flux_eb most_flux(surf_temp_flux, surf_moist_flux, cons_qflux);
127  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
128  } else {
129  amrex::Abort("Unknown value for rough_type_land");
130  }
133  surface_temp_eb most_flux(surf_temp_flux, surf_moist_flux, cons_qflux);
134  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
135  } else {
136  amrex::Abort("Unknown value for rough_type_land");
137  }
138  } else if ((theta_type == ThetaCalcType::ADIABATIC) &&
142  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
143  } else {
144  amrex::Abort("Unknown value for rough_type_land");
145  }
146  } else {
147  amrex::Abort("Unknown value for theta_type");
148  }
149  } // EB
150  } // MOENG -- LAND
151 
152  // Update u*/T*/q*/L over land (iterations or from LSM fluxes)
153  if (m_has_lsm_fluxes && elapsed_time > zero) {
155  }
156 
157  // ***************************************************************
158  // Iterate the fluxes if moeng type
159  // Next iterate over sea -- the models for surface roughness
160  // over sea are CHARNOCK, DONELAN, MODIFIED_CHARNOCK or WAVE_COUPLED
161  // NOTE: Sea surface fluxes are not supported for EB terrain
162  // ***************************************************************
163  if ((flux_type == FluxCalcType::MOENG ||
165  m_terrain_type != TerrainType::EB) {
166  bool is_land = false;
167  // NOTE: Do not allow default to adiabatic over sea (we have Qvs at surface)
168  // Do we have a constant flux for moisture over sea?
169  bool cons_qflux = (moist_type == MoistCalcType::MOISTURE_FLUX);
173  cnk_a, smooth_flow_visc, cons_qflux);
174  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
177  depth, smooth_flow_visc, cons_qflux);
178  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
179  } else if (rough_type_sea == RoughCalcType::DONELAN) {
181  smooth_flow_visc, cons_qflux);
182  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
185  smooth_flow_visc, cons_qflux);
186  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
187  } else {
188  amrex::Abort("Unknown value for rough_type_sea");
189  }
190 
194  cnk_a, smooth_flow_visc, cons_qflux);
195  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
198  depth, smooth_flow_visc, cons_qflux);
199  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
200  } else if (rough_type_sea == RoughCalcType::DONELAN) {
202  smooth_flow_visc, cons_qflux);
203  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
206  smooth_flow_visc, cons_qflux);
207  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
208  } else {
209  amrex::Abort("Unknown value for rough_type_sea");
210  }
211 
212  } else if ((theta_type == ThetaCalcType::ADIABATIC) &&
217  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
221  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
222  } else if (rough_type_sea == RoughCalcType::DONELAN) {
224  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
227  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
228  } else {
229  amrex::Abort("Unknown value for rough_type_sea");
230  }
231  } else {
232  amrex::Abort("Unknown value for theta_type");
233  }
234  } // MOENG -- SEA
235 
237  if (m_use_sfc_fluxes) {
238  // update custom surface fluxes interpolated from file
239  update_sfc_time_index(elapsed_time);
240  sfc_tflux = interpolate_sfc_column(elapsed_time, 2);
241  sfc_qflux = interpolate_sfc_column(elapsed_time, 3);
242  sfc_ustar = interpolate_sfc_column(elapsed_time, 4);
243 
244  amrex::Print() << " ABLMOST: Interpolating SHF and LHF at time "
245  << elapsed_time
246  << ": SHF = " << sfc_tflux
247  << " (W/m^2) LHF = " << sfc_qflux
248  << " (W/m^2) TAU = " << sfc_ustar
249  << " (m^2/s^2)" << std::endl;
250 
251  // overwrite the custom_ustar/tstar/qstar values with the new values and
252  // use the existing pathway to set u*,t*,q* with or without a custom_rhosurf
253  // note - when m_use_sfc_fluxes=true, custom_flux has specified_rho_surf=true,
254  // so there is no rho factor here
255  custom_ustar = std::sqrt(sfc_ustar); // convert tau from file to u*
258  }
259 
260  if (custom_rhosurf > 0) {
261  specified_rho_surf = true;
262  u_star[lev]->setVal(std::sqrt(custom_rhosurf) * custom_ustar);
263  t_star[lev]->setVal(custom_rhosurf * custom_tstar);
264  q_star[lev]->setVal(custom_rhosurf * custom_qstar);
265  } else {
266  u_star[lev]->setVal(custom_ustar);
267  t_star[lev]->setVal(custom_tstar);
268  q_star[lev]->setVal(custom_qstar);
269  }
270  }
271 
272  if (m_update_k_rans) {
273  // Clamped divisor: the select is if-converted, so 1/theta_ref runs even
274  // when theta_ref = 0 and would trip fpe_trap_zero (see ERF_SetupDiff.H)
275  const bool use_ref_theta = (theta_ref > 0);
276  const Real inv_theta_ref = one / amrex::max(theta_ref, std::numeric_limits<Real>::min());
277  const Real l_inv_theta0 = (use_ref_theta) ? inv_theta_ref : one;
278  const Real l_inv_Cmu2 = inv_Cmu2;
279  const int klo = m_geom[lev].Domain().smallEnd(2);
280  IntVect ng = u_star[lev]->nGrowVect(); ng[2] = 0;
281 
282  for (MFIter mfi(cons_in); mfi.isValid(); ++mfi)
283  {
284  Box gpbx = mfi.tilebox(IntVect(0),ng);
285 
286  if (gpbx.smallEnd(2) != klo) { continue; }
287 
288  gpbx.makeSlab(2,klo);
289  gpbx &= cons_in.fabbox(mfi.index());
290  gpbx &= walldist->fabbox(mfi.index());
291  if (gpbx.isEmpty()) { continue; }
292 
293  auto cons_arr = cons_in.array(mfi);
294  const auto& u_star_arr = u_star[lev]->const_array(mfi);
295  const auto& t_star_arr = t_star[lev]->const_array(mfi);
296  const auto& dist_arr = walldist->const_array(mfi);
297 
298  ParallelFor(gpbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
299  {
300  Real rho = cons_arr(i,j,k,Rho_comp);
301  if (t_star_arr(i,j,0) < -1e-8) {
302  // Only destabilizing buoyancy flux affects the boundary k
303  // tstar < 0 ==> B > 0
304  Real B = -CONST_GRAV * l_inv_theta0 * u_star_arr(i,j,0) * t_star_arr(i,j,0);
305  if (!use_ref_theta) {
306  B *= cons_arr(i,j,k,Rho_comp) /
307  cons_arr(i,j,k,RhoTheta_comp);
308  }
309 
310  // Axell & Liungman 2001, Eqn. 16
311  cons_arr(i,j,k,RhoKE_comp) = rho * l_inv_Cmu2 *
312  std::pow(
313  u_star_arr(i,j,0) * u_star_arr(i,j,0) * u_star_arr(i,j,0)
314  + KAPPA * B * dist_arr(i,j,k),
315  two/three);
316  } else {
317  cons_arr(i,j,k,RhoKE_comp) = rho * l_inv_Cmu2 * u_star_arr(i,j,0) * u_star_arr(i,j,0);
318  }
319  });
320  }
321  }
322 
323  // If using Land and/or Urban models, then overwrite u_star and t_star with values calculated from the surface models
324  //
325  // Gate on whether the surface models have actually advanced, not on the lower-boundary-data
326  // clock: elapsed_time_since_start_low is offset by (start_time - start_low_time), so it is
327  // already positive at step 0 when the wrflowinp series starts before erf.start_time (which
328  // would let the still-uninitialized u*/t*/q* overwrite the MOST values), and it never becomes
329  // positive at all when the series starts after it.
331  for (MFIter mfi(*u_star[lev]); mfi.isValid(); ++mfi)
332  {
333  Box gtbx = mfi.growntilebox();
334 
335  auto u_star_arr = u_star[lev]->array(mfi);
336  auto t_star_arr = t_star[lev]->array(mfi);
337  auto q_star_arr = q_star[lev]->array(mfi);
338  auto olen_arr = olen[lev]->array(mfi);
339 
340  // Land mask array if it exists
341  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
342  Array4<int> {};
343 
344  auto lsm_tstar_arr = Array4<Real> {};
345  auto lsm_qstar_arr = Array4<Real> {};
346  auto lsm_ustar_arr = Array4<Real> {};
347  auto lsm_olen_arr = Array4<Real> {};
348  if (use_surface_model) {
349  auto mf = m_surf_model->get_field("tstar", lev);
350  lsm_tstar_arr = (mf) ? mf->array(mfi) : Array4<Real> {};
351  mf = m_surf_model->get_field("qstar", lev);
352  lsm_qstar_arr = (mf) ? mf->array(mfi) : Array4<Real> {};
353  mf = m_surf_model->get_field("ustar", lev);
354  lsm_ustar_arr = (mf) ? mf->array(mfi) : Array4<Real> {};
355  mf = m_surf_model->get_field("olen", lev);
356  lsm_olen_arr = (mf) ? mf->array(mfi) : Array4<Real> {};
357  }
358 
359  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
360  {
361  // Overwrite ustar, tstar, qstar, and olen computed above with LSM values
362  if (lmask_arr(i,j,0) == 1) {
363  if (lsm_tstar_arr) t_star_arr(i,j,k) = lsm_tstar_arr(i,j,0);
364  if (lsm_qstar_arr) q_star_arr(i,j,k) = lsm_qstar_arr(i,j,0);
365  if (lsm_ustar_arr) u_star_arr(i,j,k) = lsm_ustar_arr(i,j,0);
366  if (lsm_olen_arr) olen_arr(i,j,k) = lsm_olen_arr(i,j,0);
367  }
368  });
369  }
370 
371  }
372 
373  if (m_terrain_type == TerrainType::EB || m_face.coordDir() == 2) {
374  fill_planar_boundary(lev, *u_star[lev]);
375  if (m_include_wstar) { fill_planar_boundary(lev, *w_star[lev]); }
376  fill_planar_boundary(lev, *t_star[lev]);
377  fill_planar_boundary(lev, *q_star[lev]);
378  fill_planar_boundary(lev, *olen[lev]);
379  } else {
380  // The ordinary FillBoundary is unsafe for lateral walls: the
381  // collapsed layout contains FABs from interior grids whose surface
382  // parameters were never computed. Exchange only after all flux
383  // iterations have written the selected face FABs, and copy the
384  // communicated values back only to those FABs.
386  }
387 }
constexpr amrex::Real Cp_d
Definition: ERF_Constants.H:48
constexpr amrex::Real L_v
Definition: ERF_Constants.H:63
constexpr amrex::Real three
Definition: ERF_NumericalConstants.H:39
constexpr amrex::Real two
Definition: ERF_NumericalConstants.H:38
void compute_averages(const int &lev)
Definition: ERF_MOSTAverage.cpp:1428
amrex::Real sfc_tflux
Definition: ERF_SurfaceLayer.H:1594
amrex::Real sfc_qflux
Definition: ERF_SurfaceLayer.H:1593
void get_lsm_tsurf(const int &lev)
Definition: ERF_SurfaceLayer.cpp:2129
void fill_qsurf_with_qsat(const int &lev, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
Definition: ERF_SurfaceLayer.cpp:1962
void fill_tsurf_with_coupled_sst(const int &lev, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
Definition: ERF_SurfaceLayer.cpp:2178
void compute_fluxes(const int &lev, const int &max_iters, amrex::MultiFab &cons_in, const FluxIter &most_flux, bool is_land)
void fill_tsurf_with_skin_temperature(const int &lev, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
Definition: ERF_SurfaceLayer.cpp:2315
amrex::Real sfc_ustar
Definition: ERF_SurfaceLayer.H:1595
void update_surf_temp(const double &time)
Definition: ERF_SurfaceLayer.H:1115
void fill_tsurf_with_sst_and_tsk(const int &lev, const double &time)
Definition: ERF_SurfaceLayer.cpp:1761
void compute_sfc_params_from_lsm_fluxes(const int &lev, amrex::MultiFab &cons_in)
Definition: ERF_SurfaceLayer.cpp:1665
void fill_tsurf_with_sfc_sst(const int &lev, const double &time, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
Definition: ERF_SurfaceLayer.cpp:1848
bool fields_are_valid() const
Returns whether the surface models have advanced at least once.
Definition: ERF_SurfaceModel.H:509
amrex::MultiFab * get_field(const std::string &name, int lev=0)
Retrieves a registered field by its common name.
Definition: ERF_SurfaceModel.H:722
Definition: ERF_MOSTStress.H:77
Definition: ERF_MOSTStress.H:290
EB surface-layer model for adiabatic constant-roughness fluxes.
Definition: ERF_EBMOSTStress.H:14
Definition: ERF_MOSTStress.H:188
Definition: ERF_MOSTStress.H:389
Definition: ERF_MOSTStress.H:13
Definition: ERF_MOSTStress.H:641
Definition: ERF_MOSTStress.H:909
EB surface-layer model with prescribed surface fluxes and constant roughness.
Definition: ERF_EBMOSTStress.H:207
Definition: ERF_MOSTStress.H:779
Definition: ERF_MOSTStress.H:1036
Definition: ERF_MOSTStress.H:494
Definition: ERF_MOSTStress.H:1351
Definition: ERF_MOSTStress.H:1727
EB surface-layer model with prescribed surface temperature and constant roughness.
Definition: ERF_EBMOSTStress.H:67
Definition: ERF_MOSTStress.H:1543
Definition: ERF_MOSTStress.H:1908
Definition: ERF_MOSTStress.H:1169
Here is the call graph for this function:

◆ update_mac_ptrs()

void SurfaceLayer::update_mac_ptrs ( const int &  lev,
amrex::Vector< amrex::Vector< amrex::MultiFab >> &  vars_old,
amrex::Vector< std::unique_ptr< amrex::MultiFab >> &  Theta_prim,
amrex::Vector< std::unique_ptr< amrex::MultiFab >> &  Qv_prim,
amrex::Vector< std::unique_ptr< amrex::MultiFab >> &  Qr_prim 
)
inline

Update MOST-average field pointers.

Parameters
[in]levlevel index
[in]vars_oldold-time state variables
[in]Theta_primprimitive potential-temperature fields by level
[in]Qv_primprimitive water-vapor fields by level
[in]Qr_primprimitive rain-water fields by level
1149  {
1150  m_ma.update_field_ptrs(lev, vars_old, Theta_prim, Qv_prim, Qr_prim);
1151  }
void update_field_ptrs(const int &lev, amrex::Vector< amrex::Vector< amrex::MultiFab >> &vars_old, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Theta_prim, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Qv_prim, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Qr_prim)
Definition: ERF_MOSTAverage.cpp:441
Here is the call graph for this function:

◆ update_pblh()

void SurfaceLayer::update_pblh ( const int &  lev,
amrex::Vector< amrex::Vector< amrex::MultiFab >> &  vars,
amrex::MultiFab *  z_phys_cc,
const MoistureComponentIndices &  moisture_indices 
)

Wrapper around compute_pblh.

Parameters
[in]levlevel index
[in,out]varsstate variables used by the PBL-height calculation
[in]z_phys_cccell-centered physical-height field
[in]moisture_indicesindices for moisture components

Update PBL height using the configured estimator.

Parameters
[in]levCurrent level
[in]varsLevel-indexed state MultiFabs passed to the PBL height estimator
[in]z_phys_ccCell-centered physical height used by the PBL height estimator
[in]moisture_indicesMoisture component indices used by the PBL height estimator
2416 {
2418  MYNNPBLH estimator;
2419  compute_pblh(lev, vars, z_phys_cc, estimator, moisture_indices);
2421  {
2422  //amrex::Error("YSU/MRF PBLH calc not implemented yet");
2423  // PBLH for MRF/YSU is computed inside ComputeDiffusivityMRF
2424  // and written back via set_pblh(). Nothing to do here.
2425  }
2426 }
void compute_pblh(const int &lev, amrex::Vector< amrex::Vector< amrex::MultiFab >> &vars, amrex::MultiFab *z_phys_cc, const PBLHeightEstimator &est, const MoistureComponentIndices &moisture_indice)
Diagnostic utility for the planetary boundary layer height.
Definition: ERF_PBLHeight.H:12

◆ update_sfc_time_index()

void SurfaceLayer::update_sfc_time_index ( const amrex::Real &  time)

Updates current time index for interpolating data from SFC/SST file.

Parameters
[in]timeelapsed time
439 {
440  if (sfc.empty() || sfc[0].size() < 2) { return; }
441 
442  Real t1 = sfc[0][sfc_time_ind+1];
443  while (elapsed_time >= t1)
444  {
445  int prev_index = sfc_time_ind;
446  sfc_time_ind = std::min(sfc_time_ind + 1, int(sfc[0].size() - 2));
447  t1 = sfc[0][sfc_time_ind+1];
448  if (prev_index == sfc_time_ind) {
449  break;
450  }
451  }
452 }

◆ update_sst_ptr()

void SurfaceLayer::update_sst_ptr ( const int  lev,
const int  itime,
amrex::MultiFab *  sst_ptr 
)
inline

Update one stored sea-surface-temperature pointer.

Parameters
[in]levlevel index
[in]itimetime-slice index
[in]sst_ptrsea-surface-temperature field pointer
1428  {
1429  m_sst_lev[lev][itime] = sst_ptr;
1430  }

◆ update_surf_temp()

void SurfaceLayer::update_surf_temp ( const double &  time)
inline

Update prescribed surface temperature from the configured heating rate.

Parameters
[in]timeelapsed simulation time
1116  {
1117  // NOTE: this is a whole-domain setVal, so it overwrites the SST/TSK fill
1118  // done earlier in update_fluxes. Coupled SST is applied after this
1119  // call and therefore still wins on the water cells it covers.
1120  if (surf_heating_rate != 0) {
1121  // Use the actual size of t_surf, not m_geom.size(), which is always
1122  // max_level+1 and so runs past the levels that exist. t_surf is sized for
1123  // all levels up front but filled one level at a time, so we also have to
1124  // skip the entries that have not been allocated yet.
1125  int nlevs = static_cast<int>(t_surf.size());
1126  for (int lev = 0; lev < nlevs; lev++) {
1127  if (!t_surf[lev]) { continue; }
1128  t_surf[lev]->setVal(surf_temp + surf_heating_rate * static_cast<amrex::Real>(time));
1129  amrex::Print() << "Surface temp at t=" << time << ": "
1130  << surf_temp + surf_heating_rate * time << std::endl;
1131  }
1132  }
1133  }

◆ update_tsk_ptr()

void SurfaceLayer::update_tsk_ptr ( const int  lev,
const int  itime,
amrex::MultiFab *  tsk_ptr 
)
inline

Update one stored skin-temperature pointer.

Parameters
[in]levlevel index
[in]itimetime-slice index
[in]tsk_ptrskin-temperature field pointer
1439  {
1440  m_tsk_lev[lev][itime] = tsk_ptr;
1441  }

◆ use_sfc_fluxes()

bool SurfaceLayer::use_sfc_fluxes ( ) const
inline

Return whether prescribed surface fluxes are active.

1324 { return m_use_sfc_fluxes; }

Member Data Documentation

◆ cnk_a

amrex::Real SurfaceLayer::cnk_a {amrex::Real(0.0185)}
private

◆ custom_qstar

amrex::Real SurfaceLayer::custom_qstar {0}
private

◆ custom_rhosurf

amrex::Real SurfaceLayer::custom_rhosurf {0}
private

◆ custom_tstar

amrex::Real SurfaceLayer::custom_tstar {0}
private

◆ custom_ustar

amrex::Real SurfaceLayer::custom_ustar {0}
private

◆ default_land_surf_moist

amrex::Real SurfaceLayer::default_land_surf_moist {zero}
private

◆ default_land_surf_temp

amrex::Real SurfaceLayer::default_land_surf_temp {amrex::Real(300.)}
private

◆ depth

amrex::Real SurfaceLayer::depth {amrex::Real(30.0)}
private

◆ flux_type

FluxCalcType SurfaceLayer::flux_type {FluxCalcType::MOENG}

◆ inv_Cmu2

amrex::Real SurfaceLayer::inv_Cmu2 = zero
private

◆ m_Cd

amrex::Real SurfaceLayer::m_Cd = zero
private

◆ m_Ch

amrex::Real SurfaceLayer::m_Ch = zero
private

◆ m_coupled_sst_lev

amrex::Vector<amrex::MultiFab*> SurfaceLayer::m_coupled_sst_lev
private

◆ m_coupled_sst_valid_lev

amrex::Vector<amrex::iMultiFab*> SurfaceLayer::m_coupled_sst_valid_lev
private

◆ m_Cq

amrex::Real SurfaceLayer::m_Cq = zero
private

◆ m_eb_vec

amrex::Vector<const eb_*> SurfaceLayer::m_eb_vec
private

◆ m_eddyDiffs_lev

amrex::Vector<amrex::MultiFab*> SurfaceLayer::m_eddyDiffs_lev
private

◆ m_face

amrex::Orientation SurfaceLayer::m_face
private

◆ m_final_low_time

double SurfaceLayer::m_final_low_time
private

◆ m_geom

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

◆ m_has_lsm_fluxes

bool SurfaceLayer::m_has_lsm_fluxes = false
private

◆ m_has_lsm_tsurf

bool SurfaceLayer::m_has_lsm_tsurf = false
private

◆ m_Hwave_lev

amrex::Vector<amrex::MultiFab*> SurfaceLayer::m_Hwave_lev
private

◆ m_ignore_sst

bool SurfaceLayer::m_ignore_sst = false
private

◆ m_include_wstar

bool SurfaceLayer::m_include_wstar = false
private

Referenced by computes_w_star().

◆ m_lmask_lev

amrex::Vector<amrex::Vector<amrex::iMultiFab*> > SurfaceLayer::m_lmask_lev
private

◆ m_low_time_interval

double SurfaceLayer::m_low_time_interval
private

◆ m_lsm_data_lev

amrex::Vector<amrex::Vector<amrex::MultiFab*> > SurfaceLayer::m_lsm_data_lev
private

◆ m_lsm_data_name

amrex::Vector<std::string> SurfaceLayer::m_lsm_data_name
private

◆ m_lsm_flux_lev

amrex::Vector<amrex::Vector<amrex::MultiFab*> > SurfaceLayer::m_lsm_flux_lev
private

◆ m_lsm_flux_name

amrex::Vector<std::string> SurfaceLayer::m_lsm_flux_name
private

◆ m_lsm_tsurf_indx

int SurfaceLayer::m_lsm_tsurf_indx = -1
private

◆ m_Lwave_lev

amrex::Vector<amrex::MultiFab*> SurfaceLayer::m_Lwave_lev
private

◆ m_ma

◆ m_pblh_columns

amrex::Vector<PBLHColumns> SurfaceLayer::m_pblh_columns
private

◆ m_planar_bndry

amrex::Vector<PlanarBoundary> SurfaceLayer::m_planar_bndry
private

◆ m_pp_prefix

std::string SurfaceLayer::m_pp_prefix
private

◆ m_rdOcp

amrex::Real SurfaceLayer::m_rdOcp = RdoCp
private

◆ m_rotate

bool SurfaceLayer::m_rotate = false
private

◆ m_skin_tsurf_lev

amrex::Vector<const amrex::MultiFab*> SurfaceLayer::m_skin_tsurf_lev
private

Referenced by set_skin_temperature().

◆ m_sst_lev

amrex::Vector<amrex::Vector<amrex::MultiFab*> > SurfaceLayer::m_sst_lev
private

◆ m_start_low_time

double SurfaceLayer::m_start_low_time
private

◆ m_surf_model

SurfaceModel* SurfaceLayer::m_surf_model = nullptr
private

◆ m_surface_layer_faces

amrex::GpuArray<int, AMREX_SPACEDIM*2> SurfaceLayer::m_surface_layer_faces {}
private

Referenced by set_surface_layer_faces().

◆ m_terrain_type

TerrainType SurfaceLayer::m_terrain_type
private

◆ m_tsk_lev

amrex::Vector<amrex::Vector<amrex::MultiFab*> > SurfaceLayer::m_tsk_lev
private

◆ m_update_k_rans

bool SurfaceLayer::m_update_k_rans = false
private

◆ m_use_coupled_sst

bool SurfaceLayer::m_use_coupled_sst = false
private

◆ m_use_sfc_fluxes

bool SurfaceLayer::m_use_sfc_fluxes = false
private

◆ m_use_sfc_sst

bool SurfaceLayer::m_use_sfc_sst = false
private

◆ m_var_z0

bool SurfaceLayer::m_var_z0 {false}
private

◆ moist_type

MoistCalcType SurfaceLayer::moist_type {MoistCalcType::ADIABATIC}

◆ olen

amrex::Vector<std::unique_ptr<amrex::MultiFab> > SurfaceLayer::olen
private

◆ pblh

amrex::Vector<std::unique_ptr<amrex::MultiFab> > SurfaceLayer::pblh
private

◆ pblh_type

PBLHeightCalcType SurfaceLayer::pblh_type {PBLHeightCalcType::None}

Referenced by computes_pblh().

◆ q_star

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

◆ q_surf

amrex::Vector<std::unique_ptr<amrex::MultiFab> > SurfaceLayer::q_surf
private

◆ rico_qsat_z0

amrex::Real SurfaceLayer::rico_qsat_z0 {amrex::Real(0.001)}
private

◆ rico_theta_z0

amrex::Real SurfaceLayer::rico_theta_z0 {amrex::Real(298.0)}
private

◆ rough_type_land

RoughCalcType SurfaceLayer::rough_type_land {RoughCalcType::CONSTANT}

◆ rough_type_sea

RoughCalcType SurfaceLayer::rough_type_sea {RoughCalcType::CHARNOCK}

◆ sfc

amrex::Vector<amrex::Vector<amrex::Real> > SurfaceLayer::sfc
private

◆ sfc_qflux

amrex::Real SurfaceLayer::sfc_qflux = zero
private

◆ sfc_tflux

amrex::Real SurfaceLayer::sfc_tflux = zero
private

◆ sfc_time_ind

int SurfaceLayer::sfc_time_ind = 0
private

◆ sfc_ustar

amrex::Real SurfaceLayer::sfc_ustar = zero
private

◆ smooth_flow_visc

bool SurfaceLayer::smooth_flow_visc {true}
private

◆ specified_rho_surf

bool SurfaceLayer::specified_rho_surf {false}
private

◆ surf_heating_rate

amrex::Real SurfaceLayer::surf_heating_rate {0}
private

◆ surf_model_fluxes

bool SurfaceLayer::surf_model_fluxes = false
private

◆ surf_moist

amrex::Real SurfaceLayer::surf_moist {amrex::Real(-1.)}
private

◆ surf_moist_flux

amrex::Real SurfaceLayer::surf_moist_flux {0}
private

◆ surf_temp

amrex::Real SurfaceLayer::surf_temp {amrex::Real(-1.)}
private

Referenced by update_surf_temp().

◆ surf_temp_flux

amrex::Real SurfaceLayer::surf_temp_flux {0}
private

◆ surface_diagnostic_source

amrex::Vector<std::unique_ptr<amrex::MultiFab> > SurfaceLayer::surface_diagnostic_source
private

◆ t_star

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

◆ t_surf

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

◆ theta_ref

amrex::Real SurfaceLayer::theta_ref = zero
private

◆ theta_type

◆ u_star

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

◆ use_moisture

bool SurfaceLayer::use_moisture
private

◆ use_surface_model

bool SurfaceLayer::use_surface_model = false
private

◆ w_star

amrex::Vector<std::unique_ptr<amrex::MultiFab> > SurfaceLayer::w_star
private

◆ z0_const

amrex::Real SurfaceLayer::z0_const {amrex::Real(0.1)}
private

◆ z_0

amrex::Vector<amrex::MultiFab> SurfaceLayer::z_0
private

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