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

#include <ERF_SurfaceLayer.H>

Collaboration diagram for SurfaceLayer:

Classes

struct  PBLHColumns
 

Public Types

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

Public Member Functions

 SurfaceLayer (amrex::Orientation face, const amrex::Vector< amrex::Geometry > &geom, bool &use_rot_surface_flux, std::string a_pp_prefix, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Qv_prim, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &z_phys_nd, const amrex::Vector< amrex::Vector< amrex::Real >> &zlevels_stag, const MeshType &a_mesh_type, const TerrainType &a_terrain_type, const TurbChoice &a_turb_choice, amrex::Real a_rdOcp, double start_low_time, double final_low_time, double low_time_interval=0.0, const amrex::Vector< const eb_ * > &eb_vec={}, SurfaceModel *surf_model=nullptr, bool a_has_immersed_buildings=false)
 
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 set_immersed_surface_params (const int &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, amrex::MultiFab *mf_surface_fluxes=nullptr)
 
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, amrex::MultiFab *mf_surface_fluxes=nullptr)
 
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 set_sfc_params_from_file_fluxes (const int &lev, const amrex::MultiFab &cons_in, const amrex::Real &tflux_in, const amrex::Real &qflux_in, const amrex::Real &sfc_tau)
 
void fill_tsurf_with_sst_and_tsk (const int &lev, const double &time)
 
void fill_tsurf_with_sfc_sst (const int &lev, const double &time, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
 
void fill_tsurf_with_coupled_sst (const int &lev, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
 
void fill_tsurf_with_abs_surf_temp (const int &lev, const double &time, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
 
void fill_tsurf_with_skin_temperature (const int &lev, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
 
void fill_qsurf_with_skin_moisture (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, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
 
void update_pblh (const int &lev, amrex::Vector< amrex::Vector< amrex::MultiFab >> &vars, amrex::MultiFab *z_phys_cc, const MoistureComponentIndices &moisture_indices)
 
void fill_pblh_from_coarser (const int &lev)
 
void inject_pblh_from_coarser (const int &lev, amrex::MultiFab &fine) const
 
template<typename PBLHeightEstimator >
void compute_pblh (const int &lev, amrex::Vector< amrex::Vector< amrex::MultiFab >> &vars, amrex::MultiFab *z_phys_cc, const PBLHeightEstimator &est, const MoistureComponentIndices &moisture_indice)
 
void read_custom_roughness (const int &lev, const std::string &fname)
 
void update_surf_temp (const double &time)
 
void update_mac_ptrs (const int &lev, amrex::Vector< amrex::Vector< amrex::MultiFab >> &vars_old, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Theta_prim, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Qv_prim, amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Qr_prim)
 
amrex::MultiFab * get_u_star (const int &lev)
 
amrex::MultiFab * get_w_star (const int &lev)
 
bool computes_w_star () const
 
bool computes_pblh () const
 
amrex::MultiFab * get_t_star (const int &lev)
 
amrex::MultiFab * get_q_star (const int &lev)
 
amrex::MultiFab * get_olen (const int &lev)
 
amrex::MultiFab * get_pblh (const int &lev)
 
const amrex::MultiFab * get_mac_avg (const int &lev, int comp)
 
bool mac_avg_is_time_averaged () const
 
int get_num_mac_avg () const
 
bool mac_avg_is_initialized (const int &lev) const
 
void set_mac_avg_initialized (const int &lev)
 
amrex::MultiFab * get_mac_avg_ptr (const int &lev, int comp)
 
amrex::Vector< amrex::Real > get_mac_plane_avg (const int &lev) const
 
bool set_mac_plane_avg (const int &lev, const amrex::Vector< amrex::Real > &pavg)
 
amrex::MultiFab * get_t_surf (const int &lev)
 
void set_skin_temperature (const int &lev, const amrex::MultiFab *t_skin)
 
std::string skin_temperature_conflict () const
 
void set_skin_moisture (const int &lev, const amrex::MultiFab *moisture, bool use_resistances=false, amrex::Real hs=amrex::Real(0))
 
std::string skin_moisture_conflict () const
 
bool set_default_roughness (amrex::Real z0)
 
amrex::Real roughness () const
 
bool use_sfc_fluxes () const
 
bool rotates_surface_fluxes () const
 
void set_t_surf (const int &lev, const amrex::Real tsurf)
 
amrex::MultiFab * get_q_surf (const int &lev)
 
void set_q_surf (const int &lev, const amrex::Real qsurf)
 
amrex::MultiFab * get_surface_diagnostic_source (const int &lev)
 
amrex::MultiFab * get_zref (const int &lev)
 
amrex::Real get_zref_min (const int &lev)
 
amrex::MultiFab * get_z0 (const int &lev)
 
bool have_variable_sea_roughness ()
 
amrex::iMultiFab * get_lmask (const int &lev)
 
int lmask_min_reduce (amrex::iMultiFab &lmask, const int &nghost)
 
void update_sst_ptr (const int lev, const int itime, amrex::MultiFab *sst_ptr)
 
void update_tsk_ptr (const int lev, const int itime, amrex::MultiFab *tsk_ptr)
 
void update_coupled_sst_ptr (const int lev, amrex::MultiFab *sst_ptr, amrex::iMultiFab *valid_ptr)
 
void set_coupled_sst_active (const bool active)
 
void set_surface_layer_faces (const amrex::GpuArray< int, AMREX_SPACEDIM *2 > &active_faces)
 
void fill_lateral_surface_parameter_ghosts (const int &lev, amrex::MultiFab *selected_field=nullptr)
 
template<typename FluxIter >
void compute_fluxes (const int &lev, const int &max_iters, MultiFab &, const FluxIter &most_flux, bool is_land)
 
template<typename FluxCalc >
void compute_SurfaceLayer_bcs (const int &lev, Vector< const MultiFab * > mfs, Vector< std::unique_ptr< MultiFab >> &Tau_lev, MultiFab *xheat_flux, MultiFab *yheat_flux, MultiFab *zheat_flux, MultiFab *xqv_flux, MultiFab *yqv_flux, MultiFab *zqv_flux, const MultiFab *z_phys, const FluxCalc &flux_comp, MultiFab *mf_surface_fluxes)
 
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)}
 
bool m_z0_given {false}
 
amrex::Real default_land_surf_temp {amrex::Real(300.)}
 
amrex::Real surf_temp {amrex::Real(-1.)}
 
bool surf_temp_is_absolute {false}
 
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_lsm_tsurf_is_theta = false
 
bool m_use_lsm_skin_temperature = false
 
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
 
bool m_has_immersed_buildings = false
 
amrex::Real m_rdOcp = RdoCp
 
MOSTAverage m_ma
 
SurfaceModel * m_surf_model = nullptr
 
bool use_surface_model = false
 
bool surf_model_fluxes = false
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > u_star
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > w_star
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_star
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > q_star
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > olen
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > pblh
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_surf
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > q_surf
 
amrex::Vector< PlanarBoundary > m_planar_bndry
 
amrex::Vector< PBLHColumns > m_pblh_columns
 
amrex::Vector< std::unique_ptr< amrex::MultiFab > > surface_diagnostic_source
 
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_sst_lev
 
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_tsk_lev
 
amrex::Vector< amrex::Vector< amrex::iMultiFab * > > m_lmask_lev
 
amrex::Vector< amrex::MultiFab * > m_coupled_sst_lev
 
amrex::Vector< amrex::iMultiFab * > m_coupled_sst_valid_lev
 
amrex::Vector< const amrex::MultiFab * > m_skin_tsurf_lev
 
amrex::Vector< const amrex::MultiFab * > m_skin_moisture_lev
 
amrex::Vector< int > m_skin_moisture_resistances_lev
 
amrex::Vector< amrex::Real > m_skin_moisture_hs_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 
1755  {
1756  MOENG = 0, ///< Moeng functional form
1757  CUSTOM, ///< Custom constant flux functional form
1758  BULK_COEFF, ///< Bulk transfer coefficient functional form
1759  ROTATE, ///< Terrain rotation flux functional form
1760  RICO
1761  };

◆ MoistCalcType

Enumerator
ADIABATIC 
MOISTURE_FLUX 

Qv-flux specified.

SURFACE_MOISTURE 

Surface Qv specified.

1769  {
1770  ADIABATIC = 0,
1771  MOISTURE_FLUX, ///< Qv-flux specified
1772  SURFACE_MOISTURE ///< Surface Qv specified
1773  };

◆ PBLHeightCalcType

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

◆ RoughCalcType

Enumerator
CONSTANT 

Constant z0.

CHARNOCK 
MODIFIED_CHARNOCK 
DONELAN 
WAVE_COUPLED 
1775  {
1776  CONSTANT = 0, ///< Constant z0
1777  CHARNOCK,
1778  MODIFIED_CHARNOCK,
1779  DONELAN,
1780  WAVE_COUPLED
1781  };

◆ ThetaCalcType

Enumerator
ADIABATIC 
HEAT_FLUX 

Heat-flux specified.

SURFACE_TEMPERATURE 

Surface temperature specified.

1763  {
1764  ADIABATIC = 0,
1765  HEAT_FLUX, ///< Heat-flux specified
1766  SURFACE_TEMPERATURE ///< Surface temperature specified
1767  };

Constructor & Destructor Documentation

◆ SurfaceLayer()

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

Construct the surface-layer interface.

Parameters
[in]faceorientation of face (for wall geometries)
[in]geomgeometry for all AMR levels
[in,out]use_rot_surface_fluxwhether to use rotated surface fluxes
[in]a_pp_prefixParmParse prefix used by MOST averages
[in]Qv_primprimitive water-vapor fields by level
[in]z_phys_ndnodal physical-height fields by level
[in]zlevels_stagnominal staggered z levels by level
[in]a_mesh_typemesh type
[in]a_terrain_typeterrain representation
[in]a_turb_choiceturbulence-model options
[in]a_rdOcpconfigured Rd/cp exponent for T-theta conversions
[in]start_low_timefirst available low-boundary-data time
[in]final_low_timefinal available low-boundary-data time
[in]low_time_intervallow-boundary-data time interval
[in]eb_vecoptional embedded-boundary geometry data
[in]surf_modeloptional weighted surface-model interface
[in]a_has_immersed_buildingswhether building immersed forcing is configured
98  {},
99  SurfaceModel* surf_model = nullptr,
100  bool a_has_immersed_buildings = false)
101  : m_face(face),
102  m_pp_prefix(a_pp_prefix),
103  m_geom(geom),
104  m_rotate(use_rot_surface_flux),
105  m_start_low_time(start_low_time),
106  m_final_low_time(final_low_time),
107  m_low_time_interval(low_time_interval),
108  m_eb_vec(eb_vec),
109  m_terrain_type(a_terrain_type),
110  m_has_immersed_buildings(a_has_immersed_buildings),
111  m_rdOcp(a_rdOcp),
112  m_ma(face, geom, (z_phys_nd[0] != nullptr), a_pp_prefix, a_mesh_type, a_terrain_type,
113  zlevels_stag, eb_vec),
114  m_surf_model(surf_model)
115  {
116  // We have a moisture model if Qv_prim is a valid pointer
117  use_moisture = (Qv_prim[0].get());
118 
119  // Keep standalone SurfaceLayer instances compatible with the historical
120  // single-face behavior. ERF replaces this with the complete active-face
121  // set after constructing all SurfaceLayer objects.
122  m_surface_layer_faces[static_cast<int>(face)] = 1;
123 
124  // Get roughness
125  amrex::ParmParse pp(a_pp_prefix);
126  m_z0_given = pp.contains("most.z0");
127  pp.queryAdd("most.z0", z0_const);
128 
129  // Specify how to compute the flux
130  if (use_rot_surface_flux) {
132  } else {
133  std::string flux_string_in;
134  std::string flux_string{"moeng"};
135  auto read_flux = pp.queryAdd("surface_layer.flux_type", flux_string_in);
136  if (read_flux) {
137  flux_string = amrex::toLower(flux_string_in);
138  }
139  if (flux_string == "moeng") {
141  } else if (flux_string == "rico") {
143  } else if (flux_string == "bulk_coeff") {
145  } else if (flux_string == "custom") {
147  } else {
148  amrex::Abort("Undefined MOST flux type!");
149  }
150  }
151 
156  m_face.coordDir() == 2 && m_face.isLow(),
157  "BULK_COEFF, CUSTOM, and RICO surface-layer fluxes are supported only on the z-low face.");
158  }
159 
160  // Include w* to handle free convection (Beljaars 1995, QJRMS)
161  pp.queryAdd("most.include_wstar", m_include_wstar);
162 
163  std::string pblh_string_in;
164  std::string pblh_string{"none"};
165  auto read_pblh = pp.queryAdd("most.pblh_calc", pblh_string_in);
166  if (read_pblh) {
167  pblh_string = amrex::toLower(pblh_string_in);
168  }
169  if (pblh_string == "none") {
171  } else if (pblh_string == "mynn25") {
173  } else if (pblh_string == "mynnedmf") {
175  } else if (pblh_string == "ysu") {
177  } else if (pblh_string == "mrf") {
179  } else {
180  amrex::Abort("Undefined PBLH calc type!");
181  }
182 
185  m_face.coordDir() == 2 && m_face.isLow(),
186  "MOST PBL-height calculation and wstar correction are supported only on the z-low face.");
187  }
188 
189  // The w* correction is computed from the PBL height, so it needs a scheme that
190  // actually diagnoses one. With pblh_calc = "none" the pblh MultiFab keeps the
191  // bogus_large_value it was initialized with and calc_wstar turns that into a
192  // convective velocity scale of ~1e50, which destroys the surface fluxes.
194  amrex::Abort("erf.most.include_wstar requires a PBL height: set "
195  "erf.most.pblh_calc (MYNN25 is the only scheme implemented)");
196  }
197 
198  if (m_surf_model) {
199  use_surface_model = true;
200  amrex::Print() << " Using fluxes from surface model!" << std::endl;
202  }
203 
204  // Get surface temperature, as a potential temperature (most.pot_surf_temp) or an
205  // absolute temperature (most.abs_surf_temp); most.surf_temp is the deprecated
206  // spelling of most.pot_surf_temp. These are read with query, not queryAdd, so
207  // that whether a key was given is the query return value: queryAdd would add a
208  // key that is absent, and these keys are also read by ERF_InputSoundingData.H and
209  // by the SurfaceLayer of every face, so the first reader would make the key look
210  // given to the rest. A key that is given must be positive; it is never ignored.
211  {
212  amrex::Real pot_st = amrex::Real(-1.);
213  amrex::Real abs_st = amrex::Real(-1.);
214  amrex::Real old_st = amrex::Real(-1.);
215  const bool has_pot = pp.query("most.pot_surf_temp", pot_st);
216  const bool has_abs = pp.query("most.abs_surf_temp", abs_st);
217  const bool has_old = pp.query("most.surf_temp", old_st);
218  if (int(has_pot) + int(has_abs) + int(has_old) > 1) {
219  amrex::Abort("Specify only one of most.pot_surf_temp, most.abs_surf_temp "
220  "and the deprecated most.surf_temp");
221  }
222  if ((has_pot && !(pot_st > amrex::Real(0))) ||
223  (has_abs && !(abs_st > amrex::Real(0))) ||
224  (has_old && !(old_st > amrex::Real(0)))) {
225  amrex::Abort("most.pot_surf_temp, most.abs_surf_temp and most.surf_temp "
226  "must be positive temperatures [K]");
227  }
228  if (has_old) {
229  amrex::Print() << "WARNING: most.surf_temp is deprecated and is read as a surface "
230  << "potential temperature; use most.pot_surf_temp (potential) or "
231  << "most.abs_surf_temp (absolute) instead" << std::endl;
232  pot_st = old_st;
233  }
234  if (has_abs) {
235  surf_temp = abs_st;
236  surf_temp_is_absolute = true;
237  } else {
238  surf_temp = pot_st;
239  }
240  }
241  const bool erf_st = (surf_temp > amrex::Real(0));
242  // With an absolute temperature this is only the placeholder until update_fluxes
243  // converts it with the surface pressure.
244  if (erf_st) { default_land_surf_temp = surf_temp; }
245 
246  if (surf_temp_is_absolute) {
248  m_face.coordDir() == 2 && m_face.isLow() && m_terrain_type != TerrainType::EB,
249  "most.abs_surf_temp is supported only on the z-low face without EB terrain");
253  "most.abs_surf_temp is not supported with custom, rico or bulk_coeff fluxes; "
254  "use most.pot_surf_temp");
255  }
256 
257  // Get surface moisture
258  bool erf_sq = false;
259  if (use_moisture) {
260  pp.queryAdd("most.surf_moist", surf_moist);
261  erf_sq = (surf_moist >= amrex::Real(0));
262  }
263  if (erf_sq) { default_land_surf_moist = surf_moist; }
264 
265  // Custom type user must specify the fluxes
270  pp.get("most.ustar", custom_ustar);
271  pp.get("most.tstar", custom_tstar);
272  pp.get("most.qstar", custom_qstar);
273  pp.queryAdd("most.rhosurf", custom_rhosurf);
274  if (custom_qstar != 0) {
276  "Specified custom MOST qv flux without moisture model!");
277  }
278  amrex::Print() << "Using specified ustar, tstar, qstar for MOST = "
279  << custom_ustar << " " << custom_tstar << " "
280  << custom_qstar << std::endl;
281 
282  // Bulk transfer coefficient (must specify coeffs and surface values)
283  } else if (flux_type == FluxCalcType::BULK_COEFF) {
284  pp.get("most.Cd", m_Cd);
285  pp.get("most.Ch", m_Ch);
286  pp.get("most.Cq", m_Cq);
288  "bulk_coeff fluxes need most.pot_surf_temp");
290  pp.get("most.surf_moist", default_land_surf_moist);
291  amrex::Print() << "Using specified Cd, Ch, Cq for MOST = "
292  << m_Cd << " " << m_Ch << " "
293  << m_Cq << std::endl;
294 
295  // Specify surface temperature/moisture or surface flux
296  } else {
297  if (erf_st) {
299  pp.queryAdd("most.surf_heating_rate", surf_heating_rate); // [K/h]
300 
301  // Modify rate to be in units of K / s rather than K / hr
302  surf_heating_rate /= amrex::Real(3600.0); // [K/s]
303 
304  if (pp.query("most.surf_temp_flux", surf_temp_flux)) {
305  amrex::Abort("Can only specify one of surf_temp_flux or surf_heating_rate");
306  }
307  } else {
308  pp.queryAdd("most.surf_temp_flux", surf_temp_flux);
309 
310  if (pp.query("most.surf_heating_rate", surf_heating_rate)) {
311  amrex::Abort("Can only specify one of surf_temp_flux or surf_heating_rate");
312  }
313  if (std::abs(surf_temp_flux) >
316  } else {
318  }
319  }
320 
321  if (erf_sq) {
323  } else {
324  pp.queryAdd("most.surf_moist_flux", surf_moist_flux);
325  if (std::abs(surf_moist_flux) >
328  } else {
330  }
331  }
332  }
333 
338  m_face.coordDir() == 2 && m_face.isLow(),
339  "HEAT_FLUX and ADIABATIC surface-layer fluxes are supported only on the z-low face.");
340  }
341 
343  {
344  pp.queryAdd("most.rico.theta_z0", rico_theta_z0);
345  pp.queryAdd("most.rico.qsat_z0", rico_qsat_z0);
346  }
347 
348  // Make sure the inputs file doesn't try to use most.roughness_type
349  std::string bogus_input;
350  if (pp.queryAdd("most.roughness_type", bogus_input) > 0) {
351  amrex::Abort("most.roughness_type is deprecated; use "
352  "most.roughness_type_land and/or most.roughness_type_sea");
353  }
354 
355  // Specify how to compute the surface flux over land (if there is any)
356  std::string rough_land_string_in;
357  std::string rough_land_string{"constant"};
358  auto read_rough_land =
359  pp.queryAdd("most.roughness_type_land", rough_land_string_in);
360  if (read_rough_land) {
361  rough_land_string = amrex::toLower(rough_land_string_in);
362  }
363  if (rough_land_string == "constant") {
365  } else {
366  amrex::Abort("Undefined MOST roughness type for land!");
367  }
368 
369  // Allow for smooth-flow limit?
370  pp.queryAdd("most.smooth_flow_viscosity", smooth_flow_visc);
371  amrex::Print() << "The smooth-flow limit will be included for variable roughness models over sea: " << smooth_flow_visc << "\n";
372 
373  // Specify how to compute the surface flux over sea (if there is any)
374  std::string rough_sea_string_in;
375  std::string rough_sea_string{"charnock"};
376  auto read_rough_sea = pp.queryAdd("most.roughness_type_sea", rough_sea_string_in);
377  if (read_rough_sea) {
378  rough_sea_string = amrex::toLower(rough_sea_string_in);
379  }
380  if (rough_sea_string == "charnock") {
382  pp.queryAdd("most.charnock_constant", cnk_a);
383  if (cnk_a > 0) {
384  amrex::Print() << "If there is water, Charnock relation with C_a="
385  << cnk_a << " will be used" << std::endl;
386  } else {
387  amrex::Print() << "If there is water, Charnock relation with variable "
388  "Charnock parameter (COARE3.0) will be used" << std::endl;
389  }
390  } else if (rough_sea_string == "coare3.0") {
392  amrex::Print() << "If there is water, Charnock relation with variable "
393  "Charnock parameter (COARE3.0) will be used" << std::endl;
394  cnk_a = -1;
395  } else if (rough_sea_string == "donelan") {
397  } else if (rough_sea_string == "modified_charnock") {
399  pp.queryAdd("most.modified_charnock_depth", depth);
400  if (depth < amrex::Real(10.0) || depth > amrex::Real(100.0) ) {
401  amrex::Print() << "Specified depth of " << depth
402  << " is outside the valid range of [10 100], resetting to bounds now."
403  << std::endl;
404  }
405  // Limiter based upon the fit range in Jiménez & Dudhia
406  depth = amrex::min(amrex::max(depth,amrex::Real(10.0)),amrex::Real(100.0));
407  } else if (rough_sea_string == "wave_coupled") {
409  } else if (rough_sea_string == "constant") {
411  } else {
412  amrex::Abort("Undefined MOST roughness type for sea!");
413  }
414 
415  // use skin temperature instead of sea-surface temperature
416  // (wrfinput data may have lower resolution SST data)
417  pp.queryAdd("most.ignore_sst", m_ignore_sst);
418 
419  // If we're using the RANS k model, then we need to update the dirichlet
420  // wall value of k (written into the first cell above the wall and held
421  // there through the step) based on the instantaneous u* and θ*; the turbulence modeling
422  // choices can vary per level but for now, assume that if specified then
423  // all levels are using the same RANS model.
424  m_update_k_rans = (a_turb_choice.rans_type == RANSType::kEqn &&
425  a_turb_choice.dirichlet_k == true);
426  if (m_update_k_rans) {
428  m_face.coordDir() == 2 && m_face.isLow(),
429  "RANS surface-layer k updates are supported only on the z-low face.");
430  }
431  if (m_update_k_rans) {
432  inv_Cmu2 = one / (a_turb_choice.Cmu0 * a_turb_choice.Cmu0);
433  theta_ref = a_turb_choice.theta_ref;
434  }
435 
436  } // constructor
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")
ParmParse pp("prob")
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:37
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_ASSERT_WITH_MESSAGE(wbar_cutoff_min > wbar_cutoff_max, "ERROR: wbar_cutoff_min < wbar_cutoff_max")
bool m_has_immersed_buildings
Definition: ERF_SurfaceLayer.H:1865
ThetaCalcType theta_type
Definition: ERF_SurfaceLayer.H:1786
bool m_include_wstar
Definition: ERF_SurfaceLayer.H:1803
bool m_rotate
Definition: ERF_SurfaceLayer.H:1797
PBLHeightCalcType pblh_type
Definition: ERF_SurfaceLayer.H:1790
double m_final_low_time
Definition: ERF_SurfaceLayer.H:1800
bool use_moisture
Definition: ERF_SurfaceLayer.H:1837
amrex::Real m_Cq
Definition: ERF_SurfaceLayer.H:1860
amrex::Vector< const eb_ * > m_eb_vec
Definition: ERF_SurfaceLayer.H:1863
RoughCalcType rough_type_land
Definition: ERF_SurfaceLayer.H:1788
SurfaceModel * m_surf_model
Definition: ERF_SurfaceLayer.H:1868
amrex::Real z0_const
Definition: ERF_SurfaceLayer.H:1804
amrex::Real cnk_a
Definition: ERF_SurfaceLayer.H:1828
amrex::Real m_Ch
Definition: ERF_SurfaceLayer.H:1859
amrex::Real surf_temp
Definition: ERF_SurfaceLayer.H:1814
double m_start_low_time
Definition: ERF_SurfaceLayer.H:1799
amrex::Real rico_qsat_z0
Definition: ERF_SurfaceLayer.H:1835
bool surf_temp_is_absolute
Definition: ERF_SurfaceLayer.H:1817
bool m_update_k_rans
Definition: ERF_SurfaceLayer.H:1939
amrex::Real surf_moist_flux
Definition: ERF_SurfaceLayer.H:1822
bool smooth_flow_visc
Definition: ERF_SurfaceLayer.H:1829
RoughCalcType rough_type_sea
Definition: ERF_SurfaceLayer.H:1789
std::string m_pp_prefix
Definition: ERF_SurfaceLayer.H:1795
amrex::Real surf_moist
Definition: ERF_SurfaceLayer.H:1821
bool m_ignore_sst
Definition: ERF_SurfaceLayer.H:1861
double m_low_time_interval
Definition: ERF_SurfaceLayer.H:1801
amrex::GpuArray< int, AMREX_SPACEDIM *2 > m_surface_layer_faces
Definition: ERF_SurfaceLayer.H:1798
amrex::Real custom_qstar
Definition: ERF_SurfaceLayer.H:1825
bool surf_model_fluxes
Definition: ERF_SurfaceLayer.H:1870
amrex::Real custom_rhosurf
Definition: ERF_SurfaceLayer.H:1826
@ 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:1866
@ SURFACE_MOISTURE
Surface Qv specified.
@ MOISTURE_FLUX
Qv-flux specified.
amrex::Real depth
Definition: ERF_SurfaceLayer.H:1830
amrex::Real default_land_surf_moist
Definition: ERF_SurfaceLayer.H:1820
bool m_z0_given
Definition: ERF_SurfaceLayer.H:1806
bool use_surface_model
Definition: ERF_SurfaceLayer.H:1869
amrex::Real rico_theta_z0
Definition: ERF_SurfaceLayer.H:1834
amrex::Real surf_temp_flux
Definition: ERF_SurfaceLayer.H:1819
amrex::Vector< amrex::Geometry > m_geom
Definition: ERF_SurfaceLayer.H:1796
amrex::Real theta_ref
Definition: ERF_SurfaceLayer.H:1941
amrex::Real custom_tstar
Definition: ERF_SurfaceLayer.H:1824
amrex::Real surf_heating_rate
Definition: ERF_SurfaceLayer.H:1818
FluxCalcType flux_type
Definition: ERF_SurfaceLayer.H:1785
MoistCalcType moist_type
Definition: ERF_SurfaceLayer.H:1787
amrex::Real inv_Cmu2
Definition: ERF_SurfaceLayer.H:1940
amrex::Real custom_ustar
Definition: ERF_SurfaceLayer.H:1823
amrex::Orientation m_face
Definition: ERF_SurfaceLayer.H:1794
amrex::Real m_Cd
Definition: ERF_SurfaceLayer.H:1858
amrex::Real default_land_surf_temp
Definition: ERF_SurfaceLayer.H:1807
@ SURFACE_TEMPERATURE
Surface temperature specified.
@ HEAT_FLUX
Heat-flux specified.
TerrainType m_terrain_type
Definition: ERF_SurfaceLayer.H:1864
MOSTAverage m_ma
Definition: ERF_SurfaceLayer.H:1867
Interface between ERF and land-surface and urban models.
Definition: ERF_SurfaceModel.H:47
bool are_fluxes()
Returns whether the interface exports fluxes rather than MOST variables.
Definition: ERF_SurfaceModel.H:432
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12
RANSType rans_type
Selected RANS closure.
Definition: ERF_TurbStruct.H:877
amrex::Real theta_ref
Reference potential temperature for stable stratification.
Definition: ERF_TurbStruct.H:864
bool dirichlet_k
Whether TKE uses Dirichlet boundary treatment.
Definition: ERF_TurbStruct.H:879
amrex::Real Cmu0
One-equation RANS Cmu0 coefficient.
Definition: ERF_TurbStruct.H:853

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
575 {
576  // Pointers to the computed averages
577  const auto *const tm_ptr = m_ma.get_average(lev,3); // potential temperature
578  const auto *const qvm_ptr = m_ma.get_average(lev,4); // water vapor mixing ratio
579  const auto *const tvm_ptr = m_ma.get_average(lev,5); // virtual potential temperature
580  const auto *const umm_ptr = m_ma.get_average(lev,6); // horizontal velocity magnitude
581  const auto *const uw_mag_mean = m_ma.get_average(lev,7); // x/z velocity magnitude
582  const auto *const vw_mag_mean = m_ma.get_average(lev,8); // y/z velocity magnitude
583  const auto *const zref_ptr = m_ma.get_zref(lev); // reference height
584  const bool l_use_eb = (m_terrain_type == TerrainType::EB);
585 
586  const int dir = m_face.coordDir();
587  // The source fields and Taus use the same BoxArray ordering and
588  // DistributionMapping. Full-state fields follow cons_in, while the
589  // collapsed SurfaceLayer/MOSTAverage fields follow u_star.
590  int sm_index = 0;
591  if (m_face.isLow()) {
592  sm_index = m_geom[lev].Domain().smallEnd(dir);
593  } else {
594  sm_index = m_geom[lev].Domain().bigEnd(dir);
595  }
596 
597  // Use the 2-D surface mask as the iterator so ranks participate only when
598  // their grids coincide with the selected face.
599  for (MFIter mfi(*m_lmask_lev[lev][0]); mfi.isValid(); ++mfi)
600  {
601  // Face ownership is a property of the full grid, not an individual
602  // tile. Keep the tile box for the kernel below, but test the valid
603  // box so all tiles of a face-owning grid are processed.
604  Box vbx = mfi.validbox();
605  const Box tbx = mfi.tilebox();
606  Box gtbx = mfi.growntilebox();
607 
608  // Since lmask is used in the MFIter, its z extent is collapsed. The
609  // lateral kernels need the full z column; z-face ownership is instead
610  // determined from the original 3-D surface-copy mapping.
611  if (dir != 2 || l_use_eb) {
612  gtbx.setSmall(2, m_geom[lev].Domain().smallEnd(2));
613  gtbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
614  }
615 
616  const bool owns_surface = l_use_eb || dir != 2 ||
617  m_planar_bndry[lev].is_surface_copy(mfi.index());
618  if (!owns_surface) {
619  continue;
620  }
621 
622  if (dir != 2) {
623  if (m_face.isLow()) {
624  if (vbx.smallEnd(dir) != sm_index ||
625  tbx.smallEnd(dir) != sm_index) {
626  continue;
627  }
628  } else {
629  if (vbx.bigEnd(dir) != sm_index ||
630  tbx.bigEnd(dir) != sm_index) {
631  continue;
632  }
633  }
634  }
635 
636  if (m_face.isLow()) {
637  gtbx.setSmall(dir, sm_index);
638  gtbx.setBig(dir, sm_index);
639  } else {
640  gtbx.setSmall(dir, sm_index);
641  gtbx.setBig(dir, sm_index);
642  }
643 
644  // X/Y faces still need the full valid z column
645  if (!l_use_eb && dir == 2) {
646  gtbx.makeSlab(2, sm_index);
647  } else {
648  gtbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
649  }
650 
651  // The mask iterator can have more lateral ghost cells than the
652  // surface-layer fields, and an EB FAB may be decomposed in z. Keep
653  // the face selection above, but never launch outside the target FAB.
654  gtbx &= u_star[lev]->fabbox(mfi.index());
655  if (gtbx.isEmpty()) { continue; }
656 
657  auto u_star_arr = u_star[lev]->array(mfi);
658  auto t_star_arr = t_star[lev]->array(mfi);
659  auto q_star_arr = q_star[lev]->array(mfi);
660  auto t_surf_arr = t_surf[lev]->array(mfi);
661  auto q_surf_arr = q_surf[lev]->array(mfi);
662  auto olen_arr = olen[lev]->array(mfi);
663 
664  const auto tm_arr = tm_ptr->array(mfi);
665  const auto tvm_arr = tvm_ptr->array(mfi);
666  const auto qvm_arr = qvm_ptr->array(mfi);
667  const auto umm_arr = umm_ptr->array(mfi);
668  const auto vwmm_arr = (dir == 0) ? vw_mag_mean->array(mfi) : Array4<Real>{};
669  const auto uwmm_arr = (dir == 1) ? uw_mag_mean->array(mfi) : Array4<Real>{};
670  const auto zref_arr = zref_ptr->array(mfi);
671 
672  // umm depending on face direction (YZ, XZ, XY)
673  const auto dir_umm_arr = ((dir == 0) ? vwmm_arr : ((dir == 1) ? uwmm_arr : umm_arr));
674  const auto z0_arr = z_0[lev].array(mfi);
675 
676  // PBL height if we need to calculate wstar for the Beljaars correction
677  // TODO: can/should we apply this in LES mode?
678  const auto w_star_arr = (m_include_wstar) ? w_star[lev].get()->array(mfi) : Array4<Real> {};
679  const auto pblh_arr = (m_include_wstar) ? pblh[lev].get()->array(mfi) : Array4<Real> {};
680 
681  // Wave properties if they exist
682  const auto Hwave_arr = (m_Hwave_lev[lev]) ? m_Hwave_lev[lev]->array(mfi) : Array4<Real> {};
683  const auto Lwave_arr = (m_Lwave_lev[lev]) ? m_Lwave_lev[lev]->array(mfi) : Array4<Real> {};
684  const auto eta_arr = (m_eddyDiffs_lev[lev]) ? m_eddyDiffs_lev[lev]->array(mfi) : Array4<Real> {};
685 
686  // Land mask array if it exists
687  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
688  Array4<int> {};
689 
690  // Get EB flags if needed
691  const auto flag_arr = (l_use_eb) ? m_eb_vec[lev]->get_const_factory()->getMultiEBCellFlagFab()[mfi].const_array() : Array4<const EBCellFlag>{};
692 
693  if (!l_use_eb) {
694  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
695  {
696  // always check for land mask at k=0 even if on other faces
697  if (( is_land && lmask_arr(i,j,0) == 1) ||
698  (!is_land && lmask_arr(i,j,0) == 0))
699  {
700  // NOTE: All 2D MFs so k index is always 0 from ba2d definition
701  most_flux.iterate_flux(i, j, k, max_iters,
702  zref_arr, // set in most average
703  z0_arr, // updated if(!is_land)
704  dir_umm_arr, tm_arr, tvm_arr, qvm_arr,
705  u_star_arr, // updated
706  w_star_arr, // updated if(m_include_wstar)
707  t_star_arr, q_star_arr, // updated
708  t_surf_arr, q_surf_arr, olen_arr, // updated
709  pblh_arr, // updated if(m_include_wstar)
710  Hwave_arr, Lwave_arr, eta_arr);
711  }
712  });
713  // EB
714  } else {
715  if (std::is_same<FluxIter, adiabatic_eb>::value ||
716  std::is_same<FluxIter, surface_temp_eb>::value ||
717  std::is_same<FluxIter, surface_flux_eb>::value) {
718  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
719  {
720  if (( is_land && lmask_arr(i,j,0) == 1) ||
721  (!is_land && lmask_arr(i,j,0) == 0))
722  {
723  if (flag_arr(i,j,k).isSingleValued()) {
724  most_flux.iterate_flux(i, j, k, max_iters,
725  zref_arr, // set in most average
726  z0_arr, // updated if(!is_land)
727  umm_arr, tm_arr, tvm_arr, qvm_arr,
728  u_star_arr, // updated
729  w_star_arr, // updated if(m_include_wstar)
730  t_star_arr, q_star_arr, // updated
731  t_surf_arr, q_surf_arr, olen_arr, // updated
732  pblh_arr, // updated if(m_include_wstar)
733  Hwave_arr, Lwave_arr, eta_arr);
734  }
735  }
736  });
737  } else {
738  amrex::Abort("FluxIter type not supported for EB");
739  }
740  }
741  }
742 }
pp_for_pert_vels get("prob_type", prob_type)
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:1913
amrex::Vector< PlanarBoundary > m_planar_bndry
Definition: ERF_SurfaceLayer.H:1883
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_surf
Definition: ERF_SurfaceLayer.H:1877
amrex::Vector< std::unique_ptr< amrex::MultiFab > > q_star
Definition: ERF_SurfaceLayer.H:1874
amrex::Vector< amrex::MultiFab * > m_Lwave_lev
Definition: ERF_SurfaceLayer.H:1936
amrex::Vector< amrex::MultiFab > z_0
Definition: ERF_SurfaceLayer.H:1831
amrex::Vector< std::unique_ptr< amrex::MultiFab > > w_star
Definition: ERF_SurfaceLayer.H:1872
amrex::Vector< std::unique_ptr< amrex::MultiFab > > u_star
Definition: ERF_SurfaceLayer.H:1871
amrex::Vector< std::unique_ptr< amrex::MultiFab > > q_surf
Definition: ERF_SurfaceLayer.H:1878
amrex::Vector< amrex::MultiFab * > m_Hwave_lev
Definition: ERF_SurfaceLayer.H:1935
amrex::Vector< std::unique_ptr< amrex::MultiFab > > t_star
Definition: ERF_SurfaceLayer.H:1873
amrex::Vector< amrex::MultiFab * > m_eddyDiffs_lev
Definition: ERF_SurfaceLayer.H:1937
amrex::Vector< std::unique_ptr< amrex::MultiFab > > olen
Definition: ERF_SurfaceLayer.H:1875
amrex::Vector< std::unique_ptr< amrex::MultiFab > > pblh
Definition: ERF_SurfaceLayer.H:1876
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
3144 {
3145  const MultiFab& cons = vars[lev][Vars::cons];
3146  const iMultiFab* lmask = m_lmask_lev[lev][0];
3147 
3148  // The estimator scans each box from its lowest cell to its highest and writes the planar
3149  // pblh of that box, so every box it is given must start at the ground. Grids that hold
3150  // such boxes only -- full height or not -- go straight to it. Any other grids (boxes
3151  // stacked in z, or boxes aloft) go through columns: the runs of cells that start at the
3152  // ground, each as one box (see define_pblh_columns).
3153  if (static_cast<int>(m_pblh_columns.size()) <= lev) { m_pblh_columns.resize(lev+1); }
3154  if (m_pblh_columns[lev].ba != cons.boxArray() ||
3155  m_pblh_columns[lev].dm != cons.DistributionMap()) {
3156  define_pblh_columns(lev, cons.boxArray(), cons.DistributionMap());
3157  }
3158  const PBLHColumns& cols = m_pblh_columns[lev];
3159 
3160  // A refined level whose grids do not run from the ground to the top of the domain in every
3161  // column cannot diagnose the boundary layer there: a level that ends below the top would
3162  // return a height capped by its grids, and a level that does not reach the ground has no
3163  // column to scan (it was left at zero). Such a level takes the PBL height of the next
3164  // coarser level at each of its columns. Levels are updated coarse to fine, so that height
3165  // is the one set at the start of the enclosing coarse step: with subcycling, the later fine
3166  // substeps read a coarse height up to one coarse step old. A refined level that spans the
3167  // full height diagnoses its own.
3168  if (lev > 0 && !cols.full_height && m_face.coordDir() == 2 && m_face.isLow() &&
3169  m_terrain_type != TerrainType::EB) {
3171  return;
3172  }
3173 
3174  if (!cols.needed) {
3175  est.compute_pblh(m_geom[lev], z_phys_cc, pblh[lev].get(), cons, lmask, moisture_indices);
3176  return;
3177  }
3178 
3179  // Zero is the estimator's own value for a height it did not find. It stays on the planar
3180  // boxes over which no box of this level reaches the ground: all of them on a level that
3181  // lies entirely aloft.
3182  pblh[lev]->setVal(zero);
3183  if (cols.ba_col.empty()) { return; }
3184 
3185  const Periodicity period = m_geom[lev].periodicity();
3186 
3187  // The estimator reads the density, the potential temperature, the TKE and the moisture
3188  // species that enter theta_v. Those live in [0, RhoKE_comp] and in the moist window, so
3189  // the columns carry the state up to the highest of them and no further: the species above
3190  // it (the number concentrations of a two-moment scheme, the non-water species) are the
3191  // bulk of a moist state and are never read here. The span is contiguous rather than the
3192  // two pieces it is made of, so that every component of cons_col is filled by the copies
3193  // below -- a component the estimator reads must never be one this routine left unset --
3194  // and the components keep their place, since the estimator indexes the state by
3195  // component number.
3196  int q_hi = -1;
3197  for (const int q : {moisture_indices.qv, moisture_indices.qc, moisture_indices.qi,
3198  moisture_indices.qr, moisture_indices.qs, moisture_indices.qg}) {
3199  AMREX_ALWAYS_ASSERT((q < 0) || ((q > RhoKE_comp) && (q < cons.nComp())));
3200  q_hi = std::max(q_hi, q);
3201  }
3202  const int ncomp_col = std::max(RhoKE_comp+1, q_hi+1);
3203 
3204  // The halo the columns need: in x and y the ghost cells of pblh, which the estimator
3205  // writes and so reads the state over, and in z the one cell above the top of each column
3206  // that the scan's k+1 reads end in. The state must hold that halo for the copies below
3207  // to have anything to take it from.
3208  const IntVect ng_pblh = pblh[lev]->nGrowVect();
3209  const IntVect ng_col = elemwiseMax(ng_pblh, IntVect(AMREX_D_DECL(0,0,1)));
3210  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(cons.nGrowVect().allGE(ng_col),
3211  "erf.most.pblh_calc = MYNN25 on grids that do not all start at the ground needs the "
3212  "state to carry at least the ghost cells of the surface-layer fields, and one in z.");
3213 
3214  // Every valid cell of a column is a valid cell of this level, and every ghost cell of a
3215  // column is a valid or a ghost cell of the box that holds the cell next to it, so the two
3216  // passes below leave no cell of the columns unset. Ghost cells go first (they hold the
3217  // physical boundary values and, next to a coarser level, the values interpolated from
3218  // it), then the valid cells, so that every cell this level owns comes from the box that
3219  // owns it and not from a neighbour's ghost cell.
3220  MultiFab cons_col(cols.ba_col, cols.dm_col, ncomp_col, ng_col);
3221  for (const IntVect& ng_src : {ng_col, IntVect(0)}) {
3222  cons_col.ParallelCopy(cons, 0, 0, ncomp_col, ng_src, ng_col, period);
3223  }
3224 
3225  std::unique_ptr<MultiFab> zcc_col;
3226  if (z_phys_cc) {
3227  const IntVect ng_z = z_phys_cc->nGrowVect();
3228  zcc_col = std::make_unique<MultiFab>(cols.ba_col, cols.dm_col, 1, ng_z);
3229  zcc_col->ParallelCopy(*z_phys_cc, 0, 0, 1, ng_z, ng_z, period);
3230  zcc_col->ParallelCopy(*z_phys_cc, 0, 0, 1, IntVect(0), ng_z, period);
3231  }
3232 
3233  std::unique_ptr<iMultiFab> lmask_col;
3234  if (lmask) {
3235  lmask_col = std::make_unique<iMultiFab>(cols.ba_col2d, cols.dm_col, 1, ng_pblh);
3236  lmask_col->setVal(1);
3237  lmask_col->ParallelCopy(*lmask, 0, 0, 1, elemwiseMin(lmask->nGrowVect(), ng_pblh), ng_pblh, period);
3238  lmask_col->ParallelCopy(*lmask, 0, 0, 1, IntVect(0), ng_pblh, period);
3239  }
3240 
3241  MultiFab pblh_col(cols.ba_col2d, cols.dm_col, 1, ng_pblh);
3242  est.compute_pblh(m_geom[lev], zcc_col.get(), &pblh_col, cons_col, lmask_col.get(), moisture_indices);
3243 
3244  // Onto every planar box. The ghost cells of the columns go first, for the ghost cells of
3245  // pblh outside the domain. They also reach valid cells of pblh over which no box of this
3246  // level starts at the ground (the estimator fills the ghost cells of a column next to such
3247  // a gap from ghost data), so those are set back to zero before the valid cells of the
3248  // columns are copied. Every planar copy of a cell then holds the same value, which makes
3249  // the FillBoundary that ends this well defined despite the duplicate boxes.
3250  pblh[lev]->ParallelCopy(pblh_col, 0, 0, 1, ng_pblh, ng_pblh, period);
3251  pblh[lev]->setVal(zero, 0, 1, 0);
3252  pblh[lev]->ParallelCopy(pblh_col, 0, 0, 1, IntVect(0), IntVect(0), period);
3253  pblh[lev]->FillBoundary(period);
3254 }
#define RhoKE_comp
Definition: ERF_IndexDefines.H:55
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:36
amrex::Vector< PBLHColumns > m_pblh_columns
Definition: ERF_SurfaceLayer.H:1902
void fill_pblh_from_coarser(const int &lev)
Definition: ERF_SurfaceLayer.cpp:3334
void define_pblh_columns(const int &lev, const amrex::BoxArray &ba, const amrex::DistributionMapping &dm)
Definition: ERF_SurfaceLayer.cpp:3265
@ cons
Definition: ERF_IndexDefines.H:254
@ q
Definition: ERF_WSM6.H:294
int qs
snow
Definition: ERF_DataStruct.H:266
int qr
rain
Definition: ERF_DataStruct.H:265
int qi
cloud ice
Definition: ERF_DataStruct.H:264
int qv
water vapor
Definition: ERF_DataStruct.H:262
int qc
cloud liquid water
Definition: ERF_DataStruct.H:263
int qg
graupel
Definition: ERF_DataStruct.H:267
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
1826 {
1828  static_cast<int>(m_face) == Orientation::zlo(),
1829  "LSM surface-layer parameters are supported only on the z-low face.");
1830 
1832  bool has_moisture = use_moisture;
1833  const bool use_surface_model_fluxes = use_surface_model && surf_model_fluxes;
1834  const int klo = m_geom[lev].Domain().smallEnd(2);
1835  const auto *const umm_ptr = m_ma.get_average(lev,6); // horizontal velocity magnitude
1836  const auto *const zref_ptr = m_ma.get_zref(lev); // reference height
1837  for (MFIter mfi(cons_in); mfi.isValid(); ++mfi) {
1838 
1839  Box vbx = mfi.validbox();
1840  if (vbx.smallEnd(2) != klo) { continue; }
1841  vbx.makeSlab(2,0);
1842 
1843  // The ghost columns are set too, so that they hold the same kind of value as the
1844  // valid columns: compute_fluxes writes MOST values over the grown box, and
1845  // fill_planar_boundary below refills only the interior and periodic ghosts, so a
1846  // coarse/fine or domain-edge ghost would otherwise keep a MOST value next to
1847  // LSM values. The LSM fluxes do not carry lateral ghost cells, so a ghost column
1848  // takes the fluxes of the nearest valid column, as in get_lsm_tsurf, with its own
1849  // atmospheric state.
1850  const NearestColumn col(vbx);
1851  const int idx = mfi.index();
1852  Box gbx = amrex::grow(vbx, IntVect(AMREX_D_DECL(u_star[lev]->nGrow(0),
1853  u_star[lev]->nGrow(1), 0)));
1854  Box cons_slab = cons_in.fabbox(idx);
1855  cons_slab.setRange(2, 0);
1856  gbx &= u_star[lev]->fabbox(idx);
1857  gbx &= z_0[lev].fabbox(idx);
1858  gbx &= umm_ptr->fabbox(idx);
1859  gbx &= zref_ptr->fabbox(idx);
1860  gbx &= cons_slab;
1861  if (m_lmask_lev[lev][0]) { gbx &= m_lmask_lev[lev][0]->fabbox(idx); }
1862 
1863  // Get CC state
1864  const Array4<const Real> cons_arr = cons_in.const_array(mfi);
1865 
1866  // Get SL params
1867  const auto u_star_arr = u_star[lev]->array(mfi);
1868  const auto t_star_arr = t_star[lev]->array(mfi);
1869  const auto q_star_arr = q_star[lev]->array(mfi);
1870  const auto olen_arr = olen[lev]->array(mfi);
1871 
1872  const auto umm_arr = umm_ptr->array(mfi);
1873  const auto zref_arr = zref_ptr->array(mfi);
1874  const auto z0_arr = z_0[lev].array(mfi);
1875 
1876  // Get LSM fluxes
1877  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
1878  Array4<int> {};
1879  auto lsm_t_flux_arr = Array4<const Real> {};
1880  auto lsm_q_flux_arr = Array4<const Real> {};
1881  auto lsm_tau13_arr = Array4<const Real> {};
1882  auto lsm_tau23_arr = Array4<const Real> {};
1883  // compute_sfc_params_from_lsm_fluxes consumes signed kinematic stress
1884  // components; their vector magnitude determines u_star^2.
1885  if (use_surface_model_fluxes) {
1886  const SurfaceFluxView surf_flux = m_surf_model->get_surface_flux_view(lev, mfi);
1887  lsm_t_flux_arr = surf_flux.t_flux;
1888  lsm_q_flux_arr = surf_flux.q_flux;
1889  lsm_tau13_arr = surf_flux.tau13;
1890  lsm_tau23_arr = surf_flux.tau23;
1891  } else {
1892  for (int n(0); n<m_lsm_flux_lev[lev].size(); ++n) {
1893  if (toLower(m_lsm_flux_name[n]) == "t_flux") { lsm_t_flux_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1894  if (toLower(m_lsm_flux_name[n]) == "q_flux") { lsm_q_flux_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1895  if (toLower(m_lsm_flux_name[n]) == "tau13") { lsm_tau13_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1896  if (toLower(m_lsm_flux_name[n]) == "tau23") { lsm_tau23_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1897  }
1898  }
1899 
1900  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int /*k*/) noexcept
1901  {
1902  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
1903  const int li = col.i(i);
1904  const int lj = col.j(j);
1905  // Only ordinary land (mask 1) uses LSM fluxes. Immersed buildings and
1906  // terrain (mask 2) retain their existing surface-layer parameters.
1907  // Skip cells where the LSM does not have a valid value (finite and below
1908  // lsm_undefined) for every one of the four fluxes; these keep the MOST values.
1909  if (is_land == 1 && lsm_t_flux_arr &&
1910  is_valid_lsm_value(lsm_t_flux_arr(li,lj,0)) &&
1911  is_valid_lsm_value(lsm_q_flux_arr(li,lj,0)) &&
1912  is_valid_lsm_value(lsm_tau13_arr (li,lj,0)) &&
1913  is_valid_lsm_value(lsm_tau23_arr (li,lj,0))) {
1914  const Real t_flux = lsm_t_flux_arr(li,lj,0);
1915  const Real q_flux = lsm_q_flux_arr(li,lj,0);
1916  Real rho = cons_arr(i,j,klo,Rho_comp);
1917  Real Thd = cons_arr(i,j,klo,RhoTheta_comp) / rho;
1918  Real qv = (has_moisture) ? cons_arr(i,j,klo,RhoQ1_comp) / rho : zero;
1919  Real Thv = Thd * (one + epsv*qv);
1920  Real tau = std::sqrt( lsm_tau13_arr(li,lj,0)*lsm_tau13_arr(li,lj,0)
1921  + lsm_tau23_arr(li,lj,0)*lsm_tau23_arr(li,lj,0) );
1922  u_star_arr(i,j,0) = amrex::max(std::sqrt(tau),eps);
1923  if (t_flux>=zero) {
1924  t_star_arr(i,j,0) = amrex::min(-t_flux / u_star_arr(i,j,0),-eps);
1925  } else {
1926  t_star_arr(i,j,0) = amrex::max(-t_flux / u_star_arr(i,j,0),eps);
1927  }
1928  if (q_flux>=zero) {
1929  q_star_arr(i,j,0) = amrex::min(-q_flux / u_star_arr(i,j,0),-eps);
1930  } else {
1931  q_star_arr(i,j,0) = amrex::max(-q_flux / u_star_arr(i,j,0),eps);
1932  }
1933  Real tstv = t_star_arr(i,j,0)*(one + epsv*qv) + epsv*Thd*q_star_arr(i,j,0);
1934  tstv = (tstv >= zero) ? amrex::max(tstv, eps) : amrex::min(tstv, -eps);
1935  olen_arr(i,j,0) = ( u_star_arr(i,j,0) * u_star_arr(i,j,0) * Thv ) /
1936  ( KAPPA * CONST_GRAV * tstv );
1937  z0_arr(i,j,0) = Compute_roughness(zref_arr(i,j,0), olen_arr(i,j,0),
1938  umm_arr(i,j,0), u_star_arr(i,j,0));
1939  }
1940  });
1941  } // mfi
1942 }
constexpr amrex::Real epsv
Definition: ERF_Constants.H:56
constexpr amrex::Real KAPPA
Definition: ERF_Constants.H:71
constexpr amrex::Real CONST_GRAV
Definition: ERF_Constants.H:72
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool is_valid_lsm_value(amrex::Real v)
Definition: ERF_Constants.H:38
#define Rho_comp
Definition: ERF_IndexDefines.H:53
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:54
#define RhoQ1_comp
Definition: ERF_IndexDefines.H:66
const int klo
Definition: ERF_InitCustomPert_KE.H:9
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE int idx(int i, int j, int k, int nx, int ny)
Definition: ERF_InitForEnsemble.cpp:404
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:757
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_lsm_flux_lev
Definition: ERF_SurfaceLayer.H:1932
amrex::Vector< std::string > m_lsm_flux_name
Definition: ERF_SurfaceLayer.H:1934
SurfaceFluxView get_surface_flux_view(const int lev, const amrex::MFIter &mfi) const
Returns provider or blended surface-flux views for one tile.
Definition: ERF_SurfaceModel.H:514
@ t_flux
Definition: ERF_NOAHMP_Fields.H:125
@ q_flux
Definition: ERF_NOAHMP_Fields.H:126
@ rho
Definition: ERF_Kessler.H:25
@ qv
Definition: ERF_Kessler.H:31
Non-owning views of surface fluxes for one tile.
Definition: ERF_SurfaceModel.H:22
amrex::Array4< const amrex::Real > q_flux
Definition: ERF_SurfaceModel.H:26
amrex::Array4< const amrex::Real > tau23
Definition: ERF_SurfaceModel.H:24
amrex::Array4< const amrex::Real > tau13
Definition: ERF_SurfaceModel.H:23
amrex::Array4< const amrex::Real > t_flux
Definition: ERF_SurfaceModel.H:25
Here is the call graph for this function:

◆ compute_SurfaceLayer_bcs() [1/2]

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

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,
MultiFab *  mf_surface_fluxes 
)

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
978 {
979  bool rotate = m_rotate;
980 
981  const int dir = m_face.coordDir();
983  !mfs.empty() && mfs[0] != nullptr,
984  "Surface-layer stress computation requires a conserved-state MultiFab.");
986  Tau_lev[TauType::tau13] != nullptr && Tau_lev[TauType::tau23] != nullptr,
987  "tau13 and tau23 are required by surface-layer stress computation.");
988  int sm_index = 0;
989  if (m_face.isLow()) {
990  sm_index = m_geom[lev].Domain().smallEnd(dir);
991  } else {
992  sm_index = m_geom[lev].Domain().bigEnd(dir);
993  }
994 
995  // These requirements are invariant over the loop below. Check them once
996  // before acquiring per-tile array views.
997  if (dir == 0) {
999  Tau_lev[TauType::tau31] != nullptr,
1000  "tau31 is required when imposing an x-face surface layer.");
1002  Tau_lev[TauType::tau21] != nullptr,
1003  "tau21 is required when imposing an x-face surface layer.");
1004  } else if (dir == 1) {
1006  Tau_lev[TauType::tau32] != nullptr,
1007  "tau32 is required when imposing a y-face surface layer.");
1009  Tau_lev[TauType::tau12] != nullptr,
1010  "tau12 is required when imposing a y-face surface layer.");
1011  }
1012 
1013  const int klo = sm_index;
1014  const auto& dxInv = m_geom[lev].InvCellSizeArray();
1015  const Box domain = m_geom[lev].Domain();
1016  const int xlo_node = domain.smallEnd(0);
1017  const int xhi_node = domain.bigEnd(0) + 1;
1018  const int ylo_node = domain.smallEnd(1);
1019  const int yhi_node = domain.bigEnd(1) + 1;
1020  const int zlo_node = domain.smallEnd(2);
1021  const int zhi_node = domain.bigEnd(2) + 1;
1022  const bool xlo_surface = m_surface_layer_faces[Orientation::xlo()];
1023  const bool xhi_surface = m_surface_layer_faces[Orientation::xhi()];
1024  const bool ylo_surface = m_surface_layer_faces[Orientation::ylo()];
1025  const bool yhi_surface = m_surface_layer_faces[Orientation::yhi()];
1026  const bool zlo_surface = m_surface_layer_faces[Orientation::zlo()];
1027  const bool zhi_surface = m_surface_layer_faces[Orientation::zhi()];
1028 
1029  for (MFIter mfi(*mfs[0]); mfi.isValid(); ++mfi)
1030  {
1031  // Skip boxes that do not own the selected face before acquiring any
1032  // arrays from collapsed or staggered layouts. Interior boxes may
1033  // share the same collapsed coordinates but do not contain surface
1034  // data for this boundary.
1035  Box bx = mfi.tilebox();
1036  const Box valid_bx = mfi.validbox();
1037  if (m_face.isLow()) {
1038  if (valid_bx.smallEnd(dir) != klo ||
1039  bx.smallEnd(dir) != klo) {
1040  continue;
1041  }
1042  bx.setBig(dir, bx.smallEnd(dir));
1043  } else {
1044  if (valid_bx.bigEnd(dir) != klo ||
1045  bx.bigEnd(dir) != klo) {
1046  continue;
1047  }
1048  bx.setSmall(dir, bx.bigEnd(dir));
1049  }
1050 
1051  // Get field arrays
1052  const auto cons_arr = mfs[Vars::cons]->array(mfi);
1053  const auto velx_arr = mfs[Vars::xvel]->array(mfi);
1054  const auto vely_arr = mfs[Vars::yvel]->array(mfi);
1055  const auto velz_arr = mfs[Vars::zvel]->array(mfi);
1056 
1057  // Output stresses:
1058  // T Q U V
1059  // X-faces: hfx1 qfx1 t21 t31
1060  // Y-faces: hfx2 qfx2 t12 t32
1061  // Z-faces: hfx3 qfx3 t13 t23
1062 
1063  // Output stresses nodal locations:
1064  // T Q
1065  // X-faces: hfx1 qfx1(1,0,0) t21(V)(1,1,0) t31(W)(1,0,1)
1066  // Y-faces: hfx2 qfx2(0,1,0) t12(U)(1,1,0) t32(W)(0,1,1)
1067  // Z-faces: hfx3 qfx3(0,0,1) t13(U)(1,0,1) t23(V)(0,1,1)
1068 
1069  // Diffusive stress vars
1070  auto t13_arr = Tau_lev[TauType::tau13]->array(mfi);
1071  auto t31_arr = Tau_lev[TauType::tau31]
1072  ? Tau_lev[TauType::tau31]->array(mfi) : Array4<Real>{};
1073 
1074  auto t23_arr = Tau_lev[TauType::tau23]->array(mfi);
1075  auto t32_arr = Tau_lev[TauType::tau32]
1076  ? Tau_lev[TauType::tau32]->array(mfi) : Array4<Real>{};
1077 
1078 
1079  auto hfx3_arr = zheat_flux->array(mfi);
1080  auto qfx3_arr = (zqv_flux) ? zqv_flux->array(mfi) : Array4<Real>{};
1081 
1082  auto olen_arr = olen[lev]->array(mfi);
1083 
1084  // Rotated stress vars
1085  auto t11_arr = (m_rotate) ? Tau_lev[TauType::tau11]->array(mfi) : Array4<Real>{};
1086  auto t22_arr = (m_rotate) ? Tau_lev[TauType::tau22]->array(mfi) : Array4<Real>{};
1087  auto t33_arr = (m_rotate) ? Tau_lev[TauType::tau33]->array(mfi) : Array4<Real>{};
1088  auto t12_arr = Tau_lev[TauType::tau12]
1089  ? Tau_lev[TauType::tau12]->array(mfi) : Array4<Real>{};
1090  auto t21_arr = Tau_lev[TauType::tau21]
1091  ? Tau_lev[TauType::tau21]->array(mfi) : Array4<Real>{};
1092 
1093  auto hfx1_arr = (m_rotate || dir == 0) ? xheat_flux->array(mfi) : Array4<Real>{};
1094  auto hfx2_arr = (m_rotate || dir == 1) ? yheat_flux->array(mfi) : Array4<Real>{};
1095  auto qfx1_arr = (xqv_flux && (m_rotate || dir == 0)) ? xqv_flux->array(mfi) : Array4<Real>{};
1096  auto qfx2_arr = (yqv_flux && (m_rotate || dir == 1)) ? yqv_flux->array(mfi) : Array4<Real>{};
1097 
1098  // Terrain
1099  const auto zphys_arr = (z_phys) ? z_phys->const_array(mfi) : Array4<const Real>{};
1100 
1101  // Get average arrays
1102  const auto *const u_mean = m_ma.get_average(lev,0);
1103  const auto *const v_mean = m_ma.get_average(lev,1);
1104  const auto *const w_mean = m_ma.get_average(lev,2);
1105  const auto *const t_mean = m_ma.get_average(lev,3);
1106  const auto *const q_mean = m_ma.get_average(lev,4);
1107  const auto *const u_mag_mean = m_ma.get_average(lev,6);
1108  const auto *const uw_mag_mean = m_ma.get_average(lev,7);
1109  const auto *const vw_mag_mean = m_ma.get_average(lev,8);
1110  const auto *const zref_ptr = m_ma.get_zref(lev);
1111 
1112  const auto um_arr = u_mean->array(mfi);
1113  const auto vm_arr = v_mean->array(mfi);
1114  const auto wm_arr = w_mean->array(mfi);
1115  const auto tm_arr = t_mean->array(mfi);
1116  const auto qm_arr = q_mean->array(mfi);
1117  const auto umm_arr = u_mag_mean->array(mfi);
1118  const auto vwmm_arr = (dir == 0) ? vw_mag_mean->array(mfi) : Array4<Real>{};
1119  const auto uwmm_arr = (dir == 1) ? uw_mag_mean->array(mfi) : Array4<Real>{};
1120 
1121  // umm depending on face direction (YZ, XZ, XY)
1122  const auto dir_umm_arr = ((dir == 0) ? vwmm_arr : ((dir == 1) ? uwmm_arr : umm_arr));
1123 
1124  const auto zref_arr = zref_ptr->array(mfi);
1125  const auto z0_arr = z_0[lev].array(mfi);
1126 
1127  // Get derived arrays
1128  const auto u_star_arr = u_star[lev]->array(mfi);
1129  const auto t_star_arr = t_star[lev]->array(mfi);
1130  const auto q_star_arr = q_star[lev]->array(mfi);
1131  const auto t_surf_arr = t_surf[lev]->array(mfi);
1132  const auto q_surf_arr = q_surf[lev]->array(mfi);
1133  auto surface_source_arr = surface_diagnostic_source[lev]->array(mfi);
1134 
1135  // Get LSM fluxes
1136  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
1137  Array4<int> {};
1138  auto lsm_t_flux_arr = Array4<const Real> {};
1139  auto soil_t_flux_arr = Array4<Real> {};
1140  auto lsm_q_flux_arr = Array4<const Real> {};
1141  auto lsm_tau13_arr = Array4<const Real> {};
1142  auto lsm_tau23_arr = Array4<const Real> {};
1143  // LSM tau fields are cell-centered kinematic stresses [m2 s-2].
1144  // Tau_lev tau13/tau23 are face-centered conservative stresses [N m-2].
1145  for (int n(0); n<m_lsm_flux_lev[lev].size(); ++n) {
1146  if (toLower(m_lsm_flux_name[n]) == "t_flux") { lsm_t_flux_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1147  if (toLower(m_lsm_flux_name[n]) == "soil_t_flux") { soil_t_flux_arr = m_lsm_flux_lev[lev][n]->array(mfi); }
1148  if (toLower(m_lsm_flux_name[n]) == "q_flux") { lsm_q_flux_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1149  if (toLower(m_lsm_flux_name[n]) == "tau13") { lsm_tau13_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1150  if (toLower(m_lsm_flux_name[n]) == "tau23") { lsm_tau23_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
1151  }
1152 
1153  // Get provider or weight-averaged LSM+Urban fluxes through SurfaceModel.
1154  const SurfaceFluxView surf_flux = (use_surface_model && surf_model_fluxes)
1156  const auto surf_tflux_arr = surf_flux.t_flux;
1157  const auto surf_qflux_arr = surf_flux.q_flux;
1158  const auto surf_uflux_arr = surf_flux.tau13;
1159  const auto surf_vflux_arr = surf_flux.tau23;
1160  const bool use_surface_model_fluxes = use_surface_model && surf_model_fluxes;
1161  const auto t_flux_arr = use_surface_model_fluxes ? surf_tflux_arr : lsm_t_flux_arr;
1162  const auto q_flux_arr = use_surface_model_fluxes ? surf_qflux_arr : lsm_q_flux_arr;
1163  const auto surface_tau13_arr = use_surface_model_fluxes ? surf_uflux_arr : lsm_tau13_arr;
1164  const auto surface_tau23_arr = use_surface_model_fluxes ? surf_vflux_arr : lsm_tau23_arr;
1165  const bool has_lsm_t_flux = static_cast<bool>(t_flux_arr);
1166  const bool is_custom = (flux_type == FluxCalcType::CUSTOM);
1167  const bool is_rico = (flux_type == FluxCalcType::RICO);
1168 
1169 
1170  // Rho*Theta flux
1171  //============================================================================
1172  const bool is_low_face = m_face.isLow();
1173 
1174  amrex::Array4<amrex::Real> surface_flux_arr;
1175  if (mf_surface_fluxes != nullptr) {
1176  surface_flux_arr = mf_surface_fluxes->array(mfi);
1177  }
1178 
1179  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1180  {
1181  // Valid theta flux from LSM and over land. The LSM writes the
1182  // lsm_undefined sentinel for cells it did not process (sea-ice /
1183  // open water); fall back to MOST there instead of applying garbage.
1184  Real Tflux;
1185  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
1186  const bool lsm_flux_is_valid = (t_flux_arr) ? (t_flux_arr(i,j,0) < lsm_undefined) :
1187  false;
1188  const bool has_land_and_flux = (is_land == 1 && lsm_flux_is_valid);
1189  if (t_flux_arr && has_land_and_flux) {
1190  // LSM flux MultiFabs store kinematic fluxes for MOST parameter
1191  // updates. The applied hfx array stores the conservative RHS flux.
1192  Tflux = cons_arr(i,j,k,Rho_comp) * t_flux_arr(i,j,0);
1193  if(surface_flux_arr) {
1194  surface_flux_arr(i,j,0,0) = Tflux;
1195  }
1196  } else if (is_land == 2) { // no temperature flux within buildings
1197  Tflux = zero;
1198  if(surface_flux_arr) {
1199  surface_flux_arr(i,j,0,0) = Tflux;
1200  }
1201  } else {
1202  Tflux = flux_comp.compute_t_flux(i, j, k, dir,
1203  cons_arr, velx_arr, vely_arr, velz_arr,
1204  dir_umm_arr, tm_arr, u_star_arr,
1205  t_star_arr, t_surf_arr);
1206  // NOTE: do NOT write the MOST-fallback flux back into lsm_t_flux_arr.
1207  // Doing so flips a sentinel (water/unprocessed) cell to "valid LSM"
1208  // on the next step, so a MOST-derived value is re-read as an LSM flux
1209  // Only Noah-MP should populate the LSM cache.
1210  if (surface_flux_arr) {
1211  surface_flux_arr(i,j,0,0) = Tflux;
1212  }
1213  }
1214 
1215  if (soil_t_flux_arr && is_land == 1) {
1216  soil_t_flux_arr(i,j,k) = Tflux / cons_arr(i,j,k,Rho_comp);
1217  }
1218 
1219  surface_source_arr(i,j,k) = surface_diagnostics::to_plot_value(
1221  is_custom, is_rico, is_land, has_lsm_t_flux, lsm_flux_is_valid));
1222 
1223  // Do scalar flux rotations?
1224  if (rotate) {
1225  rotate_scalar_flux(i, j, k, dir, Tflux, dxInv, zphys_arr,
1226  hfx1_arr, hfx2_arr, hfx3_arr);
1227  } else {
1228  // swap sign for upward faces
1229  if (!is_low_face && dir != 2) {
1230  Tflux = -Tflux;
1231  }
1232 
1233  // write out to corresponding face
1234  if (dir == 0) {
1235  if (!is_low_face) {
1236  hfx1_arr(i+1,j,k) = Tflux;
1237  } else {
1238  hfx1_arr(i,j,k) = Tflux;
1239  }
1240  } else if (dir == 1) {
1241  if (!is_low_face) {
1242  hfx2_arr(i,j+1,k) = Tflux;
1243  } else {
1244  hfx2_arr(i,j,k) = Tflux;
1245  }
1246  } else {
1247  if (!is_low_face) {
1248  hfx3_arr(i,j,k+1) = Tflux;
1249  } else {
1250  hfx3_arr(i,j,k) = Tflux;
1251  }
1252  }
1253  }
1254  });
1255 
1256  // Rho*Qv flux
1257  //============================================================================
1258  if (use_moisture) {
1259  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1260  {
1261  // Valid qv flux from LSM and over land (sentinel -> fall back to MOST)
1262  Real Qflux;
1263  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
1264  const bool lsm_flux_is_valid = (q_flux_arr) ? (q_flux_arr(i,j,0) < lsm_undefined) :
1265  false;
1266  const bool has_land_and_flux = (is_land == 1 && lsm_flux_is_valid);
1267  if (q_flux_arr && has_land_and_flux) {
1268  // LSM flux MultiFabs store kinematic fluxes for MOST parameter
1269  // updates. The applied qfx array stores the conservative RHS flux.
1270  Qflux = cons_arr(i,j,k,Rho_comp) * q_flux_arr(i,j,0);
1271  if(surface_flux_arr) {
1272  surface_flux_arr(i,j,0,1) = Qflux;
1273  }
1274  } else if (is_land == 2) { // no moisture flux within buildings
1275  Qflux = zero;
1276  if(surface_flux_arr) {
1277  surface_flux_arr(i,j,0,1) = Qflux;
1278  }
1279  } else {
1280  Qflux = flux_comp.compute_q_flux(i, j, k, dir,
1281  cons_arr, velx_arr, vely_arr, velz_arr,
1282  dir_umm_arr, qm_arr, u_star_arr,
1283  q_star_arr, q_surf_arr);
1284  // NOTE: no writeback into lsm_q_flux_arr -- see the matching
1285  // t_flux note above.
1286  if (surface_flux_arr) {
1287  surface_flux_arr(i,j,0,1) = Qflux;
1288  }
1289  }
1290 
1291  // Do scalar flux rotations?
1292  if (rotate) {
1293  rotate_scalar_flux(i, j, k, dir, Qflux, dxInv, zphys_arr,
1294  qfx1_arr, qfx2_arr, qfx3_arr);
1295  } else {
1296  // swap sign for upward faces
1297  if (!is_low_face && dir != 2) {
1298  Qflux = -Qflux;
1299  }
1300 
1301  // write out to corresponding face
1302  if (dir == 0) {
1303  if (!is_low_face) {
1304  qfx1_arr(i+1,j,k) = Qflux;
1305  } else {
1306  qfx1_arr(i,j,k) = Qflux;
1307  }
1308  } else if (dir == 1) {
1309  if (!is_low_face) {
1310  qfx2_arr(i,j+1,k) = Qflux;
1311  } else {
1312  qfx2_arr(i,j,k) = Qflux;
1313  }
1314  } else {
1315  if (!is_low_face) {
1316  qfx3_arr(i,j,k+1) = Qflux;
1317  } else {
1318  qfx3_arr(i,j,k) = Qflux;
1319  }
1320  }
1321  }
1322  });
1323  } // custom
1324 
1325  if (!rotate) {
1326  // Rho*u flux
1327  //============================================================================
1328  const IntVect stressx_nodal = (dir == 2) ? IntVect(1,0,1) : IntVect(1,1,0);
1329  Box bxx = convert(bx, stressx_nodal);
1330  const int stressx_face_index = is_low_face
1331  ? m_geom[lev].Domain().smallEnd(dir)
1332  : m_geom[lev].Domain().bigEnd(dir) + 1;
1333  bxx.setRange(dir, stressx_face_index);
1334  ParallelFor(bxx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1335  {
1336  // Valid tau13 from LSM and over land. A side that is land but
1337  // whose LSM flux is the sentinel (sea-ice / open water) is treated
1338  // as non-LSM so that side uses the MOST stress instead.
1339  Real stressx;
1340  int is_land_hi = (lmask_arr) ? lmask_arr(i ,j,0) : 1;
1341  int is_land_lo = (lmask_arr) ? lmask_arr(i-1,j,0) : 1;
1342  const bool lsm_hi_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1343  static_cast<bool>(surface_tau13_arr), is_land_hi == 1,
1344  surface_tau13_arr ? surface_tau13_arr(i ,j,0) : zero, lsm_undefined);
1345  const bool lsm_lo_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1346  static_cast<bool>(surface_tau13_arr), is_land_lo == 1,
1347  surface_tau13_arr ? surface_tau13_arr(i-1,j,0) : zero, lsm_undefined);
1348  const bool has_land_and_flux_hi = (is_land_hi == 1 && lsm_hi_flux_is_valid);
1349  const bool has_land_and_flux_lo = (is_land_lo == 1 && lsm_lo_flux_is_valid);
1350  if (surface_tau13_arr && (has_land_and_flux_hi || has_land_and_flux_lo)) {
1351  const Real rho_hi = cons_arr(i ,j,k,Rho_comp);
1352  const Real rho_lo = cons_arr(i-1,j,k,Rho_comp);
1353  const Real most_stress = (!has_land_and_flux_hi || !has_land_and_flux_lo) ?
1354  flux_comp.compute_u_flux(i, j, k, dir,
1355  cons_arr, velx_arr, vely_arr, velz_arr,
1356  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr) : zero;
1358  rho_lo, rho_hi, surface_tau13_arr(i-1,j,0), surface_tau13_arr(i,j,0),
1359  has_land_and_flux_lo, has_land_and_flux_hi, most_stress);
1360  stressx = result.face_stress;
1361  // NOTE: do NOT write the MOST-fallback stress back into the
1362  // cell-centered lsm_tau13_arr. This face-indexed ParallelFor
1363  // touches cells (i) and (i-1), so each cell is written by two
1364  // adjacent face threads in the same launch -> nondeterministic
1365  // write-write race on GPU (ERF #3446). It also spuriously flips
1366  // a sentinel (water/unprocessed) cell to "valid LSM" for the
1367  // next step. The face stress is fully determined here; the LSM
1368  // cache is (re)filled only by Noah-MP. Matches baseline 3ab899d3.
1369  } else if (is_land_hi == 2 || is_land_lo == 2) { // no stress within buildings
1370  stressx = zero;
1371  } else {
1372  stressx = flux_comp.compute_u_flux(i, j, k, dir,
1373  cons_arr, velx_arr, vely_arr, velz_arr,
1374  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr);
1375  }
1376 
1377  // write out to corresponding face
1378  if (dir == 0) {
1379  t21_arr(i,j,k) = stressx;
1380  if (t12_arr &&
1381  (!ylo_surface || j != ylo_node) &&
1382  (!yhi_surface || j != yhi_node)) {
1383  t12_arr(i,j,k) = stressx;
1384  }
1385  } else if (dir == 1) {
1386  t12_arr(i,j,k) = stressx;
1387  if (t21_arr &&
1388  (!xlo_surface || i != xlo_node) &&
1389  (!xhi_surface || i != xhi_node)) {
1390  t21_arr(i,j,k) = stressx;
1391  }
1392  } else {
1393  t13_arr(i,j,k) = stressx;
1394  if (t31_arr &&
1395  (!xlo_surface || i != xlo_node) &&
1396  (!xhi_surface || i != xhi_node)) {
1397  t31_arr(i,j,k) = stressx;
1398  }
1399  }
1400  });
1401 
1402  // Rho*v flux
1403  //============================================================================
1404  const IntVect stressy_nodal = (dir == 0) ? IntVect(1,0,1) : IntVect(0,1,1);
1405  Box bxy = convert(bx, stressy_nodal);
1406  const int stressy_face_index = is_low_face
1407  ? m_geom[lev].Domain().smallEnd(dir)
1408  : m_geom[lev].Domain().bigEnd(dir) + 1;
1409  bxy.setRange(dir, stressy_face_index);
1410  ParallelFor(bxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1411  {
1412  // Valid tau23 from LSM and over land (sentinel side -> MOST stress)
1413  Real stressy;
1414  int is_land_hi = (lmask_arr) ? lmask_arr(i,j ,0) : 1;
1415  int is_land_lo = (lmask_arr) ? lmask_arr(i,j-1,0) : 1;
1416  const bool lsm_hi_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1417  static_cast<bool>(surface_tau23_arr), is_land_hi == 1,
1418  surface_tau23_arr ? surface_tau23_arr(i,j ,0) : zero, lsm_undefined);
1419  const bool lsm_lo_flux_is_valid = surface_layer_stress::lsm_flux_is_valid(
1420  static_cast<bool>(surface_tau23_arr), is_land_lo == 1,
1421  surface_tau23_arr ? surface_tau23_arr(i,j-1,0) : zero, lsm_undefined);
1422  const bool has_land_and_flux_hi = (is_land_hi == 1 && lsm_hi_flux_is_valid);
1423  const bool has_land_and_flux_lo = (is_land_lo == 1 && lsm_lo_flux_is_valid);
1424  if (surface_tau23_arr && (has_land_and_flux_hi || has_land_and_flux_lo)) {
1425  const Real rho_hi = cons_arr(i,j ,k,Rho_comp);
1426  const Real rho_lo = cons_arr(i,j-1,k,Rho_comp);
1427  const Real most_stress = (!has_land_and_flux_hi || !has_land_and_flux_lo) ?
1428  flux_comp.compute_v_flux(i, j, k, dir,
1429  cons_arr, velx_arr, vely_arr, velz_arr,
1430  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr) : zero;
1432  rho_lo, rho_hi, surface_tau23_arr(i,j-1,0), surface_tau23_arr(i,j,0),
1433  has_land_and_flux_lo, has_land_and_flux_hi, most_stress);
1434  stressy = result.face_stress;
1435  // NOTE: no writeback into cell-centered lsm_tau23_arr -- see the
1436  // matching tau13 note above (ERF #3446 write-write race + stale
1437  // sentinel-becomes-valid). Face stress is complete here.
1438  } else if (is_land_hi == 2 || is_land_lo == 2) { // no stress within buildings
1439  stressy = zero;
1440  } else {
1441  stressy = flux_comp.compute_v_flux(i, j, k, dir,
1442  cons_arr, velx_arr, vely_arr, velz_arr,
1443  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr);
1444  }
1445 
1446  // write out to corresponding face
1447  if (dir == 0) {
1448  t31_arr(i,j,k) = stressy;
1449  if (t13_arr &&
1450  (!zlo_surface || k != zlo_node) &&
1451  (!zhi_surface || k != zhi_node)) {
1452  t13_arr(i,j,k) = stressy;
1453  }
1454  } else if (dir == 1) {
1455  t32_arr(i,j,k) = stressy;
1456  if (t23_arr &&
1457  (!zlo_surface || k != zlo_node) &&
1458  (!zhi_surface || k != zhi_node)) {
1459  t23_arr(i,j,k) = stressy;
1460  }
1461  } else {
1462  t23_arr(i,j,k) = stressy;
1463  if (t32_arr &&
1464  (!ylo_surface || j != ylo_node) &&
1465  (!yhi_surface || j != yhi_node)) {
1466  t32_arr(i,j,k) = stressy;
1467  }
1468  }
1469  });
1470  } else {
1471  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(dir == 2 && m_face.isLow(), "Stress rotation only supported for zlo face");
1472  // All fluxes with rotation
1473  //============================================================================
1474  Box bxxy = convert(bx, IntVect(1,1,0));
1475  ParallelFor(bxxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1476  {
1477  Real stresst = flux_comp.compute_u_flux(i, j, k, dir,
1478  cons_arr, velx_arr, vely_arr, velz_arr,
1479  dir_umm_arr, um_arr, vm_arr, wm_arr, u_star_arr);
1480  rotate_stress_tensor(i, j, k, dir, stresst, dxInv, zphys_arr,
1481  velx_arr, vely_arr, velz_arr,
1482  t11_arr, t22_arr, t33_arr,
1483  t12_arr, t21_arr,
1484  t13_arr, t31_arr,
1485  t23_arr, t32_arr);
1486  });
1487  }
1488 
1489  // For models that do not do iterations to yield u*/T*/q*,
1490  // fill these values from the fluxes that were computed.
1491 
1492  // NOTE: For LSM, this has been handled in "compute_sfc_params_from_lsm_fluxes"
1493  // NOTE: Fluxes here are for conserved quantities, we divide by rho
1495  constexpr Real eps = std::numeric_limits<Real>::epsilon();
1496  bool l_use_moisture = use_moisture;
1497  ParallelFor(bx, [=] AMREX_GPU_DEVICE(int i, int j, int /*k*/)
1498  {
1499  Real rho = cons_arr(i,j,klo,Rho_comp);
1500  Real Thd = cons_arr(i,j,klo,RhoTheta_comp) / rho;
1501  Real qv = (l_use_moisture) ? cons_arr(i,j,klo,RhoQ1_comp) / rho : zero;
1502  Real Thv = Thd * (one + epsv*qv);
1503 
1504  Real tau = std::sqrt( t13_arr(i,j,klo)/rho * t13_arr(i,j,klo)/rho
1505  + t23_arr(i,j,klo)/rho * t23_arr(i,j,klo)/rho );
1506  u_star_arr(i,j,0) = amrex::max(std::sqrt(tau),eps);
1507 
1508  if (hfx3_arr(i,j,klo)>=zero) {
1509  t_star_arr(i,j,0) = amrex::min(-hfx3_arr(i,j,klo) / (rho * u_star_arr(i,j,0)),-eps);
1510  } else {
1511  t_star_arr(i,j,0) = amrex::max(-hfx3_arr(i,j,klo) / (rho * u_star_arr(i,j,0)),eps);
1512  }
1513  if (!l_use_moisture) {
1514  q_star_arr(i,j,0) = zero;
1515  } else if (qfx3_arr(i,j,klo)>=zero) {
1516  q_star_arr(i,j,0) = amrex::min(-qfx3_arr(i,j,klo) / (rho * u_star_arr(i,j,0)),-eps);
1517  } else {
1518  q_star_arr(i,j,0) = amrex::max(-qfx3_arr(i,j,klo) / ( rho * u_star_arr(i,j,0)),eps);
1519  }
1520  Real tstv = t_star_arr(i,j,0)*(one + epsv*qv) + epsv*Thd*q_star_arr(i,j,0);
1521  tstv = (tstv >= zero) ? amrex::max(tstv, eps) : amrex::min(tstv, -eps);
1522  olen_arr(i,j,0) = ( u_star_arr(i,j,0) * u_star_arr(i,j,0) * Thv ) /
1523  ( KAPPA * CONST_GRAV * tstv );
1524  z0_arr(i,j,0) = Compute_roughness(zref_arr(i,j,0), olen_arr(i,j,0),
1525  umm_arr(i,j,0), u_star_arr(i,j,0));
1526  });
1527  }
1528 
1529  } // mfiter
1530 
1532 }
constexpr amrex::Real lsm_undefined
Definition: ERF_Constants.H:28
@ tau12
Definition: ERF_DataStruct.H:49
@ tau23
Definition: ERF_DataStruct.H:49
@ tau33
Definition: ERF_DataStruct.H:49
@ tau22
Definition: ERF_DataStruct.H:49
@ tau11
Definition: ERF_DataStruct.H:49
@ tau32
Definition: ERF_DataStruct.H:49
@ tau31
Definition: ERF_DataStruct.H:49
@ tau21
Definition: ERF_DataStruct.H:49
@ tau13
Definition: ERF_DataStruct.H:49
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:1057
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:1101
amrex::Vector< std::unique_ptr< amrex::MultiFab > > surface_diagnostic_source
Definition: ERF_SurfaceLayer.H:1909
void fill_planar_boundary(const int &lev, amrex::MultiFab &mf)
Definition: ERF_SurfaceLayer.cpp:483
@ xvel
Definition: ERF_IndexDefines.H:255
@ zvel
Definition: ERF_IndexDefines.H:257
@ yvel
Definition: ERF_IndexDefines.H:256
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
1560 {
1561  const int dir = m_face.coordDir();
1562  // Get EB flags for all centerings
1563  const auto& cc_factory = m_eb_vec[lev]->get_const_factory();
1564  const auto& cc_flags = cc_factory->getMultiEBCellFlagFab();
1565  const auto& cc_vfrac = cc_factory->getVolFrac();
1566 
1567  const auto& u_factory = m_eb_vec[lev]->get_u_const_factory();
1568  const auto& u_flags = u_factory->getMultiEBCellFlagFab();
1569  const auto& u_vfrac = u_factory->getVolFrac();
1570 
1571  const auto& v_factory = m_eb_vec[lev]->get_v_const_factory();
1572  const auto& v_flags = v_factory->getMultiEBCellFlagFab();
1573  const auto& v_vfrac = v_factory->getVolFrac();
1574 
1575  const auto& w_factory = m_eb_vec[lev]->get_w_const_factory();
1576  const auto& w_flags = w_factory->getMultiEBCellFlagFab();
1577  const auto& w_vfrac = w_factory->getVolFrac();
1578 
1579  // EB does not currently have a cell-centered scalar-source classification.
1580  // Keep the provenance mask missing rather than inventing face-aware
1581  // semantics for the staggered stress path.
1582  surface_diagnostic_source[lev]->setVal(
1584 
1585  for (MFIter mfi(*mfs[0]); mfi.isValid(); ++mfi)
1586  {
1587  // Get flags for this box (all centerings)
1588  const auto& cc_flag = cc_flags[mfi];
1589  const auto& u_flag = u_flags[mfi];
1590  const auto& v_flag = v_flags[mfi];
1591  const auto& w_flag = w_flags[mfi];
1592 
1593  // Skip boxes that have no cut cells at any centering
1594  if (cc_flag.getType() != FabType::singlevalued &&
1595  u_flag.getType() != FabType::singlevalued &&
1596  v_flag.getType() != FabType::singlevalued &&
1597  w_flag.getType() != FabType::singlevalued
1598  ) continue;
1599 
1600  // Get EB flag and volfrac arrays
1601  auto const cc_flag_arr = cc_flag.const_array();
1602  auto const u_flag_arr = u_flag.const_array();
1603  auto const v_flag_arr = v_flag.const_array();
1604  auto const w_flag_arr = w_flag.const_array();
1605 
1606  auto const cc_vfrac_arr = cc_vfrac.const_array(mfi);
1607  auto const u_vfrac_arr = u_vfrac.const_array(mfi);
1608  auto const v_vfrac_arr = v_vfrac.const_array(mfi);
1609  auto const w_vfrac_arr = w_vfrac.const_array(mfi);
1610 
1611  // Get boundary normals only if cut cells exist
1612  auto const bnorm_arr = (cc_flag.getType() == FabType::singlevalued) ?
1613  cc_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1614  auto const u_bnorm_arr = (u_flag.getType() == FabType::singlevalued) ?
1615  u_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1616  auto const v_bnorm_arr = (v_flag.getType() == FabType::singlevalued) ?
1617  v_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1618  auto const w_bnorm_arr = (w_flag.getType() == FabType::singlevalued) ?
1619  w_factory->getBndryNormal().const_array(mfi) : Array4<const Real>{};
1620 
1621  // Get field arrays
1622  const auto cons_arr = mfs[Vars::cons]->array(mfi);
1623  const auto velx_arr = mfs[Vars::xvel]->array(mfi);
1624  const auto vely_arr = mfs[Vars::yvel]->array(mfi);
1625  const auto velz_arr = mfs[Vars::zvel]->array(mfi);
1626 
1627  // Diffusive stress vars - t13 and t23 components for all grid types
1628  auto u_t13_arr = Tau_EB[EBTauType::tau_eb13][EBGridType::xface]->array(mfi);
1629  auto v_t13_arr = Tau_EB[EBTauType::tau_eb13][EBGridType::yface]->array(mfi);
1630  auto w_t13_arr = Tau_EB[EBTauType::tau_eb13][EBGridType::zface]->array(mfi);
1631 
1632  auto u_t23_arr = Tau_EB[EBTauType::tau_eb23][EBGridType::xface]->array(mfi);
1633  auto v_t23_arr = Tau_EB[EBTauType::tau_eb23][EBGridType::yface]->array(mfi);
1634  auto w_t23_arr = Tau_EB[EBTauType::tau_eb23][EBGridType::zface]->array(mfi);
1635 
1636  auto hfx3_arr = Hfx3_EB->array(mfi);
1637 
1638  // Get average arrays
1639  const auto *const u_mean = m_ma.get_average(lev,0);
1640  const auto *const v_mean = m_ma.get_average(lev,1);
1641  // const auto *const w_mean = m_ma.get_average(lev,2);
1642 
1643  const auto *const t_mean = m_ma.get_average(lev,3);
1644  // const auto *const q_mean = m_ma.get_average(lev,4);
1645  const auto *const u_mag_mean = m_ma.get_average(lev,6);
1646  const auto *const uw_mag_mean = m_ma.get_average(lev,7);
1647  const auto *const vw_mag_mean = m_ma.get_average(lev,8);
1648 
1649  const auto um_arr = u_mean->array(mfi);
1650  const auto vm_arr = v_mean->array(mfi);
1651  // const auto wm_arr = w_mean->array(mfi);
1652  const auto tm_arr = t_mean->array(mfi);
1653  // const auto qm_arr = q_mean->array(mfi);
1654  const auto umm_arr = u_mag_mean->array(mfi);
1655  const auto vwmm_arr = (dir == 0) ? vw_mag_mean->array(mfi) : Array4<Real>{};
1656  const auto uwmm_arr = (dir == 1) ? uw_mag_mean->array(mfi) : Array4<Real>{};
1657 
1658  // umm depending on face direction (YZ, XZ, XY)
1659  const auto dir_umm_arr = ((dir == 0) ? vwmm_arr : ((dir == 1) ? uwmm_arr : umm_arr));
1660 
1661  // Get derived arrays
1662  const auto u_star_arr = u_star[lev]->array(mfi);
1663  const auto t_star_arr = t_star[lev]->array(mfi);
1664  const auto t_surf_arr = t_surf[lev]->array(mfi);
1665 
1666  // Rho*Theta flux
1667  //============================================================================
1668  Box bx = mfi.tilebox();
1669  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1670  {
1671  if (cc_flag_arr(i,j,k).isSingleValued()) {
1672  Real Tflux = flux_comp.compute_t_flux(i, j, k,
1673  cons_arr, velx_arr, vely_arr, velz_arr,
1674  dir_umm_arr, tm_arr, u_star_arr,
1675  t_star_arr, t_surf_arr,
1676  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1677  bnorm_arr);
1678  hfx3_arr(i,j,k) = Tflux;
1679  }
1680  });
1681 
1682  // Rho*u flux
1683  //============================================================================
1684  Box bxx = surroundingNodes(bx,0);
1685  Box bxy = surroundingNodes(bx,1);
1686  Box bxz = surroundingNodes(bx,2);
1687  ParallelFor(bxx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1688  {
1689  if (u_flag_arr(i,j,k).isSingleValued()) {
1690  Real stressx = flux_comp.compute_u_flux(i, j, k,
1691  cons_arr, velx_arr, vely_arr, velz_arr,
1692  dir_umm_arr, um_arr, u_star_arr,
1693  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1694  cc_vfrac_arr, cc_flag_arr,
1695  u_bnorm_arr, 0);
1696  u_t13_arr(i,j,k) = stressx;
1697  }
1698  });
1699  ParallelFor(bxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1700  {
1701  if (v_flag_arr(i,j,k).isSingleValued()) {
1702  Real stressx = flux_comp.compute_u_flux(i, j, k,
1703  cons_arr, velx_arr, vely_arr, velz_arr,
1704  dir_umm_arr, um_arr, u_star_arr,
1705  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1706  cc_vfrac_arr, cc_flag_arr,
1707  v_bnorm_arr, 1);
1708  v_t13_arr(i,j,k) = stressx;
1709  }
1710  });
1711  ParallelFor(bxz, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1712  {
1713  if (w_flag_arr(i,j,k).isSingleValued()) {
1714  Real stressx = flux_comp.compute_u_flux(i, j, k,
1715  cons_arr, velx_arr, vely_arr, velz_arr,
1716  dir_umm_arr, um_arr, u_star_arr,
1717  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1718  cc_vfrac_arr, cc_flag_arr,
1719  w_bnorm_arr, 2);
1720  w_t13_arr(i,j,k) = stressx;
1721  }
1722  });
1723 
1724  // Rho*v flux
1725  //============================================================================
1726  ParallelFor(bxx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1727  {
1728  if (u_flag_arr(i,j,k).isSingleValued()) {
1729  Real stressy = flux_comp.compute_v_flux(i, j, k,
1730  cons_arr, velx_arr, vely_arr, velz_arr,
1731  dir_umm_arr, vm_arr, u_star_arr,
1732  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1733  cc_vfrac_arr, cc_flag_arr, u_bnorm_arr, 0);
1734  u_t23_arr(i,j,k) = stressy;
1735  }
1736  });
1737  ParallelFor(bxy, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1738  {
1739  if (v_flag_arr(i,j,k).isSingleValued()) {
1740  Real stressy = flux_comp.compute_v_flux(i, j, k,
1741  cons_arr, velx_arr, vely_arr, velz_arr,
1742  dir_umm_arr, vm_arr, u_star_arr,
1743  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1744  cc_vfrac_arr, cc_flag_arr, v_bnorm_arr, 1);
1745  v_t23_arr(i,j,k) = stressy;
1746  }
1747  });
1748  ParallelFor(bxz, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1749  {
1750  if (w_flag_arr(i,j,k).isSingleValued()) {
1751  Real stressy = flux_comp.compute_v_flux(i, j, k,
1752  cons_arr, velx_arr, vely_arr, velz_arr,
1753  dir_umm_arr, vm_arr, u_star_arr,
1754  u_vfrac_arr, v_vfrac_arr, w_vfrac_arr,
1755  cc_vfrac_arr, cc_flag_arr, w_bnorm_arr, 2);
1756  w_t23_arr(i,j,k) = stressy;
1757  }
1758  });
1759  } // mfiter
1760 
1761 }
@ 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.

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

1343 { 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
3268 {
3269  PBLHColumns& cols = m_pblh_columns[lev];
3270  cols = PBLHColumns{};
3271  cols.ba = ba;
3272  cols.dm = dm;
3273 
3274  const int k_ground = m_geom[lev].Domain().smallEnd(2);
3275  const int k_top = m_geom[lev].Domain().bigEnd(2);
3276  for (int ib = 0; ib < ba.size(); ++ib) {
3277  if (ba[ib].smallEnd(2) != k_ground) { cols.needed = true; }
3278  }
3279 
3280  // The runs of cells in z, each as one box
3281  const BoxArray ba_joined = join_boxes_stacked_in_z(ba);
3282  for (int ib = 0; ib < ba_joined.size(); ++ib) {
3283  if (ba_joined[ib].smallEnd(2) != k_ground || ba_joined[ib].bigEnd(2) != k_top) { cols.full_height = false; }
3284  }
3285  if (!cols.needed) { return; }
3286 
3287  // With EB the surface-layer fields live on the 3D grids and the estimator writes their
3288  // k = 0 plane, which a box that does not start at the ground does not hold
3289  if (m_terrain_type == TerrainType::EB) {
3290  Abort("erf.most.pblh_calc = MYNN25 with EB needs every grid at level " + std::to_string(lev) +
3291  " to start at the bottom of the domain: the PBL height is written into the lowest "
3292  "plane of each grid. Choose grids that are not decomposed in z "
3293  "(amr.max_grid_size_z) and refined regions that reach the ground.");
3294  }
3295 
3296  // Those of the runs of cells in z that start at the ground are the columns. A column goes
3297  // to the rank that owns its lowest corner cell, which keeps most of the copies to and from
3298  // the columns on the rank.
3299  BoxList bl_col(IndexType::TheCellType());
3300  BoxList bl_col2d(IndexType::TheCellType());
3301  Vector<int> pmap;
3302  for (int ib = 0; ib < ba_joined.size(); ++ib) {
3303  const Box& b = ba_joined[ib];
3304  if (b.smallEnd(2) != k_ground) { continue; }
3305  const auto& owners = ba.intersections(Box(b.smallEnd(), b.smallEnd()));
3306  AMREX_ALWAYS_ASSERT(!owners.empty());
3307  Box b2d(b);
3308  b2d.setRange(2, k_ground);
3309  bl_col.push_back(b);
3310  bl_col2d.push_back(b2d);
3311  pmap.push_back(dm[owners[0].first]);
3312  }
3313  if (bl_col.isEmpty()) { return; }
3314 
3315  cols.ba_col = BoxArray(std::move(bl_col));
3316  cols.ba_col2d = BoxArray(std::move(bl_col2d));
3317  cols.dm_col = DistributionMapping(std::move(pmap));
3318 
3319  // These hold no data. AMReX drops the communication metadata of a BoxArray and
3320  // DistributionMapping pair with the last FabArray built on it, and the MultiFabs that
3321  // compute_pblh builds on the columns are temporaries.
3322  cols.hold_col.define(cols.ba_col, cols.dm_col, 1, 0, MFInfo().SetAlloc(false));
3323  cols.hold_col2d.define(cols.ba_col2d, cols.dm_col, 1, 0, MFInfo().SetAlloc(false));
3324 }
BoxArray join_boxes_stacked_in_z(const BoxArray &ba)
Definition: ERF_TerrainMetrics.cpp:154
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
747 {
748  const int dir = m_face.coordDir();
749  AMREX_ALWAYS_ASSERT(dir < 2);
750 
751  const iMultiFab& surface_mask = *m_lmask_lev[lev][0];
752  const int face_index = m_face.isLow()
753  ? m_geom[lev].Domain().smallEnd(dir)
754  : m_geom[lev].Domain().bigEnd(dir);
755  const auto& mask_ba = surface_mask.boxArray();
756 
757  IntVect period = m_geom[lev].periodicity().intVect();
758  period[dir] = 0; // The selected wall is not a periodic source plane.
759  const Periodicity tangential_periodicity(period);
760 
761  Vector<MultiFab*> fields;
762  if (selected_field) {
763  fields.push_back(selected_field);
764  } else {
765  const Vector<MultiFab*> all_fields{
766  u_star[lev].get(), t_star[lev].get(), q_star[lev].get(), olen[lev].get(),
767  t_surf[lev].get(), q_surf[lev].get(), pblh[lev].get(),
768  surface_diagnostic_source[lev].get()};
769  for (MultiFab* field : all_fields) {
770  if (field) { fields.push_back(field); }
771  }
772  if (m_include_wstar && w_star[lev]) { fields.push_back(w_star[lev].get()); }
773  }
774 
775  for (MultiFab* field : fields) {
776  // Each parameter field owns its own index type and BoxArray. Build
777  // the compact layout from that field rather than reusing u_star's
778  // layout for all parameters.
779  BoxList face_boxes;
780  Vector<int> face_pmap;
781  Vector<int> source_indices;
782  const auto& source_ba = field->boxArray();
783  const auto& source_pmap = field->DistributionMap().ProcessorMap();
784  for (int ibox = 0; ibox < mask_ba.size(); ++ibox) {
785  const Box& mask_box = mask_ba[ibox];
786  const bool owns_face = m_face.isLow()
787  ? mask_box.smallEnd(dir) == face_index
788  : mask_box.bigEnd(dir) == face_index;
789  if (!owns_face) { continue; }
790 
791  face_boxes.push_back(source_ba[ibox]);
792  face_pmap.push_back(source_pmap[ibox]);
793  source_indices.push_back(ibox);
794  }
795  if (face_boxes.isEmpty()) { continue; }
796 
797  BoxArray face_ba(std::move(face_boxes));
798  DistributionMapping face_dm(std::move(face_pmap));
799  MultiFab face_values(face_ba, face_dm, 1, field->nGrowVect());
800 
801  // Selectively gather only face-owned FABs. A direct FillBoundary on
802  // field would also treat interior-grid FABs as valid sources because
803  // all lateral FABs collapse onto the same wall plane.
804  for (MFIter fmfi(face_values, false); fmfi.isValid(); ++fmfi) {
805  const int compact_index = fmfi.index();
806  const int source_index = source_indices[compact_index];
807  const auto src = field->const_array(source_index);
808  const auto dst = face_values.array(fmfi);
809  // Preserve the source FAB's existing ghosts. A nonperiodic
810  // physical ghost has no FillBoundary source, so leaving the
811  // temporary FAB uninitialized would replace a valid local value
812  // with garbage at mixed-face corners.
813  Box source_fab = field->boxArray()[source_index];
814  source_fab.grow(field->nGrowVect());
815  const Box copy_box = fmfi.fabbox() & source_fab;
817  !copy_box.isEmpty(),
818  "Temporary surface-layer source copy has no valid overlap.");
819  ParallelFor(copy_box, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
820  {
821  dst(i,j,k) = src(i,j,k);
822  });
823  }
824  Gpu::streamSynchronize();
825 
826  face_values.FillBoundary(tangential_periodicity);
827 
828  for (MFIter fmfi(face_values, false); fmfi.isValid(); ++fmfi) {
829  const int compact_index = fmfi.index();
830  const int source_index = source_indices[compact_index];
831  const auto src = face_values.const_array(fmfi);
832  const auto dst = field->array(source_index);
833  Box source_fab = field->boxArray()[source_index];
834  source_fab.grow(field->nGrowVect());
835  const Box copy_box = fmfi.fabbox() & source_fab;
837  !copy_box.isEmpty(),
838  "Mapped surface-layer ghost copy has no valid overlap with its source FAB.");
839  ParallelFor(copy_box, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
840  {
841  dst(i,j,k) = src(i,j,k);
842  });
843  }
844  }
845  Gpu::streamSynchronize();
846 }
Here is the call graph for this function:

◆ fill_pblh_from_coarser()

void SurfaceLayer::fill_pblh_from_coarser ( const int &  lev)

Set the PBL height of a refined level whose grids do not run from the ground to the top of the domain in every column from the next coarser level, column by column (see compute_pblh). Public because it launches a device lambda.

Parameters
[in]levCurrent level (> 0)

Set the PBL height of a refined level from the next coarser level: each column of this level takes the height of the coarse column that holds it. Used for a level whose grids do not run from the ground to the top of the domain in every column (see compute_pblh).

Parameters
[in]levCurrent level (> 0)
3335 {
3336  AMREX_ALWAYS_ASSERT(lev > 0 && pblh[lev]);
3337  inject_pblh_from_coarser(lev, *pblh[lev]);
3338  fill_planar_boundary(lev, *pblh[lev]);
3339 }
void inject_pblh_from_coarser(const int &lev, amrex::MultiFab &fine) const
Definition: ERF_SurfaceLayer.cpp:3349
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
484 {
485  // PlanarBoundary handles both z-low and z-high z-collapsed arrays split
486  // in z. EB fields use ordinary FillBoundary because their 3-D layout has
487  // no duplicate collapsed surface boxes. Lateral planar fields need a
488  // selective exchange so an interior grid cannot provide wall data.
489  if (m_terrain_type == TerrainType::EB) {
490  mf.FillBoundary(m_geom[lev].periodicity());
491  } else if (m_face.coordDir() != 2) {
493  } else {
494  m_planar_bndry[lev].fill(mf, m_geom[lev].periodicity());
495  }
496 }
void fill_lateral_surface_parameter_ghosts(const int &lev, amrex::MultiFab *selected_field=nullptr)
Definition: ERF_SurfaceLayer.cpp:745

◆ 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
2166 {
2167  const int dir = m_face.coordDir();
2168  int sm_index;
2169  if (m_face.isLow()) {
2170  sm_index = m_geom[lev].Domain().smallEnd(dir);
2171  } else {
2172  sm_index = m_geom[lev].Domain().bigEnd(dir);
2173  }
2174 
2175  // Populate q_surf with qsat over water. The selected face is the only
2176  // authoritative slab; state/terrain ghosts are useful when valid, but a
2177  // bad halo must not turn into a collective conversion failure.
2178  const Real dz = m_geom[lev].CellSize(2);
2179  const int ng_z = amrex::min(cons_in.nGrowVect()[2],
2180  amrex::min(t_surf[lev]->nGrowVect()[2],
2181  q_surf[lev]->nGrowVect()[2]));
2182  const bool moist = use_moisture;
2183  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
2184  const Real rdOcp = m_rdOcp;
2185  amrex::Gpu::DeviceScalar<erf_surface_temperature::ConversionFailure>
2187  auto* fail = d_fail.dataPtr();
2188  // Use the 2-D surface mask as the iterator so ranks participate only when
2189  // their grids coincide with the selected face.
2190  for (MFIter mfi(*m_lmask_lev[lev][0]); mfi.isValid(); ++mfi)
2191  {
2192  Box gtbx = mfi.growntilebox();
2193  Box tbx = mfi.validbox();
2194  const Box tilebx = mfi.tilebox();
2195 
2196  // Since lmask is used in the MFIter, its Z dimension is 0. These are
2197  // temporary geometry boxes, so use the physical domain's Z range for
2198  // indexing the 3-D surface/state arrays.
2199  tbx.setSmall(2, m_geom[lev].Domain().smallEnd(2));
2200  tbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
2201  gtbx.setSmall(2, m_geom[lev].Domain().smallEnd(2));
2202  gtbx.setBig(2, m_geom[lev].Domain().bigEnd(2));
2203 
2204  if (dir == 2) {
2205  gtbx.makeSlab(2, sm_index);
2206  } else {
2207  gtbx.grow(2, ng_z);
2208  gtbx.setSmall(dir, sm_index);
2209  gtbx.setBig(dir, sm_index);
2210  }
2211 
2212  if (dir == 2) {
2213  if (m_terrain_type != TerrainType::EB &&
2214  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
2215  continue;
2216  }
2217  } else {
2218  if (tbx[m_face] != sm_index || tilebx[m_face] != sm_index) {
2219  continue;
2220  }
2221  }
2222 
2223  // The mask iterator can have more ghosts than the surface fields.
2224  // Limit every array accessed by the kernel to its corresponding FAB.
2225  gtbx &= t_surf[lev]->fabbox(mfi.index());
2226  gtbx &= q_surf[lev]->fabbox(mfi.index());
2227  gtbx &= cons_in.fabbox(mfi.index());
2228  if (z_phys_nd) {
2229  // z_phys_nd is nodal; the cell box whose corner nodes all lie in its FAB
2230  // is the nodal box converted to cells (one fewer cell on the high side).
2231  gtbx &= amrex::convert(z_phys_nd->fabbox(mfi.index()), IntVect::TheCellVector());
2232  }
2233  if (gtbx.isEmpty()) { continue; }
2234 
2235  auto t_surf_arr = t_surf[lev]->array(mfi);
2236  auto q_surf_arr = q_surf[lev]->array(mfi);
2237  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2238  Array4<int> {};
2239  const auto cons_arr = cons_in.const_array(mfi);
2240  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
2241  Array4<const Real> {};
2242  const Box source_box = cons_in.boxArray()[mfi.index()];
2243  const int src_i_lo = source_box.smallEnd(0);
2244  const int src_i_hi = source_box.bigEnd(0);
2245  const int src_j_lo = source_box.smallEnd(1);
2246  const int src_j_hi = source_box.bigEnd(1);
2247  const int src_k_lo = source_box.smallEnd(2);
2248  const int src_k_hi = source_box.bigEnd(2);
2249  const bool z_face = (dir == 2);
2250  const bool low_face = m_face.isLow();
2251 
2252  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2253  {
2254  int is_land = (lmask_arr) ? lmask_arr(i,j,0) : 1;
2255  if (!is_land) {
2256  const bool authoritative = i >= src_i_lo && i <= src_i_hi &&
2257  j >= src_j_lo && j <= src_j_hi &&
2258  k >= src_k_lo && k <= src_k_hi;
2259  const Real rho = cons_arr(i,j,k,Rho_comp);
2260  const Real rho_theta = cons_arr(i,j,k,RhoTheta_comp);
2261  if (!amrex::Math::isfinite(rho) || rho <= Real(0.0) ||
2262  !amrex::Math::isfinite(rho_theta)) {
2264  i, j, k, rho, rho_theta);
2265  return;
2266  }
2267  Real qv = Real(0.0);
2268  if (moist) {
2269  if (!have_rho_qv) {
2271  i, j, k);
2272  return;
2273  }
2274  const Real rho_qv = cons_arr(i,j,k,RhoQ1_comp);
2275  if (!amrex::Math::isfinite(rho_qv)) {
2277  i, j, k, rho, rho_theta, rho_qv);
2278  return;
2279  }
2280  qv = rho_qv / rho;
2281  if (!amrex::Math::isfinite(qv)) {
2283  i, j, k, rho, rho_theta, rho_qv);
2284  return;
2285  }
2286  }
2287  Real delta_z = Real(0.0);
2288  if (z_face) {
2289  if (z_arr) {
2290  const Real z_cc = Compute_Z_AtCellCenter(i,j,k,z_arr);
2291  const Real z_face_local = low_face
2292  ? Compute_Z_AtWFace(i,j,k,z_arr)
2293  : Compute_Z_AtWFace(i,j,k+1,z_arr);
2294  delta_z = z_cc - z_face_local;
2295  } else {
2296  delta_z = low_face ? myhalf*dz : -myhalf*dz;
2297  }
2298  }
2300  rho, rho_theta, qv, delta_z);
2301  Real t_surface = t_surf_arr(i,j,k);
2303  t_surface, pressure, rdOcp, t_surface)) {
2304  erf_qsatw(t_surface, pressure * Real(0.01), q_surf_arr(i,j,k));
2305  } else {
2307  i, j, k, rho, rho_theta,
2308  moist ? cons_arr(i,j,k,RhoQ1_comp) : Real(0.0),
2309  t_surface, pressure, delta_z);
2310  }
2311  }
2312  });
2313  }
2314  amrex::Gpu::streamSynchronize();
2315  erf_surface_temperature::abort_on_conversion_failure("SurfaceLayer fill_qsurf_with_qsat",
2316  lev, d_fail.dataValue());
2317  fill_planar_boundary(lev, *q_surf[lev]);
2318 }
const Real rdOcp
Definition: ERF_InitCustomPert_ABL.H:72
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void erf_qsatw(amrex::Real t, amrex::Real p, amrex::Real &qsatw)
Definition: ERF_MicrophysicsUtils.H:264
constexpr amrex::Real myhalf
Definition: ERF_NumericalConstants.H:41
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real Compute_Z_AtCellCenter(const int &i, const int &j, const int &k, const amrex::Array4< const amrex::Real > &z_nd)
Definition: ERF_TerrainMetrics.H:716
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real Compute_Z_AtWFace(const int &i, const int &j, const int &k, const amrex::Array4< const amrex::Real > &z_nd)
Definition: ERF_TerrainMetrics.H:740
@ dz
Definition: ERF_AdvanceWDM6.cpp:273
AMREX_GPU_DEVICE AMREX_FORCE_INLINE void record_conversion_failure(ConversionFailure *fail, const bool fatal, const ConversionError code, const int i, const int j, const int k, const amrex::Real rho=amrex::Real(0.0), const amrex::Real rho_theta=amrex::Real(0.0), const amrex::Real rho_qv=amrex::Real(0.0), const amrex::Real t_surf=amrex::Real(0.0), const amrex::Real pressure=amrex::Real(0.0), const amrex::Real delta_z=amrex::Real(0.0), const amrex::Real q_air=amrex::Real(0.0))
Definition: ERF_SurfaceTemperature.H:159
void abort_on_conversion_failure(const char *who, const int lev, ConversionFailure const &fail)
Definition: ERF_SurfaceTemperature.H:236
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:26
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:60
Definition: ERF_SurfaceTemperature.H:130
Here is the call graph for this function:

◆ fill_qsurf_with_skin_moisture()

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

Overwrite the land surface mixing ratio from the external skin temperature and the moisture field of set_skin_moisture, with q_air the reference-height mixing ratio the flux uses. Without resistances: beta * q_sat(T_skin, p_s) + (1 - beta) * q_air (erf_surface_moisture::surface_mixing_ratio) with beta the soil-water factor, so the moisture flux is beta times the potential one. With resistances: erf_surface_moisture::two_source_surface_mixing_ratio of the aerodynamic resistance (from the last u* and Obukhov length; neutral before the first flux), the canopy resistance with its vapour-deficit factor, the soil resistance and the soil pore air's relative humidity. Needs set_skin_temperature and the averages of this step.

Parameters
[in]levlevel index
[in]cons_inconserved state (surface pressure and q_air from the lowest cell)
[in]z_phys_ndnodal heights, or nullptr on a flat mesh

Overwrite the land surface mixing ratio with that of a surface of moisture availability beta at the external skin temperature (see the declaration). Like the skin temperature, written on the land cells of the surface slab, the halo taking the nearest valid column.

Parameters
[in]levCurrent level
[in]cons_inConserved state (surface pressure and q_air from the lowest cell)
[in]z_phys_ndNodal heights, or nullptr on a flat mesh
2866 {
2867  using namespace erf_surface_moisture;
2868  const MultiFab& skin = *m_skin_tsurf_lev[lev];
2869  const MultiFab& moisture = *m_skin_moisture_lev[lev];
2870  // Indexed with the MFIter of q_surf, so the layouts must agree.
2872  skin.boxArray() == q_surf[lev]->boxArray() &&
2873  skin.DistributionMap() == q_surf[lev]->DistributionMap() &&
2874  moisture.boxArray() == q_surf[lev]->boxArray() &&
2875  moisture.DistributionMap() == q_surf[lev]->DistributionMap() &&
2876  moisture.nComp() >= NumComponents,
2877  "Skin temperature or moisture layout does not match the surface-layer layout.");
2878 
2879  const int klo = m_geom[lev].Domain().smallEnd(2);
2880  const Real dz = m_geom[lev].CellSize(2);
2881  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
2882  const bool use_resistances = m_skin_moisture_resistances_lev[lev] != 0;
2883  const Real hs = m_skin_moisture_hs_lev[lev];
2884  const auto *const qvm_ptr = m_ma.get_average(lev, 4); // reference-height mixing ratio
2885  const auto *const umm_ptr = m_ma.get_average(lev, 6); // horizontal velocity magnitude
2886  const auto *const zref_ptr = m_ma.get_zref(lev); // reference height
2887  const similarity_funs sfuns{};
2888  constexpr Real wsmin = most_min_wind_speed; // the flux kernels' WSMIN
2889  amrex::Gpu::DeviceScalar<erf_surface_temperature::ConversionFailure>
2891  auto* fail = d_fail.dataPtr();
2892 
2893  for (MFIter mfi(*q_surf[lev]); mfi.isValid(); ++mfi)
2894  {
2895  Box gtbx = mfi.growntilebox();
2896  if (gtbx.smallEnd(2) != klo ||
2897  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
2898  continue;
2899  }
2900  gtbx &= q_surf[lev]->fabbox(mfi.index());
2901  if (gtbx.isEmpty()) { continue; }
2902 
2903  const Box vbx = mfi.validbox();
2904  const int i_lo = vbx.smallEnd(0); const int i_hi = vbx.bigEnd(0);
2905  const int j_lo = vbx.smallEnd(1); const int j_hi = vbx.bigEnd(1);
2906 
2907  auto q_surf_arr = q_surf[lev]->array(mfi);
2908  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2909  Array4<int> {};
2910  const auto skin_arr = skin.const_array(mfi);
2911  const auto moist_arr = moisture.const_array(mfi);
2912  const auto cons_arr = cons_in.const_array(mfi);
2913  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
2914  Array4<const Real> {};
2915  const auto qvm_arr = qvm_ptr->const_array(mfi);
2916  const auto umm_arr = umm_ptr->const_array(mfi);
2917  const auto zref_arr = zref_ptr->const_array(mfi);
2918  const auto z0_arr = z_0[lev].const_array(mfi);
2919  const auto ustar_arr = u_star[lev]->const_array(mfi);
2920  const auto olen_arr = olen[lev]->const_array(mfi);
2921 
2922  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2923  {
2924  const int li = amrex::min(amrex::max(i, i_lo), i_hi);
2925  const int lj = amrex::min(amrex::max(j, j_lo), j_hi);
2926  const int is_land = (lmask_arr) ? lmask_arr(li,lj,0) : 1;
2927  if (!is_land) { return; }
2928  if (!have_rho_qv) {
2930  return;
2931  }
2932 
2933  // Surface pressure from the lowest cell, as for the skin temperature.
2934  const Real rho = cons_arr(li,lj,klo,Rho_comp);
2935  const Real qv_cell = cons_arr(li,lj,klo,RhoQ1_comp) / rho;
2936  const Real delta_z = z_arr
2937  ? Compute_Z_AtCellCenter(li,lj,klo,z_arr) -
2938  Compute_Z_AtWFace(li,lj,klo,z_arr)
2939  : myhalf*dz;
2941  rho, cons_arr(li,lj,klo,RhoTheta_comp), qv_cell, delta_z);
2942  const Real t_skin = skin_arr(li,lj,k);
2943  const Real q_air = qvm_arr(li,lj,k);
2944  if (!amrex::Math::isfinite(qv_cell)) {
2946  rho, cons_arr(li,lj,klo,RhoTheta_comp), cons_arr(li,lj,klo,RhoQ1_comp));
2947  return;
2948  }
2949  if (!amrex::Math::isfinite(q_air)) {
2951  rho, cons_arr(li,lj,klo,RhoTheta_comp), cons_arr(li,lj,klo,RhoQ1_comp),
2952  t_skin, pressure, delta_z, q_air);
2953  return;
2954  }
2955  if (!erf_surface_temperature::valid_pressure(pressure)) {
2957  rho, cons_arr(li,lj,klo,RhoTheta_comp), cons_arr(li,lj,klo,RhoQ1_comp),
2958  t_skin, pressure, delta_z);
2959  return;
2960  }
2963  rho, cons_arr(li,lj,klo,RhoTheta_comp), cons_arr(li,lj,klo,RhoQ1_comp),
2964  t_skin, pressure, delta_z);
2965  return;
2966  }
2967  Real q_sat = Real(0.0);
2968  erf_qsatw(t_skin, pressure * Real(0.01), q_sat);
2969 
2970  Real q_surf_value = Real(0.0);
2971  if (use_resistances) {
2972  // The aerodynamic resistance the moisture flux will see (the surface_temp
2973  // kernel's): the last u* and Obukhov length, or neutral before the first flux.
2974  // That neutral guess takes the plane-averaged wind, not the w*-enhanced wind
2975  // the kernel uses with include_wstar, so on the first step of such a run in
2976  // light wind it is not the kernel's r_a; it is only a first guess, and from
2977  // then on u* carries the enhancement.
2979  sfuns, zref_arr(li,lj,k), z0_arr(li,lj,k), ustar_arr(li,lj,k),
2980  olen_arr(li,lj,k), umm_arr(li,lj,k), wsmin, KAPPA, bogus_large_value);
2981  const Real r_c = canopy_resistance(
2982  moist_arr(li,lj,k,CanopyResistanceWithoutVPD), hs, q_sat, q_air);
2983  q_surf_value = two_source_surface_mixing_ratio(
2984  r_a, r_c, moist_arr(li,lj,k,SoilResistance),
2985  moist_arr(li,lj,k,VegetationFraction), q_sat,
2986  moist_arr(li,lj,k,GroundRelativeHumidity), q_air);
2987  } else {
2988  q_surf_value = surface_mixing_ratio(moist_arr(li,lj,k,SoilFactor), q_sat, q_air);
2989  }
2990  if (!amrex::Math::isfinite(q_surf_value)) {
2992  rho, cons_arr(li,lj,klo,RhoTheta_comp), cons_arr(li,lj,klo,RhoQ1_comp),
2993  t_skin, pressure, delta_z, q_air);
2994  return;
2995  }
2996  q_surf_arr(i,j,k) = q_surf_value;
2997  });
2998  }
2999  amrex::Gpu::streamSynchronize();
3001  "SurfaceLayer fill_qsurf_with_skin_moisture", lev, d_fail.dataValue());
3002 }
constexpr amrex::Real bogus_large_value
Definition: ERF_Constants.H:19
Real r_c
Definition: ERF_InitCustomPert_SquallLine.H:53
constexpr amrex::Real most_min_wind_speed
Definition: ERF_MOSTStress.H:14
amrex::Vector< int > m_skin_moisture_resistances_lev
Definition: ERF_SurfaceLayer.H:1929
amrex::Vector< const amrex::MultiFab * > m_skin_moisture_lev
Definition: ERF_SurfaceLayer.H:1928
amrex::Vector< const amrex::MultiFab * > m_skin_tsurf_lev
Definition: ERF_SurfaceLayer.H:1925
amrex::Vector< amrex::Real > m_skin_moisture_hs_lev
Definition: ERF_SurfaceLayer.H:1930
Definition: ERF_SurfaceMoisture.H:21
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real surface_mixing_ratio(const amrex::Real beta, const amrex::Real q_sat, const amrex::Real q_air)
Definition: ERF_SurfaceMoisture.H:52
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real canopy_resistance(const amrex::Real r_c_without_vpd, const amrex::Real hs, const amrex::Real q_sat, const amrex::Real q_air)
Definition: ERF_SurfaceMoisture.H:94
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real surface_layer_aerodynamic_resistance(const similarity_funs &sfuns, const amrex::Real z_ref, const amrex::Real z0, const amrex::Real u_star, const amrex::Real olen, const amrex::Real wind, const amrex::Real wind_min, const amrex::Real kappa, const amrex::Real unset)
Definition: ERF_SurfaceMoisture.H:141
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real two_source_surface_mixing_ratio(const amrex::Real r_a, const amrex::Real r_c, const amrex::Real r_soil, const amrex::Real f_veg, const amrex::Real q_sat, const amrex::Real rh_ground, const amrex::Real q_air)
Definition: ERF_SurfaceMoisture.H:178
@ GroundRelativeHumidity
Definition: ERF_SurfaceMoisture.H:36
@ SoilResistance
Definition: ERF_SurfaceMoisture.H:35
@ VegetationFraction
Definition: ERF_SurfaceMoisture.H:33
@ NumComponents
Definition: ERF_SurfaceMoisture.H:37
@ CanopyResistanceWithoutVPD
Definition: ERF_SurfaceMoisture.H:34
@ SoilFactor
Definition: ERF_SurfaceMoisture.H:32
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool valid_pressure(const amrex::Real pressure)
Definition: ERF_SurfaceTemperature.H:46
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool valid_temperature(const amrex::Real temperature)
Definition: ERF_SurfaceTemperature.H:53
Definition: ERF_MOSTUtils.H:67
Here is the call graph for this function:

◆ fill_tsurf_with_abs_surf_temp()

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

Set t_surf from the prescribed absolute surface temperature (most.abs_surf_temp, plus most.surf_heating_rate times the time), converted to the potential temperature the surface layer works in with the pressure at the surface diagnosed from the lowest cell, as for coupled SST. Like update_surf_temp, which it replaces in that case, it writes every cell of the surface.

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

Set t_surf from the prescribed absolute surface temperature, converted to potential temperature at the surface pressure.

Parameters
[in]levCurrent level
[in]timeElapsed time, for the heating rate
[in]cons_inConserved state (surface pressure from the lowest cell)
[in]z_phys_ndNodal heights, or nullptr on a flat mesh
2661 {
2662  const Real t_abs = surf_temp + surf_heating_rate * static_cast<Real>(time);
2663  if (surf_heating_rate != 0) {
2664  amrex::Print() << "Surface temp (absolute) at t=" << time << ": " << t_abs << std::endl;
2665  }
2666 
2667  const int klo = m_geom[lev].Domain().smallEnd(2);
2668  const Real dz = m_geom[lev].CellSize(2);
2669  const bool moist = use_moisture;
2670  const Real rdOcp = m_rdOcp;
2671  // As in fill_tsurf_with_coupled_sst: a moist run whose state lacks the vapour
2672  // component aborts below rather than reading past the components.
2673  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
2674  amrex::Gpu::DeviceScalar<erf_surface_temperature::ConversionFailure>
2676  auto* fail = d_fail.dataPtr();
2677 
2678  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
2679  {
2680  Box gtbx = mfi.growntilebox();
2681  if (gtbx.smallEnd(2) != klo ||
2682  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
2683  continue;
2684  }
2685  gtbx &= t_surf[lev]->fabbox(mfi.index());
2686  if (gtbx.isEmpty()) { continue; }
2687 
2688  // The halo takes the pressure of the nearest valid column; fill_planar_boundary
2689  // in update_fluxes then fills the interior and periodic ghosts.
2690  const Box vbx = mfi.validbox();
2691  const int i_lo = vbx.smallEnd(0); const int i_hi = vbx.bigEnd(0);
2692  const int j_lo = vbx.smallEnd(1); const int j_hi = vbx.bigEnd(1);
2693 
2694  auto t_surf_arr = t_surf[lev]->array(mfi);
2695  const auto cons_arr = cons_in.const_array(mfi);
2696  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
2697  Array4<const Real> {};
2698 
2699  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2700  {
2701  const int li = amrex::min(amrex::max(i, i_lo), i_hi);
2702  const int lj = amrex::min(amrex::max(j, j_lo), j_hi);
2703 
2704  // The state is checked before it is divided by, as in fill_tsurf_with_coupled_sst,
2705  // so that a bad state reaches the diagnostic rather than a floating-point trap.
2706  const Real rho = cons_arr(li,lj,klo,Rho_comp);
2707  const Real rho_theta = cons_arr(li,lj,klo,RhoTheta_comp);
2708  if (!amrex::Math::isfinite(rho) || rho <= Real(0.0) ||
2709  !amrex::Math::isfinite(rho_theta)) {
2711  li, lj, klo, rho, rho_theta);
2712  return;
2713  }
2714  Real rho_qv = Real(0.0);
2715  Real qv = Real(0.0);
2716  if (moist) {
2717  if (!have_rho_qv) {
2719  li, lj, klo);
2720  return;
2721  }
2722  rho_qv = cons_arr(li,lj,klo,RhoQ1_comp);
2723  if (!amrex::Math::isfinite(rho_qv)) {
2725  li, lj, klo, rho, rho_theta, rho_qv);
2726  return;
2727  }
2728  qv = rho_qv / rho;
2729  if (!amrex::Math::isfinite(qv)) {
2731  li, lj, klo, rho, rho_theta, rho_qv);
2732  return;
2733  }
2734  }
2735  const Real delta_z = z_arr
2736  ? Compute_Z_AtCellCenter(li,lj,klo,z_arr) -
2737  Compute_Z_AtWFace(li,lj,klo,z_arr)
2738  : myhalf*dz;
2740  rho, rho_theta, qv, delta_z);
2741  Real theta = t_surf_arr(i,j,k);
2743  t_abs, pressure, rdOcp, theta)) {
2744  t_surf_arr(i,j,k) = theta;
2745  } else {
2747  li, lj, klo, rho, rho_theta, rho_qv, t_abs, pressure, delta_z);
2748  }
2749  });
2750  }
2751  amrex::Gpu::streamSynchronize();
2753  "SurfaceLayer fill_tsurf_with_abs_surf_temp", lev, d_fail.dataValue());
2754 }
@ theta
Definition: ERF_Kessler.H:26
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool temperature_to_theta(const amrex::Real temperature, const amrex::Real pressure, const amrex::Real rdOcp, amrex::Real &theta)
Definition: ERF_SurfaceTemperature.H:79
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
2518 {
2519  // No coupler has handed us anything yet. Whatever fill_tsurf_with_sst_and_tsk
2520  // wrote stands, which is the correct answer for one-way and uncoupled runs.
2521  if (m_coupled_sst_lev.empty() || !m_coupled_sst_lev[lev]) { return; }
2522 
2523  // The loop below iterates t_surf and indexes the coupled arrays with the
2524  // same MFIter, so the layouts must agree. They do for planar terrain, where
2525  // t_surf is grids[lev] flattened with setRange(2,0) -- the same construction
2526  // GetOceanToAtmosSurfaceLayout reports. Under EB terrain t_surf keeps the
2527  // full 3D BoxArray and they would not, so fail loudly rather than read the
2528  // wrong fab.
2530  m_coupled_sst_lev[lev]->boxArray() == t_surf[lev]->boxArray() &&
2531  m_coupled_sst_lev[lev]->DistributionMap() == t_surf[lev]->DistributionMap(),
2532  "Coupled SST layout does not match the surface-layer layout.");
2533 
2534  const int klo = m_geom[lev].Domain().smallEnd(2);
2535  const Real dz = m_geom[lev].CellSize(2);
2536  const bool moist = use_moisture;
2537  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
2538  const Real rdOcp = m_rdOcp;
2539  amrex::Gpu::DeviceScalar<erf_surface_temperature::ConversionFailure>
2541  auto* fail = d_fail.dataPtr();
2542 
2543  // Absent coverage information we must assume nothing is covered: silently
2544  // treating the whole field as valid is how an uncovered cell ends up holding
2545  // the remap's zero fill.
2546  const bool has_valid = (m_coupled_sst_valid_lev[lev] != nullptr);
2547 
2548  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
2549  {
2550  Box gtbx = mfi.growntilebox();
2551 
2552  if (gtbx.smallEnd(2) != klo ||
2553  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
2554  continue;
2555  }
2556 
2557  // NOTE: the coupled lane does not carry lateral ghost cells, so clamp
2558  // into the physical source box exactly as the fallback path does.
2559  // FillBoundary in update_fluxes picks up the interior and
2560  // periodic directions.
2561  const Box source_box = cons_in.boxArray()[mfi.index()];
2562  const Box donor_box = m_coupled_sst_lev[lev]->boxArray()[mfi.index()];
2563  const int source_i_lo = source_box.smallEnd(0);
2564  const int source_i_hi = source_box.bigEnd(0);
2565  const int source_j_lo = source_box.smallEnd(1);
2566  const int source_j_hi = source_box.bigEnd(1);
2567  const int donor_i_lo = donor_box.smallEnd(0);
2568  const int donor_i_hi = donor_box.bigEnd(0);
2569  const int donor_j_lo = donor_box.smallEnd(1);
2570  const int donor_j_hi = donor_box.bigEnd(1);
2571  const int donor_k = donor_box.smallEnd(2);
2572  gtbx &= t_surf[lev]->fabbox(mfi.index());
2573  if (gtbx.isEmpty()) { continue; }
2574 
2575  auto t_surf_arr = t_surf[lev]->array(mfi);
2576  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2577  Array4<int> {};
2578  const auto coupled_sst_arr = m_coupled_sst_lev[lev]->const_array(mfi);
2579  auto const& valid_arr = has_valid ? m_coupled_sst_valid_lev[lev]->const_array(mfi)
2580  : Array4<const int>{};
2581  const auto cons_arr = cons_in.const_array(mfi);
2582  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
2583  Array4<const Real> {};
2584 
2585  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2586  {
2587  const int li = amrex::min(amrex::max(i, source_i_lo), source_i_hi);
2588  const int lj = amrex::min(amrex::max(j, source_j_lo), source_j_hi);
2589  const int si = amrex::min(amrex::max(li, donor_i_lo), donor_i_hi);
2590  const int sj = amrex::min(amrex::max(lj, donor_j_lo), donor_j_hi);
2591  int is_land = (lmask_arr) ? lmask_arr(li,lj,0) : 1;
2592  if (is_land) { return; }
2593 
2594  if (!has_valid || valid_arr(si,sj,donor_k) == 0) { return; }
2595 
2596  const Real rho = cons_arr(li,lj,klo,Rho_comp);
2597  const Real rho_theta = cons_arr(li,lj,klo,RhoTheta_comp);
2598  if (!amrex::Math::isfinite(rho) || rho <= Real(0.0) ||
2599  !amrex::Math::isfinite(rho_theta)) {
2601  li, lj, klo, rho, rho_theta);
2602  return;
2603  }
2604  Real qv = Real(0.0);
2605  if (moist) {
2606  if (!have_rho_qv) {
2608  li, lj, klo);
2609  return;
2610  }
2611  const Real rho_qv = cons_arr(li,lj,klo,RhoQ1_comp);
2612  if (!amrex::Math::isfinite(rho_qv)) {
2614  li, lj, klo, rho, rho_theta, rho_qv);
2615  return;
2616  }
2617  qv = rho_qv / rho;
2618  if (!amrex::Math::isfinite(qv)) {
2620  li, lj, klo, rho, rho_theta, rho_qv);
2621  return;
2622  }
2623  }
2624  const Real delta_z = z_arr
2625  ? Compute_Z_AtCellCenter(li,lj,klo,z_arr) -
2626  Compute_Z_AtWFace(li,lj,klo,z_arr)
2627  : myhalf*dz;
2629  rho, rho_theta, qv, delta_z);
2630  Real theta = t_surf_arr(i,j,k);
2632  coupled_sst_arr(si,sj,donor_k), pressure, rdOcp, theta)) {
2633  t_surf_arr(i,j,k) = theta;
2634  } else {
2636  li, lj, klo, rho, rho_theta,
2637  moist ? cons_arr(li,lj,klo,RhoQ1_comp) : Real(0.0),
2638  coupled_sst_arr(si,sj,donor_k), pressure, delta_z);
2639  }
2640  });
2641  }
2642  amrex::Gpu::streamSynchronize();
2643  erf_surface_temperature::abort_on_conversion_failure("SurfaceLayer fill_tsurf_with_coupled_sst",
2644  lev, d_fail.dataValue());
2645 }
amrex::Vector< amrex::MultiFab * > m_coupled_sst_lev
Definition: ERF_SurfaceLayer.H:1921
amrex::Vector< amrex::iMultiFab * > m_coupled_sst_valid_lev
Definition: ERF_SurfaceLayer.H:1922
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
2049 {
2050  update_sfc_time_index(elapsed_time);
2051  const Real sfc_sst = interpolate_sfc_column(elapsed_time, 1);
2052  const int klo = m_geom[lev].Domain().smallEnd(2);
2053  const Real dz = m_geom[lev].CellSize(2);
2054  const bool moist = use_moisture;
2055  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
2056  const Real rdOcp = m_rdOcp;
2057  amrex::Gpu::DeviceScalar<erf_surface_temperature::ConversionFailure>
2059  auto* fail = d_fail.dataPtr();
2060 
2061  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
2062  {
2063  Box gtbx = mfi.growntilebox();
2064 
2065  // A z-split planar field has one copy for every stacked 3-D box. Only
2066  // the copy whose source box touches the physical z-low face owns the
2067  // text-SST conversion and may read the 3-D state or terrain.
2068  if (gtbx.smallEnd(2) != klo ||
2069  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
2070  continue;
2071  }
2072 
2073  const Box source_box = cons_in.boxArray()[mfi.index()];
2074  gtbx &= t_surf[lev]->fabbox(mfi.index());
2075  gtbx &= cons_in.fabbox(mfi.index());
2076  if (z_phys_nd) {
2077  gtbx &= amrex::convert(z_phys_nd->fabbox(mfi.index()), IntVect::TheCellVector());
2078  }
2079  if (m_lmask_lev[lev][0]) {
2080  gtbx &= m_lmask_lev[lev][0]->fabbox(mfi.index());
2081  }
2082  if (gtbx.isEmpty()) { continue; }
2083 
2084  auto t_surf_arr = t_surf[lev]->array(mfi);
2085  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2086  Array4<int> {};
2087  const auto cons_arr = cons_in.const_array(mfi);
2088  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
2089  Array4<const Real> {};
2090  const int i_lo = source_box.smallEnd(0);
2091  const int i_hi = source_box.bigEnd(0);
2092  const int j_lo = source_box.smallEnd(1);
2093  const int j_hi = source_box.bigEnd(1);
2094 
2095  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2096  {
2097  const int li = amrex::min(amrex::max(i, i_lo), i_hi);
2098  const int lj = amrex::min(amrex::max(j, j_lo), j_hi);
2099  int is_land = (lmask_arr) ? lmask_arr(li,lj,0) : 0;
2100  if (!is_land) {
2101  const Real rho = cons_arr(li,lj,klo,Rho_comp);
2102  const Real rho_theta = cons_arr(li,lj,klo,RhoTheta_comp);
2103  if (!amrex::Math::isfinite(rho) || rho <= Real(0.0) ||
2104  !amrex::Math::isfinite(rho_theta)) {
2106  li, lj, klo, rho, rho_theta);
2107  return;
2108  }
2109  Real qv = Real(0.0);
2110  if (moist) {
2111  if (!have_rho_qv) {
2113  li, lj, klo);
2114  return;
2115  }
2116  const Real rho_qv = cons_arr(li,lj,klo,RhoQ1_comp);
2117  if (!amrex::Math::isfinite(rho_qv)) {
2119  li, lj, klo, rho, rho_theta, rho_qv);
2120  return;
2121  }
2122  qv = rho_qv / rho;
2123  if (!amrex::Math::isfinite(qv)) {
2125  li, lj, klo, rho, rho_theta, rho_qv);
2126  return;
2127  }
2128  }
2129  const Real delta_z = z_arr
2130  ? Compute_Z_AtCellCenter(li,lj,klo,z_arr) -
2131  Compute_Z_AtWFace(li,lj,klo,z_arr)
2132  : myhalf*dz;
2134  rho, rho_theta, qv, delta_z);
2135  Real theta = t_surf_arr(i,j,k);
2136  if (erf_surface_temperature::temperature_to_theta(sfc_sst, pressure, rdOcp, theta)) {
2137  t_surf_arr(i,j,k) = theta;
2138  } else {
2140  li, lj, klo, rho, rho_theta,
2141  moist ? cons_arr(li,lj,klo,RhoQ1_comp) : Real(0.0),
2142  sfc_sst, pressure, delta_z);
2143  }
2144  }
2145  });
2146  }
2147 
2148  amrex::Gpu::streamSynchronize();
2149  erf_surface_temperature::abort_on_conversion_failure("SurfaceLayer fill_tsurf_with_sfc_sst",
2150  lev, d_fail.dataValue());
2151 
2152  fill_planar_boundary(lev, *t_surf[lev]);
2153 }
amrex::Real interpolate_sfc_column(const amrex::Real &time, int col) const
Definition: ERF_SurfaceLayer.cpp:536
void update_sfc_time_index(const amrex::Real &time)
Definition: ERF_SurfaceLayer.cpp:519
Here is the call graph for this function:

◆ fill_tsurf_with_skin_temperature()

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

Overwrite the land surface temperature with an external skin temperature.

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

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

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

Parameters
[in]levCurrent level
[in]cons_inConserved state (surface pressure from the lowest cell)
[in]z_phys_ndNodal heights, or nullptr on a flat mesh
2768 {
2769  const MultiFab& skin = *m_skin_tsurf_lev[lev];
2770  // Indexed with the MFIter of t_surf, so the layouts must agree: both are the
2771  // level's grids flattened to the surface for planar terrain.
2773  skin.boxArray() == t_surf[lev]->boxArray() &&
2774  skin.DistributionMap() == t_surf[lev]->DistributionMap(),
2775  "Skin temperature layout does not match the surface-layer layout.");
2776 
2777  const int klo = m_geom[lev].Domain().smallEnd(2);
2778  const Real dz = m_geom[lev].CellSize(2);
2779  const bool moist = use_moisture;
2780  const Real rdOcp = m_rdOcp;
2781  // As in fill_tsurf_with_coupled_sst: a moist run whose state lacks the vapour
2782  // component aborts below rather than reading past the components.
2783  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
2784  amrex::Gpu::DeviceScalar<erf_surface_temperature::ConversionFailure>
2786  auto* fail = d_fail.dataPtr();
2787 
2788  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
2789  {
2790  Box gtbx = mfi.growntilebox();
2791  if (gtbx.smallEnd(2) != klo ||
2792  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
2793  continue;
2794  }
2795  gtbx &= t_surf[lev]->fabbox(mfi.index());
2796  if (gtbx.isEmpty()) { continue; }
2797 
2798  // The skin is written on valid cells only, so the halo takes the nearest
2799  // valid column; fill_planar_boundary in update_fluxes then fills the
2800  // interior and periodic ghosts.
2801  const Box vbx = mfi.validbox();
2802  const int i_lo = vbx.smallEnd(0); const int i_hi = vbx.bigEnd(0);
2803  const int j_lo = vbx.smallEnd(1); const int j_hi = vbx.bigEnd(1);
2804 
2805  auto t_surf_arr = t_surf[lev]->array(mfi);
2806  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2807  Array4<int> {};
2808  const auto skin_arr = skin.const_array(mfi);
2809  const auto cons_arr = cons_in.const_array(mfi);
2810  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
2811  Array4<const Real> {};
2812 
2813  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2814  {
2815  const int li = amrex::min(amrex::max(i, i_lo), i_hi);
2816  const int lj = amrex::min(amrex::max(j, j_lo), j_hi);
2817  const int is_land = (lmask_arr) ? lmask_arr(li,lj,0) : 1;
2818  if (!is_land) { return; }
2819 
2820  const Real rho = cons_arr(li,lj,klo,Rho_comp);
2821  const Real rho_theta = cons_arr(li,lj,klo,RhoTheta_comp);
2822  if (moist && !have_rho_qv) {
2824  return;
2825  }
2826  const Real qv = moist ? cons_arr(li,lj,klo,RhoQ1_comp) / rho : Real(0.0);
2827  const Real delta_z = z_arr
2828  ? Compute_Z_AtCellCenter(li,lj,klo,z_arr) -
2829  Compute_Z_AtWFace(li,lj,klo,z_arr)
2830  : myhalf*dz;
2832  rho, rho_theta, qv, delta_z);
2833  Real theta = t_surf_arr(i,j,k);
2834  if (!amrex::Math::isfinite(qv)) {
2836  moist ? cons_arr(li,lj,klo,RhoQ1_comp) : Real(0.0));
2838  skin_arr(li,lj,k), pressure, rdOcp, theta)) {
2839  t_surf_arr(i,j,k) = theta;
2840  } else {
2842  li, lj, klo, rho, rho_theta,
2843  moist ? cons_arr(li,lj,klo,RhoQ1_comp) : Real(0.0),
2844  skin_arr(li,lj,k), pressure, delta_z);
2845  }
2846  });
2847  }
2848  amrex::Gpu::streamSynchronize();
2850  "SurfaceLayer fill_tsurf_with_skin_temperature", lev, d_fail.dataValue());
2851 }
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
1953 {
1954  int n_times_in_sst = static_cast<int>(m_sst_lev[lev].size());
1955 
1956  double dT = m_low_time_interval;
1957 
1958  int n_time_lo, n_time_hi;
1959  Real alpha;
1960 
1961  if (n_times_in_sst > 1) {
1962  n_time_lo = static_cast<int>( elapsed_time_since_start_low / dT);
1963  alpha = static_cast<Real>((elapsed_time_since_start_low - n_time_lo * dT) / dT);
1964 
1965  AMREX_ALWAYS_ASSERT( alpha >= zero && alpha <= one);
1966 
1967  n_time_hi = n_time_lo + 1;
1968 
1969  // Do not over run the last sst file
1970  if (m_start_low_time + elapsed_time_since_start_low >= m_final_low_time) {
1971  n_time_lo = static_cast<int>(m_sst_lev[lev].size())-1;
1972  n_time_hi = n_time_lo;
1973  alpha = zero;
1974  }
1975 
1976  AMREX_ALWAYS_ASSERT( (n_time_lo >= 0) && (n_time_hi < m_sst_lev[lev].size()));
1977  } else {
1978  n_time_lo = 0;
1979  n_time_hi = 0;
1980  alpha = one;
1981  }
1982  AMREX_ALWAYS_ASSERT( alpha >= zero && alpha <= one);
1983 
1984  Real oma = one - alpha;
1985 
1986  // Define a default land surface temperature if we don't read in tsk
1988 
1989  bool use_tsk = (m_tsk_lev[lev][0]);
1990  bool ignore_sst = m_ignore_sst;
1991 
1992  const int klo = m_geom[lev].Domain().smallEnd(2);
1993 
1994  // Populate t_surf
1995  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
1996  {
1997  Box gtbx = mfi.growntilebox();
1998 
1999  if (gtbx.smallEnd(2) != klo) { continue; }
2000 
2001  auto t_surf_arr = t_surf[lev]->array(mfi);
2002 
2003  const auto sst_lo_arr = m_sst_lev[lev][n_time_lo]->const_array(mfi);
2004  const auto sst_hi_arr = m_sst_lev[lev][n_time_hi]->const_array(mfi);
2005 
2006  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2007  Array4<int> {};
2008 
2009  if (use_tsk) {
2010  // TSK carries the SST from the low file over water (update_sst_tsk), so it is
2011  // interpolated in time between the same two slices as the SST. Written as
2012  // lo + alpha * (hi - lo) so that a cell whose TSK is the same in both slices,
2013  // as it is over land, keeps exactly that value.
2014  const auto tsk_lo_arr = m_tsk_lev[lev][n_time_lo]->const_array(mfi);
2015  const auto tsk_hi_arr = (m_tsk_lev[lev][n_time_hi]) ? m_tsk_lev[lev][n_time_hi]->const_array(mfi)
2016  : tsk_lo_arr;
2017  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2018  {
2019  int is_land = (lmask_arr) ? lmask_arr(i,j,k) : 1;
2020  if (!is_land && !ignore_sst) {
2021  t_surf_arr(i,j,k) = oma * sst_lo_arr(i,j,k)
2022  + alpha * sst_hi_arr(i,j,k);
2023  } else {
2024  t_surf_arr(i,j,k) = tsk_lo_arr(i,j,k)
2025  + alpha * (tsk_hi_arr(i,j,k) - tsk_lo_arr(i,j,k));
2026  }
2027  });
2028  } else {
2029  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2030  {
2031  int is_land = (lmask_arr) ? lmask_arr(i,j,k) : 1;
2032  if (!is_land) {
2033  t_surf_arr(i,j,k) = oma * sst_lo_arr(i,j,k)
2034  + alpha * sst_hi_arr(i,j,k);
2035  } else {
2036  t_surf_arr(i,j,k) = lst;
2037  }
2038  });
2039  }
2040  }
2041  fill_planar_boundary(lev, *t_surf[lev]);
2042 }
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_sst_lev
Definition: ERF_SurfaceLayer.H:1911
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_tsk_lev
Definition: ERF_SurfaceLayer.H:1912
@ alpha
Definition: ERF_SLM.H:151
Here is the call graph for this function:

◆ get_lmask()

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

Return the land-mask field.

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

◆ get_lsm_tsurf()

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

Fill surface temperature from the LSM surface-temperature field, converted to potential temperature at the surface pressure when the field is an absolute temperature.

Parameters
[in]levlevel index
[in]cons_inconserved state (surface pressure from the lowest cell)
[in]z_phys_ndnodal heights, or nullptr on a flat mesh

Fill surface temperature from land-surface-model data.

By default the LSM field is copied into t_surf unchanged. With most.use_lsm_skin_temperature, t_surf is a potential temperature: an LSM skin temperature that is an absolute temperature (m_lsm_tsurf_is_theta false) is converted at the surface pressure, as fill_tsurf_with_skin_temperature does. When the LSM also supplies the surface fluxes, t_surf follows the skin only where all four fluxes are valid: those are the cells whose fluxes and u*, t*, q*, L come from the LSM, so the MOST fluxes of the remaining cells keep the surface temperature they had.

Parameters
[in]levCurrent level
[in]cons_inConserved state (surface pressure from the lowest cell)
[in]z_phys_ndNodal heights, or nullptr on a flat mesh
2340 {
2341  const int klo = m_geom[lev].Domain().smallEnd(2);
2342 
2344  // Default: the LSM field is copied into t_surf as it is.
2345  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
2346  {
2347  Box gtbx = mfi.growntilebox();
2348 
2349  if (gtbx.smallEnd(2) != klo) { continue; }
2350 
2351  // NOTE: LSM does not carry lateral ghost cells.
2352  // This copies the valid box into the ghost cells.
2353  // Fillboundary is called after this to pick up the
2354  // interior ghost and periodic directions.
2355  const NearestColumn col(mfi.validbox());
2356 
2357  auto t_surf_arr = t_surf[lev]->array(mfi);
2358  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2359  Array4<int> {};
2360  const auto& lsm_tsurf = *m_lsm_data_lev[lev][m_lsm_tsurf_indx];
2361  // get the top-most index of the LSM to use as the surface temperature
2362  const auto& lsm_box = lsm_tsurf.box(mfi.index());
2363  const int lsm_khi = lsm_box.bigEnd(2);
2364  const auto lsm_arr = lsm_tsurf.const_array(mfi);
2365 
2366  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2367  {
2368  int is_land = (lmask_arr) ? lmask_arr(i,j,k) : 1;
2369  if (is_land) {
2370  const Real lsm_value = lsm_arr(col.i(i),col.j(j),lsm_khi);
2371  if (amrex::Math::isfinite(lsm_value) && lsm_value > Real(0.0) &&
2372  lsm_value < lsm_undefined) {
2373  t_surf_arr(i,j,k) = lsm_value;
2374  }
2375  }
2376  });
2377  }
2378  return;
2379  }
2380 
2381  const Real dz = m_geom[lev].CellSize(2);
2382  const bool moist = use_moisture;
2383  const bool have_rho_qv = cons_in.nComp() > RhoQ1_comp;
2384  const Real rdOcp = m_rdOcp;
2385  const bool convert = !m_lsm_tsurf_is_theta;
2386  const bool gate_on_fluxes = m_has_lsm_fluxes;
2387  const bool use_surface_model_fluxes = use_surface_model && surf_model_fluxes;
2388  amrex::Gpu::DeviceScalar<erf_surface_temperature::ConversionFailure>
2390  auto* fail = d_fail.dataPtr();
2391 
2392  for (MFIter mfi(*t_surf[lev]); mfi.isValid(); ++mfi)
2393  {
2394  Box gtbx = mfi.growntilebox();
2395 
2396  // Only the copy whose source box touches the z-low face may read the
2397  // state there; fill_planar_boundary in update_fluxes fills the others.
2398  if (gtbx.smallEnd(2) != klo ||
2399  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
2400  continue;
2401  }
2402  gtbx &= t_surf[lev]->fabbox(mfi.index());
2403  if (gtbx.isEmpty()) { continue; }
2404 
2405  // NOTE: LSM does not carry lateral ghost cells.
2406  // This copies the valid box into the ghost cells.
2407  // Fillboundary is called after this to pick up the
2408  // interior ghost and periodic directions.
2409  const NearestColumn col(mfi.validbox());
2410 
2411  auto t_surf_arr = t_surf[lev]->array(mfi);
2412  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
2413  Array4<int> {};
2414  const auto& lsm_tsurf = *m_lsm_data_lev[lev][m_lsm_tsurf_indx];
2415  // get the top-most index of the LSM to use as the surface temperature
2416  // this is -1 for SLM, but could be different for other models?
2417  const auto& lsm_box = lsm_tsurf.box(mfi.index());
2418  const int lsm_khi = lsm_box.bigEnd(2);
2419  const auto lsm_arr = lsm_tsurf.const_array(mfi);
2420  const auto cons_arr = cons_in.const_array(mfi);
2421  const auto z_arr = (z_phys_nd) ? z_phys_nd->const_array(mfi) :
2422  Array4<const Real> {};
2423 
2424  // The same four fluxes, and the same validity test, as
2425  // compute_sfc_params_from_lsm_fluxes
2426  auto lsm_t_flux_arr = Array4<const Real> {};
2427  auto lsm_q_flux_arr = Array4<const Real> {};
2428  auto lsm_tau13_arr = Array4<const Real> {};
2429  auto lsm_tau23_arr = Array4<const Real> {};
2430  if (gate_on_fluxes) {
2431  if (use_surface_model_fluxes) {
2432  const SurfaceFluxView surf_flux = m_surf_model->get_surface_flux_view(lev, mfi);
2433  lsm_t_flux_arr = surf_flux.t_flux;
2434  lsm_q_flux_arr = surf_flux.q_flux;
2435  lsm_tau13_arr = surf_flux.tau13;
2436  lsm_tau23_arr = surf_flux.tau23;
2437  } else {
2438  for (int n(0); n<m_lsm_flux_lev[lev].size(); ++n) {
2439  if (toLower(m_lsm_flux_name[n]) == "t_flux") { lsm_t_flux_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
2440  if (toLower(m_lsm_flux_name[n]) == "q_flux") { lsm_q_flux_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
2441  if (toLower(m_lsm_flux_name[n]) == "tau13") { lsm_tau13_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
2442  if (toLower(m_lsm_flux_name[n]) == "tau23") { lsm_tau23_arr = m_lsm_flux_lev[lev][n]->const_array(mfi); }
2443  }
2444  }
2445  }
2446  const bool have_fluxes = lsm_t_flux_arr && lsm_q_flux_arr && lsm_tau13_arr && lsm_tau23_arr;
2447 
2448  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
2449  {
2450  const int li = col.i(i);
2451  const int lj = col.j(j);
2452  int is_land = (lmask_arr) ? lmask_arr(i,j,k) : 1;
2453  if (!is_land) { return; }
2454 
2455  const Real lsm_value = lsm_arr(li,lj,lsm_khi);
2456  if (!amrex::Math::isfinite(lsm_value) || lsm_value <= Real(0.0) ||
2457  lsm_value >= lsm_undefined) {
2458  return;
2459  }
2460 
2461  if (gate_on_fluxes) {
2462  if (!have_fluxes ||
2463  !is_valid_lsm_value(lsm_t_flux_arr(li,lj,0)) ||
2464  !is_valid_lsm_value(lsm_q_flux_arr(li,lj,0)) ||
2465  !is_valid_lsm_value(lsm_tau13_arr (li,lj,0)) ||
2466  !is_valid_lsm_value(lsm_tau23_arr (li,lj,0))) {
2467  return;
2468  }
2469  }
2470 
2471  if (!convert) {
2472  t_surf_arr(i,j,k) = lsm_value;
2473  return;
2474  }
2475 
2476  const Real rho = cons_arr(li,lj,klo,Rho_comp);
2477  const Real rho_theta = cons_arr(li,lj,klo,RhoTheta_comp);
2478  if (moist && !have_rho_qv) {
2480  return;
2481  }
2482  const Real qv = moist ? cons_arr(li,lj,klo,RhoQ1_comp) / rho : Real(0.0);
2483  const Real delta_z = z_arr
2484  ? Compute_Z_AtCellCenter(li,lj,klo,z_arr) -
2485  Compute_Z_AtWFace(li,lj,klo,z_arr)
2486  : myhalf*dz;
2488  rho, rho_theta, qv, delta_z);
2489  Real theta = t_surf_arr(i,j,k);
2490  if (!amrex::Math::isfinite(qv)) {
2492  moist ? cons_arr(li,lj,klo,RhoQ1_comp) : Real(0.0));
2494  lsm_value, pressure, rdOcp, theta)) {
2495  t_surf_arr(i,j,k) = theta;
2496  } else {
2498  li, lj, klo, rho, rho_theta,
2499  moist ? cons_arr(li,lj,klo,RhoQ1_comp) : Real(0.0),
2500  lsm_value, pressure, delta_z);
2501  }
2502  });
2503  }
2504  amrex::Gpu::streamSynchronize();
2505  erf_surface_temperature::abort_on_conversion_failure("SurfaceLayer get_lsm_tsurf",
2506  lev, d_fail.dataValue());
2507 }
bool m_lsm_tsurf_is_theta
Definition: ERF_SurfaceLayer.H:1841
int m_lsm_tsurf_indx
Definition: ERF_SurfaceLayer.H:1840
bool m_has_lsm_fluxes
Definition: ERF_SurfaceLayer.H:1838
bool m_use_lsm_skin_temperature
Definition: ERF_SurfaceLayer.H:1842
amrex::Vector< amrex::Vector< amrex::MultiFab * > > m_lsm_data_lev
Definition: ERF_SurfaceLayer.H:1931
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
1386  {
1387  return m_ma.get_average(lev, comp);
1388  }
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
1420 { 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
1428 { 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.

1398 { 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
1370 { 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
1377 { 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
1363 { 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
1591 { 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
1606 { 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
1356 { 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
1444 { 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
1330 { 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
1337 { 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
1627 { return &z_0[lev]; }

Referenced by ERF::FillPlot2DVars().

Here is the caller graph for this function:

◆ get_zref()

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

Return the 2D MF of zref heights.

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

◆ get_zref_min()

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

Return the minimum reference height for one level.

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

1632 { return m_var_z0; }
bool m_var_z0
Definition: ERF_SurfaceLayer.H:1832

◆ 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,
amrex::MultiFab *  mf_surface_fluxes = nullptr 
)

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
875 {
877  amrex::Real wsmin = most_min_wind_speed; // TODO: change for different faces
878  const Box& domain = m_geom[lev].Domain();
879  moeng_flux flux_comp(wsmin, m_face.isLow(),
880  domain.smallEnd(2), domain.bigEnd(2));
881  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
882  xheat_flux, yheat_flux, zheat_flux,
883  xqv_flux, yqv_flux, zqv_flux,
884  z_phys, flux_comp, mf_surface_fluxes);
885  } else if (flux_type == FluxCalcType::ROTATE) {
886  rotate_flux flux_comp;
887  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
888  xheat_flux, yheat_flux, zheat_flux,
889  xqv_flux, yqv_flux, zqv_flux,
890  z_phys, flux_comp, mf_surface_fluxes);
891  } else if (flux_type == FluxCalcType::RICO) {
893  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
894  xheat_flux, yheat_flux, zheat_flux,
895  xqv_flux, yqv_flux, zqv_flux,
896  z_phys, flux_comp, mf_surface_fluxes);
897  } else if (flux_type == FluxCalcType::BULK_COEFF) {
898  bulk_coeff_flux flux_comp(m_Cd, m_Ch, m_Cq);
899  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
900  xheat_flux, yheat_flux, zheat_flux,
901  xqv_flux, yqv_flux, zqv_flux,
902  z_phys, flux_comp, mf_surface_fluxes);
903  } else if (flux_type == FluxCalcType::CUSTOM) {
904  custom_flux flux_comp(specified_rho_surf);
905  compute_SurfaceLayer_bcs(lev, mfs, Tau_lev,
906  xheat_flux, yheat_flux, zheat_flux,
907  xqv_flux, yqv_flux, zqv_flux,
908  z_phys, flux_comp, mf_surface_fluxes);
909  } else {
910  amrex::Abort("Unknown surface layer flux calculation type");
911  }
912 }
bool specified_rho_surf
Definition: ERF_SurfaceLayer.H:1827
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, amrex::MultiFab *mf_surface_fluxes=nullptr)
Definition: ERF_MOSTStress.H:2599
Definition: ERF_MOSTStress.H:2433
Definition: ERF_MOSTStress.H:2109
Definition: ERF_MOSTStress.H:2773
Definition: ERF_MOSTStress.H:2951

◆ 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
937 {
939  moeng_flux_eb flux_comp;
940  compute_SurfaceLayer_bcs_EB(lev, mfs, Tau_EB,
941  xheat_flux, yheat_flux, Hfx3_EB,
942  xqv_flux, yqv_flux, zqv_flux,
943  flux_comp);
944  } else {
945  amrex::Abort("Not implemented surface layer flux calculation type for EB");
946  }
947 }
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:327

◆ 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
3413 {
3415  static_cast<int>(m_face) == Orientation::zlo(),
3416  "TKE initialization from surface-layer ustar is supported only on the z-low face.");
3417 
3418  Print() << "Initializing TKE from surface layer ustar on level " << lev << std::endl;
3419 
3420  // Handle vertical decomposition by selectively copying into
3421  // a FArrayBox section on each rank. Then doing a reduce real sum
3422  // and broadcasting to each rank. No mask since all CC data
3423  //
3424  // Pinned so the reduction below can read them on the host; the device
3425  // still reaches pinned memory, so the loops here are unaffected.
3426  const int klo = m_geom[lev].Domain().smallEnd(2);
3427  Box bx_lo = u_star[lev]->boxArray().minimalBox();
3428  FArrayBox u_star_lo(bx_lo, 1, The_Pinned_Arena()); u_star_lo.setVal<RunOn::Host>(0);
3429  FArrayBox z_surf_lo(bx_lo, 1, The_Pinned_Arena()); z_surf_lo.setVal<RunOn::Host>(0);
3430  Real* ustar_ptr = u_star_lo.dataPtr();
3431  Real* zsurf_ptr = z_surf_lo.dataPtr();
3432  for (MFIter mfi(cons); mfi.isValid(); ++mfi)
3433  {
3434  Box vbx = mfi.validbox();
3435  if (vbx.smallEnd(2) != klo) { continue; }
3436  vbx.makeSlab(2,0);
3437 
3438  auto const& u_star_arr = u_star[lev]->const_array(mfi);
3439  auto u_star_all = u_star_lo.array();
3440 
3441  auto const& z_phys_arr = z_phys_nd->const_array(mfi);
3442  auto z_surf_all = z_surf_lo.array();
3443 
3444  ParallelFor(vbx, [=] AMREX_GPU_DEVICE(int i, int j, int ) noexcept
3445  {
3446  u_star_all(i,j,0) = u_star_arr(i,j,0);
3447  z_surf_all(i,j,0) = fourth * ( z_phys_arr(i ,j ,klo) + z_phys_arr(i+1,j ,klo)
3448  + z_phys_arr(i ,j+1,klo) + z_phys_arr(i+1,j+1,klo) );
3449  });
3450  }
3451  Gpu::streamSynchronize(); // the fills above are async, the reduction is not
3452  ParallelDescriptor::ReduceRealSum(ustar_ptr, static_cast<int>(bx_lo.numPts()));
3453  ParallelDescriptor::ReduceRealSum(zsurf_ptr, static_cast<int>(bx_lo.numPts()));
3454 
3455  // Now work on all boxes (ustar has been filled above)
3456  constexpr Real small = Real(0.01);
3457  for (MFIter mfi(cons); mfi.isValid(); ++mfi)
3458  {
3459  Box vbx = mfi.validbox();
3460 
3461  auto const& u_star_arr = u_star_lo.const_array();
3462  auto const& z_surf_arr = z_surf_lo.const_array();
3463  auto const& z_phys_arr = z_phys_nd->const_array(mfi);
3464 
3465  auto const& cons_arr = cons.array(mfi);
3466 
3467  ParallelFor(vbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
3468  {
3469  Real rho = cons_arr(i, j, k, Rho_comp);
3470  Real ust = u_star_arr(i, j, 0);
3471  Real tke0 = tkefac * ust * ust; // surface value
3472  Real zagl = Compute_Z_AtCellCenter(i, j, k, z_phys_arr) - z_surf_arr(i,j,0);
3473 
3474  // linearly tapering profile -- following WRF, approximate top of
3475  // PBL as ustar * zscale
3476  cons_arr(i, j, k, RhoKE_comp) = rho * tke0 * std::max(
3477  (ust * zscale - zagl) / (std::max(ust, small) * zscale),
3478  small);
3479  });
3480  }
3481 }
constexpr amrex::Real fourth
Definition: ERF_NumericalConstants.H:42
Here is the call graph for this function:

◆ inject_pblh_from_coarser()

void SurfaceLayer::inject_pblh_from_coarser ( const int &  lev,
amrex::MultiFab &  fine 
) const

The PBL height of the next coarser level, injected onto the grids of this level over all of their cells, ghost cells included. Public because it launches a device lambda.

Parameters
[in]levCurrent level (> 0)
[out]fineMultiFab on the grids of pblh[lev]

Write into fine, a MultiFab on the grids of pblh[lev], the PBL height of the next coarser level over all of its cells, ghost cells included (see fill_pblh_from_coarser).

Parameters
[in]levCurrent level (> 0)
[out]finePBL height of level lev-1, injected onto the grids of level lev
3350 {
3351  AMREX_ALWAYS_ASSERT(lev > 0 && pblh[lev-1]);
3352  const Geometry& geom_f = m_geom[lev];
3353  const Geometry& geom_c = m_geom[lev-1];
3354  const IntVect rr(AMREX_D_DECL(static_cast<int>(std::lround(geom_c.CellSize(0) / geom_f.CellSize(0))),
3355  static_cast<int>(std::lround(geom_c.CellSize(1) / geom_f.CellSize(1))),
3356  1));
3357 
3358  const MultiFab& crse = *pblh[lev-1];
3359 
3360  // Each fine column takes the height of the coarse column that holds it (injection, no
3361  // interpolation): the fine height is then exactly the coarse diagnostic, and independent of
3362  // the decomposition, at the price of being constant over each block of rr x rr fine columns.
3363  // The coarse level's own height varies at that resolution too. Interpolating would reach
3364  // further into the coarse halo than these planar fields hold: a bilinear stencil for a fine
3365  // ghost cell at a non-periodic domain boundary wants a second coarse cell outside the
3366  // domain, and pblh carries one ghost cell. A smooth cap would need a wider halo there, or
3367  // a one-sided stencil.
3368  //
3369  // The coarse heights over the boxes of this level, with enough ghost cells to cover theirs.
3370  // Ghost cells go first, then the valid cells, so every cell the coarse level owns comes
3371  // from the box that owns it. The estimator writes pblh over its grown box (it clips only
3372  // in z), so the coarse ghost cells the first copy reads hold a diagnosed height, outside a
3373  // non-periodic domain too.
3374  const IntVect ng_f = fine.nGrowVect();
3375  const IntVect ng_c(AMREX_D_DECL((ng_f[0] + rr[0] - 1) / rr[0], (ng_f[1] + rr[1] - 1) / rr[1], ng_f[2]));
3376  MultiFab crse_on_fine(amrex::coarsen(fine.boxArray(), rr), fine.DistributionMap(), 1, ng_c);
3377  crse_on_fine.setVal(zero);
3378  for (const IntVect& ng_src : {crse.nGrowVect(), IntVect(0)}) {
3379  crse_on_fine.ParallelCopy(crse, 0, 0, 1, elemwiseMin(ng_src, ng_c), ng_c, geom_c.periodicity());
3380  }
3381 
3382 #ifdef _OPENMP
3383 #pragma omp parallel if (Gpu::notInLaunchRegion())
3384 #endif
3385  for (MFIter mfi(fine, TilingIfNotGPU()); mfi.isValid(); ++mfi) {
3386  const Box bx = mfi.growntilebox();
3387  const Array4<Real> f = fine.array(mfi);
3388  const Array4<Real const> c = crse_on_fine.const_array(mfi);
3389  const int rx = rr[0];
3390  const int ry = rr[1];
3391  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
3392  {
3393  f(i,j,k) = c(amrex::coarsen(i,rx), amrex::coarsen(j,ry), k);
3394  });
3395  }
3396 }
real(c_double), private rr
Definition: ERF_module_mp_morr_two_moment.F90:224
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
538 {
539  if (sfc.empty() || sfc[0].empty()) { return zero; }
540  if (sfc[0].size() == 1) { return sfc[col][0]; }
541 
542  const Real t0 = sfc[0][sfc_time_ind];
543  const Real t1 = sfc[0][sfc_time_ind+1];
544  const Real x0 = sfc[col][sfc_time_ind];
545  const Real x1 = sfc[col][sfc_time_ind+1];
546 
547  if (elapsed_time < t0) {
548  return x0;
549  }
550 
551  if (t0 == t1 || elapsed_time > t1) {
552  return x1;
553  }
554 
555  const Real dt = (elapsed_time - t0) / (t1 - t0);
556  return x0 + (x1 - x0) * dt;
557 }
amrex::Vector< amrex::Vector< amrex::Real > > sfc
Definition: ERF_SurfaceLayer.H:1848
int sfc_time_ind
Definition: ERF_SurfaceLayer.H:1847
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
1649  {
1650  int lmask_min = amrex::ReduceMin(lmask, nghost, [=] AMREX_GPU_HOST_DEVICE(
1651  amrex::Box const& bx, amrex::Array4<int const> const& lm_arr) -> int
1652  {
1653  int locmin = std::numeric_limits<int>::max();
1654  const auto lo = lbound(bx);
1655  const auto hi = ubound(bx);
1656  for (int j = lo.y; j <= hi.y; ++j) {
1657  for (int i = lo.x; i <= hi.x; ++i) {
1658  locmin = std::min(locmin, lm_arr(i, j, 0));
1659  }
1660  }
1661  return locmin;
1662  });
1663 
1664  return lmask_min;
1665  }

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

1393 { 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
476  {
477  // Update MOST Average
479  Theta_prim, Qv_prim, Qr_prim,
480  z_phys_nd);
481 
482  // Get CC vars
483  amrex::MultiFab& mf = *(mfv[0]);
484 
485  amrex::ParmParse pp(m_pp_prefix);
486 
487  // Do we have a time-varying surface roughness that needs to be saved?
488  if (lev == 0) {
489  const int nghost = 0; // ghost cells not included
490  int lmask_min = lmask_min_reduce(*lmask_lev[0].get(), nghost);
491  amrex::ParallelDescriptor::ReduceIntMin(lmask_min);
492 
493  m_var_z0 = (lmask_min < 1) & (rough_type_sea != RoughCalcType::CONSTANT);
494  if (m_var_z0) {
496  m_face.coordDir() == 2 && m_face.isLow(),
497  "Variable sea roughness surface-layer fluxes are supported only on the z-low face.");
498 
499  std::string rough_sea_string{"charnock"};
500  pp.queryAdd("most.roughness_type_sea", rough_sea_string);
501  amrex::Print() << "Variable sea roughness (type " << rough_sea_string
502  << ")" << std::endl;
503  }
504  }
505 
506  if (m_eddyDiffs_lev.size() < lev+1) {
507  m_Hwave_lev.resize(nlevs);
508  m_Lwave_lev.resize(nlevs);
509  m_eddyDiffs_lev.resize(nlevs);
510 
511  m_lsm_data_lev.resize(nlevs);
512  m_lsm_flux_lev.resize(nlevs);
513 
514  m_sst_lev.resize(nlevs);
515  m_tsk_lev.resize(nlevs);
516  m_lmask_lev.resize(nlevs);
517 
518  m_coupled_sst_lev.resize(nlevs, nullptr);
519  m_coupled_sst_valid_lev.resize(nlevs, nullptr);
520 
521  // Size the MOST params for all levels
522  z_0.resize(nlevs);
523  u_star.resize(nlevs);
524  w_star.resize(nlevs);
525  t_star.resize(nlevs);
526  q_star.resize(nlevs);
527  t_surf.resize(nlevs);
528  q_surf.resize(nlevs);
529  surface_diagnostic_source.resize(nlevs);
530  olen.resize(nlevs);
531  pblh.resize(nlevs);
532  m_planar_bndry.resize(nlevs);
533  }
534 
535  // Get pointers to SST,TSK and LANDMASK data
536  int nt_tot_sst = sst_lev.size();
537  m_sst_lev[lev].resize(nt_tot_sst);
538  for (int nt(0); nt < nt_tot_sst; ++nt) {
539  m_sst_lev[lev][nt] = sst_lev[nt].get();
540  }
541  int nt_tot_tsk = static_cast<int>(tsk_lev.size());
542  m_tsk_lev[lev].resize(nt_tot_tsk);
543  for (int nt(0); nt < nt_tot_tsk; ++nt) {
544  m_tsk_lev[lev][nt] = tsk_lev[nt].get();
545  }
546  int nt_tot_lmask = static_cast<int>(lmask_lev.size());
547  m_lmask_lev[lev].resize(nt_tot_lmask);
548  for (int nt(0); nt < nt_tot_lmask; ++nt) {
549  m_lmask_lev[lev][nt] = lmask_lev[nt].get();
550  }
551 
552  // Get pointers to wave data
553  m_Hwave_lev[lev] = Hwave;
554  m_Lwave_lev[lev] = Lwave;
555  m_eddyDiffs_lev[lev] = eddyDiffs;
556 
557  // Text-file driven surface forcing modes. The file always contains at
558  // least time(day) and absolute sst(K); the value is normalized to the
559  // canonical SurfaceLayer theta field at the z-low surface.
560  pp.queryAdd("most.use_sfc_fluxes", m_use_sfc_fluxes);
561  pp.queryAdd("most.use_sfc_sst", m_use_sfc_sst);
563  amrex::Abort("Only one of most.use_sfc_fluxes and most.use_sfc_sst may be enabled");
564  }
566  if (m_use_sfc_fluxes) {
567  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(!use_surface_model, "Cannot use surface flux forcing with LSM/Urban model!");
568  // The file's H and LE already carry the density, and the stress
569  // takes the local density, so a surface density would count it
570  // twice in H and LE (see set_sfc_params_from_file_fluxes).
571  // The value is tested, not the key: flux_type = custom has already
572  // queryAdd-ed most.rhosurf with its default of 0 (use the first cell).
573  amrex::Real rhosurf_in = zero;
574  pp.query("most.rhosurf", rhosurf_in);
576  "most.rhosurf cannot be combined with most.use_sfc_fluxes");
577  }
578  if (m_terrain_type == TerrainType::EB) {
579  amrex::Abort("Text-file surface forcing is not supported with EB terrain");
580  }
582  m_face.coordDir() == 2 && m_face.isLow(),
583  "most.use_sfc_fluxes and most.use_sfc_sst are supported only on the z-low face.");
584 
585  // load sensible and latent heat fluxes from sfc to prescribe
586  std::string sfc_file = "";
587  pp.queryAdd("most.sfc_file", sfc_file);
588  if (sfc_file.empty()) {
589  amrex::Abort("most.sfc_file must be set when using text-file surface forcing");
590  }
591 
592  // sfc contains: time(day) sst(K) H(W/m2) LE(W/m2) TAU(m2/s2)
593  sfc = read_cols(sfc_file, 1);
594 
595  const int min_cols = m_use_sfc_fluxes ? 5 : 2;
596  if (static_cast<int>(sfc.size()) < min_cols) {
597  amrex::Abort("Surface forcing file does not contain the required number of columns");
598  }
599 
600  // shift time column in days to be relative to current elapsed time
601  const amrex::Real start_day = sfc[0][0];
602  for (int i = 0; i < static_cast<int>(sfc[0].size()); ++i) {
603  sfc[0][i] = 86400.0 * (sfc[0][i] - start_day);
604  }
605 
606  if (m_use_sfc_sst) {
608  amrex::Abort("most.use_sfc_sst cannot be combined with prescribed heat flux or surf_heating_rate");
609  }
611  amrex::Abort("most.use_sfc_sst cannot be combined with prescribed moisture flux");
612  }
614  amrex::Print() << "Using MOST with prescribed SST from most.sfc_file '" << sfc_file << "' over sea" << std::endl;
615  }
616 
617  if (m_use_sfc_fluxes) {
619  amrex::Print() << "Using MOST with prescribed time-varying surface fluxes from '" << sfc_file << "'" << std::endl;
620  }
621  }
622 
623  // The constructor's check of most.abs_surf_temp sees only most.flux_type; the
624  // modes known here replace the prescribed temperature too. The surface-flux file
625  // makes the fluxes custom, and the SST/TSK and sfc-file SST fills use the
626  // prescribed value as their land temperature, which is a potential temperature there.
627  if (surf_temp_is_absolute) {
629  "most.abs_surf_temp cannot be combined with most.use_sfc_fluxes");
631  (m_sst_lev[lev].empty() || !m_sst_lev[lev][0]) && !m_use_sfc_sst,
632  "most.abs_surf_temp cannot be combined with SST/TSK or sfc-file SST input");
633  }
634 
635  // By default an LSM field named "tsurf", "theta" or "t_surf" is copied into
636  // t_surf unchanged. With this option the LSM skin temperature ("tsurf" from
637  // SLM, "t_sfc" from Noah-MP) is taken as an absolute temperature and
638  // converted to potential temperature at the surface pressure.
639  pp.queryAdd("most.use_lsm_skin_temperature", m_use_lsm_skin_temperature);
640 
641  // Get pointers to LSM data and Fluxes
642  int ndata = static_cast<int>(lsm_data.size());
643  int nflux = static_cast<int>(lsm_flux.size());
644  m_lsm_data_name.resize(ndata);
645  m_lsm_data_lev[lev].resize(ndata);
646  m_lsm_flux_name.resize(nflux);
647  m_lsm_flux_lev[lev].resize(nflux);
648  for (int n(0); n < ndata; ++n) {
649  m_lsm_data_name[n] = lsm_data_name[n];
650  m_lsm_data_lev[lev][n] = lsm_data[n];
651  const std::string lc_name = amrex::toLower(lsm_data_name[n]);
652  // With most.use_lsm_skin_temperature, "tsurf" (SLM) and "t_sfc" (Noah-MP)
653  // are absolute skin temperatures and are converted to potential
654  // temperature in get_lsm_tsurf; "theta" and "t_surf" are taken to be
655  // potential temperature already.
656  if (lc_name == "tsurf" || (m_use_lsm_skin_temperature && lc_name == "t_sfc") ||
657  lc_name == "theta" || lc_name == "t_surf") {
658  m_has_lsm_tsurf = true;
659  m_lsm_tsurf_indx = n;
660  m_lsm_tsurf_is_theta = (lc_name == "theta" || lc_name == "t_surf");
661  }
662  }
664  amrex::Abort("most.use_sfc_sst cannot be combined with an ocean LSM t_surf input");
665  }
666  int n_valid_lsm_flux = 0;
667  bool has_soil_t_flux = false;
668  for (int n(0); n < nflux; ++n) {
669  m_lsm_flux_name[n] = lsm_flux_name[n];
670  m_lsm_flux_lev[lev][n] = lsm_flux[n];
671  if (m_lsm_flux_lev[lev][n]) { ++n_valid_lsm_flux; }
672  if (amrex::toLower(m_lsm_flux_name[n]) == "soil_t_flux") {
673  has_soil_t_flux = true;
674  }
675  }
676  AMREX_ALWAYS_ASSERT((n_valid_lsm_flux==0 || n_valid_lsm_flux>=4 ||
677  (n_valid_lsm_flux==1 && has_soil_t_flux)));
678  if (n_valid_lsm_flux>=4) { m_has_lsm_fluxes = true; }
679 
680  const bool use_sst = (!m_sst_lev[lev].empty() && m_sst_lev[lev][0]);
681  const bool use_tsk = (!m_tsk_lev[lev].empty() && m_tsk_lev[lev][0]);
682 
683  // Check if there is a user-specified roughness file to be read
684  std::string fname;
685  bool read_z0 = false;
686  if ( (flux_type == FluxCalcType::MOENG) ||
688  int count = pp.countval("most.roughness_file_name");
689  if (count > 1) {
690  AMREX_ALWAYS_ASSERT(count >= lev+1);
691  pp.query("most.roughness_file_name", fname, lev);
692  read_z0 = true;
693  } else if (count == 1) {
694  if (lev == 0) {
695  pp.queryAdd("most.roughness_file_name", fname);
696  } else {
697  // we will interpolate from the coarsest level
698  fname = "";
699  }
700  read_z0 = true;
701  }
702  // else use z0_const
703  }
704  if (read_z0) {
706  m_face.coordDir() == 2 && m_face.isLow(),
707  "Custom MOST roughness is supported only on the z-low face.");
708  }
709 
710  // LSM flux arrays are planar and compute_sfc_params_from_lsm_fluxes only
711  // writes the k=0 slab, while EB MOST consumes surface parameters at
712  // arbitrary cut-cell k; no valid planar-to-cut-cell mapping exists yet.
714  m_terrain_type, use_sst, use_tsk, m_use_coupled_sst,
715  m_has_lsm_tsurf, m_has_lsm_fluxes, read_z0)) {
716  amrex::Abort(
717  "EB SurfaceLayer does not support planar SST/TSK, coupled SST, LSM surface "
718  "temperature or fluxes, or custom/file-driven roughness; no mapping exists "
719  "from those planar inputs to arbitrary EB cut cells.");
720  }
721 
722  // Attributes for MFs and FABs
723  //--------------------------------------------------------
724  // Create a 2D ba for planar terrain, 3D for EB terrain
725  const int dir = m_face.coordDir();
726  int sm_index;
727  if (m_face.isLow()) {
728  sm_index = m_geom[lev].Domain().smallEnd(dir);
729  } else {
730  sm_index = m_geom[lev].Domain().bigEnd(dir);
731  }
732 
733  amrex::BoxArray ba = mf.boxArray();
734  amrex::BoxArray ba_flux;
735  amrex::IntVect ng{1,1,0};
736 
737  // The lateral-wall implementation uses the two-dimensional land-mask
738  // layout to identify grids on the requested face. Lateral walls are
739  // intentionally restricted to grids spanning the complete z domain;
740  // z-decomposed and partial-height grids are not supported.
741  if (dir != 2) {
742  const int dom_lo_z = m_geom[lev].Domain().smallEnd(2);
743  const int dom_hi_z = m_geom[lev].Domain().bigEnd(2);
744  for (int ibox = 0; ibox < ba.size(); ++ibox) {
746  ba[ibox].smallEnd(2) == dom_lo_z && ba[ibox].bigEnd(2) == dom_hi_z,
747  "Surface layer boundaries on x/y faces require Cartesian grids that "
748  "span the full level z domain; partial-height refined grids and grids "
749  "decomposed in z are not supported. Set erf.max_grid_size_z accordingly.");
750  }
751  }
752 
753  if (m_terrain_type == TerrainType::EB) {
754  // Use full 3D BoxArray for EB terrain
755  ba_flux = ba;
756  ng = amrex::IntVect{1,1,1}; // Include z ghost cells
757  } else {
758  // Collapse to 2D for planar terrain
759  amrex::BoxList bl2d = ba.boxList();
760  for (auto& b : bl2d) {
761  b.setRange(dir,sm_index);
762  }
763  ba_flux = amrex::BoxArray(std::move(bl2d));
764  // Lateral faces need state-width ghosts in the tangential z
765  // direction. A z face only needs the original one-cell x/y halo;
766  // kernels using the wider state/mask boxes clip to the FAB bounds.
767  if (dir == 2) {
768  ng = amrex::IntVect{1,1,0};
769  } else {
770  ng = mf.nGrowVect();
771  ng[dir] = 0;
772  }
773  }
774 
775  const amrex::DistributionMapping& dm = mf.DistributionMap();
776  const int ncomp = 1;
777 
778  // Surface copies of the planar boxes (see fill_planar_boundary)
779  // PlanarBoundary handles duplicate boxes created by a z-split. The x/y
780  // layouts use selective lateral exchange instead.
781  if (m_terrain_type != TerrainType::EB && dir == 2) {
782  const int ksurface = m_face.isLow()
783  ? m_geom[lev].Domain().smallEnd(2)
784  : m_geom[lev].Domain().bigEnd(2);
785  m_planar_bndry[lev].define(ba, ba_flux, dm, ksurface, m_face.isLow());
786  }
787 
788  // Z0 heights FAB
789  //--------------------------------------------------------
790  z_0[lev].define(ba_flux, dm, ncomp, ng);
791  z_0[lev].setVal(z0_const);
792  if (read_z0) {
793  read_custom_roughness(lev, fname);
794  }
795 
796  // 2D MFs for U*, T*, T_surf
797  //--------------------------------------------------------
798  u_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
799  u_star[lev]->setVal(bogus_large_value);
800 
801  w_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
802  w_star[lev]->setVal(bogus_large_value);
803 
804  t_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
805  t_star[lev]->setVal(zero); // default to neutral
806 
807  q_star[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
808  q_star[lev]->setVal(zero); // default to dry
809 
810  olen[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
811  olen[lev]->setVal(bogus_large_value);
812 
813  pblh[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
814  pblh[lev]->setVal(bogus_large_value);
815 
816  t_surf[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
817  t_surf[lev]->setVal(default_land_surf_temp);
818 
819  q_surf[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
820  q_surf[lev]->setVal(default_land_surf_moist);
821 
822  const amrex::iMultiFab& surface_mask = *m_lmask_lev[lev][0];
824  surface_mask.boxArray().size() == mf.boxArray().size(),
825  "Surface-layer mask and state must have the same number of boxes.");
827  surface_mask.DistributionMap() == mf.DistributionMap(),
828  "Surface-layer mask and state must have identical ownership.");
830  surface_mask.boxArray().size() == u_star[lev]->boxArray().size(),
831  "Surface-layer mask and parameters must have the same number of boxes.");
833  surface_mask.DistributionMap() == u_star[lev]->DistributionMap(),
834  "Surface-layer mask and parameters must have identical ownership.");
835 
837 
838  surface_diagnostic_source[lev] = std::make_unique<amrex::MultiFab>(ba_flux, dm, ncomp, ng);
839  surface_diagnostic_source[lev]->setVal(
841 
842  // TODO: Do we want an enum struct for indexing?
843 
844  if (use_sst || use_tsk || m_has_lsm_tsurf || m_use_coupled_sst || use_surface_model) {
845  // Valid SST, TSK, LSM or coupled-ocean data; t_surf set before computing
846  // fluxes (avoids extended lambda capture) Note that land temp will be set
847  // from m_tsk_lev while sea temp will be set from m_sst_lev
849 
850  // Pathways in fill_tsurf_with_sst_and_tsk
851  amrex::Print() << "Using MOST with specified surface temperature ";
852  if (m_has_lsm_tsurf && !use_sst && !use_tsk) {
853  amrex::Print() << "(LSM: " << m_lsm_data_name[m_lsm_tsurf_indx] << ")";
854  } else if (!use_sst && !use_tsk) {
855  amrex::Print() << "(land: T0, sea: none)";
856  } else {
857  // NOTE: SST from the LOW file populates TSK in update_sst_tsk.
858  // So if we have TSK, it contains everything and has been
859  // sanity checked for valid SST values.
860  if (use_tsk) { m_ignore_sst = true; }
861  if (use_tsk) {
862  amrex::Print() << "(land: TSK, ";
863  } else {
864  amrex::Print() << "(land: T0, ";
865  }
866  if (use_tsk && !use_sst) {
867  amrex::Print() << "sea: TSK)";
868  } else {
869  amrex::Print() << "sea: SST)";
871  }
872  }
873  // The coupler is layered on top of whatever the above selected: it
874  // overwrites only the water cells it actually covers, so the pathway
875  // named above remains the value for land and for uncovered water.
876  if (m_use_coupled_sst) {
877  amrex::Print() << " + coupled ocean SST where covered";
878  }
879  amrex::Print() << std::endl;
880  }
881  }
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:172
int lmask_min_reduce(amrex::iMultiFab &lmask, const int &nghost)
Definition: ERF_SurfaceLayer.H:1647
amrex::Vector< std::string > m_lsm_data_name
Definition: ERF_SurfaceLayer.H:1933
bool m_has_lsm_tsurf
Definition: ERF_SurfaceLayer.H:1839
bool m_use_sfc_fluxes
Definition: ERF_SurfaceLayer.H:1845
void set_immersed_surface_params(const int &lev)
Definition: ERF_SurfaceLayer.cpp:38
static amrex::Vector< amrex::Vector< amrex::Real > > read_cols(const std::string &fname, const int skip_nlines=1)
Definition: ERF_SurfaceLayer.cpp:3639
bool m_use_sfc_sst
Definition: ERF_SurfaceLayer.H:1846
bool m_use_coupled_sst
Definition: ERF_SurfaceLayer.H:1856
void read_custom_roughness(const int &lev, const std::string &fname)
Definition: ERF_SurfaceLayer.cpp:3491
@ ng
Definition: ERF_Morrison.H:51
bool planar_sources_supported_for_terrain(TerrainType terrain_type, bool use_sst, bool use_tsk, bool use_coupled_sst, bool has_lsm_tsurf, bool has_lsm_fluxes, bool has_custom_roughness)
Definition: ERF_SurfaceLayer.H:32
Here is the call graph for this function:

◆ read_cols()

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

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

Parameters
[in]fnamepath to text file
[in]skip_nlinesnumber of lines to skip before reading data (e.g, header lines)
Returns
Vector containing each column in the file as a vector
3640 {
3641  std::ifstream ifs(fname);
3642  if (!ifs.is_open())
3643  {
3644  amrex::Error("Error opening input file " + fname);
3645  }
3646 
3647  amrex::Vector<amrex::Vector<amrex::Real>> col_data;
3648  std::string line;
3649  int nlines = 0;
3650  int ncols = -1;
3651 
3652  const auto print_err = [](const std::string &err_fname, int lineno, int cols, int expected_cols) {
3653  amrex::Error("Error reading file '" + err_fname + "': expected line " +
3654  std::to_string(lineno) + " to have " + std::to_string(expected_cols) +
3655  " columns, but got " + std::to_string(cols));
3656  };
3657 
3658  while (std::getline(ifs, line))
3659  {
3660  nlines++;
3661  if (nlines <= skip_nlines) continue;
3662  if (line.empty()) continue;
3663 
3664  std::istringstream iss(line);
3665 
3666  amrex::Real tmp;
3667  // Get the number of columns in the file
3668  if (ncols == -1) {
3669  int j = 0;
3670  while (iss >> tmp) {
3671  col_data.push_back(amrex::Vector<amrex::Real>());
3672  j+= 1;
3673  }
3674 
3675  ncols = j;
3676  iss = std::istringstream(line);
3677  }
3678 
3679  int j = 0;
3680  while (iss >> tmp) {
3681  // verify each line has the same number of columns
3682  if (j >= ncols) {
3683  print_err(fname, nlines, j+1, ncols);
3684  }
3685  col_data[j].push_back(tmp);
3686  j+= 1;
3687  }
3688 
3689  // throw error if there are fewer columns in the line than expected
3690  if (j != ncols) {
3691  print_err(fname, nlines, j, ncols);
3692  }
3693  }
3694 
3695  ifs.close();
3696 
3697  return col_data;
3698 }
@ 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
3493 {
3494  // Read the file if we have it
3495  if (!fname.empty()) {
3496  // Only the ioproc reads the file
3497  Gpu::HostVector<Real> m_x,m_y,m_z0;
3498  if (ParallelDescriptor::IOProcessor()) {
3499  Print()<<"Reading MOST roughness file at level " << lev << " : " << fname << std::endl;
3500  std::ifstream file(fname);
3501  if (!file.is_open()) {
3502  Abort("Could not open the MOST roughness file \"" + fname + "\" given as "
3503  "erf.most.roughness_file_name -- please check the file name in the inputs file");
3504  }
3505  Real value1,value2,value3;
3506  while(file>>value1>>value2>>value3){
3507  m_x.push_back(value1);
3508  m_y.push_back(value2);
3509  m_z0.push_back(value3);
3510  }
3511  file.close();
3512 
3513  AMREX_ALWAYS_ASSERT(m_x.size() == m_y.size());
3514  AMREX_ALWAYS_ASSERT(m_x.size() == m_z0.size());
3515 
3516  if (m_x.empty()) {
3517  Abort("The MOST roughness file \"" + fname + "\" holds no (x, y, z0) triples");
3518  }
3519  }
3520 
3521  // Broadcast the whole domain to every rank
3522  int ioproc = ParallelDescriptor::IOProcessorNumber();
3523  int nnode = static_cast<int>(m_x.size());
3524  ParallelDescriptor::Bcast(&nnode, 1, ioproc);
3525 
3526  if (!ParallelDescriptor::IOProcessor()) {
3527  m_x.resize(nnode);
3528  m_y.resize(nnode);
3529  m_z0.resize(nnode);
3530  }
3531  ParallelDescriptor::Bcast(m_x.data() , nnode, ioproc);
3532  ParallelDescriptor::Bcast(m_y.data() , nnode, ioproc);
3533  ParallelDescriptor::Bcast(m_z0.data(), nnode, ioproc);
3534 
3535  // The loop below indexes the file's values as nodes of this level's grid, so a file
3536  // holding fewer values than the grid has nodes would be read past its end
3537  {
3538  const Box& dom = m_geom[lev].Domain();
3539  const int nnode_needed = dom.bigEnd(0) + dom.bigEnd(1) * (dom.length(0)+1) + 1;
3540  if (nnode < nnode_needed) {
3541  Abort("The MOST roughness file \"" + fname + "\" holds " + std::to_string(nnode) +
3542  " values but the grid at level " + std::to_string(lev) + " needs at least " +
3543  std::to_string(nnode_needed) + " -- is this file written for this grid?");
3544  }
3545  }
3546 
3547  // Copy data to the GPU
3548  Gpu::DeviceVector<Real> d_x(nnode),d_y(nnode),d_z0(nnode);
3549  Gpu::copy(Gpu::hostToDevice, m_x.begin(), m_x.end(), d_x.begin());
3550  Gpu::copy(Gpu::hostToDevice, m_y.begin(), m_y.end(), d_y.begin());
3551  Gpu::copy(Gpu::hostToDevice, m_z0.begin(), m_z0.end(), d_z0.begin());
3552  Real* xp = d_x.data();
3553  Real* yp = d_y.data();
3554  Real* z0p = d_z0.data();
3555 
3556  // Each rank populates it's z_0[lev] MultiFab
3557  const int klo = m_geom[lev].Domain().smallEnd(2);
3558  for (MFIter mfi(z_0[lev]); mfi.isValid(); ++mfi)
3559  {
3560  Box gtbx = mfi.growntilebox();
3561 
3562  if (gtbx.smallEnd(2) != klo) { continue; }
3563 
3564  // Populate z_phys data
3565  Real tol = Real(1.0e-4);
3566  auto dx = m_geom[lev].CellSizeArray();
3567  auto ProbLoArr = m_geom[lev].ProbLoArray();
3568  int ilo = m_geom[lev].Domain().smallEnd(0);
3569  int jlo = m_geom[lev].Domain().smallEnd(1);
3570  int ihi = m_geom[lev].Domain().bigEnd(0);
3571  int jhi = m_geom[lev].Domain().bigEnd(1);
3572 
3573  Array4<Real> const& z0_arr = z_0[lev].array(mfi);
3574  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE (int i, int j, int /*k*/)
3575  {
3576  // Clip indices for ghost-cells
3577  int ii = amrex::min(amrex::max(i,ilo),ihi);
3578  int jj = amrex::min(amrex::max(j,jlo),jhi);
3579 
3580  // Location of nodes
3581  Real x = ProbLoArr[0] + ii * dx[0];
3582  Real y = ProbLoArr[1] + jj * dx[1];
3583  int inode = ii + jj * (ihi-ilo+2); // stride is Nx+1
3584  if (std::sqrt(amrex::Math::powi<2>(x-xp[inode])+amrex::Math::powi<2>(y-yp[inode])) < tol) {
3585  z0_arr(i,j,klo) = z0p[inode];
3586  } else {
3587  // Unexpected list order, do brute force search
3588  Real z0loc = zero;
3589  bool found = false;
3590  for (int n=0; n<nnode; ++n) {
3591  Real delta=std::sqrt(amrex::Math::powi<2>(x-xp[n])+amrex::Math::powi<2>(y-yp[n]));
3592  if (delta < tol) {
3593  found = true;
3594  z0loc = z0p[n];
3595  break;
3596  }
3597  }
3598  AMREX_ASSERT_WITH_MESSAGE(found, "Location read from terrain file does not match the grid!");
3599  amrex::ignore_unused(found);
3600  z0_arr(i,j,klo) = z0loc;
3601  }
3602  });
3603  } // mfi
3604  } else {
3605  AMREX_ALWAYS_ASSERT(lev > 0);
3606 
3607  Print()<<"Interpolating MOST roughness at level " << lev << std::endl;
3608 
3609  // Create a BC mapper that uses FOEXTRAP at domain bndry
3610  Vector<int> bc_lo(3,ERFBCType::foextrap);
3611  Vector<int> bc_hi(3,ERFBCType::foextrap);
3612  Vector<BCRec> bcr; bcr.push_back(BCRec(bc_lo.data(),bc_hi.data()));
3613 
3614  // Create ref ratio
3615  IntVect ratio;
3616  for (int idim = 0; idim < AMREX_SPACEDIM; ++idim) {
3617  ratio[idim] = m_geom[lev].Domain().length(idim) / m_geom[0].Domain().length(idim);
3618  }
3619 
3620  // Create interp object and interpolate from the coarsest grid
3621  MFInterpolater* interp = &mf_cell_cons_interp;
3622  interp->interp(z_0[0] , 0,
3623  z_0[lev], 0,
3624  1, z_0[lev].nGrowVect(),
3625  m_geom[0], m_geom[lev],
3626  m_geom[lev].Domain(),ratio,
3627  bcr, 0);
3628  }
3629 }
@ m_y
Definition: ERF_DataStruct.H:39
@ m_x
Definition: ERF_DataStruct.H:38
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
@ foextrap
Definition: ERF_IndexDefines.H:339

Referenced by make_SurfaceLayer_at_level().

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

◆ rotates_surface_fluxes()

bool SurfaceLayer::rotates_surface_fluxes ( ) const
inline

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

1576 { return m_rotate; }

◆ roughness()

amrex::Real SurfaceLayer::roughness ( ) const
inline

The constant roughness length [m] the levels take.

1565 { return z0_const; }

◆ 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
1718 { m_use_coupled_sst = active; }

◆ set_default_roughness()

bool SurfaceLayer::set_default_roughness ( amrex::Real  z0)
inline

The land roughness length [m] to use when erf.most.z0 is not given (the two-stream balance's, from Noah-MP's tables). Call before the levels are made, which set z0 from it.

Taking it also replaces the erf.most.z0 entry that the constructor's queryAdd put in the ParmParse table. job_info (ParmParse::dumpTable) then records the roughness the run used.

Returns
whether the roughness was taken: an erf.most.z0 given in the inputs stands.
1555  {
1556  if (m_z0_given) { return false; }
1557  z0_const = z0;
1558  amrex::ParmParse pp(m_pp_prefix);
1559  pp.remove("most.z0");
1560  pp.add("most.z0", z0_const);
1561  return true;
1562  }
Real z0
Definition: ERF_InitCustomPertVels_ScalarAdvDiff.H:12
Here is the call graph for this function:

◆ set_immersed_surface_params()

void SurfaceLayer::set_immersed_surface_params ( const int &  lev)

Set benign surface scales in immersed columns (land mask 2), including ghosts. Called after allocation, checkpoint restoration, and surface-scale updates. Preserve PBL height, roughness, surface temperature, and surface moisture.

Parameters
[in]levlevel index
39 {
40  // The land mask describes ground columns, not the full height of a lateral wall.
41  if (m_face.coordDir() != 2 ||
42  (m_terrain_type != TerrainType::ImmersedForcing && !m_has_immersed_buildings)) {
43  return;
44  }
45  const auto& surface_mask = *m_lmask_lev[lev][0];
46  for (MFIter mfi(*u_star[lev]); mfi.isValid(); ++mfi) {
47  Box bx = mfi.fabbox();
48  // Limit the halo to the mask's available ghost cells.
49  Box mask_columns = surface_mask.fabbox(mfi.index());
50  mask_columns.setRange(2, bx.smallEnd(2), bx.length(2));
51  bx &= mask_columns;
52 
53  const auto mask_arr = surface_mask.const_array(mfi);
54  const auto u_star_arr = u_star[lev]->array(mfi);
55  const auto w_star_arr = w_star[lev]->array(mfi);
56  const auto t_star_arr = t_star[lev]->array(mfi);
57  const auto q_star_arr = q_star[lev]->array(mfi);
58  const auto olen_arr = olen[lev]->array(mfi);
59  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
60  if (mask_arr(i,j,0) == 2) {
61  // MOST and LSM updates skip immersed columns. Finite, inactive
62  // scales keep their initialization sentinels out of stress/TKE
63  // calculations; immersed forcing handles roof and wall physics.
64  u_star_arr(i,j,k) = zero;
65  w_star_arr(i,j,k) = zero;
66  t_star_arr(i,j,k) = zero;
67  q_star_arr(i,j,k) = zero;
68  olen_arr(i,j,k) = immersed_neutral_obukhov_length;
69  }
70  });
71  }
72 }
constexpr amrex::Real immersed_neutral_obukhov_length
Definition: ERF_Constants.H:31

Referenced by make_SurfaceLayer_at_level().

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

◆ 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
1437 { 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 
)

Store the PBL height a PBL scheme (MRF, YSU, YSUNew) has diagnosed.

pblh_in has no ghost cells, so the ghost cells of pblh are filled here: those that another grid of this level or a periodic image holds by fill_planar_boundary; those at a coarse/fine boundary from the next coarser level (the same injection as fill_pblh_from_coarser), so that they agree with the coarse state the other surface fields there are computed from; those outside a non-periodic domain from the nearest column of the domain (after fill_planar_boundary, so that a corner beside another grid takes that grid's height); and the rest – where the coarser level has no height yet – from the nearest column of their own grid.

Parameters
[in]levCurrent level
[in]pblh_inPBL height, on the grids of the eddy diffusivities (3D, no ghost cells)
3046 {
3047  // NOTE: pblh[lev] is a 2D MF (except with EB)
3048  // pblh_in is a 3D MF built off the eddydiffs BA
3049  AMREX_ALWAYS_ASSERT(pblh[lev]);
3050  MultiFab& dst = *pblh[lev];
3051 
3052  // The boxes of the two are paired by index below
3054  pblh_in.size() == dst.size() && pblh_in.DistributionMap() == dst.DistributionMap(),
3055  "SurfaceLayer::set_pblh: the PBL height must be on the grids of the surface layer");
3056 
3057  std::unique_ptr<MultiFab> crse;
3058  if (lev > 0 && pblh[lev-1]) {
3059  crse = std::make_unique<MultiFab>(dst.boxArray(), dst.DistributionMap(), 1, dst.nGrowVect());
3060  inject_pblh_from_coarser(lev, *crse);
3061  }
3062  // Only the columns inside the domain (or a periodic image of it) take the coarse height
3063  const Geometry& geom = m_geom[lev];
3064  const NearestColumn in_domain(geom.Domain());
3065  const bool per_x = geom.isPeriodic(0);
3066  const bool per_y = geom.isPeriodic(1);
3067 
3068 #ifdef _OPENMP
3069 #pragma omp parallel if (Gpu::notInLaunchRegion())
3070 #endif
3071  for (MFIter mfi(dst, TilingIfNotGPU()); mfi.isValid(); ++mfi)
3072  {
3073  const Box gtbx = mfi.growntilebox();
3074  const Box& vbx = mfi.validbox();
3075  const Box& sbx = pblh_in.box(mfi.index());
3077  sbx.smallEnd(0) == vbx.smallEnd(0) && sbx.bigEnd(0) == vbx.bigEnd(0) &&
3078  sbx.smallEnd(1) == vbx.smallEnd(1) && sbx.bigEnd(1) == vbx.bigEnd(1),
3079  "SurfaceLayer::set_pblh: the PBL height must be on the grids of the surface layer");
3080 
3081  // Clamp to the source box, which is the one we read
3082  const NearestColumn col(sbx);
3083  const int klo = sbx.smallEnd(2);
3084  const int khi = sbx.bigEnd(2);
3085 
3086  const Array4< Real> pblh_dst = dst.array(mfi);
3087  const Array4<const Real> pblh_src = pblh_in.const_array(mfi);
3088  const Array4<const Real> pblh_crse = (crse) ? crse->const_array(mfi) : Array4<const Real>{};
3089  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
3090  {
3091  const int li = col.i(i);
3092  const int lj = col.j(j);
3093  const int lk = amrex::min(amrex::max(k, klo), khi);
3094  Real h = pblh_src(li,lj,lk);
3095  const bool inside = (per_x || in_domain.i(i) == i) && (per_y || in_domain.j(j) == j);
3096  if (pblh_crse && inside && (li != i || lj != j) && pblh_is_diagnosed(pblh_crse(i,j,k))) {
3097  h = pblh_crse(i,j,k);
3098  }
3099  pblh_dst(i,j,k) = h;
3100  });
3101  }
3102  fill_planar_boundary(lev, dst);
3103 
3104  // A ghost cell outside a non-periodic domain but beside another grid of this level (a
3105  // corner) was left above with the nearest column of its own grid, which fill_planar_boundary
3106  // does not overwrite; give it the nearest column of the domain, which is inside this fab and
3107  // now holds the height of the grid that owns it
3108  if (!per_x || !per_y) {
3109 #ifdef _OPENMP
3110 #pragma omp parallel if (Gpu::notInLaunchRegion())
3111 #endif
3112  for (MFIter mfi(dst, TilingIfNotGPU()); mfi.isValid(); ++mfi)
3113  {
3114  const Box gtbx = mfi.growntilebox();
3115  const Array4<Real> pblh_dst = dst.array(mfi);
3116  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
3117  {
3118  const int ci = (per_x) ? i : in_domain.i(i);
3119  const int cj = (per_y) ? j : in_domain.j(j);
3120  if (ci != i || cj != j) {
3121  pblh_dst(i,j,k) = pblh_dst(ci,cj,k);
3122  }
3123  });
3124  }
3125  }
3126 }
const int khi
Definition: ERF_InitCustomPert_Bubble.H:25
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool pblh_is_diagnosed(const amrex::Real &pblh)
Definition: ERF_Wstar.H:15
real(c_double), private ci
Definition: ERF_module_mp_morr_two_moment.F90:203
Here is the call graph for this function:

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

◆ set_sfc_params_from_file_fluxes()

void SurfaceLayer::set_sfc_params_from_file_fluxes ( const int &  lev,
const amrex::MultiFab &  cons_in,
const amrex::Real &  tflux_in,
const amrex::Real &  qflux_in,
const amrex::Real &  sfc_tau 
)

Set kinematic u*, t*, and q* from the fluxes interpolated from most.sfc_file.

Parameters
[in]levlevel index
[in]cons_inconserved state supplying the first-cell density
[in]tflux_insensible heat flux H [W/m2]
[in]qflux_inlatent heat flux LE [W/m2]
[in]sfc_taukinematic surface stress TAU [m2/s2]

Set u*, t*, and q* from the fluxes interpolated from most.sfc_file.

The file's H and LE [W/m2] are dynamic fluxes, but its TAU [m2/s2] is kinematic. All three are stored kinematic, as most.ustar/tstar/qstar are without most.rhosurf, and custom_flux multiplies each by the local density: H/Cp_d and LE/L_v are divided here by the first-cell density that custom_flux multiplies back, so they are applied unchanged, and the applied stress is rho*TAU.

Parameters
[in]levCurrent level
[in]cons_inConserved state supplying the first-cell density
[in]tflux_inSensible heat flux H [W/m2]
[in]qflux_inLatent heat flux LE [W/m2]
[in]sfc_tauKinematic surface stress TAU [m2/s2]
1784 {
1785  u_star[lev]->setVal(std::sqrt(sfc_tau));
1786 
1787  const int klo = m_geom[lev].Domain().smallEnd(2);
1788  const Real rho_tflux = tflux_in / Cp_d;
1789  const Real rho_qflux = qflux_in / L_v;
1790 
1791  for (MFIter mfi(*t_star[lev]); mfi.isValid(); ++mfi)
1792  {
1793  Box gtbx = mfi.growntilebox();
1794 
1795  // Only the copy of a z-split planar field on the z-low face reads the 3-D
1796  // state; fill_planar_boundary at the end of update_fluxes fills the others.
1797  if (gtbx.smallEnd(2) != klo ||
1798  !m_planar_bndry[lev].is_surface_copy(mfi.index())) {
1799  continue;
1800  }
1801  gtbx &= cons_in.fabbox(mfi.index());
1802  if (gtbx.isEmpty()) { continue; }
1803 
1804  const auto cons_arr = cons_in.const_array(mfi);
1805  const auto t_star_arr = t_star[lev]->array(mfi);
1806  const auto q_star_arr = q_star[lev]->array(mfi);
1807 
1808  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
1809  {
1810  const Real rho = cons_arr(i,j,klo,Rho_comp);
1811  t_star_arr(i,j,k) = rho_tflux / rho;
1812  q_star_arr(i,j,k) = rho_qflux / rho;
1813  });
1814  }
1815 }
constexpr amrex::Real Cp_d
Definition: ERF_Constants.H:51
constexpr amrex::Real L_v
Definition: ERF_Constants.H:67
Here is the call graph for this function:

◆ set_skin_moisture()

void SurfaceLayer::set_skin_moisture ( const int &  lev,
const amrex::MultiFab *  moisture,
bool  use_resistances = false,
amrex::Real  hs = amrex::Real(0) 
)
inline

Take the land surface mixing ratio of level lev from the skin set by set_skin_temperature (fill_qsurf_with_skin_moisture), or stop doing so (nullptr). The owner keeps the field alive and resets the pointer whenever it reallocates it.

Parameters
[in]levlevel index
[in]moistureper-column field in the components of erf_surface_moisture::Component, on the surface-layer layout
[in]use_resistancesform q_surf from the soil resistance and pore humidity and, where the field's VegetationFraction is positive, the canopy's (two_source_surface_mixing_ratio), not from the soil-water factor alone: the two-stream balance passes true whenever it has a soil type
[in]hsthe canopy's vapour-deficit parameter [1/(kg/kg)]; 0 without vegetation
1512  {
1513  if (lev >= static_cast<int>(m_skin_moisture_lev.size())) {
1514  m_skin_moisture_lev.resize(lev + 1, nullptr);
1515  m_skin_moisture_resistances_lev.resize(lev + 1, 0);
1516  m_skin_moisture_hs_lev.resize(lev + 1, amrex::Real(0));
1517  }
1518  m_skin_moisture_lev[lev] = moisture;
1519  m_skin_moisture_resistances_lev[lev] = use_resistances ? 1 : 0;
1520  m_skin_moisture_hs_lev[lev] = hs;
1521  }

◆ set_skin_temperature()

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

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

Parameters
[in]levlevel index
[in]t_skinabsolute skin temperature, or nullptr
1456  {
1457  if (lev >= static_cast<int>(m_skin_tsurf_lev.size())) {
1458  m_skin_tsurf_lev.resize(lev + 1, nullptr);
1459  }
1460  m_skin_tsurf_lev[lev] = t_skin;
1461  }

◆ 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
1729  {
1730  m_surface_layer_faces = active_faces;
1731  }

◆ 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
1584 { t_surf[lev]->setVal(tsurf); }
@ tsurf
Definition: ERF_SLM.H:27

◆ skin_moisture_conflict()

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

Why this surface layer cannot take its surface mixing ratio from the skin (set_skin_moisture), or an empty string when it can: everything skin_temperature_conflict asks, plus a moisture model and a moisture flux computed from a surface mixing ratio (erf.most.surf_moist given).

1530  {
1531  const std::string temperature = skin_temperature_conflict();
1532  if (!temperature.empty()) { return temperature; }
1533  if (!use_moisture) {
1534  return "the run has no moisture model";
1535  }
1537  return "the surface layer does not compute its moisture flux from a surface "
1538  "mixing ratio (set erf.most.surf_moist, not a flux)";
1539  }
1540  return "";
1541  }
std::string skin_temperature_conflict() const
Definition: ERF_SurfaceLayer.H:1470
Here is the call graph for this function:

◆ skin_temperature_conflict()

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

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

1471  {
1472  if (m_terrain_type == TerrainType::EB) {
1473  return "the surface layer is on EB terrain";
1474  }
1475  // Rotated, the flux is split over hfx1/hfx2/hfx3 and the balance, which removes
1476  // the vertical-face flux hfx3 only, would lose cos(slope) of what the air gains.
1477  if (m_rotate) {
1478  return "erf.use_rotate_surface_flux is set";
1479  }
1481  return "the surface layer does not compute its heat flux from a surface "
1482  "temperature (set erf.most.pot_surf_temp or erf.most.abs_surf_temp, not a "
1483  "flux or custom/bulk fluxes)";
1484  }
1485  if (surf_heating_rate != amrex::Real(0)) {
1486  return "erf.most.surf_heating_rate is set";
1487  }
1489  return "a land-surface or surface model supplies the surface temperature";
1490  }
1491  return "";
1492  }

Referenced by skin_moisture_conflict().

Here is the caller graph for this function:

◆ 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
500 {
502  "SurfaceLayer::surface_sum is only defined for a surface layer on a z face");
503  if (m_terrain_type == TerrainType::EB) {
504  // EB fields live on the full 3D BoxArray without duplicates: sum the lowest plane
505  return sumToLine(mf, comp, 1, m_geom[lev].Domain(), 2)[0];
506  }
507  // A face-centered planar field (the MOST velocity averages) shares the planar BoxArray
508  // but not its index type, and neighbouring surface boxes would then share a face, which
509  // this sum would count twice
510  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(mf.boxArray().ixType().cellCentered(),
511  "SurfaceLayer::surface_sum is only defined for a cell-centered planar MultiFab");
512  const PlanarBoundary& pb = m_planar_bndry[lev];
513  MultiFab surface_copy(pb.surface_boxes(), pb.surface_dm(), 1, 0);
514  pb.gather_surface(mf, surface_copy, comp, 0, 1);
515  return surface_copy.sum(0);
516 }
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.
1705  {
1706  m_coupled_sst_lev[lev] = sst_ptr;
1707  m_coupled_sst_valid_lev[lev] = valid_ptr;
1708  }

◆ 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
93 {
94  bool zlo = (int) m_face == Orientation::zlo();
95  // Update with SST/TSK data if we have a valid pointer.
96  //
97  // This runs even when an ocean coupler is active: it is the only writer of
98  // t_surf over land, and it is the fallback for the water cells the coupler
99  // does not cover. Coupled SST is applied below and only where the coupler
100  // actually supplied a value, so the lower-boundary data is the base layer
101  // rather than an alternative to it.
102  if (zlo && !m_sst_lev[lev].empty() && m_sst_lev[lev][0]) {
103  fill_tsurf_with_sst_and_tsk(lev, elapsed_time_since_start_low);
104  }
105  if (zlo && m_use_sfc_sst) {
106  // Set tsurf to time varying SST from sfc file
107  fill_tsurf_with_sfc_sst(lev, elapsed_time, cons_in, z_phys_nd);
108  }
109 
110  // Apply heating rate if needed. The rate is applied from the start of the
111  // simulation, so it is given the elapsed simulation time, not the time since the
112  // start of the lower-boundary data: that includes start_time, which with
113  // erf.start_datetime and no wrflowinp file is the epoch time of the start.
115  update_surf_temp(elapsed_time);
116  if (surf_temp_is_absolute) {
117  fill_tsurf_with_abs_surf_temp(lev, elapsed_time, cons_in, z_phys_nd);
118  }
119  }
120 
121  // Overwrite the covered water cells with coupled ocean SST. This must come
122  // after update_surf_temp, which is a whole-domain setVal, and before
123  // fill_qsurf_with_qsat, which derives sea-surface humidity from t_surf.
124  if (zlo) {
125  fill_tsurf_with_coupled_sst(lev, cons_in, z_phys_nd);
126  }
127 
128  // Update land surface temp if we have a valid pointer
129  if (zlo && m_has_lsm_tsurf) {
130  get_lsm_tsurf(lev, cons_in, z_phys_nd);
131  }
132 
133  // An external skin temperature (the two-stream surface energy balance's) owns
134  // the land surface temperature; last, so it replaces what was written above.
135  if (zlo && lev < static_cast<int>(m_skin_tsurf_lev.size()) && m_skin_tsurf_lev[lev]) {
136  fill_tsurf_with_skin_temperature(lev, cons_in, z_phys_nd);
137  }
138 
139  // Update qsurf with qsat over sea
140  if (use_moisture) {
141  fill_qsurf_with_qsat(lev, cons_in, z_phys_nd);
142  }
143 
144  // Fill interior ghost cells
145  fill_planar_boundary(lev, *t_surf[lev]);
146 
147  // Compute plane averages for all vars (regardless of flux type)
148  m_ma.compute_averages(lev);
149 
150  // The skin's surface mixing ratio over land (the two-stream balance's), relative to the
151  // reference-height mixing ratio just averaged, which the moisture flux then uses.
152  if (zlo && use_moisture &&
153  lev < static_cast<int>(m_skin_tsurf_lev.size()) && m_skin_tsurf_lev[lev] &&
154  lev < static_cast<int>(m_skin_moisture_lev.size()) && m_skin_moisture_lev[lev]) {
155  fill_qsurf_with_skin_moisture(lev, cons_in, z_phys_nd);
156  fill_planar_boundary(lev, *q_surf[lev]);
157  }
158 
159  // NOTE: Do iterations to seed variables on the first step (LSM called post step)
160  // as well as compute values where invalid LSM fluxes may reside
161  //*******************************************************************************
162  // ***************************************************************
163  // Iterate the fluxes if moeng type
164  // First iterate over land -- the only model for surface roughness
165  // over land is RoughCalcType::CONSTANT
166  // ***************************************************************
169  bool is_land = true;
170  // Do we have a constant flux for moisture over land?
171  bool cons_qflux = ( (moist_type == MoistCalcType::MOISTURE_FLUX) ||
173  if (m_terrain_type != TerrainType::EB) {
176  surface_flux most_flux(surf_temp_flux, surf_moist_flux, cons_qflux);
177  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
178  } else {
179  amrex::Abort("Unknown value for rough_type_land");
180  }
183  surface_temp most_flux(surf_temp_flux, surf_moist_flux, cons_qflux,
184  m_face.coordDir(), m_face.isLow());
185  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
186  } else {
187  amrex::Abort("Unknown value for rough_type_land");
188  }
189  } else if ((theta_type == ThetaCalcType::ADIABATIC) &&
193  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
194  } else {
195  amrex::Abort("Unknown value for rough_type_land");
196  }
197  } else {
198  amrex::Abort("Unknown value for theta_type");
199  }
200  // EB
201  } else {
204  surface_flux_eb most_flux(surf_temp_flux, surf_moist_flux, cons_qflux);
205  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
206  } else {
207  amrex::Abort("Unknown value for rough_type_land");
208  }
211  surface_temp_eb most_flux(surf_temp_flux, surf_moist_flux, cons_qflux);
212  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
213  } else {
214  amrex::Abort("Unknown value for rough_type_land");
215  }
216  } else if ((theta_type == ThetaCalcType::ADIABATIC) &&
220  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
221  } else {
222  amrex::Abort("Unknown value for rough_type_land");
223  }
224  } else {
225  amrex::Abort("Unknown value for theta_type");
226  }
227  } // EB
228  } // MOENG -- LAND
229 
230  // Update u*/T*/q*/L over land (iterations or from LSM fluxes)
231  if (m_has_lsm_fluxes && elapsed_time > zero) {
233  }
234 
235  // ***************************************************************
236  // Iterate the fluxes if moeng type
237  // Next iterate over sea -- the models for surface roughness
238  // over sea are CHARNOCK, DONELAN, MODIFIED_CHARNOCK or WAVE_COUPLED
239  // NOTE: Sea surface fluxes are not supported for EB terrain
240  // ***************************************************************
241  if ((flux_type == FluxCalcType::MOENG ||
243  m_terrain_type != TerrainType::EB) {
244  bool is_land = false;
245  // NOTE: Do not allow default to adiabatic over sea (we have Qvs at surface)
246  // Do we have a constant flux for moisture over sea?
247  bool cons_qflux = (moist_type == MoistCalcType::MOISTURE_FLUX);
251  cnk_a, smooth_flow_visc, cons_qflux);
252  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
255  depth, smooth_flow_visc, cons_qflux);
256  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
257  } else if (rough_type_sea == RoughCalcType::DONELAN) {
259  smooth_flow_visc, cons_qflux);
260  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
263  smooth_flow_visc, cons_qflux);
264  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
265  } else {
266  amrex::Abort("Unknown value for rough_type_sea");
267  }
268 
272  cnk_a, smooth_flow_visc, cons_qflux);
273  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
276  depth, smooth_flow_visc, cons_qflux);
277  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
278  } else if (rough_type_sea == RoughCalcType::DONELAN) {
280  smooth_flow_visc, cons_qflux);
281  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
284  smooth_flow_visc, cons_qflux);
285  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
286  } else {
287  amrex::Abort("Unknown value for rough_type_sea");
288  }
289 
290  } else if ((theta_type == ThetaCalcType::ADIABATIC) &&
295  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
299  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
300  } else if (rough_type_sea == RoughCalcType::DONELAN) {
302  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
305  compute_fluxes(lev, max_iters, cons_in, most_flux, is_land);
306  } else {
307  amrex::Abort("Unknown value for rough_type_sea");
308  }
309  } else {
310  amrex::Abort("Unknown value for theta_type");
311  }
312  } // MOENG -- SEA
313 
315  if (m_use_sfc_fluxes) {
316  // update custom surface fluxes interpolated from file
317  update_sfc_time_index(elapsed_time);
318  sfc_tflux = interpolate_sfc_column(elapsed_time, 2);
319  sfc_qflux = interpolate_sfc_column(elapsed_time, 3);
320  sfc_ustar = interpolate_sfc_column(elapsed_time, 4);
321 
322  amrex::Print() << " ABLMOST: Interpolating SHF and LHF at time "
323  << elapsed_time
324  << ": SHF = " << sfc_tflux
325  << " (W/m^2) LHF = " << sfc_qflux
326  << " (W/m^2) TAU = " << sfc_ustar
327  << " (m^2/s^2)" << std::endl;
328 
330  } else if (custom_rhosurf > 0) {
331  specified_rho_surf = true;
332  u_star[lev]->setVal(std::sqrt(custom_rhosurf) * custom_ustar);
333  t_star[lev]->setVal(custom_rhosurf * custom_tstar);
334  q_star[lev]->setVal(custom_rhosurf * custom_qstar);
335  } else {
336  u_star[lev]->setVal(custom_ustar);
337  t_star[lev]->setVal(custom_tstar);
338  q_star[lev]->setVal(custom_qstar);
339  }
340  }
341 
342  // Custom fluxes can write whole fields; reset immersed columns before
343  // the RANS boundary TKE calculation or any subsequent stress calculation.
345 
346  if (m_update_k_rans) {
347  // Clamped divisor: the select is if-converted, so 1/theta_ref runs even
348  // when theta_ref = 0 and would trip fpe_trap_zero (see ERF_SetupDiff.H)
349  const bool use_ref_theta = (theta_ref > 0);
350  const Real inv_theta_ref = one / amrex::max(theta_ref, std::numeric_limits<Real>::min());
351  const Real l_inv_theta0 = (use_ref_theta) ? inv_theta_ref : one;
352  const Real l_inv_Cmu2 = inv_Cmu2;
353  const int klo = m_geom[lev].Domain().smallEnd(2);
354  IntVect ng = u_star[lev]->nGrowVect(); ng[2] = 0;
355 
356  for (MFIter mfi(cons_in); mfi.isValid(); ++mfi)
357  {
358  Box gpbx = mfi.tilebox(IntVect(0),ng);
359 
360  if (gpbx.smallEnd(2) != klo) { continue; }
361 
362  gpbx.makeSlab(2,klo);
363  gpbx &= cons_in.fabbox(mfi.index());
364  gpbx &= walldist->fabbox(mfi.index());
365  if (gpbx.isEmpty()) { continue; }
366 
367  auto cons_arr = cons_in.array(mfi);
368  const auto& u_star_arr = u_star[lev]->const_array(mfi);
369  const auto& t_star_arr = t_star[lev]->const_array(mfi);
370  const auto& dist_arr = walldist->const_array(mfi);
371 
372  ParallelFor(gpbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
373  {
374  Real rho = cons_arr(i,j,k,Rho_comp);
375  if (t_star_arr(i,j,0) < -1e-8) {
376  // Only destabilizing buoyancy flux affects the boundary k
377  // tstar < 0 ==> B > 0
378  Real B = -CONST_GRAV * l_inv_theta0 * u_star_arr(i,j,0) * t_star_arr(i,j,0);
379  if (!use_ref_theta) {
380  B *= cons_arr(i,j,k,Rho_comp) /
381  cons_arr(i,j,k,RhoTheta_comp);
382  }
383 
384  // Axell & Liungman 2001, Eqn. 16
385  cons_arr(i,j,k,RhoKE_comp) = rho * l_inv_Cmu2 *
386  std::pow(
387  u_star_arr(i,j,0) * u_star_arr(i,j,0) * u_star_arr(i,j,0)
388  + KAPPA * B * dist_arr(i,j,k),
389  two/three);
390  } else {
391  cons_arr(i,j,k,RhoKE_comp) = rho * l_inv_Cmu2 * u_star_arr(i,j,0) * u_star_arr(i,j,0);
392  }
393  });
394  }
395  }
396 
397  // If using Land and/or Urban models, then overwrite u_star and t_star with values calculated from the surface models
398  //
399  // Gate on whether the surface models have actually advanced, not on the lower-boundary-data
400  // clock: elapsed_time_since_start_low is offset by (start_time - start_low_time), so it is
401  // already positive at step 0 when the wrflowinp series starts before erf.start_time (which
402  // would let the still-uninitialized u*/t*/q* overwrite the MOST values), and it never becomes
403  // positive at all when the series starts after it.
405  for (MFIter mfi(*u_star[lev]); mfi.isValid(); ++mfi)
406  {
407  Box gtbx = mfi.growntilebox();
408  // The surface-model fields need not carry lateral ghost cells, so a ghost
409  // column takes the nearest valid column's values, as in
410  // compute_sfc_params_from_lsm_fluxes; fill_planar_boundary below then fills
411  // the interior and periodic ghosts.
412  const NearestColumn col(mfi.validbox());
413 
414  auto u_star_arr = u_star[lev]->array(mfi);
415  auto t_star_arr = t_star[lev]->array(mfi);
416  auto q_star_arr = q_star[lev]->array(mfi);
417  auto olen_arr = olen[lev]->array(mfi);
418 
419  // Land mask array if it exists
420  auto lmask_arr = (m_lmask_lev[lev][0]) ? m_lmask_lev[lev][0]->array(mfi) :
421  Array4<int> {};
422 
423  auto lsm_tstar_arr = Array4<Real> {};
424  auto lsm_qstar_arr = Array4<Real> {};
425  auto lsm_ustar_arr = Array4<Real> {};
426  auto lsm_olen_arr = Array4<Real> {};
427  if (use_surface_model) {
428  auto mf = m_surf_model->get_field("tstar", lev);
429  lsm_tstar_arr = (mf) ? mf->array(mfi) : Array4<Real> {};
430  mf = m_surf_model->get_field("qstar", lev);
431  lsm_qstar_arr = (mf) ? mf->array(mfi) : Array4<Real> {};
432  mf = m_surf_model->get_field("ustar", lev);
433  lsm_ustar_arr = (mf) ? mf->array(mfi) : Array4<Real> {};
434  mf = m_surf_model->get_field("olen", lev);
435  lsm_olen_arr = (mf) ? mf->array(mfi) : Array4<Real> {};
436  }
437 
438  ParallelFor(gtbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
439  {
440  // Overwrite ustar, tstar, qstar, and olen computed above with LSM values
441  if (lmask_arr(i,j,0) == 1) {
442  const int li = col.i(i);
443  const int lj = col.j(j);
444  if (lsm_tstar_arr) t_star_arr(i,j,k) = lsm_tstar_arr(li,lj,0);
445  if (lsm_qstar_arr) q_star_arr(i,j,k) = lsm_qstar_arr(li,lj,0);
446  if (lsm_ustar_arr) u_star_arr(i,j,k) = lsm_ustar_arr(li,lj,0);
447  if (lsm_olen_arr) olen_arr(i,j,k) = lsm_olen_arr(li,lj,0);
448  }
449  });
450  }
451 
452  }
453 
454  if (m_terrain_type == TerrainType::EB || m_face.coordDir() == 2) {
455  fill_planar_boundary(lev, *u_star[lev]);
456  if (m_include_wstar) { fill_planar_boundary(lev, *w_star[lev]); }
457  fill_planar_boundary(lev, *t_star[lev]);
458  fill_planar_boundary(lev, *q_star[lev]);
459  fill_planar_boundary(lev, *olen[lev]);
460  } else {
461  // The ordinary FillBoundary is unsafe for lateral walls: the
462  // collapsed layout contains FABs from interior grids whose surface
463  // parameters were never computed. Exchange only after all flux
464  // iterations have written the selected face FABs, and copy the
465  // communicated values back only to those FABs.
467  }
468 }
constexpr amrex::Real three
Definition: ERF_NumericalConstants.H:39
constexpr amrex::Real two
Definition: ERF_NumericalConstants.H:38
void compute_averages(const int &lev)
Definition: ERF_MOSTAverage.cpp:1459
amrex::Real sfc_tflux
Definition: ERF_SurfaceLayer.H:1850
amrex::Real sfc_qflux
Definition: ERF_SurfaceLayer.H:1849
void set_sfc_params_from_file_fluxes(const int &lev, const amrex::MultiFab &cons_in, const amrex::Real &tflux_in, const amrex::Real &qflux_in, const amrex::Real &sfc_tau)
Definition: ERF_SurfaceLayer.cpp:1779
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:2163
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:2515
void get_lsm_tsurf(const int &lev, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
Definition: ERF_SurfaceLayer.cpp:2337
void compute_fluxes(const int &lev, const int &max_iters, amrex::MultiFab &cons_in, const FluxIter &most_flux, bool is_land)
void fill_tsurf_with_skin_temperature(const int &lev, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
Definition: ERF_SurfaceLayer.cpp:2765
amrex::Real sfc_ustar
Definition: ERF_SurfaceLayer.H:1851
void update_surf_temp(const double &time)
Definition: ERF_SurfaceLayer.H:1285
void fill_tsurf_with_sst_and_tsk(const int &lev, const double &time)
Definition: ERF_SurfaceLayer.cpp:1951
void compute_sfc_params_from_lsm_fluxes(const int &lev, amrex::MultiFab &cons_in)
Definition: ERF_SurfaceLayer.cpp:1824
void fill_qsurf_with_skin_moisture(const int &lev, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
Definition: ERF_SurfaceLayer.cpp:2863
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:2045
void fill_tsurf_with_abs_surf_temp(const int &lev, const double &time, const amrex::MultiFab &cons_in, const std::unique_ptr< amrex::MultiFab > &z_phys_nd)
Definition: ERF_SurfaceLayer.cpp:2657
bool fields_are_valid() const
Returns whether the surface models have advanced at least once.
Definition: ERF_SurfaceModel.H:470
amrex::MultiFab * get_field(const std::string &name, int lev=0)
Retrieves a registered field by its common name.
Definition: ERF_SurfaceModel.H:683
Definition: ERF_MOSTStress.H:86
Definition: ERF_MOSTStress.H:299
EB surface-layer model for adiabatic constant-roughness fluxes.
Definition: ERF_EBMOSTStress.H:14
Definition: ERF_MOSTStress.H:197
Definition: ERF_MOSTStress.H:398
Definition: ERF_MOSTStress.H:20
Definition: ERF_MOSTStress.H:631
Definition: ERF_MOSTStress.H:909
EB surface-layer model with prescribed surface fluxes and constant roughness.
Definition: ERF_EBMOSTStress.H:215
Definition: ERF_MOSTStress.H:774
Definition: ERF_MOSTStress.H:1041
Definition: ERF_MOSTStress.H:503
Definition: ERF_MOSTStress.H:1368
Definition: ERF_MOSTStress.H:1744
EB surface-layer model with prescribed surface temperature and constant roughness.
Definition: ERF_EBMOSTStress.H:68
Definition: ERF_MOSTStress.H:1560
Definition: ERF_MOSTStress.H:1925
Definition: ERF_MOSTStress.H:1179
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
1321  {
1322  m_ma.update_field_ptrs(lev, vars_old, Theta_prim, Qv_prim, Qr_prim);
1323  }
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:442
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
3017 {
3019  MYNNPBLH estimator;
3020  compute_pblh(lev, vars, z_phys_cc, estimator, moisture_indices);
3022  {
3023  //amrex::Error("YSU/MRF PBLH calc not implemented yet");
3024  // PBLH for MRF/YSU is computed inside ComputeDiffusivityMRF
3025  // and written back via set_pblh(). Nothing to do here.
3026  }
3027 }
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
520 {
521  if (sfc.empty() || sfc[0].size() < 2) { return; }
522 
523  Real t1 = sfc[0][sfc_time_ind+1];
524  while (elapsed_time >= t1)
525  {
526  int prev_index = sfc_time_ind;
527  sfc_time_ind = std::min(sfc_time_ind + 1, int(sfc[0].size() - 2));
528  t1 = sfc[0][sfc_time_ind+1];
529  if (prev_index == sfc_time_ind) {
530  break;
531  }
532  }
533 }

◆ 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
1674  {
1675  AMREX_ALWAYS_ASSERT(itime >= 0 && itime < static_cast<int>(m_sst_lev[lev].size()));
1676  m_sst_lev[lev][itime] = sst_ptr;
1677  }
Here is the call graph for this function:

◆ 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
1286  {
1287  // NOTE: this is a whole-domain setVal, so it overwrites the SST/TSK fill
1288  // done earlier in update_fluxes. Coupled SST is applied after this
1289  // call and therefore still wins on the water cells it covers.
1290  // An absolute surface temperature is written, converted, by fill_tsurf_with_abs_surf_temp
1291  if (surf_temp_is_absolute) { return; }
1292  if (surf_heating_rate != 0) {
1293  // Use the actual size of t_surf, not m_geom.size(), which is always
1294  // max_level+1 and so runs past the levels that exist. t_surf is sized for
1295  // all levels up front but filled one level at a time, so we also have to
1296  // skip the entries that have not been allocated yet.
1297  int nlevs = static_cast<int>(t_surf.size());
1298  for (int lev = 0; lev < nlevs; lev++) {
1299  if (!t_surf[lev]) { continue; }
1300  t_surf[lev]->setVal(surf_temp + surf_heating_rate * static_cast<amrex::Real>(time));
1301  amrex::Print() << "Surface temp at t=" << time << ": "
1302  << surf_temp + surf_heating_rate * time << std::endl;
1303  }
1304  }
1305  }

◆ 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
1686  {
1687  AMREX_ALWAYS_ASSERT(itime >= 0 && itime < static_cast<int>(m_tsk_lev[lev].size()));
1688  m_tsk_lev[lev][itime] = tsk_ptr;
1689  }
Here is the call graph for this function:

◆ use_sfc_fluxes()

bool SurfaceLayer::use_sfc_fluxes ( ) const
inline

Return whether prescribed surface fluxes are active.

1570 { return m_use_sfc_fluxes; }

Member Data Documentation

◆ cnk_a

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

◆ custom_qstar

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

◆ custom_rhosurf

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

◆ custom_tstar

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

◆ custom_ustar

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

◆ default_land_surf_moist

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

◆ default_land_surf_temp

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

◆ depth

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

◆ flux_type

FluxCalcType SurfaceLayer::flux_type {FluxCalcType::MOENG}

◆ inv_Cmu2

amrex::Real SurfaceLayer::inv_Cmu2 = zero
private

◆ m_Cd

amrex::Real SurfaceLayer::m_Cd = zero
private

◆ m_Ch

amrex::Real SurfaceLayer::m_Ch = zero
private

◆ m_coupled_sst_lev

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

◆ m_coupled_sst_valid_lev

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

◆ m_Cq

amrex::Real SurfaceLayer::m_Cq = zero
private

◆ m_eb_vec

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

◆ m_eddyDiffs_lev

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

◆ m_face

amrex::Orientation SurfaceLayer::m_face
private

◆ m_final_low_time

double SurfaceLayer::m_final_low_time
private

◆ m_geom

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

◆ m_has_immersed_buildings

bool SurfaceLayer::m_has_immersed_buildings = false
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_lsm_tsurf_is_theta

bool SurfaceLayer::m_lsm_tsurf_is_theta = false
private

◆ m_Lwave_lev

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

◆ m_ma

◆ m_pblh_columns

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

◆ m_planar_bndry

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

◆ m_pp_prefix

std::string SurfaceLayer::m_pp_prefix
private

◆ m_rdOcp

amrex::Real SurfaceLayer::m_rdOcp = RdoCp
private

◆ m_rotate

bool SurfaceLayer::m_rotate = false
private

◆ m_skin_moisture_hs_lev

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

Referenced by set_skin_moisture().

◆ m_skin_moisture_lev

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

Referenced by set_skin_moisture().

◆ m_skin_moisture_resistances_lev

amrex::Vector<int> SurfaceLayer::m_skin_moisture_resistances_lev
private

Referenced by set_skin_moisture().

◆ m_skin_tsurf_lev

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

Referenced by set_skin_temperature().

◆ m_sst_lev

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

◆ m_start_low_time

double SurfaceLayer::m_start_low_time
private

◆ m_surf_model

SurfaceModel* SurfaceLayer::m_surf_model = nullptr
private

◆ m_surface_layer_faces

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

Referenced by set_surface_layer_faces().

◆ m_terrain_type

TerrainType SurfaceLayer::m_terrain_type
private

◆ m_tsk_lev

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

◆ m_update_k_rans

bool SurfaceLayer::m_update_k_rans = false
private

◆ m_use_coupled_sst

bool SurfaceLayer::m_use_coupled_sst = false
private

◆ m_use_lsm_skin_temperature

bool SurfaceLayer::m_use_lsm_skin_temperature = 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

◆ m_z0_given

bool SurfaceLayer::m_z0_given {false}
private

Referenced by set_default_roughness().

◆ moist_type

◆ olen

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

◆ pblh

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

◆ pblh_type

PBLHeightCalcType SurfaceLayer::pblh_type {PBLHeightCalcType::None}

Referenced by computes_pblh().

◆ q_star

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

◆ q_surf

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

◆ rico_qsat_z0

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

◆ rico_theta_z0

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

◆ rough_type_land

RoughCalcType SurfaceLayer::rough_type_land {RoughCalcType::CONSTANT}

◆ rough_type_sea

RoughCalcType SurfaceLayer::rough_type_sea {RoughCalcType::CHARNOCK}

◆ sfc

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

◆ sfc_qflux

amrex::Real SurfaceLayer::sfc_qflux = zero
private

◆ sfc_tflux

amrex::Real SurfaceLayer::sfc_tflux = zero
private

◆ sfc_time_ind

int SurfaceLayer::sfc_time_ind = 0
private

◆ sfc_ustar

amrex::Real SurfaceLayer::sfc_ustar = zero
private

◆ smooth_flow_visc

bool SurfaceLayer::smooth_flow_visc {true}
private

◆ specified_rho_surf

bool SurfaceLayer::specified_rho_surf {false}
private

◆ surf_heating_rate

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

◆ surf_model_fluxes

bool SurfaceLayer::surf_model_fluxes = false
private

◆ surf_moist

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

◆ surf_moist_flux

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

◆ surf_temp

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

Referenced by update_surf_temp().

◆ surf_temp_flux

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

◆ surf_temp_is_absolute

bool SurfaceLayer::surf_temp_is_absolute {false}
private

◆ surface_diagnostic_source

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

◆ t_star

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

◆ t_surf

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

◆ theta_ref

amrex::Real SurfaceLayer::theta_ref = zero
private

◆ theta_type

◆ u_star

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

◆ use_moisture

bool SurfaceLayer::use_moisture
private

Referenced by skin_moisture_conflict().

◆ use_surface_model

bool SurfaceLayer::use_surface_model = false
private

◆ w_star

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

◆ z0_const

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

◆ z_0

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

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