ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_CanopyTurbulence.H
Go to the documentation of this file.
1 #ifndef ERF_CANOPY_TURBULENCE_H_
2 #define ERF_CANOPY_TURBULENCE_H_
3 
4 #include <cmath>
5 #include <limits>
6 #include <string>
7 
8 #include <AMReX_GpuQualifiers.H>
9 #include <AMReX_Array4.H>
10 #include <AMReX_Box.H>
11 #include <AMReX_GpuLaunch.H>
12 #include <AMReX_REAL.H>
13 
14 #include <ERF_CanopyTurbStruct.H>
15 #include <ERF_IndexDefines.H>
16 
17 /**
18  * Canopy terms of the one-equation k RANS model (erf.rans_type = kEqn).
19  *
20  * The forest drag removes mean kinetic energy from the flow. Part of it becomes
21  * turbulence in the wakes of leaves and branches, at scales far below the mesh,
22  * and dissipates quickly (the spectral short cut). With
23  * erf.forest_canopy_tke = true, two terms act where the forest drag field
24  * f = C_d LAD of ForestDrag is positive:
25  *
26  * - a TKE source, S = rho f (beta_p |U|^3 - beta_d |U| k)
27  * (Green 1992, Liu et al. 1996, Sanz 2003). Production and sink are
28  * integrated together and exactly over each RK stage, from the k at the
29  * start of the step (see tke_source), so the canopy sink never removes more
30  * than that k;
31  * - a cap on the turbulent length scale, L <= alpha / f (Harman and Finnigan
32  * 2007, alpha = 2 beta^3 with beta = u_* / U_h), applied before the
33  * dissipation, the Richardson number and the stability functions are
34  * evaluated from L. The cap needs erf.implicit_tke_dissipation = true
35  * (CanopyTurbulence::check_config): with L = alpha / f the explicit
36  * dissipation rate Cmu0^3 sqrt(k) f / alpha can exceed 1/dt.
37  *
38  * Input names, defaults and formulas follow the ForestDrag.canopy_* inputs of
39  * the KLAxell model in kynema-sgf (branch f/winsent of rthedin/amr-wind). In
40  * neutral air both codes run the same canopy model; Forest.rst lists where the
41  * time integration and the stratified closure differ.
42  */
43 namespace canopy_turbulence {
44 
45 /**
46  * Turbulent length scale capped in the canopy, min(length, alpha / f).
47  * Cells with f <= 0 are unchanged, and alpha = 0 disables the cap. The test
48  * alpha < length f avoids comparing with a large alpha / f. The cap is
49  * computed with a denominator that is never zero, because an optimised build
50  * may evaluate it before the test (as for 1/theta_ref in ERF_TurbKESources.H);
51  * adding the smallest normal Real leaves any f above min / epsilon unchanged.
52  */
53 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
55 {
56  const amrex::Real cap = alpha / (amrex::max(f_drag, amrex::Real(0.0))
57  + std::numeric_limits<amrex::Real>::min());
58  const bool capped = (alpha > amrex::Real(0.0)) && (f_drag > amrex::Real(0.0))
59  && (alpha < length * f_drag);
60  return capped ? cap : length;
61 }
62 
63 /**
64  * Canopy source of rho k per unit volume and time for an RK stage that updates
65  * rho k from its value at the start of the step, (rho k)^n, over dt:
66  * rho beta_p f |U|^3 phi1(x) - (rho k)^n (1 - exp(-x)) / dt,
67  * with x = beta_d f |U| dt and phi1(x) = (1 - exp(-x)) / x. Taken alone, the
68  * stage update (rho k)^n + dt S is the exact solution of
69  * d(rho k)/dt = rho P - c rho k with P = beta_p f |U|^3 and c = beta_d f |U|
70  * frozen over the stage,
71  * (rho k)^n exp(-x) + rho (P / c) (1 - exp(-x)),
72  * so the canopy terms balance at rho P / c at any dt, and the canopy sink never
73  * removes more than (rho k)^n. The RK3 stages (dt/3, dt/2, dt from the start of
74  * the step) and the trapezoidal anelastic RK2 (both stages over dt) both update
75  * from (rho k)^n.
76  * The closure's own dissipation is a separate term: the explicit form adds
77  * -dt rho Cmu0^3 k^1.5 / L to that update, and the implicit form divides the
78  * update by 1 + dt Cmu0^3 sqrt(k) / L (positive, so k stays non-negative when
79  * the other terms do). With MovingFittedMesh the update is also scaled by the
80  * old and new detJ. In these cases the stage value is not the expression above,
81  * and the steady k is the balance of all the terms, not P / c.
82  * For f = 0 the source is zero (x = 0, phi1 = 1).
83  * @param rho density of the stage
84  * @param rhok_old rho k at the start of the step
85  * @param f_drag forest drag field f = C_d LAD (1/m), not negative
86  * @param wind cell-centred wind speed |U| of the stage
87  * @param beta_p wake production coefficient
88  * @param beta_d short-circuit dissipation coefficient
89  * @param dt time step of the update the source enters (> 0)
90  */
91 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
93  amrex::Real wind, amrex::Real beta_p, amrex::Real beta_d,
94  amrex::Real dt)
95 {
96  const amrex::Real x = beta_d * f_drag * wind * dt;
97  // 1 - exp(-x), in [0, 1) for x >= 0
98  const amrex::Real decay = -std::expm1(-x);
99  // phi1 with a divisor that is never zero (an optimised build may evaluate it
100  // for x = 0); at the smallest normal Real, phi1 rounds to its limit 1
101  const amrex::Real x_safe = amrex::max(x, std::numeric_limits<amrex::Real>::min());
102  const amrex::Real phi1 = -std::expm1(-x_safe) / x_safe;
103  return rho * beta_p * f_drag * wind * wind * wind * phi1 - rhok_old * decay / dt;
104 }
105 
106 } // namespace canopy_turbulence
107 
108 /**
109  * Add the canopy TKE source to the RhoKE component of the staged cell source
110  * on one box. Called by make_sources inside its loop over the boxes, so it adds
111  * no pass over the level; cells outside the forest (f <= 0) return before the
112  * velocities are read. Fully immersed cells (terrain_blank = 1) get no source,
113  * where ERF_AdvanceDycore.cpp also zeroes the eddy diffusivities.
114  * @param[in] bx cell-centred box to fill
115  * @param[in,out] cell_src staged source terms for the conserved state
116  * @param[in] cell_data conserved state of the stage (Rho_comp is read)
117  * @param[in] cell_old conserved state at the start of the step (RhoKE_comp is read)
118  * @param[in] u x-face velocity
119  * @param[in] v y-face velocity
120  * @param[in] w z-face velocity
121  * @param[in] f_arr cell-centred forest drag field f = C_d LAD
122  * @param[in] t_blank_arr immersed-forcing blanking, or an empty Array4 without
123  * immersed forcing
124  * @param[in] canopy canopy turbulence inputs
125  * @param[in] dt time step of the stage the source enters
126  */
127 AMREX_INLINE void
128 AddCanopyTKESources (const amrex::Box& bx,
129  const amrex::Array4<amrex::Real>& cell_src,
130  const amrex::Array4<const amrex::Real>& cell_data,
131  const amrex::Array4<const amrex::Real>& cell_old,
132  const amrex::Array4<const amrex::Real>& u,
133  const amrex::Array4<const amrex::Real>& v,
134  const amrex::Array4<const amrex::Real>& w,
135  const amrex::Array4<const amrex::Real>& f_arr,
136  const amrex::Array4<const amrex::Real>& t_blank_arr,
137  const CanopyTurbulence& canopy,
138  amrex::Real dt)
139 {
140  const amrex::Real beta_p = canopy.beta_p;
141  const amrex::Real beta_d = canopy.beta_d;
142  const bool use_blank = static_cast<bool>(t_blank_arr);
143 
144  amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
145  {
146  const amrex::Real f_drag = f_arr(i, j, k);
147  if (f_drag <= amrex::Real(0.0)) {
148  return;
149  }
150  if (use_blank && t_blank_arr(i, j, k) == amrex::Real(1.0)) {
151  return;
152  }
153  const amrex::Real rho = cell_data(i, j, k, Rho_comp);
154  const amrex::Real rhok_old = amrex::max(cell_old(i, j, k, RhoKE_comp), amrex::Real(0.0));
155  const amrex::Real u_cc = amrex::Real(0.5) * (u(i, j, k) + u(i+1, j, k));
156  const amrex::Real v_cc = amrex::Real(0.5) * (v(i, j, k) + v(i, j+1, k));
157  const amrex::Real w_cc = amrex::Real(0.5) * (w(i, j, k) + w(i, j, k+1));
158  const amrex::Real wind = std::sqrt(u_cc*u_cc + v_cc*v_cc + w_cc*w_cc);
159  cell_src(i, j, k, RhoKE_comp) +=
160  canopy_turbulence::tke_source(rho, rhok_old, f_drag, wind, beta_p, beta_d, dt);
161  });
162 }
163 
164 
165 #endif
AMREX_INLINE void AddCanopyTKESources(const amrex::Box &bx, const amrex::Array4< amrex::Real > &cell_src, const amrex::Array4< const amrex::Real > &cell_data, const amrex::Array4< const amrex::Real > &cell_old, const amrex::Array4< const amrex::Real > &u, const amrex::Array4< const amrex::Real > &v, const amrex::Array4< const amrex::Real > &w, const amrex::Array4< const amrex::Real > &f_arr, const amrex::Array4< const amrex::Real > &t_blank_arr, const CanopyTurbulence &canopy, amrex::Real dt)
Definition: ERF_CanopyTurbulence.H:128
#define Rho_comp
Definition: ERF_IndexDefines.H:53
#define RhoKE_comp
Definition: ERF_IndexDefines.H:55
const Real length
Definition: ERF_InitCustomPert_AnelasticWallDiffusion.H:16
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);})
Real w
Definition: ERF_Plotfile2DInterpolator.cpp:23
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ rho
Definition: ERF_Kessler.H:25
@ alpha
Definition: ERF_SLM.H:151
Definition: ERF_CanopyTurbulence.H:43
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real tke_source(amrex::Real rho, amrex::Real rhok_old, amrex::Real f_drag, amrex::Real wind, amrex::Real beta_p, amrex::Real beta_d, amrex::Real dt)
Definition: ERF_CanopyTurbulence.H:92
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real limited_length(amrex::Real length, amrex::Real f_drag, amrex::Real alpha)
Definition: ERF_CanopyTurbulence.H:54
Definition: ERF_CanopyTurbStruct.H:16
amrex::Real beta_d
erf.forest_canopy_beta_d: short-circuit dissipation coefficient
Definition: ERF_CanopyTurbStruct.H:22
amrex::Real beta_p
erf.forest_canopy_beta_p: fraction of the drag work that becomes TKE
Definition: ERF_CanopyTurbStruct.H:20