ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_ImmersedWallCell.H
Go to the documentation of this file.
1 #ifndef ERF_IMMERSED_WALL_CELL_H_
2 #define ERF_IMMERSED_WALL_CELL_H_
3 
4 #include <cmath>
5 #include <limits>
6 
7 #include <AMReX_REAL.H>
8 #include <AMReX_GpuQualifiers.H>
9 #include <AMReX_Algorithm.H>
10 
11 #include "ERF_Constants.H"
12 #include "ERF_MOSTUtils.H"
13 
14 /**
15  * The wall cell of immersed-forcing terrain and the fraction-stress wall law
16  * (erf.if_wall_form = fraction_stress), shared by the momentum sources, the wall-face
17  * masks of the diffusion and the RANS wall distance so they cannot disagree.
18  *
19  * A column of solid fractions beta (terrain_blanking) meets the air in its WALL CELL: a
20  * partial cell with fluid above it, or a fluid cell sitting on a solid cell. The surface is
21  * at h = z_bot + beta dz of that cell (beta = 0 for the fluid cell), the wall cell's fluid
22  * centroid at d1 = (1 - beta) dz / 2 above it and the centre of the cell above at
23  * d2 = (3/2 - beta) dz. The fraction-stress law takes the friction velocity
24  * u*^2 = (1 - beta) u*_own^2 + beta u*_ref^2,
25  * u*_own = kappa |u_t| / ln(d1/z0), u*_ref = kappa |u_t,ref| / ln(d2/z0),
26  * so a nearly full fluid cell uses its own velocity (on a face-aligned surface, beta = 0,
27  * that is the plain surface-layer law of flat ground at dz/2) and a thin fluid sliver the
28  * better resolved cell above, and applies it as a wall stress spread over the cell height,
29  * integrated exactly over the step:
30  * F = -rho C u_t, C = (1 - exp(-r dt)) / dt, r = u*^2 / (dz |u_t|).
31  * The resolved diffusive flux through the wall face (the bottom face of the wall cell) is
32  * switched off, so the column loses exactly u*^2 through the wall. This is form 7 of the
33  * kynema-sgf immersed terrain, written for the vertical columns of ERF's immersed forcing
34  * (horizontal velocity, vertical distances) and neutral stratification.
35  */
36 namespace ib_wall {
37 
38 /** A cell whose solid fraction is below small counts as fluid */
39 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
40 bool is_fluid (amrex::Real beta, amrex::Real small) noexcept
41 {
42  return beta < small;
43 }
44 
45 /** A cell whose solid fraction is within small of one counts as solid */
46 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
47 bool is_solid (amrex::Real beta, amrex::Real small) noexcept
48 {
49  return beta >= amrex::Real(1.0) - small;
50 }
51 
52 /**
53  * Cell k, with solid fractions b_km1, b_k, b_kp1 in cells k-1, k, k+1, is the wall cell:
54  * a partial cell with fluid above it, or a fluid cell on top of a solid cell.
55  */
56 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
57 bool is_wall_cell (amrex::Real b_km1, amrex::Real b_k, amrex::Real b_kp1, amrex::Real small) noexcept
58 {
59  const bool partial = !is_fluid(b_k, small) && !is_solid(b_k, small);
60  return (partial && is_fluid(b_kp1, small)) || (is_fluid(b_k, small) && is_solid(b_km1, small));
61 }
62 
63 /** Solid fraction of a wall cell as the wall law uses it (zero for a fluid wall cell) */
64 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
66 {
67  return is_fluid(b_k, small) ? amrex::Real(0.0) : amrex::min(b_k, amrex::Real(1.0));
68 }
69 
70 /**
71  * Friction velocity of the fraction-stress law.
72  * @param ut horizontal speed in the wall cell
73  * @param ut_ref horizontal speed in the cell above
74  * @param beta wall_beta of the wall cell
75  * @param dz cell height
76  * @param z0 roughness length
77  * The distances are floored at 2 z0 so that ln(d/z0) stays above ln 2 in a thin sliver,
78  * where the (1 - beta) weight of the own estimate is small anyway.
79  */
80 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
82  amrex::Real dz, amrex::Real z0) noexcept
83 {
84  const amrex::Real d_min = amrex::Real(2.0) * z0;
85  const amrex::Real d1 = amrex::max(amrex::Real(0.5) * (amrex::Real(1.0) - beta) * dz, d_min);
86  const amrex::Real d2 = amrex::max((amrex::Real(1.5) - beta) * dz, d1);
87  const amrex::Real us_own = KAPPA * ut / std::log(d1 / z0);
88  const amrex::Real us_ref = KAPPA * ut_ref / std::log(d2 / z0);
89  return std::sqrt((amrex::Real(1.0) - beta) * us_own * us_own + beta * us_ref * us_ref);
90 }
91 
92 
93 /**
94  * Factor on the diffusive coupling through the face above the wall cell (the face between the
95  * wall cell, whose fluid centroid sits d1 = (1-beta) dz/2 above the surface, and the cell above,
96  * at d2 = (3/2-beta) dz). The solver differences across that face over dz; the factor dz/dz_eff
97  * replaces dz by
98  * spacing = 0 (grid): dz (factor 1)
99  * spacing = 1 (physical): d2 - d1 = (1 - beta/2) dz
100  * spacing = 2 (log): (d1 + d2) ln(d2/d1) / (2 ln 3), the spacing that gives the face flux,
101  * with the face viscosity the mean of the two cells' kappa u* z, the
102  * same relative error for a log profile as the first interior face of
103  * flat ground (d1 = dz/2, d2 = 3 dz/2), where it equals dz: ln 3 u*^2
104  * for every beta
105  * All three are 1 for beta = 0, so a surface on a cell face stays flat ground. The factor is
106  * bounded only while z0 < dz/4 (d2 > d1 for every beta): the inputs require that.
107  */
108 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
110 {
111  const amrex::Real d1 = amrex::max(amrex::Real(0.5) * (amrex::Real(1.0) - beta) * dz, amrex::Real(2.0) * z0);
112  const amrex::Real d2 = amrex::max((amrex::Real(1.5) - beta) * dz, d1);
113  const amrex::Real dz_phys = d2 - d1;
114  const amrex::Real dz_log = (d1 + d2) * std::log(d2 / d1) / (amrex::Real(2.0) * amrex::Real(1.0986122886681098));
115  const amrex::Real dz_eff = (spacing == 1) ? dz_phys : ((spacing == 2) ? dz_log : dz);
116  return dz / amrex::max(dz_eff, std::numeric_limits<amrex::Real>::min());
117 }
118 
119 /** Surface condition of the fraction-stress law (erf.if_* inputs of the terrain immersed forcing) */
120 struct WallCond
121 {
122  static constexpr int neutral = 0; //!< no heat exchange, neutral stability functions
123  static constexpr int heat_flux = 1; //!< given kinematic surface heat flux q [K m/s]
124  static constexpr int surface_temp = 2; //!< given surface potential temperature theta_s [K]
125  static constexpr int obukhov = 3; //!< given Obukhov length (inverse Linv) [1/m]
126  int type{neutral};
130 };
131 
132 /** Friction velocity, temperature scale and inverse Obukhov length of a wall cell */
133 struct WallState
134 {
138 };
139 
140 /** ln(d/z0) - psi, floored at ln 2 (the value the 2 z0 distance floor gives in neutral air) */
141 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
143 {
144  return amrex::max(std::log(d / z0) - psi, amrex::Real(0.6931471805599453));
145 }
146 
147 /**
148  * Wall state of the fraction-stress law with MOST stability functions (the Dyer functions of
149  * the flat surface layer, similarity_funs::calc_psi_m / calc_psi_h, zeta clamped to +-100):
150  * u*^2 = (1-b) (kappa ut / phi_m(d1))^2 + b (kappa ut_ref / phi_m(d2))^2
151  * theta* = -q / u* (heat_flux)
152  * = (1-b) kappa (theta - theta_s) / phi_h(d1)
153  * + b kappa (theta_ref - theta_s) / phi_h(d2) (surface_temp)
154  * = theta u*^2 / (kappa g L) (obukhov)
155  * 1/L = kappa g theta* / (u*^2 theta) (heat_flux, surface_temp: iterated)
156  * with d1 = (1-b) dz/2, d2 = (3/2-b) dz floored at 2 z0 and phi_{m,h}(d) = ln(d/z0) - psi(d/L).
157  * The iteration starts from neutral and runs a fixed number of passes, so it is deterministic
158  * and has no data-dependent trip count.
159  */
160 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
162  amrex::Real beta, amrex::Real dz, amrex::Real z0, const WallCond& c) noexcept
163 {
164  similarity_funs sfuns;
165  const amrex::Real one_r = amrex::Real(1.0);
166  const amrex::Real d_min = amrex::Real(2.0) * z0;
167  const amrex::Real d1 = amrex::max(amrex::Real(0.5) * (one_r - beta) * dz, d_min);
168  const amrex::Real d2 = amrex::max((amrex::Real(1.5) - beta) * dz, d1);
169  const amrex::Real zmax = amrex::Real(100.0);
170  const amrex::Real us_min = amrex::Real(1.0e-3);
171  const amrex::Real kg = KAPPA * CONST_GRAV;
172  const int n_iter = (c.type == WallCond::heat_flux || c.type == WallCond::surface_temp) ? 12 : 1;
173 
174  WallState w;
175  w.Linv = (c.type == WallCond::obukhov) ? c.Linv : amrex::Real(0.0);
176  // 1/L is kept where d1/L stays within the zeta clamp: the stable given-flux problem has no
177  // solution below a wind threshold (about 3 m/s at q = -0.02 K m/s, d1 = 10 m, z0 = 0.1 m)
178  // and the iteration would otherwise run 1/L away while u* collapses to the floor
179  const amrex::Real Linv_max = zmax / d1;
180  for (int it = 0; it < n_iter; ++it) {
181  w.Linv = amrex::max(amrex::min(w.Linv, Linv_max), -Linv_max);
182  const amrex::Real z1 = amrex::max(amrex::min(d1 * w.Linv, zmax), -zmax);
183  const amrex::Real z2 = amrex::max(amrex::min(d2 * w.Linv, zmax), -zmax);
184  const amrex::Real us_own = KAPPA * ut / phi(d1, z0, sfuns.calc_psi_m(z1));
185  const amrex::Real us_ref = KAPPA * ut_ref / phi(d2, z0, sfuns.calc_psi_m(z2));
186  w.ustar = std::sqrt((one_r - beta) * us_own * us_own + beta * us_ref * us_ref);
187  const amrex::Real us_f = amrex::max(w.ustar, us_min);
188  if (c.type == WallCond::heat_flux) {
189  w.thetastar = -c.q / us_f;
190  } else if (c.type == WallCond::surface_temp) {
191  w.thetastar = (one_r - beta) * KAPPA * (theta - c.theta_s) / phi(d1, z0, sfuns.calc_psi_h(z1))
192  + beta * KAPPA * (theta_ref - c.theta_s) / phi(d2, z0, sfuns.calc_psi_h(z2));
193  } else if (c.type == WallCond::obukhov) {
194  w.thetastar = theta * us_f * us_f * c.Linv / kg;
195  } else {
196  w.thetastar = amrex::Real(0.0);
197  }
198  if (c.type == WallCond::heat_flux || c.type == WallCond::surface_temp) {
199  w.Linv = kg * w.thetastar / (us_f * us_f * theta);
200  }
201  }
202  // the stress uses the same floored u* as theta* and 1/L
203  w.ustar = amrex::max(w.ustar, us_min);
204  w.Linv = amrex::max(amrex::min(w.Linv, Linv_max), -Linv_max);
205  return w;
206 }
207 
208 /**
209  * Source of the wall heat flux in the wall cell, per unit mass (d theta / dt): the kinematic
210  * flux -u* theta* spread over the cell height. With a given surface temperature theta* is
211  * proportional to theta - theta_s and the term is a decay, integrated exactly over dt so it
212  * cannot overshoot the surface temperature; otherwise the flux is applied as it stands.
213  */
214 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
216  amrex::Real dz, amrex::Real dt) noexcept
217 {
218  const amrex::Real flux = -w.ustar * w.thetastar / dz;
219  if (c.type != WallCond::surface_temp) { return flux; }
220  const amrex::Real dth = theta - c.theta_s;
222  const amrex::Real rate = -flux / (dth + std::copysign(eps, dth));
223  const amrex::Real dt_safe = amrex::max(dt, std::numeric_limits<amrex::Real>::min());
224  // a decay (rate > 0) is integrated exactly; anything else is applied as it stands
225  const amrex::Real exact = std::expm1(-amrex::max(rate, amrex::Real(0.0)) * dt_safe) / dt_safe * dth;
226  return (rate > amrex::Real(0.0) && std::abs(dth) > eps) ? exact : flux;
227 }
228 
229 /**
230  * Effective rate C of the wall stress u*^2 spread over the cell height dz, integrated exactly
231  * over dt: the momentum source is -rho C u_t.
232  */
233 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
235 {
236  // dt is floored rather than branched on, so no guarded 0/0 can be speculated (FPE traps);
237  // (1 - exp(-r dt)) / dt tends to r as dt -> 0
239  const amrex::Real dt_safe = amrex::max(dt, std::numeric_limits<amrex::Real>::min());
240  return -std::expm1(-r * dt_safe) / dt_safe;
241 }
242 
243 /**
244  * Wall value of the k-eqn TKE in the wall cell (erf.if_wall_tke), the value erf.dirichlet_k holds in
245  * the first cell over flat ground (Axell & Liungman 2001, Eq. 16):
246  * k = (u*^3 + kappa B d1)^(2/3) / Cmu0^2, B = -g u* theta* / theta (unstable only),
247  * with u*, theta* from the wall state and d1 the fluid-centroid height of the wall cell (floored
248  * at 2 z0, as the wall state does). kynema-sgf relaxes its immersed wall cells toward the same value.
249  */
250 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
252  amrex::Real theta, amrex::Real Cmu0) noexcept
253 {
254  const amrex::Real d1 = amrex::max(amrex::Real(0.5) * (amrex::Real(1.0) - beta) * dz,
255  amrex::Real(2.0) * z0);
256  const amrex::Real B = (w.thetastar < amrex::Real(-1.e-8))
257  ? -CONST_GRAV * w.ustar * w.thetastar / theta : amrex::Real(0.0);
258  return std::pow(w.ustar * w.ustar * w.ustar + KAPPA * B * d1, amrex::Real(2.0) / amrex::Real(3.0))
259  / (Cmu0 * Cmu0);
260 }
261 
262 } // namespace ib_wall
263 
264 #endif
constexpr amrex::Real KAPPA
Definition: ERF_Constants.H:71
constexpr amrex::Real CONST_GRAV
Definition: ERF_Constants.H:72
Real z0
Definition: ERF_InitCustomPertVels_ScalarAdvDiff.H:12
Real w
Definition: ERF_Plotfile2DInterpolator.cpp:23
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ ustar
Definition: ERF_SLM.H:28
@ theta
Definition: ERF_Kessler.H:26
@ beta
Definition: ERF_SLM.H:152
@ dz
Definition: ERF_AdvanceWDM6.cpp:273
Definition: ERF_ImmersedWallCell.H:36
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool is_wall_cell(amrex::Real b_km1, amrex::Real b_k, amrex::Real b_kp1, amrex::Real small) noexcept
Definition: ERF_ImmersedWallCell.H:57
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real face_above_factor(amrex::Real beta, amrex::Real dz, amrex::Real z0, int spacing) noexcept
Definition: ERF_ImmersedWallCell.H:109
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real wall_tke(const WallState &w, amrex::Real beta, amrex::Real dz, amrex::Real z0, amrex::Real theta, amrex::Real Cmu0) noexcept
Definition: ERF_ImmersedWallCell.H:251
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real wall_beta(amrex::Real b_k, amrex::Real small) noexcept
Definition: ERF_ImmersedWallCell.H:65
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool is_fluid(amrex::Real beta, amrex::Real small) noexcept
Definition: ERF_ImmersedWallCell.H:40
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real phi(amrex::Real d, amrex::Real z0, amrex::Real psi) noexcept
Definition: ERF_ImmersedWallCell.H:142
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real friction_velocity(amrex::Real ut, amrex::Real ut_ref, amrex::Real beta, amrex::Real dz, amrex::Real z0) noexcept
Definition: ERF_ImmersedWallCell.H:81
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE WallState wall_state(amrex::Real ut, amrex::Real ut_ref, amrex::Real theta, amrex::Real theta_ref, amrex::Real beta, amrex::Real dz, amrex::Real z0, const WallCond &c) noexcept
Definition: ERF_ImmersedWallCell.H:161
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real stress_rate(amrex::Real ustar, amrex::Real ut, amrex::Real dz, amrex::Real dt) noexcept
Definition: ERF_ImmersedWallCell.H:234
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool is_solid(amrex::Real beta, amrex::Real small) noexcept
Definition: ERF_ImmersedWallCell.H:47
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real heat_source(const WallState &w, amrex::Real theta, const WallCond &c, amrex::Real dz, amrex::Real dt) noexcept
Definition: ERF_ImmersedWallCell.H:215
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12
Definition: ERF_ImmersedWallCell.H:121
static constexpr int heat_flux
given kinematic surface heat flux q [K m/s]
Definition: ERF_ImmersedWallCell.H:123
amrex::Real Linv
Definition: ERF_ImmersedWallCell.H:129
static constexpr int obukhov
given Obukhov length (inverse Linv) [1/m]
Definition: ERF_ImmersedWallCell.H:125
int type
Definition: ERF_ImmersedWallCell.H:126
static constexpr int surface_temp
given surface potential temperature theta_s [K]
Definition: ERF_ImmersedWallCell.H:124
amrex::Real q
Definition: ERF_ImmersedWallCell.H:127
amrex::Real theta_s
Definition: ERF_ImmersedWallCell.H:128
static constexpr int neutral
no heat exchange, neutral stability functions
Definition: ERF_ImmersedWallCell.H:122
Definition: ERF_ImmersedWallCell.H:134
amrex::Real thetastar
Definition: ERF_ImmersedWallCell.H:136
amrex::Real Linv
Definition: ERF_ImmersedWallCell.H:137
amrex::Real ustar
Definition: ERF_ImmersedWallCell.H:135
Definition: ERF_MOSTUtils.H:67
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real calc_psi_m(amrex::Real zeta) const
Definition: ERF_MOSTUtils.H:132
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real calc_psi_h(amrex::Real zeta) const
Definition: ERF_MOSTUtils.H:151
Definition: ERF_MOSTStress.H:1179