ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_TurbKESources.H
Go to the documentation of this file.
1 #ifndef ERF_TURB_KE_SOURCES_H_
2 #define ERF_TURB_KE_SOURCES_H_
3 
4 #include <AMReX.H>
5 #include <AMReX_BCRec.H>
6 
7 #include "ERF_DataStruct.H"
8 #include "ERF_IndexDefines.H"
10 #include "ERF_SurfaceLayer.H"
11 #include "ERF_PBLModels.H"
12 
13 /**
14  * Add the production and dissipation sources of the subgrid TKE (Deardorff or
15  * k-eqn RANS) or of the MYNN QKE to the RhoKE RHS.
16  *
17  * These sources consume the face fluxes written by the scalar diffusion of the
18  * same RK stage, so they must be called after every DiffusionSrcForState_* call
19  * of the stage has returned: the RhoTheta call in erf_slow_rhs_pre (hfx_z) and
20  * the RhoQ1 call in erf_slow_rhs_post (qfx1_z).
21  *
22  * @param[in] bx cell center box to loop over
23  * @param[in] domain box of the whole domain
24  * @param[in] u velocity in x-dir
25  * @param[in] v velocity in y-dir
26  * @param[in] cell_data conserved cell center vars
27  * @param[in] cell_prim primitive cell center vars
28  * @param[inout] cell_rhs RHS for cell center vars
29  * @param[in] SmnSmn_a strain rate magnitude
30  * @param[in] hfx_z rho<w'theta'> on z-faces
31  * @param[in] qfx1_z rho<w'qv'> on z-faces (may be null)
32  * @param[in] diss dissipation of TKE
33  * @param[in] tau13 rho<u'w'>; holds the surface-layer stress on the zlo face
34  * @param[in] tau23 rho<v'w'>; holds the surface-layer stress on the zlo face
35  * @param[in] mu_turb turbulent viscosity
36  * @param[in] solverChoice container of solver and diffusion parameters
37  * @param[in] level AMR level
38  * @param[in] tm_arr theta mean array
39  * @param[in] grav_gpu gravity vector
40  * @param[in] bc_ptr container with boundary conditions
41  * @param[in] use_SurfLayer whether surface-layer fluxes are applied in the diffusion
42  * @param[in] SurfLayer surface layer objects, one per face
43  * @param[in] pbl_derivative_dz_inv inverse vertical spacings for the mesh type
44  */
45 template <typename PBLDerivativeDzInvFunctor>
46 void
47 AddTurbKESources (const amrex::Box& bx, const amrex::Box& domain,
48  const amrex::Array4<const amrex::Real>& u,
49  const amrex::Array4<const amrex::Real>& v,
50  const amrex::Array4<const amrex::Real>& cell_data,
51  const amrex::Array4<const amrex::Real>& cell_prim,
52  const amrex::Array4<amrex::Real>& cell_rhs,
53  const amrex::Array4<const amrex::Real>& SmnSmn_a,
54  const amrex::Array4<const amrex::Real>& hfx_z,
55  const amrex::Array4<const amrex::Real>& qfx1_z,
56  const amrex::Array4<const amrex::Real>& diss,
57  const amrex::Array4<const amrex::Real>& tau13,
58  const amrex::Array4<const amrex::Real>& tau23,
59  const amrex::Array4<const amrex::Real>& mu_turb,
60  const SolverChoice& solverChoice,
61  const int level,
62  const amrex::Array4<const amrex::Real>& tm_arr,
63  const amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> grav_gpu,
64  const amrex::BCRec* bc_ptr,
65  const bool use_SurfLayer,
66  const amrex::Vector<std::unique_ptr<SurfaceLayer>>& SurfLayer,
67  const PBLDerivativeDzInvFunctor& pbl_derivative_dz_inv)
68 {
69  BL_PROFILE("AddTurbKESources()");
70 
71  const TurbChoice& turbChoice = solverChoice.turbChoice[level];
72 
73  const bool l_use_keqn = turbChoice.use_keqn;
74  const bool l_implicit_diss = turbChoice.use_keqn && turbChoice.implicit_tke_dissipation;
75  const bool l_use_mynn = turbChoice.use_pbl_tke;
76 
77  const bool SurfLayer_zlo = use_SurfLayer && SurfLayer[amrex::Orientation::zlo()] != nullptr;
78 
79  // Clamp the divisor to the smallest normal Real before the select: an optimised
80  // build if-converts the select into an unconditional 1/theta_ref (also when
81  // the select is on the divisor), which trips fpe_trap_zero with the default
82  // theta_ref = 0. The clamp is exact for any normal theta_ref > 0 and its
83  // reciprocal is finite in single and double precision.
84  const bool use_ref_theta = (turbChoice.theta_ref > 0);
85  const amrex::Real inv_theta_ref = one / amrex::max(turbChoice.theta_ref,
86  std::numeric_limits<amrex::Real>::min());
87  const amrex::Real l_inv_theta0 = (use_ref_theta) ? inv_theta_ref : one;
88  const amrex::Real l_abs_g = std::abs(grav_gpu[2]);
89 
90  // Using Deardorff (see Sullivan et al 1994)
91  // or k-eqn RANS (see Axell & Liungman 2001)
92  //
93  // Note: At this point, the thermal diffusivity ("Khv" field in ERF) and the
94  // subgrid dissipation ("diss" here) have been updated by the closure
95  // at the beginning of each timestep.
96  // The strain rate magnitude is updated at the beginning of the first
97  // RK stage only, therefore the shear production term also does not
98  // change between RK stages.
99  // hfx_z (Hfx3) is z-nodal, (0,0,1): it lives at the w-faces, not at the
100  // cell centre, so a single hfx_z(i,j,k) is the flux at the LOWER face of
101  // the cell and using it as the cell-centred value is not formally correct.
102  // hfx_z holds the face fluxes of the theta diffusion of this stage
103  // (the full flux, independent of the implicit/explicit split); at the
104  // bottom face of the domain that is the surface-layer flux when a
105  // surface layer is used. The buoyancy production therefore averages the
106  // two faces of every cell, the first cell included, for the cell-centred
107  // vertical heat flux. The first cell is not special-cased to the surface
108  // flux alone: that would again place a w-face value at the cell centre
109  // (a MOST-profile gradient evaluated at the first cell centre would be
110  // the consistent alternative).
111  // On a terrain-fitted mesh hfx_z holds the flux along zeta only: the slope
112  // corrections and the Jacobian are applied where the fluxes enter the RHS, so
113  // over sloping terrain this source omits those terms (as does the hfx3 output).
114  // REQUIRED WRITER: the RhoTheta call of DiffusionSrcForState_{N,S,T}
115  // in erf_slow_rhs_pre, on the same RK stage, which writes every z-face
116  // of each valid box (zbx = surroundingNodes(bx,2), top face included).
117  // Running the RhoKE source without that same-stage theta pass would
118  // read stale faces.
119  if (l_use_keqn) {
120  int qty_index = RhoKE_comp;
121  // The source reads hfx_z at k+1: the z-faces of bx must be in hfx_z
122  AMREX_ASSERT(hfx_z.contains(bx.smallEnd(0), bx.smallEnd(1), bx.smallEnd(2)) &&
123  hfx_z.contains(bx.bigEnd(0), bx.bigEnd(1), bx.bigEnd(2)+1));
124  amrex::ParallelFor(bx,[=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
125  {
126  // Add Buoyancy Source
127  // where the SGS buoyancy flux tau_{theta,i} = -KH * dtheta/dx_i,
128  // such that for dtheta/dz < 0, there is a positive (upward) heat
129  // flux; the TKE buoyancy production is then
130  // B = g/theta_0 * tau_{theta,w}
131  // for a dry atmosphere.
132  // TODO: To account for moisture, the Brunt-Vaisala frequency,
133  // N^2 = g[1/theta * dtheta/dz + ...]
134  // **should** be a function of the water vapor and total water
135  // mixing ratios, depending on whether conditions are saturated or
136  // not (see the WRF model description, Skamarock et al 2019).
137  // NOTE: reads hfx_z at k+1, one face above bx; Hfx3 is z-nodal and
138  // the theta diffusion writes every face of its valid box, top included.
139  const amrex::Real hfx_cc = amrex::Real(0.5) * ( hfx_z(i,j,k) + hfx_z(i,j,k+1) );
140  amrex::Real buoyancy_src = l_abs_g * l_inv_theta0 * hfx_cc;
141  if (!use_ref_theta) {
142  // l_inv_theta0 == 1, divide by actual theta
143  buoyancy_src /= cell_prim(i,j,k,PrimTheta_comp);
144  }
145  cell_rhs(i,j,k,qty_index) += buoyancy_src;
146 
147  // TKE shear production
148  // P = -tau_ij * S_ij = 2 * mu_turb * S_ij * S_ij
149  // Note: This assumes that the horizontal and vertical diffusivities
150  // of momentum are equal
151  cell_rhs(i,j,k,qty_index) += two*mu_turb(i,j,k,EddyDiff::Mom_v) * SmnSmn_a(i,j,k);
152 
153  // TKE dissipation (explicit); with erf.implicit_tke_dissipation the
154  // sink is folded into the update in erf_slow_rhs_post instead
155  if (!l_implicit_diss) {
156  cell_rhs(i,j,k,qty_index) -= diss(i,j,k);
157  }
158  });
159  }
160 
161  // Using PBL
162  if (l_use_mynn) {
163  int qty_index = RhoKE_comp;
164  auto pbl_mynn_B1_l = turbChoice.pbl_mynn.B1;
165 
166  // At k = klo the resolved vertical gradients are contaminated by the
167  // foextrap/hoextrap ghost cells (ERF #4037). When a SurfaceLayer owns the
168  // zlo face, ComputeQKESourceTerms builds the production terms there from
169  // the surface stresses and scalar fluxes that are actually applied to the
170  // momentum, heat and moisture equations instead.
171  const bool l_surface_layer_on_zlo = SurfLayer_zlo;
172 
173  amrex::ParallelFor(bx,[=,moist_indices=solverChoice.moisture_indices] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
174  {
175  bool c_ext_dir_on_zlo = ( (bc_ptr[BCVars::cons_bc].lo(2) == ERFBCType::ext_dir) );
176  bool c_ext_dir_on_zhi = ( (bc_ptr[BCVars::cons_bc].hi(2) == ERFBCType::ext_dir) );
177  bool u_ext_dir_on_zlo = ( (bc_ptr[BCVars::xvel_bc].lo(2) == ERFBCType::ext_dir) );
178  bool u_ext_dir_on_zhi = ( (bc_ptr[BCVars::xvel_bc].hi(2) == ERFBCType::ext_dir) );
179  bool v_ext_dir_on_zlo = ( (bc_ptr[BCVars::yvel_bc].lo(2) == ERFBCType::ext_dir) );
180  bool v_ext_dir_on_zhi = ( (bc_ptr[BCVars::yvel_bc].hi(2) == ERFBCType::ext_dir) );
181 
182  const auto pbl_dz_inv = pbl_derivative_dz_inv(i,j,k);
183 
184  // This computes shear production, buoyancy production, and dissipation terms only.
185  cell_rhs(i, j, k, qty_index) += ComputeQKESourceTerms(i,j,k,u,v,cell_data,cell_prim,
186  mu_turb,domain,pbl_dz_inv,
187  pbl_mynn_B1_l,tm_arr(i,j,0),
188  moist_indices,
189  c_ext_dir_on_zlo, c_ext_dir_on_zhi,
190  u_ext_dir_on_zlo, u_ext_dir_on_zhi,
191  v_ext_dir_on_zlo, v_ext_dir_on_zhi,
192  tau13, tau23, hfx_z, qfx1_z,
193  l_surface_layer_on_zlo);
194  });
195  }
196 }
197 
198 #endif
@ tau23
Definition: ERF_DataStruct.H:48
@ tau13
Definition: ERF_DataStruct.H:48
#define PrimTheta_comp
Definition: ERF_IndexDefines.H:61
#define RhoKE_comp
Definition: ERF_IndexDefines.H:44
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);})
Dimensionless numeric literals and pure mathematical constants.
constexpr amrex::Real two
Definition: ERF_NumericalConstants.H:38
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:37
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeQKESourceTerms(int i, int j, int k, const amrex::Array4< const amrex::Real > &uvel, const amrex::Array4< const amrex::Real > &vvel, const amrex::Array4< const amrex::Real > &cell_data, const amrex::Array4< const amrex::Real > &cell_prim, const amrex::Array4< const amrex::Real > &K_turb, const amrex::Box &domain, const PBLDerivativeDzInv dz_inv, amrex::Real pbl_mynn_B1_l, const amrex::Real theta_mean, const MoistureComponentIndices &moisture_indices, bool c_ext_dir_on_zlo, bool c_ext_dir_on_zhi, bool u_ext_dir_on_zlo, bool u_ext_dir_on_zhi, bool v_ext_dir_on_zlo, bool v_ext_dir_on_zhi, const amrex::Array4< const amrex::Real > &tau13, const amrex::Array4< const amrex::Real > &tau23, const amrex::Array4< const amrex::Real > &hfx_z, const amrex::Array4< const amrex::Real > &qfx1_z, bool surface_layer_on_zlo)
Definition: ERF_PBLModels.H:457
amrex::Real Real
Definition: ERF_ShocInterface.H:19
void AddTurbKESources(const amrex::Box &bx, const amrex::Box &domain, const amrex::Array4< const amrex::Real > &u, const amrex::Array4< const amrex::Real > &v, const amrex::Array4< const amrex::Real > &cell_data, const amrex::Array4< const amrex::Real > &cell_prim, const amrex::Array4< amrex::Real > &cell_rhs, const amrex::Array4< const amrex::Real > &SmnSmn_a, const amrex::Array4< const amrex::Real > &hfx_z, const amrex::Array4< const amrex::Real > &qfx1_z, const amrex::Array4< const amrex::Real > &diss, const amrex::Array4< const amrex::Real > &tau13, const amrex::Array4< const amrex::Real > &tau23, const amrex::Array4< const amrex::Real > &mu_turb, const SolverChoice &solverChoice, const int level, const amrex::Array4< const amrex::Real > &tm_arr, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > grav_gpu, const amrex::BCRec *bc_ptr, const bool use_SurfLayer, const amrex::Vector< std::unique_ptr< SurfaceLayer >> &SurfLayer, const PBLDerivativeDzInvFunctor &pbl_derivative_dz_inv)
Definition: ERF_TurbKESources.H:47
@ yvel_bc
Definition: ERF_IndexDefines.H:109
@ cons_bc
Definition: ERF_IndexDefines.H:92
@ xvel_bc
Definition: ERF_IndexDefines.H:108
@ ext_dir
Definition: ERF_IndexDefines.H:300
@ Mom_v
Definition: ERF_IndexDefines.H:252
amrex::Real B1
Definition: ERF_MYNNStruct.H:52
Definition: ERF_DataStruct.H:685
amrex::Vector< TurbChoice > turbChoice
Turbulence options for each AMR level.
Definition: ERF_DataStruct.H:2252
MoistureComponentIndices moisture_indices
Index map of the moisture data carried by the active scheme: conserved-state components for the speci...
Definition: ERF_DataStruct.H:2575
Definition: ERF_TurbStruct.H:115
bool implicit_tke_dissipation
Definition: ERF_TurbStruct.H:839
amrex::Real theta_ref
Reference potential temperature for stable stratification.
Definition: ERF_TurbStruct.H:815
bool use_keqn
Whether a microscale TKE closure is active.
Definition: ERF_TurbStruct.H:922
MYNNLevel25 pbl_mynn
MYNN level-2.5 closure coefficients.
Definition: ERF_TurbStruct.H:917
bool use_pbl_tke
Whether a mesoscale PBL TKE closure is active.
Definition: ERF_TurbStruct.H:925