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={})
 
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_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::Realget_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_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::Real get_zref (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
 
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< PlanarBoundarym_planar_bndry
 
amrex::Vector< PBLHColumnsm_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< 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 
1413  {
1414  MOENG = 0, ///< Moeng functional form
1415  CUSTOM, ///< Custom constant flux functional form
1416  BULK_COEFF, ///< Bulk transfer coefficient functional form
1417  ROTATE, ///< Terrain rotation flux functional form
1418  RICO
1419  };

◆ MoistCalcType

Enumerator
ADIABATIC 
MOISTURE_FLUX 

Qv-flux specified.

SURFACE_MOISTURE 

Surface Qv specified.

1427  {
1428  ADIABATIC = 0,
1429  MOISTURE_FLUX, ///< Qv-flux specified
1430  SURFACE_MOISTURE ///< Surface Qv specified
1431  };

◆ PBLHeightCalcType

Enumerator
None 
MYNN25 
YSU 
MRF 
YSUNew 
1441 { None, MYNN25, YSU, MRF, YSUNew };

◆ RoughCalcType

Enumerator
CONSTANT 

Constant z0.

CHARNOCK 
MODIFIED_CHARNOCK 
DONELAN 
WAVE_COUPLED 
1433  {
1434  CONSTANT = 0, ///< Constant z0
1435  CHARNOCK,
1436  MODIFIED_CHARNOCK,
1437  DONELAN,
1438  WAVE_COUPLED
1439  };

◆ ThetaCalcType

Enumerator
ADIABATIC 
HEAT_FLUX 

Heat-flux specified.

SURFACE_TEMPERATURE 

Surface temperature specified.

1421  {
1422  ADIABATIC = 0,
1423  HEAT_FLUX, ///< Heat-flux specified
1424  SURFACE_TEMPERATURE ///< Surface temperature specified
1425  };

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 = {} 
)
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
95  {})
96  : m_face(face),
97  m_pp_prefix(a_pp_prefix),
98  m_geom(geom),
99  m_rotate(use_rot_surface_flux),
100  m_start_low_time(start_low_time),
101  m_final_low_time(final_low_time),
102  m_low_time_interval(low_time_interval),
103  m_eb_vec(eb_vec),
104  m_terrain_type(a_terrain_type),
105  m_rdOcp(a_rdOcp),
106  m_ma(face, geom, (z_phys_nd[0] != nullptr), a_pp_prefix, a_mesh_type, a_terrain_type,
107  zlevels_stag, eb_vec)
108  {
109  // We have a moisture model if Qv_prim is a valid pointer
110  use_moisture = (Qv_prim[0].get());
111 
112  // Keep standalone SurfaceLayer instances compatible with the historical
113  // single-face behavior. ERF replaces this with the complete active-face
114  // set after constructing all SurfaceLayer objects.
115  m_surface_layer_faces[static_cast<int>(face)] = 1;
116 
117  // Get roughness
118  amrex::ParmParse pp(a_pp_prefix);
119  pp.queryAdd("most.z0", z0_const);
120 
121  // Specify how to compute the flux
122  if (use_rot_surface_flux) {
124  } else {
125  std::string flux_string_in;
126  std::string flux_string{"moeng"};
127  auto read_flux = pp.queryAdd("surface_layer.flux_type", flux_string_in);
128  if (read_flux) {
129  flux_string = amrex::toLower(flux_string_in);
130  }
131  if (flux_string == "moeng") {
133  } else if (flux_string == "rico") {
135  } else if (flux_string == "bulk_coeff") {
137  } else if (flux_string == "custom") {
139  } else {
140  amrex::Abort("Undefined MOST flux type!");
141  }
142  }
143 
148  m_face.coordDir() == 2 && m_face.isLow(),
149  "BULK_COEFF, CUSTOM, and RICO surface-layer fluxes are supported only on the z-low face.");
150  }
151 
152  // Include w* to handle free convection (Beljaars 1995, QJRMS)
153  pp.queryAdd("most.include_wstar", m_include_wstar);
154 
155  std::string pblh_string_in;
156  std::string pblh_string{"none"};
157  auto read_pblh = pp.queryAdd("most.pblh_calc", pblh_string_in);
158  if (read_pblh) {
159  pblh_string = amrex::toLower(pblh_string_in);
160  }
161  if (pblh_string == "none") {
163  } else if (pblh_string == "mynn25") {
165  } else if (pblh_string == "mynnedmf") {
167  } else if (pblh_string == "ysu") {
169  } else if (pblh_string == "mrf") {
171  } else {
172  amrex::Abort("Undefined PBLH calc type!");
173  }
174 
177  m_face.coordDir() == 2 && m_face.isLow(),
178  "MOST PBL-height calculation and wstar correction are supported only on the z-low face.");
179  }
180 
181  // The w* correction is computed from the PBL height, so it needs a scheme that
182  // actually diagnoses one. With pblh_calc = "none" the pblh MultiFab keeps the
183  // bogus_large_value it was initialized with and calc_wstar turns that into a
184  // convective velocity scale of ~1e50, which destroys the surface fluxes.
186  amrex::Abort("erf.most.include_wstar requires a PBL height: set "
187  "erf.most.pblh_calc (MYNN25 is the only scheme implemented)");
188  }
189 
190  // Get surface temperature. surf_temp and surf_moist are declared with negative
191  // sentinels (see below) so that "did the user set this" is a property of the
192  // value rather than of the queryAdd return value, which only reports whether the
193  // key existed before the call and so stops being meaningful once anything has
194  // parsed the key. Both most.surf_temp and most.surf_moist are also parsed by
195  // ERF_InputSoundingData.H, so the two sites would poison each other otherwise.
196  pp.queryAdd("most.surf_temp", surf_temp);
197  const bool erf_st = (surf_temp > amrex::Real(0));
198  if (erf_st) { default_land_surf_temp = surf_temp; }
199 
200  // Get surface moisture
201  bool erf_sq = false;
202  if (use_moisture) {
203  pp.queryAdd("most.surf_moist", surf_moist);
204  erf_sq = (surf_moist >= amrex::Real(0));
205  }
206  if (erf_sq) { default_land_surf_moist = surf_moist; }
207 
208  // Custom type user must specify the fluxes
213  pp.get("most.ustar", custom_ustar);
214  pp.get("most.tstar", custom_tstar);
215  pp.get("most.qstar", custom_qstar);
216  pp.queryAdd("most.rhosurf", custom_rhosurf);
217  if (custom_qstar != 0) {
219  "Specified custom MOST qv flux without moisture model!");
220  }
221  amrex::Print() << "Using specified ustar, tstar, qstar for MOST = "
222  << custom_ustar << " " << custom_tstar << " "
223  << custom_qstar << std::endl;
224 
225  // Bulk transfer coefficient (must specify coeffs and surface values)
226  } else if (flux_type == FluxCalcType::BULK_COEFF) {
227  pp.get("most.Cd", m_Cd);
228  pp.get("most.Ch", m_Ch);
229  pp.get("most.Cq", m_Cq);
230  pp.get("most.surf_temp", default_land_surf_temp);
231  pp.get("most.surf_moist", default_land_surf_moist);
232  amrex::Print() << "Using specified Cd, Ch, Cq for MOST = "
233  << m_Cd << " " << m_Ch << " "
234  << m_Cq << std::endl;
235 
236  // Specify surface temperature/moisture or surface flux
237  } else {
238  if (erf_st) {
240  pp.queryAdd("most.surf_heating_rate", surf_heating_rate); // [K/h]
241 
242  // Modify rate to be in units of K / s rather than K / hr
243  surf_heating_rate /= amrex::Real(3600.0); // [K/s]
244 
245  if (pp.query("most.surf_temp_flux", surf_temp_flux)) {
246  amrex::Abort("Can only specify one of surf_temp_flux or surf_heating_rate");
247  }
248  } else {
249  pp.queryAdd("most.surf_temp_flux", surf_temp_flux);
250 
251  if (pp.query("most.surf_heating_rate", surf_heating_rate)) {
252  amrex::Abort("Can only specify one of surf_temp_flux or surf_heating_rate");
253  }
254  if (std::abs(surf_temp_flux) >
257  } else {
259  }
260  }
261 
262  if (erf_sq) {
264  } else {
265  pp.queryAdd("most.surf_moist_flux", surf_moist_flux);
266  if (std::abs(surf_moist_flux) >
269  } else {
271  }
272  }
273  }
274 
279  m_face.coordDir() == 2 && m_face.isLow(),
280  "HEAT_FLUX and ADIABATIC surface-layer fluxes are supported only on the z-low face.");
281  }
282 
284  {
285  pp.queryAdd("most.rico.theta_z0", rico_theta_z0);
286  pp.queryAdd("most.rico.qsat_z0", rico_qsat_z0);
287  }
288 
289  // Make sure the inputs file doesn't try to use most.roughness_type
290  std::string bogus_input;
291  if (pp.queryAdd("most.roughness_type", bogus_input) > 0) {
292  amrex::Abort("most.roughness_type is deprecated; use "
293  "most.roughness_type_land and/or most.roughness_type_sea");
294  }
295 
296  // Specify how to compute the surface flux over land (if there is any)
297  std::string rough_land_string_in;
298  std::string rough_land_string{"constant"};
299  auto read_rough_land =
300  pp.queryAdd("most.roughness_type_land", rough_land_string_in);
301  if (read_rough_land) {
302  rough_land_string = amrex::toLower(rough_land_string_in);
303  }
304  if (rough_land_string == "constant") {
306  } else {
307  amrex::Abort("Undefined MOST roughness type for land!");
308  }
309 
310  // Allow for smooth-flow limit?
311  pp.queryAdd("most.smooth_flow_viscosity", smooth_flow_visc);
312  amrex::Print() << "The smooth-flow limit will be included for variable roughness models over sea: " << smooth_flow_visc << "\n";
313 
314  // Specify how to compute the surface flux over sea (if there is any)
315  std::string rough_sea_string_in;
316  std::string rough_sea_string{"charnock"};
317  auto read_rough_sea = pp.queryAdd("most.roughness_type_sea", rough_sea_string_in);
318  if (read_rough_sea) {
319  rough_sea_string = amrex::toLower(rough_sea_string_in);
320  }
321  if (rough_sea_string == "charnock") {
323  pp.queryAdd("most.charnock_constant", cnk_a);
324  if (cnk_a > 0) {
325  amrex::Print() << "If there is water, Charnock relation with C_a="
326  << cnk_a << " will be used" << std::endl;
327  } else {
328  amrex::Print() << "If there is water, Charnock relation with variable "
329  "Charnock parameter (COARE3.0) will be used" << std::endl;
330  }
331  } else if (rough_sea_string == "coare3.0") {
333  amrex::Print() << "If there is water, Charnock relation with variable "
334  "Charnock parameter (COARE3.0) will be used" << std::endl;
335  cnk_a = -1;
336  } else if (rough_sea_string == "donelan") {
338  } else if (rough_sea_string == "modified_charnock") {
340  pp.queryAdd("most.modified_charnock_depth", depth);
341  if (depth < amrex::Real(10.0) || depth > amrex::Real(100.0) ) {
342  amrex::Print() << "Specified depth of " << depth
343  << " is outside the valid range of [10 100], resetting to bounds now."
344  << std::endl;
345  }
346  // Limiter based upon the fit range in Jiménez & Dudhia
347  depth = amrex::min(amrex::max(depth,amrex::Real(10.0)),amrex::Real(100.0));
348  } else if (rough_sea_string == "wave_coupled") {
350  } else if (rough_sea_string == "constant") {
352  } else {
353  amrex::Abort("Undefined MOST roughness type for sea!");
354  }
355 
356  // use skin temperature instead of sea-surface temperature
357  // (wrfinput data may have lower resolution SST data)
358  pp.queryAdd("most.ignore_sst", m_ignore_sst);
359 
360  // If we're using the RANS k model, then we need to update the dirichlet
361  // wall value of k (written into the first cell above the wall and held
362  // there through the step) based on the instantaneous u* and θ*; the turbulence modeling
363  // choices can vary per level but for now, assume that if specified then
364  // all levels are using the same RANS model.
365  m_update_k_rans = (a_turb_choice.rans_type == RANSType::kEqn &&
366  a_turb_choice.dirichlet_k == true);
367  if (m_update_k_rans) {
369  m_face.coordDir() == 2 && m_face.isLow(),
370  "RANS surface-layer k updates are supported only on the z-low face.");
371  }
372  if (m_update_k_rans) {
373  inv_Cmu2 = one / (a_turb_choice.Cmu0 * a_turb_choice.Cmu0);
374  theta_ref = a_turb_choice.theta_ref;
375  }
376 
377  } // 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:30
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:1444
bool m_include_wstar
Definition: ERF_SurfaceLayer.H:1461
bool m_rotate
Definition: ERF_SurfaceLayer.H:1455
PBLHeightCalcType pblh_type
Definition: ERF_SurfaceLayer.H:1448
double m_final_low_time
Definition: ERF_SurfaceLayer.H:1458
bool use_moisture
Definition: ERF_SurfaceLayer.H:1489
amrex::Real m_Cq
Definition: ERF_SurfaceLayer.H:1510
amrex::Vector< const eb_ * > m_eb_vec
Definition: ERF_SurfaceLayer.H:1513
RoughCalcType rough_type_land
Definition: ERF_SurfaceLayer.H:1446
amrex::Real z0_const
Definition: ERF_SurfaceLayer.H:1462
amrex::Real cnk_a
Definition: ERF_SurfaceLayer.H:1480
amrex::Real m_Ch
Definition: ERF_SurfaceLayer.H:1509
amrex::Real surf_temp
Definition: ERF_SurfaceLayer.H:1469
double m_start_low_time
Definition: ERF_SurfaceLayer.H:1457
amrex::Real rico_qsat_z0
Definition: ERF_SurfaceLayer.H:1487
bool m_update_k_rans
Definition: ERF_SurfaceLayer.H:1576
amrex::Real surf_moist_flux
Definition: ERF_SurfaceLayer.H:1474
bool smooth_flow_visc
Definition: ERF_SurfaceLayer.H:1481
RoughCalcType rough_type_sea
Definition: ERF_SurfaceLayer.H:1447
std::string m_pp_prefix
Definition: ERF_SurfaceLayer.H:1453
amrex::Real surf_moist
Definition: ERF_SurfaceLayer.H:1473
bool m_ignore_sst
Definition: ERF_SurfaceLayer.H:1511
double m_low_time_interval
Definition: ERF_SurfaceLayer.H:1459
amrex::GpuArray< int, AMREX_SPACEDIM *2 > m_surface_layer_faces
Definition: ERF_SurfaceLayer.H:1456
amrex::Real custom_qstar
Definition: ERF_SurfaceLayer.H:1477
amrex::Real custom_rhosurf
Definition: ERF_SurfaceLayer.H:1478
@ 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:1515
@ SURFACE_MOISTURE
Surface Qv specified.
@ MOISTURE_FLUX
Qv-flux specified.
amrex::Real depth
Definition: ERF_SurfaceLayer.H:1482
amrex::Real default_land_surf_moist
Definition: ERF_SurfaceLayer.H:1472
amrex::Real rico_theta_z0
Definition: ERF_SurfaceLayer.H:1486
amrex::Real surf_temp_flux
Definition: ERF_SurfaceLayer.H:1471
amrex::Vector< amrex::Geometry > m_geom
Definition: ERF_SurfaceLayer.H:1454
amrex::Real theta_ref
Definition: ERF_SurfaceLayer.H:1578
amrex::Real custom_tstar
Definition: ERF_SurfaceLayer.H:1476
amrex::Real surf_heating_rate
Definition: ERF_SurfaceLayer.H:1470
FluxCalcType flux_type
Definition: ERF_SurfaceLayer.H:1443
MoistCalcType moist_type
Definition: ERF_SurfaceLayer.H:1445
amrex::Real inv_Cmu2
Definition: ERF_SurfaceLayer.H:1577
amrex::Real custom_ustar
Definition: ERF_SurfaceLayer.H:1475
amrex::Orientation m_face
Definition: ERF_SurfaceLayer.H:1452
amrex::Real m_Cd
Definition: ERF_SurfaceLayer.H:1508
amrex::Real default_land_surf_temp
Definition: ERF_SurfaceLayer.H:1463
@ SURFACE_TEMPERATURE
Surface temperature specified.
@ HEAT_FLUX
Heat-flux specified.
TerrainType m_terrain_type
Definition: ERF_SurfaceLayer.H:1514
MOSTAverage m_ma
Definition: ERF_SurfaceLayer.H:1516
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
436 {
437  // Pointers to the computed averages
438  const auto *const tm_ptr = m_ma.get_average(lev,3); // potential temperature
439  const auto *const qvm_ptr = m_ma.get_average(lev,4); // water vapor mixing ratio
440  const auto *const tvm_ptr = m_ma.get_average(lev,5); // virtual potential temperature
441  const auto *const umm_ptr = m_ma.get_average(lev,6); // horizontal velocity magnitude
442  const auto *const uw_mag_mean = m_ma.get_average(lev,7); // x/z velocity magnitude
443  const auto *const vw_mag_mean = m_ma.get_average(lev,8); // y/z velocity magnitude
444  const auto *const zref_ptr = m_ma.get_zref(lev); // reference height
445  const bool l_use_eb = (m_terrain_type == TerrainType::EB);
446 
447  const int dir = m_face.coordDir();
448  // The source fields and Taus use the same BoxArray ordering and
449  // DistributionMapping. Full-state fields follow cons_in, while the
450  // collapsed SurfaceLayer/MOSTAverage fields follow u_star.
451  int sm_index = 0;
452  if (m_face.isLow()) {
453  sm_index = m_geom[lev].Domain().smallEnd(dir);
454  } else {
455  sm_index = m_geom[lev].Domain().bigEnd(dir);
456  }
457 
458  // Use the 2-D surface mask as the iterator so ranks participate only when
459  // their grids coincide with the selected face.
460  for (MFIter mfi(*m_lmask_lev[lev][0]); mfi.isValid(); ++mfi)
461  {
462  // Face ownership is a property of the full grid, not an individual
463  // tile. Keep the tile box for the kernel below, but test the valid
464  // box so all tiles of a face-owning grid are processed.
465  Box vbx = mfi.validbox();
466  const Box tbx = mfi.tilebox();
467  Box gtbx = mfi.growntilebox();
468 
469  // Since lmask is used in the MFIter, its z extent is collapsed. The
470  // lateral kernels need the full z column; z-face ownership is instead
471  // determined from the original 3-D surface-copy mapping.
472  if (dir != 2 || l_use_eb) {
473  gtbx.setSmall(2, m_geom[lev].Domain().smallEnd(2));
474  gtbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
475  }
476 
477  const bool owns_surface = l_use_eb || dir != 2 ||
478  m_planar_bndry[lev].is_surface_copy(mfi.index());
479  if (!owns_surface) {
480  continue;
481  }
482 
483  if (dir != 2) {
484  if (m_face.isLow()) {
485  if (vbx.smallEnd(dir) != sm_index ||
486  tbx.smallEnd(dir) != sm_index) {
487  continue;
488  }
489  } else {
490  if (vbx.bigEnd(dir) != sm_index ||
491  tbx.bigEnd(dir) != sm_index) {
492  continue;
493  }
494  }
495  }
496 
497  if (m_face.isLow()) {
498  gtbx.setSmall(dir, sm_index);
499  gtbx.setBig(dir, sm_index);
500  } else {
501  gtbx.setSmall(dir, sm_index);
502  gtbx.setBig(dir, sm_index);
503  }
504 
505  // X/Y faces still need the full valid z column
506  if (!l_use_eb && dir == 2) {
507  gtbx.makeSlab(2, sm_index);
508  } else {
509  gtbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
510  }
511 
512  // The mask iterator can have more lateral ghost cells than the
513  // surface-layer fields, and an EB FAB may be decomposed in z. Keep
514  // the face selection above, but never launch outside the target FAB.
515  gtbx &= u_star[lev]->fabbox(mfi.index());
516  if (gtbx.isEmpty()) { continue; }
517 
518  auto u_star_arr = u_star[lev]->array(mfi);
519  auto t_star_arr = t_star[lev]->array(mfi);
520  auto q_star_arr = q_star[lev]->array(mfi);
521  auto t_surf_arr = t_surf[lev]->array(mfi);
522  auto q_surf_arr = q_surf[lev]->array(mfi);
523  auto olen_arr = olen[lev]->array(mfi);
524 
525  const auto tm_arr = tm_ptr->array(mfi);
526  const auto tvm_arr = tvm_ptr->array(mfi);
527  const auto qvm_arr = qvm_ptr->array(mfi);
528  const auto umm_arr = umm_ptr->array(mfi);
529  const auto vwmm_arr = (dir == 0) ? vw_mag_mean->array(mfi) : Array4<Real>{};
530  const auto uwmm_arr = (dir == 1) ? uw_mag_mean->array(mfi) : Array4<Real>{};
531  const auto zref_arr = zref_ptr->array(mfi);
532 
533  // umm depending on face direction (YZ, XZ, XY)
534  const auto dir_umm_arr = ((dir == 0) ? vwmm_arr : ((dir == 1) ? uwmm_arr : umm_arr));
535  const auto z0_arr = z_0[lev].array(mfi);
536 
537  // PBL height if we need to calculate wstar for the Beljaars correction
538  // TODO: can/should we apply this in LES mode?
539  const auto w_star_arr = (m_include_wstar) ? w_star[lev].get()->array(mfi) : Array4<Real> {};
540  const auto pblh_arr = (m_include_wstar) ? pblh[lev].get()->array(mfi) : Array4<Real> {};
541 
542  // Wave properties if they exist
543  const auto Hwave_arr = (m_Hwave_lev[lev]) ? m_Hwave_lev[lev]->array(mfi) : Array4<Real> {};
544  const auto Lwave_arr = (m_Lwave_lev[lev]) ? m_Lwave_lev[lev]->array(mfi) : Array4<Real> {};
545  const auto eta_arr = (m_eddyDiffs_lev[lev]) ? m_eddyDiffs_lev[lev]->array(mfi) : Array4<Real> {};
546 
547  // Land mask array if it exists
548  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
549  Array4<int> {};
550 
551  // Get EB flags if needed
552  const auto flag_arr = (l_use_eb) ? m_eb_vec[lev]->get_const_factory()->getMultiEBCellFlagFab()[mfi].const_array() : Array4<const EBCellFlag>{};
553 
554  if (!l_use_eb) {
555  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
556  {
557  // always check for land mask at k=0 even if on other faces
558  if (( is_land && lmask_arr(i,j,0) == 1) ||
559  (!is_land && lmask_arr(i,j,0) == 0))
560  {
561  // NOTE: All 2D MFs so k index is always 0 from ba2d definition
562  most_flux.iterate_flux(i, j, k, max_iters,
563  zref_arr, // set in most average
564  z0_arr, // updated if(!is_land)
565  dir_umm_arr, tm_arr, tvm_arr, qvm_arr,
566  u_star_arr, // updated
567  w_star_arr, // updated if(m_include_wstar)
568  t_star_arr, q_star_arr, // updated
569  t_surf_arr, q_surf_arr, olen_arr, // updated
570  pblh_arr, // updated if(m_include_wstar)
571  Hwave_arr, Lwave_arr, eta_arr);
572  }
573  });
574  // EB
575  } else {
576  if (std::is_same<FluxIter, adiabatic_eb>::value ||
577  std::is_same<FluxIter, surface_temp_eb>::value ||
578  std::is_same<FluxIter, surface_flux_eb>::value) {
579  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
580  {
581  if (( is_land && lmask_arr(i,j,0) == 1) ||
582  (!is_land && lmask_arr(i,j,0) == 0))
583  {
584  if (flag_arr(i,j,k).isSingleValued()) {
585  most_flux.iterate_flux(i, j, k, max_iters,
586  zref_arr, // set in most average
587  z0_arr, // updated if(!is_land)
588  umm_arr, tm_arr, tvm_arr, qvm_arr,
589  u_star_arr, // updated
590  w_star_arr, // updated if(m_include_wstar)
591  t_star_arr, q_star_arr, // updated
592  t_surf_arr, q_surf_arr, olen_arr, // updated
593  pblh_arr, // updated if(m_include_wstar)
594  Hwave_arr, Lwave_arr, eta_arr);
595  }
596  }
597  });
598  } else {
599  amrex::Abort("FluxIter type not supported for EB");
600  }
601  }
602  }
603 }
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:1558
amrex::Vector< PlanarBoundary > m_planar_bndry
Definition: ERF_SurfaceLayer.H:1529
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_surf
Definition: ERF_SurfaceLayer.H:1523
amrex::Vector< std::unique_ptr< amrex::MultiFab > > q_star
Definition: ERF_SurfaceLayer.H:1520
amrex::Vector< amrex::MultiFab * > m_Lwave_lev
Definition: ERF_SurfaceLayer.H:1573
amrex::Vector< amrex::MultiFab > z_0
Definition: ERF_SurfaceLayer.H:1483
amrex::Vector< std::unique_ptr< amrex::MultiFab > > w_star
Definition: ERF_SurfaceLayer.H:1518
amrex::Vector< std::unique_ptr< amrex::MultiFab > > u_star
Definition: ERF_SurfaceLayer.H:1517
amrex::Vector< std::unique_ptr< amrex::MultiFab > > q_surf
Definition: ERF_SurfaceLayer.H:1524
amrex::Vector< amrex::MultiFab * > m_Hwave_lev
Definition: ERF_SurfaceLayer.H:1572
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_star
Definition: ERF_SurfaceLayer.H:1519
amrex::Vector< amrex::MultiFab * > m_eddyDiffs_lev
Definition: ERF_SurfaceLayer.H:1574
amrex::Vector< std::unique_ptr< amrex::MultiFab > > olen
Definition: ERF_SurfaceLayer.H:1521
amrex::Vector< std::unique_ptr< amrex::MultiFab > > pblh
Definition: ERF_SurfaceLayer.H:1522
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
2256 {
2257  const MultiFab& cons = vars[lev][Vars::cons];
2258  const iMultiFab* lmask = m_lmask_lev[lev][0];
2259 
2260  // The estimator scans each box from its lowest cell to its highest and writes the planar
2261  // pblh of that box, so every box it is given must start at the ground. Grids that hold
2262  // such boxes only -- full height or not -- go straight to it. Any other grids (boxes
2263  // stacked in z, or boxes aloft) go through columns: the runs of cells that start at the
2264  // ground, each as one box (see define_pblh_columns).
2265  if (static_cast<int>(m_pblh_columns.size()) <= lev) { m_pblh_columns.resize(lev+1); }
2266  if (m_pblh_columns[lev].ba != cons.boxArray() ||
2267  m_pblh_columns[lev].dm != cons.DistributionMap()) {
2268  define_pblh_columns(lev, cons.boxArray(), cons.DistributionMap());
2269  }
2270  const PBLHColumns& cols = m_pblh_columns[lev];
2271 
2272  if (!cols.needed) {
2273  est.compute_pblh(m_geom[lev], z_phys_cc, pblh[lev].get(), cons, lmask, moisture_indices);
2274  return;
2275  }
2276 
2277  // Zero is the estimator's own value for a height it did not find. It stays on the planar
2278  // boxes over which no box of this level reaches the ground: all of them on a level that
2279  // lies entirely aloft.
2280  pblh[lev]->setVal(zero);
2281  if (cols.ba_col.empty()) { return; }
2282 
2283  const Periodicity period = m_geom[lev].periodicity();
2284 
2285  // The estimator reads the density, the potential temperature, the TKE and the moisture
2286  // species that enter theta_v. Those live in [0, RhoKE_comp] and in the moist window, so
2287  // the columns carry the state up to the highest of them and no further: the species above
2288  // it (the number concentrations of a two-moment scheme, the non-water species) are the
2289  // bulk of a moist state and are never read here. The span is contiguous rather than the
2290  // two pieces it is made of, so that every component of cons_col is filled by the copies
2291  // below -- a component the estimator reads must never be one this routine left unset --
2292  // and the components keep their place, since the estimator indexes the state by
2293  // component number.
2294  int q_hi = -1;
2295  for (const int q : {moisture_indices.qv, moisture_indices.qc, moisture_indices.qi,
2296  moisture_indices.qr, moisture_indices.qs, moisture_indices.qg}) {
2297  AMREX_ALWAYS_ASSERT((q < 0) || ((q > RhoKE_comp) && (q < cons.nComp())));
2298  q_hi = std::max(q_hi, q);
2299  }
2300  const int ncomp_col = std::max(RhoKE_comp+1, q_hi+1);
2301 
2302  // The halo the columns need: in x and y the ghost cells of pblh, which the estimator
2303  // writes and so reads the state over, and in z the one cell above the top of each column
2304  // that the scan's k+1 reads end in. The state must hold that halo for the copies below
2305  // to have anything to take it from.
2306  const IntVect ng_pblh = pblh[lev]->nGrowVect();
2307  const IntVect ng_col = elemwiseMax(ng_pblh, IntVect(AMREX_D_DECL(0,0,1)));
2308  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(cons.nGrowVect().allGE(ng_col),
2309  "erf.most.pblh_calc = MYNN25 on grids that do not all start at the ground needs the "
2310  "state to carry at least the ghost cells of the surface-layer fields, and one in z.");
2311 
2312  // Every valid cell of a column is a valid cell of this level, and every ghost cell of a
2313  // column is a valid or a ghost cell of the box that holds the cell next to it, so the two
2314  // passes below leave no cell of the columns unset. Ghost cells go first (they hold the
2315  // physical boundary values and, next to a coarser level, the values interpolated from
2316  // it), then the valid cells, so that every cell this level owns comes from the box that
2317  // owns it and not from a neighbour's ghost cell.
2318  MultiFab cons_col(cols.ba_col, cols.dm_col, ncomp_col, ng_col);
2319  for (const IntVect& ng_src : {ng_col, IntVect(0)}) {
2320  cons_col.ParallelCopy(cons, 0, 0, ncomp_col, ng_src, ng_col, period);
2321  }
2322 
2323  std::unique_ptr<MultiFab> zcc_col;
2324  if (z_phys_cc) {
2325  const IntVect ng_z = z_phys_cc->nGrowVect();
2326  zcc_col = std::make_unique<MultiFab>(cols.ba_col, cols.dm_col, 1, ng_z);
2327  zcc_col->ParallelCopy(*z_phys_cc, 0, 0, 1, ng_z, ng_z, period);
2328  zcc_col->ParallelCopy(*z_phys_cc, 0, 0, 1, IntVect(0), ng_z, period);
2329  }
2330 
2331  std::unique_ptr<iMultiFab> lmask_col;
2332  if (lmask) {
2333  lmask_col = std::make_unique<iMultiFab>(cols.ba_col2d, cols.dm_col, 1, ng_pblh);
2334  lmask_col->setVal(1);
2335  lmask_col->ParallelCopy(*lmask, 0, 0, 1, elemwiseMin(lmask->nGrowVect(), ng_pblh), ng_pblh, period);
2336  lmask_col->ParallelCopy(*lmask, 0, 0, 1, IntVect(0), ng_pblh, period);
2337  }
2338 
2339  MultiFab pblh_col(cols.ba_col2d, cols.dm_col, 1, ng_pblh);
2340  est.compute_pblh(m_geom[lev], zcc_col.get(), &pblh_col, cons_col, lmask_col.get(), moisture_indices);
2341 
2342  // Onto every planar box. The ghost cells of the columns go first, for the ghost cells of
2343  // pblh outside the domain. They also reach valid cells of pblh over which no box of this
2344  // level starts at the ground (the estimator fills the ghost cells of a column next to such
2345  // a gap from ghost data), so those are set back to zero before the valid cells of the
2346  // columns are copied. Every planar copy of a cell then holds the same value, which makes
2347  // the FillBoundary that ends this well defined despite the duplicate boxes.
2348  pblh[lev]->ParallelCopy(pblh_col, 0, 0, 1, ng_pblh, ng_pblh, period);
2349  pblh[lev]->setVal(zero, 0, 1, 0);
2350  pblh[lev]->ParallelCopy(pblh_col, 0, 0, 1, IntVect(0), IntVect(0), period);
2351  pblh[lev]->FillBoundary(period);
2352 }
#define RhoKE_comp
Definition: ERF_IndexDefines.H:41
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:29
amrex::Vector< PBLHColumns > m_pblh_columns
Definition: ERF_SurfaceLayer.H:1547
void define_pblh_columns(const int &lev, const amrex::BoxArray &ba, const amrex::DistributionMapping &dm)
Definition: ERF_SurfaceLayer.cpp:2363
@ cons
Definition: ERF_IndexDefines.H:214
@ q
Definition: ERF_WSM6.H:273
int qs
snow
Definition: ERF_DataStruct.H:238
int qr
rain
Definition: ERF_DataStruct.H:237
int qi
cloud ice
Definition: ERF_DataStruct.H:236
int qv
water vapor
Definition: ERF_DataStruct.H:234
int qc
cloud liquid water
Definition: ERF_DataStruct.H:235
int qg
graupel
Definition: ERF_DataStruct.H:239
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
1597 {
1599  static_cast<int>(m_face) == Orientation::zlo(),
1600  "LSM surface-layer parameters are supported only on the z-low face.");
1601 
1603  bool has_moisture = use_moisture;
1604  const int klo = m_geom[lev].Domain().smallEnd(2);
1605  const auto *const umm_ptr = m_ma.get_average(lev,6); // horizontal velocity magnitude
1606  const auto *const zref_ptr = m_ma.get_zref(lev); // reference height
1607  for (MFIter mfi(cons_in); mfi.isValid(); ++mfi) {
1608 
1609  Box vbx = mfi.validbox();
1610  if (vbx.smallEnd(2) != klo) { continue; }
1611  vbx.makeSlab(2,0);
1612 
1613  // Get CC state
1614  const Array4<const Real> cons_arr = cons_in.const_array(mfi);
1615 
1616  // Get SL params
1617  const auto u_star_arr = u_star[lev]->array(mfi);
1618  const auto t_star_arr = t_star[lev]->array(mfi);
1619  const auto q_star_arr = q_star[lev]->array(mfi);
1620  const auto olen_arr = olen[lev]->array(mfi);
1621 
1622  const auto umm_arr = umm_ptr->array(mfi);
1623  const auto zref_arr = zref_ptr->array(mfi);
1624  const auto z0_arr = z_0[lev].array(mfi);
1625 
1626  // Get LSM fluxes
1627  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
1628  Array4<int> {};
1629  auto lsm_t_flux_arr = Array4<Real> {};
1630  auto lsm_q_flux_arr = Array4<Real> {};
1631  auto lsm_tau13_arr = Array4<Real> {};
1632  auto lsm_tau23_arr = Array4<Real> {};
1633  // compute_sfc_params_from_lsm_fluxes consumes signed kinematic stress
1634  // components; their vector magnitude determines u_star^2.
1635  for (int n(0); n<m_lsm_flux_lev[lev].size(); ++n) {
1636  if (toLower(m_lsm_flux_name[n]) == "t_flux") { lsm_t_flux_arr = m_lsm_flux_lev[lev][n]->array(mfi); }
1637  if (toLower(m_lsm_flux_name[n]) == "q_flux") { lsm_q_flux_arr = m_lsm_flux_lev[lev][n]->array(mfi); }
1638  if (toLower(m_lsm_flux_name[n]) == "tau13") { lsm_tau13_arr = m_lsm_flux_lev[lev][n]->array(mfi); }
1639  if (toLower(m_lsm_flux_name[n]) == "tau23") { lsm_tau23_arr = m_lsm_flux_lev[lev][n]->array(mfi); }
1640  }
1641 
1642  ParallelFor(vbx, [=] AMREX_GPU_DEVICE(int i, int j, int /*k*/) noexcept
1643  {
1644  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
1645  // Skip cells the LSM did not have a valid flux (lsm_undefined).
1646  if (is_land && lsm_t_flux_arr && lsm_t_flux_arr(i,j,0) < lsm_undefined) {
1647  Real rho = cons_arr(i,j,klo,Rho_comp);
1648  Real Thd = cons_arr(i,j,klo,RhoTheta_comp) / rho;
1649  Real qv = (has_moisture) ? cons_arr(i,j,klo,RhoQ1_comp) / rho : zero;
1650  Real Thv = Thd * (one + epsv*qv);
1651  Real tau = std::sqrt( lsm_tau13_arr(i,j,0)*lsm_tau13_arr(i,j,0)
1652  + lsm_tau23_arr(i,j,0)*lsm_tau23_arr(i,j,0) );
1653  u_star_arr(i,j,0) = amrex::max(std::sqrt(tau),eps);
1654  if (lsm_t_flux_arr(i,j,0)>=zero) {
1655  t_star_arr(i,j,0) = amrex::min(-lsm_t_flux_arr(i,j,0) / u_star_arr(i,j,0),-eps);
1656  } else {
1657  t_star_arr(i,j,0) = amrex::max(-lsm_t_flux_arr(i,j,0) / u_star_arr(i,j,0),eps);
1658  }
1659  if (lsm_q_flux_arr(i,j,0)>=zero) {
1660  q_star_arr(i,j,0) = amrex::min(-lsm_q_flux_arr(i,j,0) / u_star_arr(i,j,0),-eps);
1661  } else {
1662  q_star_arr(i,j,0) = amrex::max(-lsm_q_flux_arr(i,j,0) / u_star_arr(i,j,0),eps);
1663  }
1664  Real tstv = t_star_arr(i,j,0)*(one + epsv*qv) + epsv*Thd*q_star_arr(i,j,0);
1665  tstv = (tstv >= zero) ? amrex::max(tstv, eps) : amrex::min(tstv, -eps);
1666  olen_arr(i,j,0) = ( u_star_arr(i,j,0) * u_star_arr(i,j,0) * Thv ) /
1667  ( KAPPA * CONST_GRAV * tstv );
1668  z0_arr(i,j,0) = Compute_roughness(zref_arr(i,j,0), olen_arr(i,j,0),
1669  umm_arr(i,j,0), u_star_arr(i,j,0));
1670  }
1671  });
1672  } // mfi
1673 }
constexpr amrex::Real epsv
Definition: ERF_Constants.H:40
constexpr amrex::Real KAPPA
Definition: ERF_Constants.H:55
constexpr amrex::Real CONST_GRAV
Definition: ERF_Constants.H:56
constexpr amrex::Real lsm_undefined
Definition: ERF_Constants.H:26
#define Rho_comp
Definition: ERF_IndexDefines.H:39
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:40
#define RhoQ1_comp
Definition: ERF_IndexDefines.H:45
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:1569
amrex::Vector< std::string > m_lsm_flux_name
Definition: ERF_SurfaceLayer.H:1571
@ rho
Definition: ERF_Kessler.H:25
@ qv
Definition: ERF_Kessler.H:31
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
838 {
839  bool rotate = m_rotate;
840 
841  const int dir = m_face.coordDir();
843  !mfs.empty() && mfs[0] != nullptr,
844  "Surface-layer stress computation requires a conserved-state MultiFab.");
846  Tau_lev[TauType::tau13] != nullptr && Tau_lev[TauType::tau23] != nullptr,
847  "tau13 and tau23 are required by surface-layer stress computation.");
848  int sm_index = 0;
849  if (m_face.isLow()) {
850  sm_index = m_geom[lev].Domain().smallEnd(dir);
851  } else {
852  sm_index = m_geom[lev].Domain().bigEnd(dir);
853  }
854 
855  // These requirements are invariant over the loop below. Check them once
856  // before acquiring per-tile array views.
857  if (dir == 0) {
859  Tau_lev[TauType::tau31] != nullptr,
860  "tau31 is required when imposing an x-face surface layer.");
862  Tau_lev[TauType::tau21] != nullptr,
863  "tau21 is required when imposing an x-face surface layer.");
864  } else if (dir == 1) {
866  Tau_lev[TauType::tau32] != nullptr,
867  "tau32 is required when imposing a y-face surface layer.");
869  Tau_lev[TauType::tau12] != nullptr,
870  "tau12 is required when imposing a y-face surface layer.");
871  }
872 
873  const int klo = sm_index;
874  const auto& dxInv = m_geom[lev].InvCellSizeArray();
875  const Box domain = m_geom[lev].Domain();
876  const int xlo_node = domain.smallEnd(0);
877  const int xhi_node = domain.bigEnd(0) + 1;
878  const int ylo_node = domain.smallEnd(1);
879  const int yhi_node = domain.bigEnd(1) + 1;
880  const int zlo_node = domain.smallEnd(2);
881  const int zhi_node = domain.bigEnd(2) + 1;
882  const bool xlo_surface = m_surface_layer_faces[Orientation::xlo()];
883  const bool xhi_surface = m_surface_layer_faces[Orientation::xhi()];
884  const bool ylo_surface = m_surface_layer_faces[Orientation::ylo()];
885  const bool yhi_surface = m_surface_layer_faces[Orientation::yhi()];
886  const bool zlo_surface = m_surface_layer_faces[Orientation::zlo()];
887  const bool zhi_surface = m_surface_layer_faces[Orientation::zhi()];
888 
889  for (MFIter mfi(*mfs[0]); mfi.isValid(); ++mfi)
890  {
891  // Skip boxes that do not own the selected face before acquiring any
892  // arrays from collapsed or staggered layouts. Interior boxes may
893  // share the same collapsed coordinates but do not contain surface
894  // data for this boundary.
895  Box bx = mfi.tilebox();
896  const Box valid_bx = mfi.validbox();
897  if (m_face.isLow()) {
898  if (valid_bx.smallEnd(dir) != klo ||
899  bx.smallEnd(dir) != klo) {
900  continue;
901  }
902  bx.setBig(dir, bx.smallEnd(dir));
903  } else {
904  if (valid_bx.bigEnd(dir) != klo ||
905  bx.bigEnd(dir) != klo) {
906  continue;
907  }
908  bx.setSmall(dir, bx.bigEnd(dir));
909  }
910 
911  // Get field arrays
912  const auto cons_arr = mfs[Vars::cons]->array(mfi);
913  const auto velx_arr = mfs[Vars::xvel]->array(mfi);
914  const auto vely_arr = mfs[Vars::yvel]->array(mfi);
915  const auto velz_arr = mfs[Vars::zvel]->array(mfi);
916 
917  // Output stresses:
918  // T Q U V
919  // X-faces: hfx1 qfx1 t21 t31
920  // Y-faces: hfx2 qfx2 t12 t32
921  // Z-faces: hfx3 qfx3 t13 t23
922 
923  // Output stresses nodal locations:
924  // T Q
925  // X-faces: hfx1 qfx1(1,0,0) t21(V)(1,1,0) t31(W)(1,0,1)
926  // Y-faces: hfx2 qfx2(0,1,0) t12(U)(1,1,0) t32(W)(0,1,1)
927  // Z-faces: hfx3 qfx3(0,0,1) t13(U)(1,0,1) t23(V)(0,1,1)
928 
929  // Diffusive stress vars
930  auto t13_arr = Tau_lev[TauType::tau13]->array(mfi);
931  auto t31_arr = Tau_lev[TauType::tau31]
932  ? Tau_lev[TauType::tau31]->array(mfi) : Array4<Real>{};
933 
934  auto t23_arr = Tau_lev[TauType::tau23]->array(mfi);
935  auto t32_arr = Tau_lev[TauType::tau32]
936  ? Tau_lev[TauType::tau32]->array(mfi) : Array4<Real>{};
937 
938 
939  auto hfx3_arr = zheat_flux->array(mfi);
940  auto qfx3_arr = (zqv_flux) ? zqv_flux->array(mfi) : Array4<Real>{};
941 
942  auto olen_arr = olen[lev]->array(mfi);
943 
944  // Rotated stress vars
945  auto t11_arr = (m_rotate) ? Tau_lev[TauType::tau11]->array(mfi) : Array4<Real>{};
946  auto t22_arr = (m_rotate) ? Tau_lev[TauType::tau22]->array(mfi) : Array4<Real>{};
947  auto t33_arr = (m_rotate) ? Tau_lev[TauType::tau33]->array(mfi) : Array4<Real>{};
948  auto t12_arr = Tau_lev[TauType::tau12]
949  ? Tau_lev[TauType::tau12]->array(mfi) : Array4<Real>{};
950  auto t21_arr = Tau_lev[TauType::tau21]
951  ? Tau_lev[TauType::tau21]->array(mfi) : Array4<Real>{};
952 
953  auto hfx1_arr = (m_rotate || dir == 0) ? xheat_flux->array(mfi) : Array4<Real>{};
954  auto hfx2_arr = (m_rotate || dir == 1) ? yheat_flux->array(mfi) : Array4<Real>{};
955  auto qfx1_arr = (xqv_flux && (m_rotate || dir == 0)) ? xqv_flux->array(mfi) : Array4<Real>{};
956  auto qfx2_arr = (yqv_flux && (m_rotate || dir == 1)) ? yqv_flux->array(mfi) : Array4<Real>{};
957 
958  // Terrain
959  const auto zphys_arr = (z_phys) ? z_phys->const_array(mfi) : Array4<const Real>{};
960 
961  // Get average arrays
962  const auto *const u_mean = m_ma.get_average(lev,0);
963  const auto *const v_mean = m_ma.get_average(lev,1);
964  const auto *const w_mean = m_ma.get_average(lev,2);
965  const auto *const t_mean = m_ma.get_average(lev,3);
966  const auto *const q_mean = m_ma.get_average(lev,4);
967  const auto *const u_mag_mean = m_ma.get_average(lev,6);
968  const auto *const uw_mag_mean = m_ma.get_average(lev,7);
969  const auto *const vw_mag_mean = m_ma.get_average(lev,8);
970  const auto *const zref_ptr = m_ma.get_zref(lev);
971 
972  const auto um_arr = u_mean->array(mfi);
973  const auto vm_arr = v_mean->array(mfi);
974  const auto wm_arr = w_mean->array(mfi);
975  const auto tm_arr = t_mean->array(mfi);
976  const auto qm_arr = q_mean->array(mfi);
977  const auto umm_arr = u_mag_mean->array(mfi);
978  const auto vwmm_arr = (dir == 0) ? vw_mag_mean->array(mfi) : Array4<Real>{};
979  const auto uwmm_arr = (dir == 1) ? uw_mag_mean->array(mfi) : Array4<Real>{};
980 
981  // umm depending on face direction (YZ, XZ, XY)
982  const auto dir_umm_arr = ((dir == 0) ? vwmm_arr : ((dir == 1) ? uwmm_arr : umm_arr));
983 
984  const auto zref_arr = zref_ptr->array(mfi);
985  const auto z0_arr = z_0[lev].array(mfi);
986 
987  // Get derived arrays
988  const auto u_star_arr = u_star[lev]->array(mfi);
989  const auto t_star_arr = t_star[lev]->array(mfi);
990  const auto q_star_arr = q_star[lev]->array(mfi);
991  const auto t_surf_arr = t_surf[lev]->array(mfi);
992  const auto q_surf_arr = q_surf[lev]->array(mfi);
993  auto surface_source_arr = surface_diagnostic_source[lev]->array(mfi);
994 
995  // Get LSM fluxes
996  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
997  Array4<int> {};
998  auto lsm_t_flux_arr = Array4<Real> {};
999  auto soil_t_flux_arr = Array4<Real> {};
1000  auto lsm_q_flux_arr = Array4<Real> {};
1001  auto lsm_tau13_arr = Array4<Real> {};
1002  auto lsm_tau23_arr = Array4<Real> {};
1003  // LSM tau fields are cell-centered kinematic stresses [m2 s-2].
1004  // Tau_lev tau13/tau23 are face-centered conservative stresses [N m-2].
1005  for (int n(0); n<m_lsm_flux_lev[lev].size(); ++n) {
1006  if (toLower(m_lsm_flux_name[n]) == "t_flux") { lsm_t_flux_arr = m_lsm_flux_lev[lev][n]->array(mfi); }
1007  if (toLower(m_lsm_flux_name[n]) == "soil_t_flux") { soil_t_flux_arr = m_lsm_flux_lev[lev][n]->array(mfi); }
1008  if (toLower(m_lsm_flux_name[n]) == "q_flux") { lsm_q_flux_arr = m_lsm_flux_lev[lev][n]->array(mfi); }
1009  if (toLower(m_lsm_flux_name[n]) == "tau13") { lsm_tau13_arr = m_lsm_flux_lev[lev][n]->array(mfi); }
1010  if (toLower(m_lsm_flux_name[n]) == "tau23") { lsm_tau23_arr = m_lsm_flux_lev[lev][n]->array(mfi); }
1011  }
1012 
1013  const bool has_lsm_t_flux = static_cast<bool>(lsm_t_flux_arr);
1014  const bool is_custom = (flux_type == FluxCalcType::CUSTOM);
1015  const bool is_rico = (flux_type == FluxCalcType::RICO);
1016 
1017 
1018  // Rho*Theta flux
1019  //============================================================================
1020  const bool is_low_face = m_face.isLow();
1021 
1022  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1023  {
1024  // Valid theta flux from LSM and over land. The LSM writes the
1025  // lsm_undefined sentinel for cells it did not process (sea-ice /
1026  // open water); fall back to MOST there instead of applying garbage.
1027  Real Tflux;
1028  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
1029  const bool lsm_flux_is_valid = (lsm_t_flux_arr) ? (lsm_t_flux_arr(i,j,0) < lsm_undefined) :
1030  false;
1031  const bool has_land_and_flux = (is_land == 1 && lsm_flux_is_valid);
1032  if (lsm_t_flux_arr && has_land_and_flux) {
1033  // LSM flux MultiFabs store kinematic fluxes for MOST parameter
1034  // updates. The applied hfx array stores the conservative RHS flux.
1035  Tflux = cons_arr(i,j,k,Rho_comp) * lsm_t_flux_arr(i,j,0);
1036  } else if (is_land == 2) { // no temperature flux within buildings
1037  Tflux = zero;
1038  } else {
1039  Tflux = flux_comp.compute_t_flux(i, j, k, dir,
1040  cons_arr, velx_arr, vely_arr, velz_arr,
1041  dir_umm_arr, tm_arr, u_star_arr,
1042  t_star_arr, t_surf_arr);
1043  // NOTE: do NOT write the MOST-fallback flux back into lsm_t_flux_arr.
1044  // Doing so flips a sentinel (water/unprocessed) cell to "valid LSM"
1045  // on the next step, so a MOST-derived value is re-read as an LSM flux
1046  // Only Noah-MP should populate the LSM cache.
1047  }
1048 
1049  if (soil_t_flux_arr && is_land == 1) {
1050  soil_t_flux_arr(i,j,k) = Tflux / cons_arr(i,j,k,Rho_comp);
1051  }
1052 
1053  surface_source_arr(i,j,k) = surface_diagnostics::to_plot_value(
1055  is_custom, is_rico, is_land, has_lsm_t_flux, lsm_flux_is_valid));
1056 
1057  // Do scalar flux rotations?
1058  if (rotate) {
1059  rotate_scalar_flux(i, j, k, dir, Tflux, dxInv, zphys_arr,
1060  hfx1_arr, hfx2_arr, hfx3_arr);
1061  } else {
1062  // swap sign for upward faces
1063  if (!is_low_face && dir != 2) {
1064  Tflux = -Tflux;
1065  }
1066 
1067  // write out to corresponding face
1068  if (dir == 0) {
1069  if (!is_low_face) {
1070  hfx1_arr(i+1,j,k) = Tflux;
1071  } else {
1072  hfx1_arr(i,j,k) = Tflux;
1073  }
1074  } else if (dir == 1) {
1075  if (!is_low_face) {
1076  hfx2_arr(i,j+1,k) = Tflux;
1077  } else {
1078  hfx2_arr(i,j,k) = Tflux;
1079  }
1080  } else {
1081  if (!is_low_face) {
1082  hfx3_arr(i,j,k+1) = Tflux;
1083  } else {
1084  hfx3_arr(i,j,k) = Tflux;
1085  }
1086  }
1087  }
1088  });
1089 
1090  // Rho*Qv flux
1091  //============================================================================
1092  if (use_moisture) {
1093  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1094  {
1095  // Valid qv flux from LSM and over land (sentinel -> fall back to MOST)
1096  Real Qflux;
1097  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
1098  const bool lsm_flux_is_valid = (lsm_q_flux_arr) ? (lsm_q_flux_arr(i,j,0) < lsm_undefined) :
1099  false;
1100  const bool has_land_and_flux = (is_land == 1 && lsm_flux_is_valid);
1101  if (lsm_q_flux_arr && has_land_and_flux) {
1102  // LSM flux MultiFabs store kinematic fluxes for MOST parameter
1103  // updates. The applied qfx array stores the conservative RHS flux.
1104  Qflux = cons_arr(i,j,k,Rho_comp) * lsm_q_flux_arr(i,j,0);
1105  } else if (is_land == 2) { // no moisture flux within buildings
1106  Qflux = zero;
1107  } else {
1108  Qflux = flux_comp.compute_q_flux(i, j, k, dir,
1109  cons_arr, velx_arr, vely_arr, velz_arr,
1110  dir_umm_arr, qm_arr, u_star_arr,
1111  q_star_arr, q_surf_arr);
1112  // NOTE: no writeback into lsm_q_flux_arr -- see the matching
1113  // t_flux note above.
1114  }
1115 
1116  // Do scalar flux rotations?
1117  if (rotate) {
1118  rotate_scalar_flux(i, j, k, dir, Qflux, dxInv, zphys_arr,
1119  qfx1_arr, qfx2_arr, qfx3_arr);
1120  } else {
1121  // swap sign for upward faces
1122  if (!is_low_face && dir != 2) {
1123  Qflux = -Qflux;
1124  }
1125 
1126  // write out to corresponding face
1127  if (dir == 0) {
1128  if (!is_low_face) {
1129  qfx1_arr(i+1,j,k) = Qflux;
1130  } else {
1131  qfx1_arr(i,j,k) = Qflux;
1132  }
1133  } else if (dir == 1) {
1134  if (!is_low_face) {
1135  qfx2_arr(i,j+1,k) = Qflux;
1136  } else {
1137  qfx2_arr(i,j,k) = Qflux;
1138  }
1139  } else {
1140  if (!is_low_face) {
1141  qfx3_arr(i,j,k+1) = Qflux;
1142  } else {
1143  qfx3_arr(i,j,k) = Qflux;
1144  }
1145  }
1146  }
1147  });
1148  } // custom
1149 
1150  if (!rotate) {
1151  // Rho*u flux
1152  //============================================================================
1153  const IntVect stressx_nodal = (dir == 2) ? IntVect(1,0,1) : IntVect(1,1,0);
1154  Box bxx = convert(bx, stressx_nodal);
1155  const int stressx_face_index = is_low_face
1156  ? m_geom[lev].Domain().smallEnd(dir)
1157  : m_geom[lev].Domain().bigEnd(dir) + 1;
1158  bxx.setRange(dir, stressx_face_index);
1159  ParallelFor(bxx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1160  {
1161  // Valid tau13 from LSM and over land. A side that is land but
1162  // whose LSM flux is the sentinel (sea-ice / open water) is treated
1163  // as non-LSM so that side uses the MOST stress instead.
1164  Real stressx;
1165  int is_land_hi = (lmask_arr) ? lmask_arr(i ,j,0) : 1;
1166  int is_land_lo = (lmask_arr) ? lmask_arr(i-1,j,0) : 1;
1167  const bool lsm_hi_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1168  static_cast<bool>(lsm_tau13_arr), is_land_hi == 1,
1169  lsm_tau13_arr ? lsm_tau13_arr(i ,j,0) : zero, lsm_undefined);
1170  const bool lsm_lo_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1171  static_cast<bool>(lsm_tau13_arr), is_land_lo == 1,
1172  lsm_tau13_arr ? lsm_tau13_arr(i-1,j,0) : zero, lsm_undefined);
1173  const bool has_land_and_flux_hi = (is_land_hi == 1 && lsm_hi_flux_is_valid);
1174  const bool has_land_and_flux_lo = (is_land_lo == 1 && lsm_lo_flux_is_valid);
1175  if (lsm_tau13_arr && (has_land_and_flux_hi || has_land_and_flux_lo)) {
1176  const Real rho_hi = cons_arr(i ,j,k,Rho_comp);
1177  const Real rho_lo = cons_arr(i-1,j,k,Rho_comp);
1178  const Real most_stress = (!has_land_and_flux_hi || !has_land_and_flux_lo) ?
1179  flux_comp.compute_u_flux(i, j, k, dir,
1180  cons_arr, velx_arr, vely_arr, velz_arr,
1181  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr) : zero;
1183  rho_lo, rho_hi, lsm_tau13_arr(i-1,j,0), lsm_tau13_arr(i,j,0),
1184  has_land_and_flux_lo, has_land_and_flux_hi, most_stress);
1185  stressx = result.face_stress;
1186  // NOTE: do NOT write the MOST-fallback stress back into the
1187  // cell-centered lsm_tau13_arr. This face-indexed ParallelFor
1188  // touches cells (i) and (i-1), so each cell is written by two
1189  // adjacent face threads in the same launch -> nondeterministic
1190  // write-write race on GPU (ERF #3446). It also spuriously flips
1191  // a sentinel (water/unprocessed) cell to "valid LSM" for the
1192  // next step. The face stress is fully determined here; the LSM
1193  // cache is (re)filled only by Noah-MP. Matches baseline 3ab899d3.
1194  } else if (is_land_hi == 2 || is_land_lo == 2) { // no stress within buildings
1195  stressx = zero;
1196  } else {
1197  stressx = flux_comp.compute_u_flux(i, j, k, dir,
1198  cons_arr, velx_arr, vely_arr, velz_arr,
1199  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr);
1200  }
1201 
1202  // write out to corresponding face
1203  if (dir == 0) {
1204  t21_arr(i,j,k) = stressx;
1205  if (t12_arr &&
1206  (!ylo_surface || j != ylo_node) &&
1207  (!yhi_surface || j != yhi_node)) {
1208  t12_arr(i,j,k) = stressx;
1209  }
1210  } else if (dir == 1) {
1211  t12_arr(i,j,k) = stressx;
1212  if (t21_arr &&
1213  (!xlo_surface || i != xlo_node) &&
1214  (!xhi_surface || i != xhi_node)) {
1215  t21_arr(i,j,k) = stressx;
1216  }
1217  } else {
1218  t13_arr(i,j,k) = stressx;
1219  if (t31_arr &&
1220  (!xlo_surface || i != xlo_node) &&
1221  (!xhi_surface || i != xhi_node)) {
1222  t31_arr(i,j,k) = stressx;
1223  }
1224  }
1225  });
1226 
1227  // Rho*v flux
1228  //============================================================================
1229  const IntVect stressy_nodal = (dir == 0) ? IntVect(1,0,1) : IntVect(0,1,1);
1230  Box bxy = convert(bx, stressy_nodal);
1231  const int stressy_face_index = is_low_face
1232  ? m_geom[lev].Domain().smallEnd(dir)
1233  : m_geom[lev].Domain().bigEnd(dir) + 1;
1234  bxy.setRange(dir, stressy_face_index);
1235  ParallelFor(bxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1236  {
1237  // Valid tau23 from LSM and over land (sentinel side -> MOST stress)
1238  Real stressy;
1239  int is_land_hi = (lmask_arr) ? lmask_arr(i,j ,0) : 1;
1240  int is_land_lo = (lmask_arr) ? lmask_arr(i,j-1,0) : 1;
1241  const bool lsm_hi_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1242  static_cast<bool>(lsm_tau23_arr), is_land_hi == 1,
1243  lsm_tau23_arr ? lsm_tau23_arr(i,j ,0) : zero, lsm_undefined);
1244  const bool lsm_lo_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1245  static_cast<bool>(lsm_tau23_arr), is_land_lo == 1,
1246  lsm_tau23_arr ? lsm_tau23_arr(i,j-1,0) : zero, lsm_undefined);
1247  const bool has_land_and_flux_hi = (is_land_hi == 1 && lsm_hi_flux_is_valid);
1248  const bool has_land_and_flux_lo = (is_land_lo == 1 && lsm_lo_flux_is_valid);
1249  if (lsm_tau23_arr && (has_land_and_flux_hi || has_land_and_flux_lo)) {
1250  const Real rho_hi = cons_arr(i,j ,k,Rho_comp);
1251  const Real rho_lo = cons_arr(i,j-1,k,Rho_comp);
1252  const Real most_stress = (!has_land_and_flux_hi || !has_land_and_flux_lo) ?
1253  flux_comp.compute_v_flux(i, j, k, dir,
1254  cons_arr, velx_arr, vely_arr, velz_arr,
1255  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr) : zero;
1257  rho_lo, rho_hi, lsm_tau23_arr(i,j-1,0), lsm_tau23_arr(i,j,0),
1258  has_land_and_flux_lo, has_land_and_flux_hi, most_stress);
1259  stressy = result.face_stress;
1260  // NOTE: no writeback into cell-centered lsm_tau23_arr -- see the
1261  // matching tau13 note above (ERF #3446 write-write race + stale
1262  // sentinel-becomes-valid). Face stress is complete here.
1263  } else if (is_land_hi == 2 || is_land_lo == 2) { // no stress within buildings
1264  stressy = zero;
1265  } else {
1266  stressy = flux_comp.compute_v_flux(i, j, k, dir,
1267  cons_arr, velx_arr, vely_arr, velz_arr,
1268  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr);
1269  }
1270 
1271  // write out to corresponding face
1272  if (dir == 0) {
1273  t31_arr(i,j,k) = stressy;
1274  if (t13_arr &&
1275  (!zlo_surface || k != zlo_node) &&
1276  (!zhi_surface || k != zhi_node)) {
1277  t13_arr(i,j,k) = stressy;
1278  }
1279  } else if (dir == 1) {
1280  t32_arr(i,j,k) = stressy;
1281  if (t23_arr &&
1282  (!zlo_surface || k != zlo_node) &&
1283  (!zhi_surface || k != zhi_node)) {
1284  t23_arr(i,j,k) = stressy;
1285  }
1286  } else {
1287  t23_arr(i,j,k) = stressy;
1288  if (t32_arr &&
1289  (!ylo_surface || j != ylo_node) &&
1290  (!yhi_surface || j != yhi_node)) {
1291  t32_arr(i,j,k) = stressy;
1292  }
1293  }
1294  });
1295  } else {
1296  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(dir == 2 && m_face.isLow(), "Stress rotation only supported for zlo face");
1297  // All fluxes with rotation
1298  //============================================================================
1299  Box bxxy = convert(bx, IntVect(1,1,0));
1300  ParallelFor(bxxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1301  {
1302  Real stresst = flux_comp.compute_u_flux(i, j, k, dir,
1303  cons_arr, velx_arr, vely_arr, velz_arr,
1304  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr);
1305  rotate_stress_tensor(i, j, k, dir, stresst, dxInv, zphys_arr,
1306  velx_arr, vely_arr, velz_arr,
1307  t11_arr, t22_arr, t33_arr,
1308  t12_arr, t21_arr,
1309  t13_arr, t31_arr,
1310  t23_arr, t32_arr);
1311  });
1312  }
1313 
1314  // For models that do not do iterations to yield u*/T*/q*,
1315  // fill these values from the fluxes that were computed.
1316 
1317  // NOTE: For LSM, this has been handled in "compute_sfc_params_from_lsm_fluxes"
1318  // NOTE: Fluxes here are for conserved quantities, we divide by rho
1320  constexpr Real eps = std::numeric_limits<Real>::epsilon();
1321  bool l_use_moisture = use_moisture;
1322  ParallelFor(bx, [=] AMREX_GPU_DEVICE(int i, int j, int /*k*/)
1323  {
1324  Real rho = cons_arr(i,j,klo,Rho_comp);
1325  Real Thd = cons_arr(i,j,klo,RhoTheta_comp) / rho;
1326  Real qv = (l_use_moisture) ? cons_arr(i,j,klo,RhoQ1_comp) / rho : zero;
1327  Real Thv = Thd * (one + epsv*qv);
1328 
1329  Real tau = std::sqrt( t13_arr(i,j,klo)/rho * t13_arr(i,j,klo)/rho
1330  + t23_arr(i,j,klo)/rho * t23_arr(i,j,klo)/rho );
1331  u_star_arr(i,j,0) = amrex::max(std::sqrt(tau),eps);
1332 
1333  if (hfx3_arr(i,j,klo)>=zero) {
1334  t_star_arr(i,j,0) = amrex::min(-hfx3_arr(i,j,klo) / (rho * u_star_arr(i,j,0)),-eps);
1335  } else {
1336  t_star_arr(i,j,0) = amrex::max(-hfx3_arr(i,j,klo) / (rho * u_star_arr(i,j,0)),eps);
1337  }
1338  if (!l_use_moisture) {
1339  q_star_arr(i,j,0) = zero;
1340  } else if (qfx3_arr(i,j,klo)>=zero) {
1341  q_star_arr(i,j,0) = amrex::min(-qfx3_arr(i,j,klo) / (rho * u_star_arr(i,j,0)),-eps);
1342  } else {
1343  q_star_arr(i,j,0) = amrex::max(-qfx3_arr(i,j,klo) / ( rho * u_star_arr(i,j,0)),eps);
1344  }
1345  Real tstv = t_star_arr(i,j,0)*(one + epsv*qv) + epsv*Thd*q_star_arr(i,j,0);
1346  tstv = (tstv >= zero) ? amrex::max(tstv, eps) : amrex::min(tstv, -eps);
1347  olen_arr(i,j,0) = ( u_star_arr(i,j,0) * u_star_arr(i,j,0) * Thv ) /
1348  ( KAPPA * CONST_GRAV * tstv );
1349  z0_arr(i,j,0) = Compute_roughness(zref_arr(i,j,0), olen_arr(i,j,0),
1350  umm_arr(i,j,0), u_star_arr(i,j,0));
1351  });
1352  }
1353 
1354  } // mfiter
1355 
1357 }
@ tau12
Definition: ERF_DataStruct.H:40
@ tau23
Definition: ERF_DataStruct.H:40
@ tau33
Definition: ERF_DataStruct.H:40
@ tau22
Definition: ERF_DataStruct.H:40
@ tau11
Definition: ERF_DataStruct.H:40
@ tau32
Definition: ERF_DataStruct.H:40
@ tau31
Definition: ERF_DataStruct.H:40
@ tau21
Definition: ERF_DataStruct.H:40
@ tau13
Definition: ERF_DataStruct.H:40
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:1046
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:1090
amrex::Vector< std::unique_ptr< amrex::MultiFab > > surface_diagnostic_source
Definition: ERF_SurfaceLayer.H:1554
void fill_planar_boundary(const int &lev, amrex::MultiFab &mf)
Definition: ERF_SurfaceLayer.cpp:344
@ xvel
Definition: ERF_IndexDefines.H:215
@ zvel
Definition: ERF_IndexDefines.H:217
@ yvel
Definition: ERF_IndexDefines.H:216
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
1385 {
1386  const int dir = m_face.coordDir();
1387  // Get EB flags for all centerings
1388  const auto& cc_factory = m_eb_vec[lev]->get_const_factory();
1389  const auto& cc_flags = cc_factory->getMultiEBCellFlagFab();
1390  const auto& cc_vfrac = cc_factory->getVolFrac();
1391 
1392  const auto& u_factory = m_eb_vec[lev]->get_u_const_factory();
1393  const auto& u_flags = u_factory->getMultiEBCellFlagFab();
1394  const auto& u_vfrac = u_factory->getVolFrac();
1395 
1396  const auto& v_factory = m_eb_vec[lev]->get_v_const_factory();
1397  const auto& v_flags = v_factory->getMultiEBCellFlagFab();
1398  const auto& v_vfrac = v_factory->getVolFrac();
1399 
1400  const auto& w_factory = m_eb_vec[lev]->get_w_const_factory();
1401  const auto& w_flags = w_factory->getMultiEBCellFlagFab();
1402  const auto& w_vfrac = w_factory->getVolFrac();
1403 
1404  // EB does not currently have a cell-centered scalar-source classification.
1405  // Keep the provenance mask missing rather than inventing face-aware
1406  // semantics for the staggered stress path.
1407  surface_diagnostic_source[lev]->setVal(
1409 
1410  for (MFIter mfi(*mfs[0]); mfi.isValid(); ++mfi)
1411  {
1412  // Get flags for this box (all centerings)
1413  const auto& cc_flag = cc_flags[mfi];
1414  const auto& u_flag = u_flags[mfi];
1415  const auto& v_flag = v_flags[mfi];
1416  const auto& w_flag = w_flags[mfi];
1417 
1418  // Skip boxes that have no cut cells at any centering
1419  if (cc_flag.getType() != FabType::singlevalued &&
1420  u_flag.getType() != FabType::singlevalued &&
1421  v_flag.getType() != FabType::singlevalued &&
1422  w_flag.getType() != FabType::singlevalued
1423  ) continue;
1424 
1425  // Get EB flag and volfrac arrays
1426  auto const cc_flag_arr = cc_flag.const_array();
1427  auto const u_flag_arr = u_flag.const_array();
1428  auto const v_flag_arr = v_flag.const_array();
1429  auto const w_flag_arr = w_flag.const_array();
1430 
1431  auto const cc_vfrac_arr = cc_vfrac.const_array(mfi);
1432  auto const u_vfrac_arr = u_vfrac.const_array(mfi);
1433  auto const v_vfrac_arr = v_vfrac.const_array(mfi);
1434  auto const w_vfrac_arr = w_vfrac.const_array(mfi);
1435 
1436  // Get boundary normals only if cut cells exist
1437  auto const bnorm_arr = (cc_flag.getType() == FabType::singlevalued) ?
1438  cc_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1439  auto const u_bnorm_arr = (u_flag.getType() == FabType::singlevalued) ?
1440  u_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1441  auto const v_bnorm_arr = (v_flag.getType() == FabType::singlevalued) ?
1442  v_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1443  auto const w_bnorm_arr = (w_flag.getType() == FabType::singlevalued) ?
1444  w_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1445 
1446  // Get field arrays
1447  const auto cons_arr = mfs[Vars::cons]->array(mfi);
1448  const auto velx_arr = mfs[Vars::xvel]->array(mfi);
1449  const auto vely_arr = mfs[Vars::yvel]->array(mfi);
1450  const auto velz_arr = mfs[Vars::zvel]->array(mfi);
1451 
1452  // Diffusive stress vars - t13 and t23 components for all grid types
1453  auto u_t13_arr = Tau_EB[EBTauType::tau_eb13][EBGridType::xface]->array(mfi);
1454  auto v_t13_arr = Tau_EB[EBTauType::tau_eb13][EBGridType::yface]->array(mfi);
1455  auto w_t13_arr = Tau_EB[EBTauType::tau_eb13][EBGridType::zface]->array(mfi);
1456 
1457  auto u_t23_arr = Tau_EB[EBTauType::tau_eb23][EBGridType::xface]->array(mfi);
1458  auto v_t23_arr = Tau_EB[EBTauType::tau_eb23][EBGridType::yface]->array(mfi);
1459  auto w_t23_arr = Tau_EB[EBTauType::tau_eb23][EBGridType::zface]->array(mfi);
1460 
1461  auto hfx3_arr = Hfx3_EB->array(mfi);
1462 
1463  // Get average arrays
1464  const auto *const u_mean = m_ma.get_average(lev,0);
1465  const auto *const v_mean = m_ma.get_average(lev,1);
1466  // const auto *const w_mean = m_ma.get_average(lev,2);
1467 
1468  const auto *const t_mean = m_ma.get_average(lev,3);
1469  // const auto *const q_mean = m_ma.get_average(lev,4);
1470  const auto *const u_mag_mean = m_ma.get_average(lev,6);
1471  const auto *const uw_mag_mean = m_ma.get_average(lev,7);
1472  const auto *const vw_mag_mean = m_ma.get_average(lev,8);
1473 
1474  const auto um_arr = u_mean->array(mfi);
1475  const auto vm_arr = v_mean->array(mfi);
1476  // const auto wm_arr = w_mean->array(mfi);
1477  const auto tm_arr = t_mean->array(mfi);
1478  // const auto qm_arr = q_mean->array(mfi);
1479  const auto umm_arr = u_mag_mean->array(mfi);
1480  const auto vwmm_arr = (dir == 0) ? vw_mag_mean->array(mfi) : Array4<Real>{};
1481  const auto uwmm_arr = (dir == 1) ? uw_mag_mean->array(mfi) : Array4<Real>{};
1482 
1483  // umm depending on face direction (YZ, XZ, XY)
1484  const auto dir_umm_arr = ((dir == 0) ? vwmm_arr : ((dir == 1) ? uwmm_arr : umm_arr));
1485 
1486  // Get derived arrays
1487  const auto u_star_arr = u_star[lev]->array(mfi);
1488  const auto t_star_arr = t_star[lev]->array(mfi);
1489  const auto t_surf_arr = t_surf[lev]->array(mfi);
1490 
1491  // Rho*Theta flux
1492  //============================================================================
1493  Box bx = mfi.tilebox();
1494  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1495  {
1496  if (cc_flag_arr(i,j,k).isSingleValued()) {
1497  Real Tflux = flux_comp.compute_t_flux(i, j, k,
1498  cons_arr, velx_arr, vely_arr, velz_arr,
1499  dir_umm_arr, tm_arr, u_star_arr,
1500  t_star_arr, t_surf_arr,
1501  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1502  bnorm_arr);
1503  hfx3_arr(i,j,k) = Tflux;
1504  }
1505  });
1506 
1507  // Rho*u flux
1508  //============================================================================
1509  Box bxx = surroundingNodes(bx,0);
1510  Box bxy = surroundingNodes(bx,1);
1511  Box bxz = surroundingNodes(bx,2);
1512  ParallelFor(bxx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1513  {
1514  if (u_flag_arr(i,j,k).isSingleValued()) {
1515  Real stressx = flux_comp.compute_u_flux(i, j, k,
1516  cons_arr, velx_arr, vely_arr, velz_arr,
1517  dir_umm_arr, um_arr, u_star_arr,
1518  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1519  cc_vfrac_arr, cc_flag_arr,
1520  u_bnorm_arr, 0);
1521  u_t13_arr(i,j,k) = stressx;
1522  }
1523  });
1524  ParallelFor(bxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1525  {
1526  if (v_flag_arr(i,j,k).isSingleValued()) {
1527  Real stressx = flux_comp.compute_u_flux(i, j, k,
1528  cons_arr, velx_arr, vely_arr, velz_arr,
1529  dir_umm_arr, um_arr, u_star_arr,
1530  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1531  cc_vfrac_arr, cc_flag_arr,
1532  v_bnorm_arr, 1);
1533  v_t13_arr(i,j,k) = stressx;
1534  }
1535  });
1536  ParallelFor(bxz, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1537  {
1538  if (w_flag_arr(i,j,k).isSingleValued()) {
1539  Real stressx = flux_comp.compute_u_flux(i, j, k,
1540  cons_arr, velx_arr, vely_arr, velz_arr,
1541  dir_umm_arr, um_arr, u_star_arr,
1542  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1543  cc_vfrac_arr, cc_flag_arr,
1544  w_bnorm_arr, 2);
1545  w_t13_arr(i,j,k) = stressx;
1546  }
1547  });
1548 
1549  // Rho*v flux
1550  //============================================================================
1551  ParallelFor(bxx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1552  {
1553  if (u_flag_arr(i,j,k).isSingleValued()) {
1554  Real stressy = flux_comp.compute_v_flux(i, j, k,
1555  cons_arr, velx_arr, vely_arr, velz_arr,
1556  dir_umm_arr, vm_arr, u_star_arr,
1557  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1558  cc_vfrac_arr, cc_flag_arr, u_bnorm_arr, 0);
1559  u_t23_arr(i,j,k) = stressy;
1560  }
1561  });
1562  ParallelFor(bxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1563  {
1564  if (v_flag_arr(i,j,k).isSingleValued()) {
1565  Real stressy = flux_comp.compute_v_flux(i, j, k,
1566  cons_arr, velx_arr, vely_arr, velz_arr,
1567  dir_umm_arr, vm_arr, u_star_arr,
1568  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1569  cc_vfrac_arr, cc_flag_arr, v_bnorm_arr, 1);
1570  v_t23_arr(i,j,k) = stressy;
1571  }
1572  });
1573  ParallelFor(bxz, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1574  {
1575  if (w_flag_arr(i,j,k).isSingleValued()) {
1576  Real stressy = flux_comp.compute_v_flux(i, j, k,
1577  cons_arr, velx_arr, vely_arr, velz_arr,
1578  dir_umm_arr, vm_arr, u_star_arr,
1579  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1580  cc_vfrac_arr, cc_flag_arr, w_bnorm_arr, 2);
1581  w_t23_arr(i,j,k) = stressy;
1582  }
1583  });
1584  } // mfiter
1585 
1586 }
@ 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.

1148 { 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.

1142 { 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
2366 {
2367  PBLHColumns& cols = m_pblh_columns[lev];
2368  cols = PBLHColumns{};
2369  cols.ba = ba;
2370  cols.dm = dm;
2371 
2372  const int k_ground = m_geom[lev].Domain().smallEnd(2);
2373  for (int ib = 0; ib < ba.size(); ++ib) {
2374  if (ba[ib].smallEnd(2) != k_ground) { cols.needed = true; }
2375  }
2376  if (!cols.needed) { return; }
2377 
2378  // With EB the surface-layer fields live on the 3D grids and the estimator writes their
2379  // k = 0 plane, which a box that does not start at the ground does not hold
2380  if (m_terrain_type == TerrainType::EB) {
2381  Abort("erf.most.pblh_calc = MYNN25 with EB needs every grid at level " + std::to_string(lev) +
2382  " to start at the bottom of the domain: the PBL height is written into the lowest "
2383  "plane of each grid. Choose grids that are not decomposed in z "
2384  "(amr.max_grid_size_z) and refined regions that reach the ground.");
2385  }
2386 
2387  // The runs of cells in z, each as one box; those that start at the ground are the columns.
2388  // A column goes to the rank that owns its lowest corner cell, which keeps most of the
2389  // copies to and from the columns on the rank.
2390  const BoxArray ba_joined = join_boxes_stacked_in_z(ba);
2391  BoxList bl_col(IndexType::TheCellType());
2392  BoxList bl_col2d(IndexType::TheCellType());
2393  Vector<int> pmap;
2394  for (int ib = 0; ib < ba_joined.size(); ++ib) {
2395  const Box& b = ba_joined[ib];
2396  if (b.smallEnd(2) != k_ground) { continue; }
2397  const auto& owners = ba.intersections(Box(b.smallEnd(), b.smallEnd()));
2398  AMREX_ALWAYS_ASSERT(!owners.empty());
2399  Box b2d(b);
2400  b2d.setRange(2, k_ground);
2401  bl_col.push_back(b);
2402  bl_col2d.push_back(b2d);
2403  pmap.push_back(dm[owners[0].first]);
2404  }
2405  if (bl_col.isEmpty()) { return; }
2406 
2407  cols.ba_col = BoxArray(std::move(bl_col));
2408  cols.ba_col2d = BoxArray(std::move(bl_col2d));
2409  cols.dm_col = DistributionMapping(std::move(pmap));
2410 
2411  // These hold no data. AMReX drops the communication metadata of a BoxArray and
2412  // DistributionMapping pair with the last FabArray built on it, and the MultiFabs that
2413  // compute_pblh builds on the columns are temporaries.
2414  cols.hold_col.define(cols.ba_col, cols.dm_col, 1, 0, MFInfo().SetAlloc(false));
2415  cols.hold_col2d.define(cols.ba_col2d, cols.dm_col, 1, 0, MFInfo().SetAlloc(false));
2416 }
BoxArray join_boxes_stacked_in_z(const BoxArray &ba)
Definition: ERF_TerrainMetrics.cpp:61
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
608 {
609  const int dir = m_face.coordDir();
610  AMREX_ALWAYS_ASSERT(dir < 2);
611 
612  const iMultiFab& surface_mask = *m_lmask_lev[lev][0];
613  const int face_index = m_face.isLow()
614  ? m_geom[lev].Domain().smallEnd(dir)
615  : m_geom[lev].Domain().bigEnd(dir);
616  const auto& mask_ba = surface_mask.boxArray();
617 
618  IntVect period = m_geom[lev].periodicity().intVect();
619  period[dir] = 0; // The selected wall is not a periodic source plane.
620  const Periodicity tangential_periodicity(period);
621 
622  Vector<MultiFab*> fields;
623  if (selected_field) {
624  fields.push_back(selected_field);
625  } else {
626  const Vector<MultiFab*> all_fields{
627  u_star[lev].get(), t_star[lev].get(), q_star[lev].get(), olen[lev].get(),
628  t_surf[lev].get(), q_surf[lev].get(), pblh[lev].get(),
629  surface_diagnostic_source[lev].get()};
630  for (MultiFab* field : all_fields) {
631  if (field) { fields.push_back(field); }
632  }
633  if (m_include_wstar && w_star[lev]) { fields.push_back(w_star[lev].get()); }
634  }
635 
636  for (MultiFab* field : fields) {
637  // Each parameter field owns its own index type and BoxArray. Build
638  // the compact layout from that field rather than reusing u_star's
639  // layout for all parameters.
640  BoxList face_boxes;
641  Vector<int> face_pmap;
642  Vector<int> source_indices;
643  const auto& source_ba = field->boxArray();
644  const auto& source_pmap = field->DistributionMap().ProcessorMap();
645  for (int ibox = 0; ibox < mask_ba.size(); ++ibox) {
646  const Box& mask_box = mask_ba[ibox];
647  const bool owns_face = m_face.isLow()
648  ? mask_box.smallEnd(dir) == face_index
649  : mask_box.bigEnd(dir) == face_index;
650  if (!owns_face) { continue; }
651 
652  face_boxes.push_back(source_ba[ibox]);
653  face_pmap.push_back(source_pmap[ibox]);
654  source_indices.push_back(ibox);
655  }
656  if (face_boxes.isEmpty()) { continue; }
657 
658  BoxArray face_ba(std::move(face_boxes));
659  DistributionMapping face_dm(std::move(face_pmap));
660  MultiFab face_values(face_ba, face_dm, 1, field->nGrowVect());
661 
662  // Selectively gather only face-owned FABs. A direct FillBoundary on
663  // field would also treat interior-grid FABs as valid sources because
664  // all lateral FABs collapse onto the same wall plane.
665  for (MFIter fmfi(face_values, false); fmfi.isValid(); ++fmfi) {
666  const int compact_index = fmfi.index();
667  const int source_index = source_indices[compact_index];
668  const auto src = field->const_array(source_index);
669  const auto dst = face_values.array(fmfi);
670  // Preserve the source FAB's existing ghosts. A nonperiodic
671  // physical ghost has no FillBoundary source, so leaving the
672  // temporary FAB uninitialized would replace a valid local value
673  // with garbage at mixed-face corners.
674  Box source_fab = field->boxArray()[source_index];
675  source_fab.grow(field->nGrowVect());
676  const Box copy_box = fmfi.fabbox() & source_fab;
678  !copy_box.isEmpty(),
679  "Temporary surface-layer source copy has no valid overlap.");
680  ParallelFor(copy_box, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
681  {
682  dst(i,j,k) = src(i,j,k);
683  });
684  }
685  Gpu::streamSynchronize();
686 
687  face_values.FillBoundary(tangential_periodicity);
688 
689  for (MFIter fmfi(face_values, false); fmfi.isValid(); ++fmfi) {
690  const int compact_index = fmfi.index();
691  const int source_index = source_indices[compact_index];
692  const auto src = face_values.const_array(fmfi);
693  const auto dst = field->array(source_index);
694  Box source_fab = field->boxArray()[source_index];
695  source_fab.grow(field->nGrowVect());
696  const Box copy_box = fmfi.fabbox() & source_fab;
698  !copy_box.isEmpty(),
699  "Mapped surface-layer ghost copy has no valid overlap with its source FAB.");
700  ParallelFor(copy_box, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
701  {
702  dst(i,j,k) = src(i,j,k);
703  });
704  }
705  }
706  Gpu::streamSynchronize();
707 }
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
345 {
346  // PlanarBoundary handles both z-low and z-high z-collapsed arrays split
347  // in z. EB fields use ordinary FillBoundary because their 3-D layout has
348  // no duplicate collapsed surface boxes. Lateral planar fields need a
349  // selective exchange so an interior grid cannot provide wall data.
350  if (m_terrain_type == TerrainType::EB) {
351  mf.FillBoundary(m_geom[lev].periodicity());
352  } else if (m_face.coordDir() != 2) {
354  } else {
355  m_planar_bndry[lev].fill(mf, m_geom[lev].periodicity());
356  }
357 }
void fill_lateral_surface_parameter_ghosts(const int &lev, amrex::MultiFab *selected_field=nullptr)
Definition: ERF_SurfaceLayer.cpp:606

◆ 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
1886 {
1887  // NOTE: We have already tested a moisture model exists
1888  const int dir = m_face.coordDir();
1889  int sm_index;
1890  if (m_face.isLow()) {
1891  sm_index = m_geom[lev].Domain().smallEnd(dir);
1892  } else {
1893  sm_index = m_geom[lev].Domain().bigEnd(dir);
1894  }
1895 
1896  // Populate q_surf with qsat over water. The selected face is the only
1897  // authoritative slab; state/terrain ghosts are useful when valid, but a
1898  // bad halo must not turn into a collective conversion failure.
1899  const Real dz = m_geom[lev].CellSize(2);
1900  const int ng_z = amrex::min(cons_in.nGrowVect()[2],
1901  amrex::min(t_surf[lev]->nGrowVect()[2],
1902  q_surf[lev]->nGrowVect()[2]));
1903  const bool moist = use_moisture;
1904  const Real rdOcp = m_rdOcp;
1905  amrex::Gpu::DeviceScalar<int> d_conversion_failed(0);
1906  int* conversion_failed = d_conversion_failed.dataPtr();
1907  // Use the 2-D surface mask as the iterator so ranks participate only when
1908  // their grids coincide with the selected face.
1909  for (MFIter mfi(*m_lmask_lev[lev][0]); mfi.isValid(); ++mfi)
1910  {
1911  Box gtbx = mfi.growntilebox();
1912  Box tbx = mfi.validbox();
1913  const Box tilebx = mfi.tilebox();
1914 
1915  // Since lmask is used in the MFIter, its Z dimension is 0. These are
1916  // temporary geometry boxes, so use the physical domain's Z range for
1917  // indexing the 3-D surface/state arrays.
1918  tbx.setSmall(2, m_geom[lev].Domain().smallEnd(2));
1919  tbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
1920  gtbx.setSmall(2, m_geom[lev].Domain().smallEnd(2));
1921  gtbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
1922 
1923  if (dir == 2) {
1924  gtbx.makeSlab(2, sm_index);
1925  } else {
1926  gtbx.grow(2, ng_z);
1927  gtbx.setSmall(dir, sm_index);
1928  gtbx.setBig(dir, sm_index);
1929  }
1930 
1931  if (dir == 2) {
1932  if (m_terrain_type != TerrainType::EB &&
1933  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
1934  continue;
1935  }
1936  } else {
1937  if (tbx[m_face] != sm_index || tilebx[m_face] != sm_index) {
1938  continue;
1939  }
1940  }
1941 
1942  // The mask iterator can have more ghosts than the surface fields.
1943  // Limit every array accessed by the kernel to its corresponding FAB.
1944  gtbx &= t_surf[lev]->fabbox(mfi.index());
1945  gtbx &= q_surf[lev]->fabbox(mfi.index());
1946  gtbx &= cons_in.fabbox(mfi.index());
1947  if (z_phys_nd) {
1948  // z_phys_nd is nodal; the cell box whose corner nodes all lie in its FAB
1949  // is the nodal box converted to cells (one fewer cell on the high side).
1950  gtbx &= amrex::convert(z_phys_nd->fabbox(mfi.index()), IntVect::TheCellVector());
1951  }
1952  if (gtbx.isEmpty()) { continue; }
1953 
1954  auto t_surf_arr = t_surf[lev]->array(mfi);
1955  auto q_surf_arr = q_surf[lev]->array(mfi);
1956  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
1957  Array4<int> {};
1958  const auto cons_arr = cons_in.const_array(mfi);
1959  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
1960  Array4<const Real> {};
1961  const Box source_box = cons_in.boxArray()[mfi.index()];
1962  const int src_i_lo = source_box.smallEnd(0);
1963  const int src_i_hi = source_box.bigEnd(0);
1964  const int src_j_lo = source_box.smallEnd(1);
1965  const int src_j_hi = source_box.bigEnd(1);
1966  const int src_k_lo = source_box.smallEnd(2);
1967  const int src_k_hi = source_box.bigEnd(2);
1968  const bool z_face = (dir == 2);
1969  const bool low_face = m_face.isLow();
1970 
1971  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
1972  {
1973  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
1974  if (!is_land) {
1975  const bool authoritative = i >= src_i_lo && i <= src_i_hi &&
1976  j >= src_j_lo && j <= src_j_hi &&
1977  k >= src_k_lo && k <= src_k_hi;
1978  const Real rho = cons_arr(i,j,k,Rho_comp);
1979  const Real rho_theta = cons_arr(i,j,k,RhoTheta_comp);
1980  const Real rho_qv = cons_arr(i,j,k,RhoQ1_comp);
1981  if (!amrex::Math::isfinite(rho) || rho <= Real(0.0) ||
1982  !amrex::Math::isfinite(rho_theta) || !amrex::Math::isfinite(rho_qv)) {
1983  if (authoritative) {
1984  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1985  }
1986  return;
1987  }
1988  const Real qv = moist ? rho_qv / rho : Real(0.0);
1989  if (!amrex::Math::isfinite(qv)) {
1990  if (authoritative) {
1991  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1992  }
1993  return;
1994  }
1995  Real delta_z = Real(0.0);
1996  if (z_face) {
1997  if (z_arr) {
1998  const Real z_cc = Compute_Z_AtCellCenter(i,j,k,z_arr);
1999  const Real z_face_local = low_face
2000  ? Compute_Z_AtWFace(i,j,k,z_arr)
2001  : Compute_Z_AtWFace(i,j,k+1,z_arr);
2002  delta_z = z_cc - z_face_local;
2003  } else {
2004  delta_z = low_face ? myhalf*dz : -myhalf*dz;
2005  }
2006  }
2008  rho, rho_theta, qv, delta_z);
2009  Real t_surface = t_surf_arr(i,j,k);
2011  t_surface, pressure, rdOcp, t_surface)) {
2012  erf_qsatw(t_surface, pressure * Real(0.01), q_surf_arr(i,j,k));
2013  } else if (authoritative) {
2014  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2015  }
2016  }
2017  });
2018  }
2019  amrex::Gpu::streamSynchronize();
2020  int conversion_failed_host = d_conversion_failed.dataValue();
2021  amrex::ParallelDescriptor::ReduceIntMax(conversion_failed_host);
2022  if (conversion_failed_host != 0) {
2023  amrex::Abort("SurfaceLayer fill_qsurf_with_qsat: failed to convert the authoritative "
2024  "surface potential temperature to absolute temperature.");
2025  }
2026  fill_planar_boundary(lev, *q_surf[lev]);
2027 }
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:34
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:705
AMREX_GPU_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:729
@ 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
2082 {
2083  // No coupler has handed us anything yet. Whatever fill_tsurf_with_sst_and_tsk
2084  // wrote stands, which is the correct answer for one-way and uncoupled runs.
2085  if (m_coupled_sst_lev.empty() || !m_coupled_sst_lev[lev]) { return; }
2086 
2087  // The loop below iterates t_surf and indexes the coupled arrays with the
2088  // same MFIter, so the layouts must agree. They do for planar terrain, where
2089  // t_surf is grids[lev] flattened with setRange(2,0) -- the same construction
2090  // GetOceanToAtmosSurfaceLayout reports. Under EB terrain t_surf keeps the
2091  // full 3D BoxArray and they would not, so fail loudly rather than read the
2092  // wrong fab.
2094  m_coupled_sst_lev[lev]->boxArray() == t_surf[lev]->boxArray() &&
2095  m_coupled_sst_lev[lev]->DistributionMap() == t_surf[lev]->DistributionMap(),
2096  "Coupled SST layout does not match the surface-layer layout.");
2097 
2098  const int klo = m_geom[lev].Domain().smallEnd(2);
2099  const Real dz = m_geom[lev].CellSize(2);
2100  const bool moist = use_moisture;
2101  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
2102  const Real rdOcp = m_rdOcp;
2103  amrex::Gpu::DeviceScalar<int> d_conversion_failed(0);
2104  int* conversion_failed = d_conversion_failed.dataPtr();
2105 
2106  // Absent coverage information we must assume nothing is covered: silently
2107  // treating the whole field as valid is how an uncovered cell ends up holding
2108  // the remap's zero fill.
2109  const bool has_valid = (m_coupled_sst_valid_lev[lev] != nullptr);
2110 
2111  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
2112  {
2113  Box gtbx = mfi.growntilebox();
2114 
2115  if (gtbx.smallEnd(2) != klo ||
2116  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
2117  continue;
2118  }
2119 
2120  // NOTE: the coupled lane does not carry lateral ghost cells, so clamp
2121  // into the physical source box exactly as the fallback path does.
2122  // FillBoundary in update_fluxes picks up the interior and
2123  // periodic directions.
2124  const Box source_box = cons_in.boxArray()[mfi.index()];
2125  const Box donor_box = m_coupled_sst_lev[lev]->boxArray()[mfi.index()];
2126  const int source_i_lo = source_box.smallEnd(0);
2127  const int source_i_hi = source_box.bigEnd(0);
2128  const int source_j_lo = source_box.smallEnd(1);
2129  const int source_j_hi = source_box.bigEnd(1);
2130  const int donor_i_lo = donor_box.smallEnd(0);
2131  const int donor_i_hi = donor_box.bigEnd(0);
2132  const int donor_j_lo = donor_box.smallEnd(1);
2133  const int donor_j_hi = donor_box.bigEnd(1);
2134  const int donor_k = donor_box.smallEnd(2);
2135  gtbx &= t_surf[lev]->fabbox(mfi.index());
2136  if (gtbx.isEmpty()) { continue; }
2137 
2138  auto t_surf_arr = t_surf[lev]->array(mfi);
2139  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2140  Array4<int> {};
2141  const auto coupled_sst_arr = m_coupled_sst_lev[lev]->const_array(mfi);
2142  auto const& valid_arr = has_valid ? m_coupled_sst_valid_lev[lev]->const_array(mfi)
2143  : Array4<const int>{};
2144  const auto cons_arr = cons_in.const_array(mfi);
2145  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
2146  Array4<const Real> {};
2147 
2148  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2149  {
2150  const int li = amrex::min(amrex::max(i, source_i_lo), source_i_hi);
2151  const int lj = amrex::min(amrex::max(j, source_j_lo), source_j_hi);
2152  const int si = amrex::min(amrex::max(li, donor_i_lo), donor_i_hi);
2153  const int sj = amrex::min(amrex::max(lj, donor_j_lo), donor_j_hi);
2154  int is_land = (lmask_arr) ? lmask_arr(li,lj,0) : 1;
2155  if (is_land) { return; }
2156 
2157  if (!has_valid || valid_arr(si,sj,donor_k) == 0) { return; }
2158 
2159  const Real rho = cons_arr(li,lj,klo,Rho_comp);
2160  const Real rho_theta = cons_arr(li,lj,klo,RhoTheta_comp);
2161  if (!amrex::Math::isfinite(rho) || rho <= Real(0.0) ||
2162  !amrex::Math::isfinite(rho_theta)) {
2163  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2164  return;
2165  }
2166  Real qv = Real(0.0);
2167  if (moist) {
2168  if (!have_rho_qv) {
2169  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2170  return;
2171  }
2172  const Real rho_qv = cons_arr(li,lj,klo,RhoQ1_comp);
2173  if (!amrex::Math::isfinite(rho_qv)) {
2174  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2175  return;
2176  }
2177  qv = rho_qv / rho;
2178  if (!amrex::Math::isfinite(qv)) {
2179  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2180  return;
2181  }
2182  }
2183  const Real delta_z = z_arr
2184  ? Compute_Z_AtCellCenter(li,lj,klo,z_arr) -
2185  Compute_Z_AtWFace(li,lj,klo,z_arr)
2186  : myhalf*dz;
2188  rho, rho_theta, qv, delta_z);
2189  Real theta = t_surf_arr(i,j,k);
2191  coupled_sst_arr(si,sj,donor_k), pressure, rdOcp, theta)) {
2192  t_surf_arr(i,j,k) = theta;
2193  } else {
2194  amrex::Gpu::Atomic::Max(conversion_failed, 1);
2195  }
2196  });
2197  }
2198  amrex::Gpu::streamSynchronize();
2199  int conversion_failed_host = d_conversion_failed.dataValue();
2200  amrex::ParallelDescriptor::ReduceIntMax(conversion_failed_host);
2201  if (conversion_failed_host != 0) {
2202  amrex::Abort("SurfaceLayer fill_tsurf_with_coupled_sst: failed to convert the authoritative "
2203  "coupled sea-surface temperature to potential temperature.");
2204  }
2205 }
amrex::Vector< amrex::MultiFab * > m_coupled_sst_lev
Definition: ERF_SurfaceLayer.H:1566
amrex::Vector< amrex::iMultiFab * > m_coupled_sst_valid_lev
Definition: ERF_SurfaceLayer.H:1567
@ theta
Definition: ERF_SLM.H:19
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
1773 {
1774  update_sfc_time_index(elapsed_time);
1775  const Real sfc_sst = interpolate_sfc_column(elapsed_time, 1);
1776  const int klo = m_geom[lev].Domain().smallEnd(2);
1777  const Real dz = m_geom[lev].CellSize(2);
1778  const bool moist = use_moisture;
1779  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
1780  const Real rdOcp = m_rdOcp;
1781  amrex::Gpu::DeviceScalar<int> d_conversion_failed(0);
1782  int* conversion_failed = d_conversion_failed.dataPtr();
1783 
1784  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
1785  {
1786  Box gtbx = mfi.growntilebox();
1787 
1788  // A z-split planar field has one copy for every stacked 3-D box. Only
1789  // the copy whose source box touches the physical z-low face owns the
1790  // text-SST conversion and may read the 3-D state or terrain.
1791  if (gtbx.smallEnd(2) != klo ||
1792  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
1793  continue;
1794  }
1795 
1796  const Box source_box = cons_in.boxArray()[mfi.index()];
1797  gtbx &= t_surf[lev]->fabbox(mfi.index());
1798  gtbx &= cons_in.fabbox(mfi.index());
1799  if (z_phys_nd) {
1800  gtbx &= amrex::convert(z_phys_nd->fabbox(mfi.index()), IntVect::TheCellVector());
1801  }
1802  if (m_lmask_lev[lev][0]) {
1803  gtbx &= m_lmask_lev[lev][0]->fabbox(mfi.index());
1804  }
1805  if (gtbx.isEmpty()) { continue; }
1806 
1807  auto t_surf_arr = t_surf[lev]->array(mfi);
1808  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
1809  Array4<int> {};
1810  const auto cons_arr = cons_in.const_array(mfi);
1811  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
1812  Array4<const Real> {};
1813  const int i_lo = source_box.smallEnd(0);
1814  const int i_hi = source_box.bigEnd(0);
1815  const int j_lo = source_box.smallEnd(1);
1816  const int j_hi = source_box.bigEnd(1);
1817 
1818  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
1819  {
1820  const int li = amrex::min(amrex::max(i, i_lo), i_hi);
1821  const int lj = amrex::min(amrex::max(j, j_lo), j_hi);
1822  int is_land = (lmask_arr) ? lmask_arr(li,lj,0) : 0;
1823  if (!is_land) {
1824  const Real rho = cons_arr(li,lj,klo,Rho_comp);
1825  const Real rho_theta = cons_arr(li,lj,klo,RhoTheta_comp);
1826  if (!amrex::Math::isfinite(rho) || rho <= Real(0.0) ||
1827  !amrex::Math::isfinite(rho_theta)) {
1828  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1829  return;
1830  }
1831  Real qv = Real(0.0);
1832  if (moist) {
1833  if (!have_rho_qv) {
1834  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1835  return;
1836  }
1837  const Real rho_qv = cons_arr(li,lj,klo,RhoQ1_comp);
1838  if (!amrex::Math::isfinite(rho_qv)) {
1839  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1840  return;
1841  }
1842  qv = rho_qv / rho;
1843  if (!amrex::Math::isfinite(qv)) {
1844  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1845  return;
1846  }
1847  }
1848  const Real delta_z = z_arr
1849  ? Compute_Z_AtCellCenter(li,lj,klo,z_arr) -
1850  Compute_Z_AtWFace(li,lj,klo,z_arr)
1851  : myhalf*dz;
1853  rho, rho_theta, qv, delta_z);
1854  Real theta = t_surf_arr(i,j,k);
1855  if (erf_surface_temperature::temperature_to_theta(sfc_sst, pressure, rdOcp, theta)) {
1856  t_surf_arr(i,j,k) = theta;
1857  } else {
1858  amrex::Gpu::Atomic::Max(conversion_failed, 1);
1859  }
1860  }
1861  });
1862  }
1863 
1864  amrex::Gpu::streamSynchronize();
1865  int conversion_failed_host = d_conversion_failed.dataValue();
1866  amrex::ParallelDescriptor::ReduceIntMax(conversion_failed_host);
1867  if (conversion_failed_host != 0) {
1868  amrex::Abort("SurfaceLayer fill_tsurf_with_sfc_sst: failed to convert the authoritative "
1869  "sea-surface temperature to potential temperature.");
1870  }
1871 
1872  fill_planar_boundary(lev, *t_surf[lev]);
1873 }
amrex::Real interpolate_sfc_column(const amrex::Real &time, int col) const
Definition: ERF_SurfaceLayer.cpp:397
void update_sfc_time_index(const amrex::Real &time)
Definition: ERF_SurfaceLayer.cpp:380
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
1684 {
1685  int n_times_in_sst = static_cast<int>(m_sst_lev[lev].size());
1686 
1687  double dT = m_low_time_interval;
1688 
1689  int n_time_lo, n_time_hi;
1690  Real alpha;
1691 
1692  if (n_times_in_sst > 1) {
1693  n_time_lo = static_cast<int>( elapsed_time_since_start_low / dT);
1694  alpha = static_cast<Real>((elapsed_time_since_start_low - n_time_lo * dT) / dT);
1695 
1696  AMREX_ALWAYS_ASSERT( alpha >= zero && alpha <= one);
1697 
1698  n_time_hi = n_time_lo + 1;
1699 
1700  // Do not over run the last sst file
1701  if (m_start_low_time + elapsed_time_since_start_low >= m_final_low_time) {
1702  n_time_lo = static_cast<int>(m_sst_lev[lev].size())-1;
1703  n_time_hi = n_time_lo;
1704  alpha = zero;
1705  }
1706 
1707  AMREX_ALWAYS_ASSERT( (n_time_lo >= 0) && (n_time_hi < m_sst_lev[lev].size()));
1708  } else {
1709  n_time_lo = 0;
1710  n_time_hi = 0;
1711  alpha = one;
1712  }
1713  AMREX_ALWAYS_ASSERT( alpha >= zero && alpha <= one);
1714 
1715  Real oma = one - alpha;
1716 
1717  // Define a default land surface temperature if we don't read in tsk
1719 
1720  bool use_tsk = (m_tsk_lev[lev][0]);
1721  bool ignore_sst = m_ignore_sst;
1722 
1723  const int klo = m_geom[lev].Domain().smallEnd(2);
1724 
1725  // Populate t_surf
1726  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
1727  {
1728  Box gtbx = mfi.growntilebox();
1729 
1730  if (gtbx.smallEnd(2) != klo) { continue; }
1731 
1732  auto t_surf_arr = t_surf[lev]->array(mfi);
1733 
1734  const auto sst_lo_arr = m_sst_lev[lev][n_time_lo]->const_array(mfi);
1735  const auto sst_hi_arr = m_sst_lev[lev][n_time_hi]->const_array(mfi);
1736 
1737  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
1738  Array4<int> {};
1739 
1740  if (use_tsk) {
1741  const auto tsk_arr = m_tsk_lev[lev][n_time_lo]->const_array(mfi);
1742  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
1743  {
1744  int is_land = (lmask_arr) ? lmask_arr(i,j,k) : 1;
1745  if (!is_land && !ignore_sst) {
1746  t_surf_arr(i,j,k) = oma * sst_lo_arr(i,j,k)
1747  + alpha * sst_hi_arr(i,j,k);
1748  } else {
1749  t_surf_arr(i,j,k) = tsk_arr(i,j,k);
1750  }
1751  });
1752  } else {
1753  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
1754  {
1755  int is_land = (lmask_arr) ? lmask_arr(i,j,k) : 1;
1756  if (!is_land) {
1757  t_surf_arr(i,j,k) = oma * sst_lo_arr(i,j,k)
1758  + alpha * sst_hi_arr(i,j,k);
1759  } else {
1760  t_surf_arr(i,j,k) = lst;
1761  }
1762  });
1763  }
1764  }
1765  fill_planar_boundary(lev, *t_surf[lev]);
1766 }
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_sst_lev
Definition: ERF_SurfaceLayer.H:1556
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_tsk_lev
Definition: ERF_SurfaceLayer.H:1557
real(kind=kind_phys), parameter, private alpha
Definition: ERF_module_mp_wdm6.F90:62
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
1299 { 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
2036 {
2037  const int klo = m_geom[lev].Domain().smallEnd(2);
2038  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
2039  {
2040  Box gtbx = mfi.growntilebox();
2041 
2042  if (gtbx.smallEnd(2) != klo) { continue; }
2043 
2044  // NOTE: LSM does not carry lateral ghost cells.
2045  // This copies the valid box into the ghost cells.
2046  // Fillboundary is called after this to pick up the
2047  // interior ghost and periodic directions.
2048  Box vbx = mfi.validbox();
2049  int i_lo = vbx.smallEnd(0); int i_hi = vbx.bigEnd(0);
2050  int j_lo = vbx.smallEnd(1); int j_hi = vbx.bigEnd(1);
2051 
2052  auto t_surf_arr = t_surf[lev]->array(mfi);
2053  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2054  Array4<int> {};
2055  const auto lsm_arr = m_lsm_data_lev[lev][m_lsm_tsurf_indx]->const_array(mfi);
2056 
2057  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2058  {
2059  int is_land = (lmask_arr) ? lmask_arr(i,j,k) : 1;
2060  if (is_land) {
2061  int li = amrex::min(amrex::max(i, i_lo), i_hi);
2062  int lj = amrex::min(amrex::max(j, j_lo), j_hi);
2063  const Real lsm_value = lsm_arr(li,lj,k);
2064  if (amrex::Math::isfinite(lsm_value) && lsm_value > Real(0.0) &&
2065  lsm_value < lsm_undefined) {
2066  t_surf_arr(i,j,k) = lsm_value;
2067  }
2068  }
2069  });
2070  }
2071 }
int m_lsm_tsurf_indx
Definition: ERF_SurfaceLayer.H:1492
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_lsm_data_lev
Definition: ERF_SurfaceLayer.H:1568
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
1185  {
1186  return m_ma.get_average(lev, comp);
1187  }
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
1219 { 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
1227 { 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.

1197 { 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
1169 { 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
1176 { 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
1162 { 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
1258 { 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
1273 { 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
1155 { 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
1243 { 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
1129 { 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
1136 { 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
1287 { return &z_0[lev]; }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_zref()

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

Return the minimum reference height for one level.

Parameters
[in]levlevel index
1280 { 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.

1292 { return m_var_z0; }
bool m_var_z0
Definition: ERF_SurfaceLayer.H:1484

◆ 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
735 {
737  amrex::Real wsmin = 0.1; // TODO: change for different faces
738  const Box& domain = m_geom[lev].Domain();
739  moeng_flux flux_comp(wsmin, m_face.isLow(),
740  domain.smallEnd(2), domain.bigEnd(2));
741  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
742  xheat_flux, yheat_flux, zheat_flux,
743  xqv_flux, yqv_flux, zqv_flux,
744  z_phys, flux_comp);
745  } else if (flux_type == FluxCalcType::ROTATE) {
746  rotate_flux flux_comp;
747  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
748  xheat_flux, yheat_flux, zheat_flux,
749  xqv_flux, yqv_flux, zqv_flux,
750  z_phys, flux_comp);
751  } else if (flux_type == FluxCalcType::RICO) {
753  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
754  xheat_flux, yheat_flux, zheat_flux,
755  xqv_flux, yqv_flux, zqv_flux,
756  z_phys, flux_comp);
757  } else if (flux_type == FluxCalcType::BULK_COEFF) {
758  bulk_coeff_flux flux_comp(m_Cd, m_Ch, m_Cq);
759  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
760  xheat_flux, yheat_flux, zheat_flux,
761  xqv_flux, yqv_flux, zqv_flux,
762  z_phys, flux_comp);
763  } else if (flux_type == FluxCalcType::CUSTOM) {
764  const bool fluxes_include_rho = specified_rho_surf || m_use_sfc_fluxes;
765  custom_flux flux_comp(fluxes_include_rho);
766  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
767  xheat_flux, yheat_flux, zheat_flux,
768  xqv_flux, yqv_flux, zqv_flux,
769  z_phys, flux_comp);
770  } else {
771  amrex::Abort("Unknown surface layer flux calculation type");
772  }
773 }
bool specified_rho_surf
Definition: ERF_SurfaceLayer.H:1479
bool m_use_sfc_fluxes
Definition: ERF_SurfaceLayer.H:1495
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
798 {
800  moeng_flux_eb flux_comp;
801  compute_SurfaceLayer_bcs_EB(lev, mfs, Tau_EB,
802  xheat_flux, yheat_flux, Hfx3_EB,
803  xqv_flux, yqv_flux, zqv_flux,
804  flux_comp);
805  } else {
806  amrex::Abort("Not implemented surface layer flux calculation type for EB");
807  }
808 }
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
2433 {
2435  static_cast<int>(m_face) == Orientation::zlo(),
2436  "TKE initialization from surface-layer ustar is supported only on the z-low face.");
2437 
2438  Print() << "Initializing TKE from surface layer ustar on level " << lev << std::endl;
2439 
2440  // Handle vertical decomposition by selectively copying into
2441  // a FArrayBox section on each rank. Then doing a reduce real sum
2442  // and broadcasting to each rank. No mask since all CC data
2443  //
2444  // Pinned so the reduction below can read them on the host; the device
2445  // still reaches pinned memory, so the loops here are unaffected.
2446  const int klo = m_geom[lev].Domain().smallEnd(2);
2447  Box bx_lo = u_star[lev]->boxArray().minimalBox();
2448  FArrayBox u_star_lo(bx_lo, 1, The_Pinned_Arena()); u_star_lo.setVal<RunOn::Host>(0);
2449  FArrayBox z_surf_lo(bx_lo, 1, The_Pinned_Arena()); z_surf_lo.setVal<RunOn::Host>(0);
2450  Real* ustar_ptr = u_star_lo.dataPtr();
2451  Real* zsurf_ptr = z_surf_lo.dataPtr();
2452  for (MFIter mfi(cons); mfi.isValid(); ++mfi)
2453  {
2454  Box vbx = mfi.validbox();
2455  if (vbx.smallEnd(2) != klo) { continue; }
2456  vbx.makeSlab(2,0);
2457 
2458  auto const& u_star_arr = u_star[lev]->const_array(mfi);
2459  auto u_star_all = u_star_lo.array();
2460 
2461  auto const& z_phys_arr = z_phys_nd->const_array(mfi);
2462  auto z_surf_all = z_surf_lo.array();
2463 
2464  ParallelFor(vbx, [=] AMREX_GPU_DEVICE(int i, int j, int ) noexcept
2465  {
2466  u_star_all(i,j,0) = u_star_arr(i,j,0);
2467  z_surf_all(i,j,0) = fourth * ( z_phys_arr(i ,j ,klo) + z_phys_arr(i+1,j ,klo)
2468  + z_phys_arr(i ,j+1,klo) + z_phys_arr(i+1,j+1,klo) );
2469  });
2470  }
2471  Gpu::streamSynchronize(); // the fills above are async, the reduction is not
2472  ParallelDescriptor::ReduceRealSum(ustar_ptr, static_cast<int>(bx_lo.numPts()));
2473  ParallelDescriptor::ReduceRealSum(zsurf_ptr, static_cast<int>(bx_lo.numPts()));
2474 
2475  // Now work on all boxes (ustar has been filled above)
2476  constexpr Real small = Real(0.01);
2477  for (MFIter mfi(cons); mfi.isValid(); ++mfi)
2478  {
2479  Box vbx = mfi.validbox();
2480 
2481  auto const& u_star_arr = u_star_lo.const_array();
2482  auto const& z_surf_arr = z_surf_lo.const_array();
2483  auto const& z_phys_arr = z_phys_nd->const_array(mfi);
2484 
2485  auto const& cons_arr = cons.array(mfi);
2486 
2487  ParallelFor(vbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2488  {
2489  Real rho = cons_arr(i, j, k, Rho_comp);
2490  Real ust = u_star_arr(i, j, 0);
2491  Real tke0 = tkefac * ust * ust; // surface value
2492  Real zagl = Compute_Z_AtCellCenter(i, j, k, z_phys_arr) - z_surf_arr(i,j,0);
2493 
2494  // linearly tapering profile -- following WRF, approximate top of
2495  // PBL as ustar * zscale
2496  cons_arr(i, j, k, RhoKE_comp) = rho * tke0 * std::max(
2497  (ust * zscale - zagl) / (std::max(ust, small) * zscale),
2498  small);
2499  });
2500  }
2501 }
constexpr amrex::Real fourth
Definition: ERF_NumericalConstants.H:35
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
399 {
400  if (sfc.empty() || sfc[0].empty()) { return zero; }
401  if (sfc[0].size() == 1) { return sfc[col][0]; }
402 
403  const Real t0 = sfc[0][sfc_time_ind];
404  const Real t1 = sfc[0][sfc_time_ind+1];
405  const Real x0 = sfc[col][sfc_time_ind];
406  const Real x1 = sfc[col][sfc_time_ind+1];
407 
408  if (elapsed_time < t0) {
409  return x0;
410  }
411 
412  if (t0 == t1 || elapsed_time > t1) {
413  return x1;
414  }
415 
416  const Real dt = (elapsed_time - t0) / (t1 - t0);
417  return x0 + (x1 - x0) * dt;
418 }
amrex::Vector< amrex::Vector< amrex::Real > > sfc
Definition: ERF_SurfaceLayer.H:1498
int sfc_time_ind
Definition: ERF_SurfaceLayer.H:1497
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
1309  {
1310  int lmask_min = amrex::ReduceMin(lmask, nghost, [=] AMREX_GPU_HOST_DEVICE(
1311  amrex::Box const& bx, amrex::Array4<int const> const& lm_arr) -> int
1312  {
1313  int locmin = std::numeric_limits<int>::max();
1314  const auto lo = lbound(bx);
1315  const auto hi = ubound(bx);
1316  for (int j = lo.y; j <= hi.y; ++j) {
1317  for (int i = lo.x; i <= hi.x; ++i) {
1318  locmin = std::min(locmin, lm_arr(i, j, 0));
1319  }
1320  }
1321  return locmin;
1322  });
1323 
1324  return lmask_min;
1325  }

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
1204 { 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.

1192 { 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
417  {
418  // Update MOST Average
420  Theta_prim, Qv_prim, Qr_prim,
421  z_phys_nd);
422 
423  // Get CC vars
424  amrex::MultiFab& mf = *(mfv[0]);
425 
426  amrex::ParmParse pp(m_pp_prefix);
427 
428  // Do we have a time-varying surface roughness that needs to be saved?
429  if (lev == 0) {
430  const int nghost = 0; // ghost cells not included
431  int lmask_min = lmask_min_reduce(*lmask_lev[0].get(), nghost);
432  amrex::ParallelDescriptor::ReduceIntMin(lmask_min);
433 
434  m_var_z0 = (lmask_min < 1) & (rough_type_sea != RoughCalcType::CONSTANT);
435  if (m_var_z0) {
437  m_face.coordDir() == 2 && m_face.isLow(),
438  "Variable sea roughness surface-layer fluxes are supported only on the z-low face.");
439 
440  std::string rough_sea_string{"charnock"};
441  pp.queryAdd("most.roughness_type_sea", rough_sea_string);
442  amrex::Print() << "Variable sea roughness (type " << rough_sea_string
443  << ")" << std::endl;
444  }
445  }
446 
447  if (m_eddyDiffs_lev.size() < lev+1) {
448  m_Hwave_lev.resize(nlevs);
449  m_Lwave_lev.resize(nlevs);
450  m_eddyDiffs_lev.resize(nlevs);
451 
452  m_lsm_data_lev.resize(nlevs);
453  m_lsm_flux_lev.resize(nlevs);
454 
455  m_sst_lev.resize(nlevs);
456  m_tsk_lev.resize(nlevs);
457  m_lmask_lev.resize(nlevs);
458 
459  m_coupled_sst_lev.resize(nlevs, nullptr);
460  m_coupled_sst_valid_lev.resize(nlevs, nullptr);
461 
462  // Size the MOST params for all levels
463  z_0.resize(nlevs);
464  u_star.resize(nlevs);
465  w_star.resize(nlevs);
466  t_star.resize(nlevs);
467  q_star.resize(nlevs);
468  t_surf.resize(nlevs);
469  q_surf.resize(nlevs);
470  surface_diagnostic_source.resize(nlevs);
471  olen.resize(nlevs);
472  pblh.resize(nlevs);
473  m_planar_bndry.resize(nlevs);
474  }
475 
476  // Get pointers to SST,TSK and LANDMASK data
477  int nt_tot_sst = sst_lev.size();
478  m_sst_lev[lev].resize(nt_tot_sst);
479  for (int nt(0); nt < nt_tot_sst; ++nt) {
480  m_sst_lev[lev][nt] = sst_lev[nt].get();
481  }
482  int nt_tot_tsk = static_cast<int>(tsk_lev.size());
483  m_tsk_lev[lev].resize(nt_tot_tsk);
484  for (int nt(0); nt < nt_tot_tsk; ++nt) {
485  m_tsk_lev[lev][nt] = tsk_lev[nt].get();
486  }
487  int nt_tot_lmask = static_cast<int>(lmask_lev.size());
488  m_lmask_lev[lev].resize(nt_tot_lmask);
489  for (int nt(0); nt < nt_tot_lmask; ++nt) {
490  m_lmask_lev[lev][nt] = lmask_lev[nt].get();
491  }
492 
493  // Get pointers to wave data
494  m_Hwave_lev[lev] = Hwave;
495  m_Lwave_lev[lev] = Lwave;
496  m_eddyDiffs_lev[lev] = eddyDiffs;
497 
498  // Text-file driven surface forcing modes. The file always contains at
499  // least time(day) and absolute sst(K); the value is normalized to the
500  // canonical SurfaceLayer theta field at the z-low surface.
501  pp.queryAdd("most.use_sfc_fluxes", m_use_sfc_fluxes);
502  pp.queryAdd("most.use_sfc_sst", m_use_sfc_sst);
504  amrex::Abort("Only one of most.use_sfc_fluxes and most.use_sfc_sst may be enabled");
505  }
507  if (m_terrain_type == TerrainType::EB) {
508  amrex::Abort("Text-file surface forcing is not supported with EB terrain");
509  }
511  m_face.coordDir() == 2 && m_face.isLow(),
512  "most.use_sfc_fluxes and most.use_sfc_sst are supported only on the z-low face.");
513 
514  // load sensible and latent heat fluxes from sfc to prescribe
515  std::string sfc_file = "";
516  pp.queryAdd("most.sfc_file", sfc_file);
517  if (sfc_file.empty()) {
518  amrex::Abort("most.sfc_file must be set when using text-file surface forcing");
519  }
520 
521  // sfc contains: time(day) sst(K) H(W/m2) LE(W/m2) TAU(m2/s2)
522  sfc = read_cols(sfc_file, 1);
523 
524  const int min_cols = m_use_sfc_fluxes ? 5 : 2;
525  if (static_cast<int>(sfc.size()) < min_cols) {
526  amrex::Abort("Surface forcing file does not contain the required number of columns");
527  }
528 
529  // shift time column in days to be relative to current elapsed time
530  const amrex::Real start_day = sfc[0][0];
531  for (int i = 0; i < static_cast<int>(sfc[0].size()); ++i) {
532  sfc[0][i] = 86400.0 * (sfc[0][i] - start_day);
533  }
534 
535  if (m_use_sfc_sst) {
537  amrex::Abort("most.use_sfc_sst cannot be combined with prescribed heat flux or surf_heating_rate");
538  }
540  amrex::Abort("most.use_sfc_sst cannot be combined with prescribed moisture flux");
541  }
543  amrex::Print() << "Using MOST with prescribed SST from most.sfc_file '" << sfc_file << "' over sea" << std::endl;
544  }
545 
546  if (m_use_sfc_fluxes) {
548  amrex::Print() << "Using MOST with prescribed time-varying surface fluxes from '" << sfc_file << "'" << std::endl;
549  }
550  }
551 
552  // Get pointers to LSM data and Fluxes
553  int ndata = static_cast<int>(lsm_data.size());
554  int nflux = static_cast<int>(lsm_flux.size());
555  m_lsm_data_name.resize(ndata);
556  m_lsm_data_lev[lev].resize(ndata);
557  m_lsm_flux_name.resize(nflux);
558  m_lsm_flux_lev[lev].resize(nflux);
559  for (int n(0); n < ndata; ++n) {
560  m_lsm_data_name[n] = lsm_data_name[n];
561  m_lsm_data_lev[lev][n] = lsm_data[n];
562  const std::string lc_name = amrex::toLower(lsm_data_name[n]);
563  if (lc_name == "theta" || lc_name == "t_surf") {
564  m_has_lsm_tsurf = true;
565  m_lsm_tsurf_indx = n;
566  }
567  }
569  amrex::Abort("most.use_sfc_sst cannot be combined with an ocean LSM t_surf input");
570  }
571  int n_valid_lsm_flux = 0;
572  bool has_soil_t_flux = false;
573  for (int n(0); n < nflux; ++n) {
574  m_lsm_flux_name[n] = lsm_flux_name[n];
575  m_lsm_flux_lev[lev][n] = lsm_flux[n];
576  if (m_lsm_flux_lev[lev][n]) { ++n_valid_lsm_flux; }
577  if (amrex::toLower(m_lsm_flux_name[n]) == "soil_t_flux") {
578  has_soil_t_flux = true;
579  }
580  }
581  AMREX_ALWAYS_ASSERT((n_valid_lsm_flux==0 || n_valid_lsm_flux>=4 ||
582  (n_valid_lsm_flux==1 && has_soil_t_flux)));
583  if (n_valid_lsm_flux>=4) { m_has_lsm_fluxes = true; }
584 
585  const bool use_sst = (!m_sst_lev[lev].empty() && m_sst_lev[lev][0]);
586  const bool use_tsk = (!m_tsk_lev[lev].empty() && m_tsk_lev[lev][0]);
587 
588  // Check if there is a user-specified roughness file to be read
589  std::string fname;
590  bool read_z0 = false;
591  if ( (flux_type == FluxCalcType::MOENG) ||
593  int count = pp.countval("most.roughness_file_name");
594  if (count > 1) {
595  AMREX_ALWAYS_ASSERT(count >= lev+1);
596  pp.query("most.roughness_file_name", fname, lev);
597  read_z0 = true;
598  } else if (count == 1) {
599  if (lev == 0) {
600  pp.queryAdd("most.roughness_file_name", fname);
601  } else {
602  // we will interpolate from the coarsest level
603  fname = "";
604  }
605  read_z0 = true;
606  }
607  // else use z0_const
608  }
609  if (read_z0) {
611  m_face.coordDir() == 2 && m_face.isLow(),
612  "Custom MOST roughness is supported only on the z-low face.");
613  }
614 
615  // LSM flux arrays are planar and compute_sfc_params_from_lsm_fluxes only
616  // writes the k=0 slab, while EB MOST consumes surface parameters at
617  // arbitrary cut-cell k; no valid planar-to-cut-cell mapping exists yet.
619  m_terrain_type, use_sst, use_tsk, m_use_coupled_sst,
620  m_has_lsm_tsurf, m_has_lsm_fluxes, read_z0)) {
621  amrex::Abort(
622  "EB SurfaceLayer does not support planar SST/TSK, coupled SST, LSM surface "
623  "temperature or fluxes, or custom/file-driven roughness; no mapping exists "
624  "from those planar inputs to arbitrary EB cut cells.");
625  }
626 
627  // Attributes for MFs and FABs
628  //--------------------------------------------------------
629  // Create a 2D ba for planar terrain, 3D for EB terrain
630  const int dir = m_face.coordDir();
631  int sm_index;
632  if (m_face.isLow()) {
633  sm_index = m_geom[lev].Domain().smallEnd(dir);
634  } else {
635  sm_index = m_geom[lev].Domain().bigEnd(dir);
636  }
637 
638  amrex::BoxArray ba = mf.boxArray();
639  amrex::BoxArray ba_flux;
640  amrex::IntVect ng{1,1,0};
641 
642  // The lateral-wall implementation uses the two-dimensional land-mask
643  // layout to identify grids on the requested face. Lateral walls are
644  // intentionally restricted to grids spanning the complete z domain;
645  // z-decomposed and partial-height grids are not supported.
646  if (dir != 2) {
647  const int dom_lo_z = m_geom[lev].Domain().smallEnd(2);
648  const int dom_hi_z = m_geom[lev].Domain().bigEnd(2);
649  for (int ibox = 0; ibox < ba.size(); ++ibox) {
651  ba[ibox].smallEnd(2) == dom_lo_z && ba[ibox].bigEnd(2) == dom_hi_z,
652  "Surface layer boundaries on x/y faces require Cartesian grids that "
653  "span the full level z domain; partial-height refined grids and grids "
654  "decomposed in z are not supported. Set erf.max_grid_size_z accordingly.");
655  }
656  }
657 
658  if (m_terrain_type == TerrainType::EB) {
659  // Use full 3D BoxArray for EB terrain
660  ba_flux = ba;
661  ng = amrex::IntVect{1,1,1}; // Include z ghost cells
662  } else {
663  // Collapse to 2D for planar terrain
664  amrex::BoxList bl2d = ba.boxList();
665  for (auto& b : bl2d) {
666  b.setRange(dir,sm_index);
667  }
668  ba_flux = amrex::BoxArray(std::move(bl2d));
669  // Lateral faces need state-width ghosts in the tangential z
670  // direction. A z face only needs the original one-cell x/y halo;
671  // kernels using the wider state/mask boxes clip to the FAB bounds.
672  if (dir == 2) {
673  ng = amrex::IntVect{1,1,0};
674  } else {
675  ng = mf.nGrowVect();
676  ng[dir] = 0;
677  }
678  }
679 
680  const amrex::DistributionMapping& dm = mf.DistributionMap();
681  const int ncomp = 1;
682 
683  // Surface copies of the planar boxes (see fill_planar_boundary)
684  // PlanarBoundary handles duplicate boxes created by a z-split. The x/y
685  // layouts use selective lateral exchange instead.
686  if (m_terrain_type != TerrainType::EB && dir == 2) {
687  const int ksurface = m_face.isLow()
688  ? m_geom[lev].Domain().smallEnd(2)
689  : m_geom[lev].Domain().bigEnd(2);
690  m_planar_bndry[lev].define(ba, ba_flux, dm, ksurface, m_face.isLow());
691  }
692 
693  // Z0 heights FAB
694  //--------------------------------------------------------
695  z_0[lev].define(ba_flux, dm, ncomp, ng);
696  z_0[lev].setVal(z0_const);
697  if (read_z0) {
698  read_custom_roughness(lev, fname);
699  }
700 
701  // 2D MFs for U*, T*, T_surf
702  //--------------------------------------------------------
703  u_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
704  u_star[lev]->setVal(bogus_large_value);
705 
706  w_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
707  w_star[lev]->setVal(bogus_large_value);
708 
709  t_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
710  t_star[lev]->setVal(zero); // default to neutral
711 
712  q_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
713  q_star[lev]->setVal(zero); // default to dry
714 
715  olen[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
716  olen[lev]->setVal(bogus_large_value);
717 
718  pblh[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
719  pblh[lev]->setVal(bogus_large_value);
720 
721  t_surf[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
722  t_surf[lev]->setVal(default_land_surf_temp);
723 
724  q_surf[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
725  q_surf[lev]->setVal(default_land_surf_moist);
726 
727  const amrex::iMultiFab& surface_mask = *m_lmask_lev[lev][0];
729  surface_mask.boxArray().size() == mf.boxArray().size(),
730  "Surface-layer mask and state must have the same number of boxes.");
732  surface_mask.DistributionMap() == mf.DistributionMap(),
733  "Surface-layer mask and state must have identical ownership.");
735  surface_mask.boxArray().size() == u_star[lev]->boxArray().size(),
736  "Surface-layer mask and parameters must have the same number of boxes.");
738  surface_mask.DistributionMap() == u_star[lev]->DistributionMap(),
739  "Surface-layer mask and parameters must have identical ownership.");
740 
741  surface_diagnostic_source[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
742  surface_diagnostic_source[lev]->setVal(
744 
745  // TODO: Do we want an enum struct for indexing?
746 
747  if (use_sst || use_tsk || m_has_lsm_tsurf || m_use_coupled_sst) {
748  // Valid SST, TSK, LSM or coupled-ocean data; t_surf set before computing
749  // fluxes (avoids extended lambda capture) Note that land temp will be set
750  // from m_tsk_lev while sea temp will be set from m_sst_lev
752 
753  // Pathways in fill_tsurf_with_sst_and_tsk
754  amrex::Print() << "Using MOST with specified surface temperature ";
755  if (m_has_lsm_tsurf && !use_sst && !use_tsk) {
756  amrex::Print() << "(LSM: " << m_lsm_data_name[m_lsm_tsurf_indx] << ")";
757  } else if (!use_sst && !use_tsk) {
758  amrex::Print() << "(land: T0, sea: none)";
759  } else {
760  // NOTE: SST from the LOW file populates TSK in update_sst_tsk.
761  // So if we have TSK, it contains everything and has been
762  // sanity checked for valid SST values.
763  if (use_tsk) { m_ignore_sst = true; }
764  if (use_tsk) {
765  amrex::Print() << "(land: TSK, ";
766  } else {
767  amrex::Print() << "(land: T0, ";
768  }
769  if (use_tsk && !use_sst) {
770  amrex::Print() << "sea: TSK)";
771  } else {
772  amrex::Print() << "sea: SST)";
774  }
775  }
776  // The coupler is layered on top of whatever the above selected: it
777  // overwrites only the water cells it actually covers, so the pathway
778  // named above remains the value for land and for uncovered water.
779  if (m_use_coupled_sst) {
780  amrex::Print() << " + coupled ocean SST where covered";
781  }
782  amrex::Print() << std::endl;
783  }
784  }
constexpr amrex::Real bogus_large_value
Definition: ERF_Constants.H:17
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:1307
amrex::Vector< std::string > m_lsm_data_name
Definition: ERF_SurfaceLayer.H:1570
bool m_has_lsm_tsurf
Definition: ERF_SurfaceLayer.H:1491
bool m_has_lsm_fluxes
Definition: ERF_SurfaceLayer.H:1490
static amrex::Vector< amrex::Vector< amrex::Real > > read_cols(const std::string &fname, const int skip_nlines=1)
Definition: ERF_SurfaceLayer.cpp:2639
bool m_use_sfc_sst
Definition: ERF_SurfaceLayer.H:1496
bool m_use_coupled_sst
Definition: ERF_SurfaceLayer.H:1506
void read_custom_roughness(const int &lev, const std::string &fname)
Definition: ERF_SurfaceLayer.cpp:2511
@ 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:31
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
2640 {
2641  std::ifstream ifs(fname);
2642  if (!ifs.is_open())
2643  {
2644  amrex::Error("Error opening input file " + fname);
2645  }
2646 
2647  amrex::Vector<amrex::Vector<amrex::Real>> col_data;
2648  std::string line;
2649  int nlines = 0;
2650  int ncols = -1;
2651 
2652  const auto print_err = [](const std::string &err_fname, int lineno, int cols, int expected_cols) {
2653  amrex::Error("Error reading file '" + err_fname + "': expected line " +
2654  std::to_string(lineno) + " to have " + std::to_string(expected_cols) +
2655  " columns, but got " + std::to_string(cols));
2656  };
2657 
2658  while (std::getline(ifs, line))
2659  {
2660  nlines++;
2661  if (nlines <= skip_nlines) continue;
2662  if (line.empty()) continue;
2663 
2664  std::istringstream iss(line);
2665 
2666  amrex::Real tmp;
2667  // Get the number of columns in the file
2668  if (ncols == -1) {
2669  int j = 0;
2670  while (iss >> tmp) {
2671  col_data.push_back(amrex::Vector<amrex::Real>());
2672  j+= 1;
2673  }
2674 
2675  ncols = j;
2676  iss = std::istringstream(line);
2677  }
2678 
2679  int j = 0;
2680  while (iss >> tmp) {
2681  // verify each line has the same number of columns
2682  if (j >= ncols) {
2683  print_err(fname, nlines, j+1, ncols);
2684  }
2685  col_data[j].push_back(tmp);
2686  j+= 1;
2687  }
2688 
2689  // throw error if there are fewer columns in the line than expected
2690  if (j != ncols) {
2691  print_err(fname, nlines, j, ncols);
2692  }
2693  }
2694 
2695  ifs.close();
2696 
2697  return col_data;
2698 }
@ 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
2513 {
2514  // Read the file if we have it
2515  if (!fname.empty()) {
2516  // Only the ioproc reads the file
2517  Gpu::HostVector<Real> m_x,m_y,m_z0;
2518  if (ParallelDescriptor::IOProcessor()) {
2519  Print()<<"Reading MOST roughness file at level " << lev << " : " << fname << std::endl;
2520  std::ifstream file(fname);
2521  Real value1,value2,value3;
2522  while(file>>value1>>value2>>value3){
2523  m_x.push_back(value1);
2524  m_y.push_back(value2);
2525  m_z0.push_back(value3);
2526  }
2527  file.close();
2528 
2529  AMREX_ALWAYS_ASSERT(m_x.size() == m_y.size());
2530  AMREX_ALWAYS_ASSERT(m_x.size() == m_z0.size());
2531  }
2532 
2533  // Broadcast the whole domain to every rank
2534  int ioproc = ParallelDescriptor::IOProcessorNumber();
2535  int nnode = static_cast<int>(m_x.size());
2536  ParallelDescriptor::Bcast(&nnode, 1, ioproc);
2537 
2538  if (!ParallelDescriptor::IOProcessor()) {
2539  m_x.resize(nnode);
2540  m_y.resize(nnode);
2541  m_z0.resize(nnode);
2542  }
2543  ParallelDescriptor::Bcast(m_x.data() , nnode, ioproc);
2544  ParallelDescriptor::Bcast(m_y.data() , nnode, ioproc);
2545  ParallelDescriptor::Bcast(m_z0.data(), nnode, ioproc);
2546 
2547  // Copy data to the GPU
2548  Gpu::DeviceVector<Real> d_x(nnode),d_y(nnode),d_z0(nnode);
2549  Gpu::copy(Gpu::hostToDevice, m_x.begin(), m_x.end(), d_x.begin());
2550  Gpu::copy(Gpu::hostToDevice, m_y.begin(), m_y.end(), d_y.begin());
2551  Gpu::copy(Gpu::hostToDevice, m_z0.begin(), m_z0.end(), d_z0.begin());
2552  Real* xp = d_x.data();
2553  Real* yp = d_y.data();
2554  Real* z0p = d_z0.data();
2555 
2556  // Each rank populates it's z_0[lev] MultiFab
2557  const int klo = m_geom[lev].Domain().smallEnd(2);
2558  for (MFIter mfi(z_0[lev]); mfi.isValid(); ++mfi)
2559  {
2560  Box gtbx = mfi.growntilebox();
2561 
2562  if (gtbx.smallEnd(2) != klo) { continue; }
2563 
2564  // Populate z_phys data
2565  Real tol = Real(1.0e-4);
2566  auto dx = m_geom[lev].CellSizeArray();
2567  auto ProbLoArr = m_geom[lev].ProbLoArray();
2568  int ilo = m_geom[lev].Domain().smallEnd(0);
2569  int jlo = m_geom[lev].Domain().smallEnd(1);
2570  int ihi = m_geom[lev].Domain().bigEnd(0);
2571  int jhi = m_geom[lev].Domain().bigEnd(1);
2572 
2573  Array4<Real> const& z0_arr = z_0[lev].array(mfi);
2574  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE (int i, int j, int /*k*/)
2575  {
2576  // Clip indices for ghost-cells
2577  int ii = amrex::min(amrex::max(i,ilo),ihi);
2578  int jj = amrex::min(amrex::max(j,jlo),jhi);
2579 
2580  // Location of nodes
2581  Real x = ProbLoArr[0] + ii * dx[0];
2582  Real y = ProbLoArr[1] + jj * dx[1];
2583  int inode = ii + jj * (ihi-ilo+2); // stride is Nx+1
2584  if (std::sqrt(amrex::Math::powi<2>(x-xp[inode])+amrex::Math::powi<2>(y-yp[inode])) < tol) {
2585  z0_arr(i,j,klo) = z0p[inode];
2586  } else {
2587  // Unexpected list order, do brute force search
2588  Real z0loc = zero;
2589  bool found = false;
2590  for (int n=0; n<nnode; ++n) {
2591  Real delta=std::sqrt(amrex::Math::powi<2>(x-xp[n])+amrex::Math::powi<2>(y-yp[n]));
2592  if (delta < tol) {
2593  found = true;
2594  z0loc = z0p[n];
2595  break;
2596  }
2597  }
2598  AMREX_ASSERT_WITH_MESSAGE(found, "Location read from terrain file does not match the grid!");
2599  amrex::ignore_unused(found);
2600  z0_arr(i,j,klo) = z0loc;
2601  }
2602  });
2603  } // mfi
2604  } else {
2605  AMREX_ALWAYS_ASSERT(lev > 0);
2606 
2607  Print()<<"Interpolating MOST roughness at level " << lev << std::endl;
2608 
2609  // Create a BC mapper that uses FOEXTRAP at domain bndry
2610  Vector<int> bc_lo(3,ERFBCType::foextrap);
2611  Vector<int> bc_hi(3,ERFBCType::foextrap);
2612  Vector<BCRec> bcr; bcr.push_back(BCRec(bc_lo.data(),bc_hi.data()));
2613 
2614  // Create ref ratio
2615  IntVect ratio;
2616  for (int idim = 0; idim < AMREX_SPACEDIM; ++idim) {
2617  ratio[idim] = m_geom[lev].Domain().length(idim) / m_geom[0].Domain().length(idim);
2618  }
2619 
2620  // Create interp object and interpolate from the coarsest grid
2621  MFInterpolater* interp = &mf_cell_cons_interp;
2622  interp->interp(z_0[0] , 0,
2623  z_0[lev], 0,
2624  1, z_0[lev].nGrowVect(),
2625  m_geom[0], m_geom[lev],
2626  m_geom[lev].Domain(),ratio,
2627  bcr, 0);
2628  }
2629 }
@ m_y
Definition: ERF_DataStruct.H:30
@ m_x
Definition: ERF_DataStruct.H:29
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
@ foextrap
Definition: ERF_IndexDefines.H:296

Referenced by make_SurfaceLayer_at_level().

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

◆ 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
1376 { 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
1236 { 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 
)
2234 {
2235  AMREX_ASSERT(pblh[lev]);
2236  amrex::MultiFab::Copy(*pblh[lev], pblh_in, 0, 0, 1, 0);
2237  fill_planar_boundary(lev, *pblh[lev]);
2238 }

◆ 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
1266 { q_surf[lev]->setVal(qsurf); }

◆ 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
1387  {
1388  m_surface_layer_faces = active_faces;
1389  }

◆ 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
1251 { t_surf[lev]->setVal(tsurf); }

◆ 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
361 {
363  "SurfaceLayer::surface_sum is only defined for a surface layer on a z face");
364  if (m_terrain_type == TerrainType::EB) {
365  // EB fields live on the full 3D BoxArray without duplicates: sum the lowest plane
366  return sumToLine(mf, comp, 1, m_geom[lev].Domain(), 2)[0];
367  }
368  // A face-centered planar field (the MOST velocity averages) shares the planar BoxArray
369  // but not its index type, and neighbouring surface boxes would then share a face, which
370  // this sum would count twice
371  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(mf.boxArray().ixType().cellCentered(),
372  "SurfaceLayer::surface_sum is only defined for a cell-centered planar MultiFab");
373  const PlanarBoundary& pb = m_planar_bndry[lev];
374  MultiFab surface_copy(pb.surface_boxes(), pb.surface_dm(), 1, 0);
375  pb.gather_surface(mf, surface_copy, comp, 0, 1);
376  return surface_copy.sum(0);
377 }
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.
1363  {
1364  m_coupled_sst_lev[lev] = sst_ptr;
1365  m_coupled_sst_valid_lev[lev] = valid_ptr;
1366  }

◆ 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 qsurf with qsat over sea
60  if (use_moisture) {
61  fill_qsurf_with_qsat(lev, cons_in, z_phys_nd);
62  }
63 
64  // Update land surface temp if we have a valid pointer
65  if (m_has_lsm_tsurf && zlo) get_lsm_tsurf(lev);
66 
67  // Fill interior ghost cells
68  fill_planar_boundary(lev, *t_surf[lev]);
69 
70  // Compute plane averages for all vars (regardless of flux type)
72 
73  // NOTE: Do iterations to seed variables on the first step (LSM called post step)
74  // as well as compute values where invalid LSM fluxes may reside
75  //*******************************************************************************
76  // ***************************************************************
77  // Iterate the fluxes if moeng type
78  // First iterate over land -- the only model for surface roughness
79  // over land is RoughCalcType::CONSTANT
80  // ***************************************************************
83  bool is_land = true;
84  // Do we have a constant flux for moisture over land?
85  bool cons_qflux = ( (moist_type == MoistCalcType::MOISTURE_FLUX) ||
87  if (m_terrain_type != TerrainType::EB) {
90  surface_flux most_flux(surf_temp_flux, surf_moist_flux, cons_qflux);
91  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
92  } else {
93  amrex::Abort("Unknown value for rough_type_land");
94  }
97  surface_temp most_flux(surf_temp_flux, surf_moist_flux, cons_qflux,
98  m_face.coordDir(), m_face.isLow());
99  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
100  } else {
101  amrex::Abort("Unknown value for rough_type_land");
102  }
103  } else if ((theta_type == ThetaCalcType::ADIABATIC) &&
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 {
112  amrex::Abort("Unknown value for theta_type");
113  }
114  // EB
115  } else {
118  surface_flux_eb most_flux(surf_temp_flux, surf_moist_flux, cons_qflux);
119  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
120  } else {
121  amrex::Abort("Unknown value for rough_type_land");
122  }
125  surface_temp_eb most_flux(surf_temp_flux, surf_moist_flux, cons_qflux);
126  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
127  } else {
128  amrex::Abort("Unknown value for rough_type_land");
129  }
130  } else if ((theta_type == ThetaCalcType::ADIABATIC) &&
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 {
139  amrex::Abort("Unknown value for theta_type");
140  }
141  } // EB
142  } // MOENG -- LAND
143 
144  // Update u*/T*/q*/L over land (iterations or from LSM fluxes)
145  if (m_has_lsm_fluxes && elapsed_time > zero) {
147  }
148 
149  // ***************************************************************
150  // Iterate the fluxes if moeng type
151  // Next iterate over sea -- the models for surface roughness
152  // over sea are CHARNOCK, DONELAN, MODIFIED_CHARNOCK or WAVE_COUPLED
153  // NOTE: Sea surface fluxes are not supported for EB terrain
154  // ***************************************************************
155  if ((flux_type == FluxCalcType::MOENG ||
157  m_terrain_type != TerrainType::EB) {
158  bool is_land = false;
159  // NOTE: Do not allow default to adiabatic over sea (we have Qvs at surface)
160  // Do we have a constant flux for moisture over sea?
161  bool cons_qflux = (moist_type == MoistCalcType::MOISTURE_FLUX);
165  cnk_a, smooth_flow_visc, cons_qflux);
166  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
169  depth, smooth_flow_visc, cons_qflux);
170  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
171  } else if (rough_type_sea == RoughCalcType::DONELAN) {
173  smooth_flow_visc, cons_qflux);
174  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
177  smooth_flow_visc, cons_qflux);
178  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
179  } else {
180  amrex::Abort("Unknown value for rough_type_sea");
181  }
182 
186  cnk_a, smooth_flow_visc, cons_qflux);
187  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
190  depth, smooth_flow_visc, cons_qflux);
191  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
192  } else if (rough_type_sea == RoughCalcType::DONELAN) {
194  smooth_flow_visc, cons_qflux);
195  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
198  smooth_flow_visc, cons_qflux);
199  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
200  } else {
201  amrex::Abort("Unknown value for rough_type_sea");
202  }
203 
204  } else if ((theta_type == ThetaCalcType::ADIABATIC) &&
209  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
213  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
214  } else if (rough_type_sea == RoughCalcType::DONELAN) {
216  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
219  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
220  } else {
221  amrex::Abort("Unknown value for rough_type_sea");
222  }
223  } else {
224  amrex::Abort("Unknown value for theta_type");
225  }
226  } // MOENG -- SEA
227 
229  if (m_use_sfc_fluxes) {
230  // update custom surface fluxes interpolated from file
231  update_sfc_time_index(elapsed_time);
232  sfc_tflux = interpolate_sfc_column(elapsed_time, 2);
233  sfc_qflux = interpolate_sfc_column(elapsed_time, 3);
234  sfc_ustar = interpolate_sfc_column(elapsed_time, 4);
235 
236  amrex::Print() << " ABLMOST: Interpolating SHF and LHF at time "
237  << elapsed_time
238  << ": SHF = " << sfc_tflux
239  << " (W/m^2) LHF = " << sfc_qflux
240  << " (W/m^2) TAU = " << sfc_ustar
241  << " (m^2/s^2)" << std::endl;
242 
243  // overwrite the custom_ustar/tstar/qstar values with the new values and
244  // use the existing pathway to set u*,t*,q* with or without a custom_rhosurf
245  // note - when m_use_sfc_fluxes=true, custom_flux has specified_rho_surf=true,
246  // so there is no rho factor here
247  custom_ustar = std::sqrt(sfc_ustar); // convert tau from file to u*
250  }
251 
252  if (custom_rhosurf > 0) {
253  specified_rho_surf = true;
254  u_star[lev]->setVal(std::sqrt(custom_rhosurf) * custom_ustar);
255  t_star[lev]->setVal(custom_rhosurf * custom_tstar);
256  q_star[lev]->setVal(custom_rhosurf * custom_qstar);
257  } else {
258  u_star[lev]->setVal(custom_ustar);
259  t_star[lev]->setVal(custom_tstar);
260  q_star[lev]->setVal(custom_qstar);
261  }
262  }
263 
264  if (m_update_k_rans) {
265  // Clamped divisor: the select is if-converted, so 1/theta_ref runs even
266  // when theta_ref = 0 and would trip fpe_trap_zero (see ERF_SetupDiff.H)
267  const bool use_ref_theta = (theta_ref > 0);
268  const Real inv_theta_ref = one / amrex::max(theta_ref, std::numeric_limits<Real>::min());
269  const Real l_inv_theta0 = (use_ref_theta) ? inv_theta_ref : one;
270  const Real l_inv_Cmu2 = inv_Cmu2;
271  const int klo = m_geom[lev].Domain().smallEnd(2);
272  IntVect ng = u_star[lev]->nGrowVect(); ng[2] = 0;
273 
274  for (MFIter mfi(cons_in); mfi.isValid(); ++mfi)
275  {
276  Box gpbx = mfi.tilebox(IntVect(0),ng);
277 
278  if (gpbx.smallEnd(2) != klo) { continue; }
279 
280  gpbx.makeSlab(2,klo);
281  gpbx &= cons_in.fabbox(mfi.index());
282  gpbx &= walldist->fabbox(mfi.index());
283  if (gpbx.isEmpty()) { continue; }
284 
285  auto cons_arr = cons_in.array(mfi);
286  const auto& u_star_arr = u_star[lev]->const_array(mfi);
287  const auto& t_star_arr = t_star[lev]->const_array(mfi);
288  const auto& dist_arr = walldist->const_array(mfi);
289 
290  ParallelFor(gpbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
291  {
292  Real rho = cons_arr(i,j,k,Rho_comp);
293  if (t_star_arr(i,j,0) < -1e-8) {
294  // Only destabilizing buoyancy flux affects the boundary k
295  // tstar < 0 ==> B > 0
296  Real B = -CONST_GRAV * l_inv_theta0 * u_star_arr(i,j,0) * t_star_arr(i,j,0);
297  if (!use_ref_theta) {
298  B *= cons_arr(i,j,k,Rho_comp) /
299  cons_arr(i,j,k,RhoTheta_comp);
300  }
301 
302  // Axell & Liungman 2001, Eqn. 16
303  cons_arr(i,j,k,RhoKE_comp) = rho * l_inv_Cmu2 *
304  std::pow(
305  u_star_arr(i,j,0) * u_star_arr(i,j,0) * u_star_arr(i,j,0)
306  + KAPPA * B * dist_arr(i,j,k),
307  two/three);
308  } else {
309  cons_arr(i,j,k,RhoKE_comp) = rho * l_inv_Cmu2 * u_star_arr(i,j,0) * u_star_arr(i,j,0);
310  }
311  });
312  }
313  }
314 
315  if (m_terrain_type == TerrainType::EB || m_face.coordDir() == 2) {
316  fill_planar_boundary(lev, *u_star[lev]);
317  if (m_include_wstar) { fill_planar_boundary(lev, *w_star[lev]); }
318  fill_planar_boundary(lev, *t_star[lev]);
319  fill_planar_boundary(lev, *q_star[lev]);
320  fill_planar_boundary(lev, *olen[lev]);
321  } else {
322  // The ordinary FillBoundary is unsafe for lateral walls: the
323  // collapsed layout contains FABs from interior grids whose surface
324  // parameters were never computed. Exchange only after all flux
325  // iterations have written the selected face FABs, and copy the
326  // communicated values back only to those FABs.
328  }
329 }
constexpr amrex::Real Cp_d
Definition: ERF_Constants.H:36
constexpr amrex::Real L_v
Definition: ERF_Constants.H:51
constexpr amrex::Real three
Definition: ERF_NumericalConstants.H:32
constexpr amrex::Real two
Definition: ERF_NumericalConstants.H:31
void compute_averages(const int &lev)
Definition: ERF_MOSTAverage.cpp:1428
amrex::Real sfc_tflux
Definition: ERF_SurfaceLayer.H:1500
amrex::Real sfc_qflux
Definition: ERF_SurfaceLayer.H:1499
void get_lsm_tsurf(const int &lev)
Definition: ERF_SurfaceLayer.cpp:2035
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:1883
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:2079
void compute_fluxes(const int &lev, const int &max_iters, amrex::MultiFab &cons_in, const FluxIter &most_flux, bool is_land)
amrex::Real sfc_ustar
Definition: ERF_SurfaceLayer.H:1501
void update_surf_temp(const double &time)
Definition: ERF_SurfaceLayer.H:1086
void fill_tsurf_with_sst_and_tsk(const int &lev, const double &time)
Definition: ERF_SurfaceLayer.cpp:1682
void compute_sfc_params_from_lsm_fluxes(const int &lev, amrex::MultiFab &cons_in)
Definition: ERF_SurfaceLayer.cpp:1595
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:1769
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
1120  {
1121  m_ma.update_field_ptrs(lev, vars_old, Theta_prim, Qv_prim, Qr_prim);
1122  }
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
2220 {
2222  MYNNPBLH estimator;
2223  compute_pblh(lev, vars, z_phys_cc, estimator, moisture_indices);
2225  {
2226  //amrex::Error("YSU/MRF PBLH calc not implemented yet");
2227  // PBLH for MRF/YSU is computed inside ComputeDiffusivityMRF
2228  // and written back via set_pblh(). Nothing to do here.
2229  }
2230 }
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
381 {
382  if (sfc.empty() || sfc[0].size() < 2) { return; }
383 
384  Real t1 = sfc[0][sfc_time_ind+1];
385  while (elapsed_time >= t1)
386  {
387  int prev_index = sfc_time_ind;
388  sfc_time_ind = std::min(sfc_time_ind + 1, int(sfc[0].size() - 2));
389  t1 = sfc[0][sfc_time_ind+1];
390  if (prev_index == sfc_time_ind) {
391  break;
392  }
393  }
394 }

◆ 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
1334  {
1335  m_sst_lev[lev][itime] = sst_ptr;
1336  }

◆ 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
1087  {
1088  // NOTE: this is a whole-domain setVal, so it overwrites the SST/TSK fill
1089  // done earlier in update_fluxes. Coupled SST is applied after this
1090  // call and therefore still wins on the water cells it covers.
1091  if (surf_heating_rate != 0) {
1092  // Use the actual size of t_surf, not m_geom.size(), which is always
1093  // max_level+1 and so runs past the levels that exist. t_surf is sized for
1094  // all levels up front but filled one level at a time, so we also have to
1095  // skip the entries that have not been allocated yet.
1096  int nlevs = static_cast<int>(t_surf.size());
1097  for (int lev = 0; lev < nlevs; lev++) {
1098  if (!t_surf[lev]) { continue; }
1099  t_surf[lev]->setVal(surf_temp + surf_heating_rate * static_cast<amrex::Real>(time));
1100  amrex::Print() << "Surface temp at t=" << time << ": "
1101  << surf_temp + surf_heating_rate * time << std::endl;
1102  }
1103  }
1104  }

◆ 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
1345  {
1346  m_tsk_lev[lev][itime] = tsk_ptr;
1347  }

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_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_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_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

ThetaCalcType SurfaceLayer::theta_type {ThetaCalcType::ADIABATIC}

◆ u_star

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

◆ use_moisture

bool SurfaceLayer::use_moisture
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: