ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_MOSTUtils.H
Go to the documentation of this file.
1 #ifndef ERF_MOSTUtils_H
2 #define ERF_MOSTUtils_H
3 
4 #include <AMReX_Algorithm.H>
5 #include <AMReX_BLassert.H>
6 #include <AMReX_Math.H>
7 #include "ERF_Constants.H"
8 
9 /**
10  * Structure of plain old data relevant to MOST BCs
11  */
12 struct most_data
13 {
14 public:
15  amrex::Real z0_const{amrex::Real(0.1)}; ///< Roughness height -- default constant value(m)
16  amrex::Real kappa{KAPPA}; ///< von Karman constant
17  amrex::Real gravity{CONST_GRAV}; ///< Acceleration due to gravity (m/s^2)
18  amrex::Real surf_temp_flux{zero}; ///< Heat flux TODO: decide whether this is <θ'w'> or <θv'w'> under moist conditions
19  amrex::Real surf_moist_flux{zero}; ///< Moisture flux
20 
21  amrex::Real Cnk_a{amrex::Real(0.0185)}; ///< Standard Charnock constant https://doi.org/amrex::Real(10.1175)/JAMC-D-17-amrex::Real(0137.1)
22  amrex::Real Cnk_b1{one/amrex::Real(30.0)}; ///< Modified Charnock Eq (4) https://doi.org/amrex::Real(10.1175)/JAMC-D-17-amrex::Real(0137.1)
23  amrex::Real Cnk_b2{amrex::Real(1260.0)}; ///< Modified Charnock Eq (4) https://doi.org/amrex::Real(10.1175)/JAMC-D-17-amrex::Real(0137.1)
24  amrex::Real Cnk_d{amrex::Real(30.0)}; ///< Modified Charnock Eq (4) https://doi.org/amrex::Real(10.1175)/JAMC-D-17-amrex::Real(0137.1)
25  amrex::Real Cnk_b = Cnk_b1 * std::log(Cnk_b2 / Cnk_d); ///< Modified Charnock derived quantity
26  bool visc{true}; ///< Use viscous Charnock formulation
27 
31 
32  const amrex::Real Bjr_beta = amrex::Real(1.2); // Empirical parameter from Beljaars 1995 QJRMS
33 };
34 
35 
36 /**
37  * Lower bound of the log-law bracket ln(zref/z0) - psi of the MOST flux kernels: C/2 held
38  * between 1/2 and 1.
39  *
40  * The bound is 1 for zref >= e^2 z0 and C/2 down to zref = e z0, so it does not exceed the
41  * neutral bracket C wherever zref >= e^(1/2) z0. The lower limit of 1/2 keeps the bracket,
42  * and so u*, positive and finite when z0 approaches or exceeds zref (C <= 0).
43  *
44  * The bound is set by the under-relaxed u* iteration, not by the physics. At an unstable
45  * fixed point the relaxed map has slope (1 - alpha) - alpha s / bracket, with
46  * s = -3 (z/L) dpsi_m/d(z/L) between 1.3 (z/L = -0.5) and 2.4 (z/L = -50) for Businger-Dyer.
47  * With alpha = 0.5 the iteration converges only for bracket > s/3 (0.4 to 0.8), so a bound of
48  * 1 keeps the switch between the bounded and free branches inside the convergent range, while
49  * 0.1 C (0.3 at C = 3) does not and the iteration oscillates between two values. If alpha
50  * changes, the bound needs revisiting; see PR #4866.
51  *
52  * @param[in] C ln(zref/z0)
53  */
54 AMREX_GPU_HOST_DEVICE
55 AMREX_FORCE_INLINE
58 {
59  return amrex::max(amrex::min(amrex::Real(0.5) * C, amrex::Real(1.0)), amrex::Real(0.5));
60 }
61 
62 
63 /**
64  * Structure of similarity functions for Moeng formulation
65  */
67 {
68  //
69  // Similarity functions from Jimenez (2012)
70  //
71  /**
72  * Compute the Jimenez momentum stability correction.
73  *
74  * @param[in] zeta stability coordinate z/L
75  */
76  AMREX_GPU_HOST_DEVICE
77  AMREX_FORCE_INLINE
79  calc_psi_m2 (amrex::Real zeta) const
80  {
81  if (zeta > 0) {
82  amrex::Real x = std::pow(one + std::pow(zeta, amrex::Real(2.5)), one/amrex::Real(2.5));
83  return ( -amrex::Real(6.1)*std::log(zeta + x) );
84  } else {
85  amrex::Real x = std::pow(one - amrex::Real(16.0)*zeta, fourth);
86  amrex::Real psi_k_m = two * std::log(myhalf * (one + x)) + std::log(myhalf * (one + x * x)) -
87  two * std::atan(x) + PIoTwo;
88  amrex::Real y = std::pow(one - amrex::Real(10.0)*zeta, one/three);
89  amrex::Real psi_c_m = (three/two)*std::log((y*y + y + one)/three)
90  - std::sqrt(three)*std::atan((two*y + one)/std::sqrt(three))
91  + PI/std::sqrt(three);
92  return ( (psi_k_m + zeta*zeta*psi_c_m) / (one + zeta*zeta) );
93  }
94  }
95 
96  /**
97  * Compute the Jimenez heat stability correction.
98  *
99  * @param[in] zeta stability coordinate z/L
100  */
101  AMREX_GPU_HOST_DEVICE
102  AMREX_FORCE_INLINE
105  {
106  if (zeta > 0) {
107  amrex::Real x = std::pow(one + std::pow(zeta, amrex::Real(1.1)), one/amrex::Real(1.1));
108  return ( -amrex::Real(5.3)*std::log(zeta + x) );
109  } else {
110  amrex::Real x = std::sqrt(one - amrex::Real(16.0)*zeta);
111  amrex::Real psi_k_h = two * std::log(myhalf * (one + x));
112  amrex::Real y = std::pow(one - amrex::Real(34.0)*zeta, one/three);
113  amrex::Real psi_c_h = (three/two)*std::log((y*y + y + one)/three)
114  - std::sqrt(three)*std::atan((two*y + one)/std::sqrt(three))
115  + PI/std::sqrt(three);
116  return ( (psi_k_h + zeta*zeta*psi_c_h) / (one + zeta*zeta) );
117  }
118  }
119 
120 
121  //
122  // Similarity functions from Businger & Dyer (1966)
123  //
124  /**
125  * Compute the Businger-Dyer momentum stability correction.
126  *
127  * @param[in] zeta stability coordinate z/L
128  */
129  AMREX_GPU_HOST_DEVICE
130  AMREX_FORCE_INLINE
132  calc_psi_m (amrex::Real zeta) const
133  {
134  if (zeta > 0) {
135  return -beta_m * zeta;
136  } else {
137  amrex::Real x = std::sqrt(std::sqrt(one - gamma_m * zeta));
138  return two * std::log(myhalf * (one + x)) + std::log(myhalf * (one + x * x)) -
139  two * std::atan(x) + PIoTwo;
140  }
141  }
142 
143  /**
144  * Compute the Businger-Dyer heat stability correction.
145  *
146  * @param[in] zeta stability coordinate z/L
147  */
148  AMREX_GPU_HOST_DEVICE
149  AMREX_FORCE_INLINE
151  calc_psi_h (amrex::Real zeta) const
152  {
153  if (zeta > 0) {
154  return -beta_h * zeta;
155  } else {
156  amrex::Real x = std::sqrt(one - gamma_h * zeta);
157  return two * std::log(myhalf * (one + x));
158  }
159  }
160 
161  //
162  // Gradient (phi) functions
163  //
164  /**
165  * Compute the Businger-Dyer momentum gradient function.
166  *
167  * This is the exact companion of calc_psi_m: the two are tied by
168  * psi_m(zeta) = int_0^zeta (1 - phi_m(x))/x dx, so differentiating the MOST
169  * wind profile u(z) = (u_star/kappa)[ln(z/z0) - psi_m(z/L)] gives
170  * du/dz = u_* phi_m(zeta) / (kappa z).
171  *
172  * NOTE: the surface_temp* flux calculators use the Jimenez psi pair
173  * (calc_psi_m2/calc_psi_h2) rather than this one; their companions are
174  * not implemented yet, so a prescribed-surface-temperature run gets the
175  * Businger-Dyer gradient here.
176  *
177  * @param[in] zeta stability coordinate z/L
178  */
179  AMREX_GPU_HOST_DEVICE
180  AMREX_FORCE_INLINE
182  calc_phi_m (amrex::Real zeta) const
183  {
184  if (zeta > 0) {
185  return one + beta_m * zeta;
186  } else {
187  return one / std::sqrt(std::sqrt(one - gamma_m * zeta));
188  }
189  }
190 
191  /**
192  * Compute the Businger-Dyer heat gradient function, the exact companion of
193  * calc_psi_h: dtheta/dz = theta_* phi_h(zeta) / (kappa z).
194  *
195  * See the note in calc_phi_m about the Jimenez pair.
196  *
197  * @param[in] zeta stability coordinate z/L
198  */
199  AMREX_GPU_HOST_DEVICE
200  AMREX_FORCE_INLINE
202  calc_phi_h (amrex::Real zeta) const
203  {
204  if (zeta > 0) {
205  return one + beta_h * zeta;
206  } else {
207  return one / std::sqrt(one - gamma_h * zeta);
208  }
209  }
210 
211  /**
212  * Stable z/L at which the prescribed-flux problem folds.
213  *
214  * With the Businger-Dyer psi_m = -beta_m z/L and L fixed by u* and the flux, the wind a
215  * given u* supports is u* max(C - psi_m, b) / kappa, with b = most_bracket_floor(C). For
216  * C > 0 it is smallest at z/L = C / (2 beta_m); for small or negative C (z0 near or above
217  * zref) the floor binds first and the minimum is at the kink z/L = (b - C) / beta_m. Below
218  * that wind the prescribed stable flux has no solution (Basu et al., BLM 2008). The
219  * physical (large u*) branch has z/L below this value, so bounding z/L by it leaves every
220  * point that has a solution unchanged. The result is positive for every C.
221  *
222  * @param[in] C ln(zref/z0)
223  */
224  AMREX_GPU_HOST_DEVICE
225  AMREX_FORCE_INLINE
228  {
229  return amrex::max(C / (two * beta_m), (most_bracket_floor(C) - C) / beta_m);
230  }
231 
232 private:
233  amrex::Real beta_m{amrex::Real(5.0)}; ///< Constants from Dyer, BLM, 1974
234  amrex::Real beta_h{amrex::Real(5.0)}; ///< https://doi.org/amrex::Real(10.1007)/BF00240838
237 };
238 
239 
240 /**
241  * Log-law bracket ln(zref/z0) - psi of the MOST flux kernels, held at or above
242  * most_bracket_floor(C).
243  *
244  * In strongly unstable air psi exceeds C = ln(zref/z0) and the bracket changes sign, which
245  * turns u* negative and puts the diagnosed surface temperature on the wrong side of the air
246  * for a prescribed flux. With the floor the bracket is at least 1/2, so u* stays positive and
247  * finite for every C.
248  *
249  * @param[in] C ln(zref/z0)
250  * @param[in] psi stability correction (momentum or heat)
251  */
252 AMREX_GPU_HOST_DEVICE
253 AMREX_FORCE_INLINE
256 {
257  return amrex::max(C - psi, most_bracket_floor(C));
258 }
259 
260 /**
261  * Bound on |z/L| of the MOST kernels (see most_rib_zeta_bracketed and most_clamp_zeta).
262  */
263 inline constexpr amrex::Real most_zeta_lim = amrex::Real(100.0);
264 
265 /**
266  * Residual zeta - Rib (C - psi_m)^2 / (C - psi_h) of the prescribed-temperature kernels'
267  * z/L equation, with the Jimenez functions and the brackets of most_bracket.
268  *
269  * @param[in] sfuns similarity functions
270  * @param[in] C ln(zref/z0)
271  * @param[in] Rib bulk Richardson number
272  * @param[in] zeta z/L
273  */
274 AMREX_GPU_HOST_DEVICE
275 AMREX_FORCE_INLINE
278 {
279  const amrex::Real CmPsim = most_bracket(C, sfuns.calc_psi_m2(zeta));
280  const amrex::Real CmPsih = most_bracket(C, sfuns.calc_psi_h2(zeta));
281  return zeta - Rib * CmPsim * CmPsim / CmPsih;
282 }
283 
284 /**
285  * Whether a root of most_rib_resid lies within delta of zeta.
286  *
287  * The relaxed fixed-point iteration stops on a small step, which a near-touch of the
288  * residual also gives where no root is near; the residual must change sign across
289  * [zeta - delta, zeta + delta] for zeta to be taken as a solution.
290  *
291  * @param[in] sfuns similarity functions
292  * @param[in] C ln(zref/z0)
293  * @param[in] Rib bulk Richardson number
294  * @param[in] zeta z/L the relaxed iteration stopped at
295  * @param[in] delta half-width of the interval checked
296  */
297 AMREX_GPU_HOST_DEVICE
298 AMREX_FORCE_INLINE
299 bool
301  amrex::Real zeta, amrex::Real delta)
302 {
303  const amrex::Real rlo = most_rib_resid(sfuns, C, Rib, zeta - delta);
304  const amrex::Real rhi = most_rib_resid(sfuns, C, Rib, zeta + delta);
305  return (rlo > zero) != (rhi > zero) || rlo == zero || rhi == zero;
306 }
307 
308 /**
309  * Solve zeta = Rib (C - psi_m)^2 / (C - psi_h) of the prescribed-temperature kernels by
310  * bracketing and bisection, with the Jimenez functions and the brackets of most_bracket.
311  *
312  * The relaxed fixed-point iteration stalls where zeta - Rib F(zeta) nearly touches zero
313  * without crossing it, which the Jimenez stable functions produce when z0 is comparable to or
314  * above zref (C below about 1): each pass then moves zeta by about the tolerance. From zeta0
315  * this steps, with doubling steps, in the direction the iteration moves until the residual
316  * changes sign, and bisects that interval, so it returns the root the iteration was heading
317  * to, or the next one past a near-touch. The first step is limited so that two nearby roots
318  * are not stepped over together. The search stays within +-most_zeta_lim, and returns that
319  * bound when no root lies inside it, as the prescribed-flux kernels bound zeta.
320  *
321  * @param[in] sfuns similarity functions
322  * @param[in] C ln(zref/z0)
323  * @param[in] Rib bulk Richardson number
324  * @param[in] zeta0 z/L to start from
325  * @param[in] tol width of the final interval
326  */
327 AMREX_GPU_HOST_DEVICE
328 AMREX_FORCE_INLINE
331  amrex::Real zeta0, amrex::Real tol)
332 {
333  auto resid = [&] (amrex::Real zeta) { return most_rib_resid(sfuns, C, Rib, zeta); };
334 
335  amrex::Real a = zeta0;
336  amrex::Real ra = resid(a);
337  if (ra == zero) { return a; }
338 
339  // A non-finite residual (a NaN or infinite C, Rib or zeta0) cannot be bracketed: with it the
340  // step below is NaN and the search never reaches zlim. Return it, so the bad input shows up
341  // in the caller's results rather than as a hang.
342  if (!amrex::Math::isfinite(ra)) { return ra; }
343 
344  // Step against the residual, within +-most_zeta_lim, until it changes sign
345  const amrex::Real dir = (ra > zero) ? amrex::Real(-1.0) : amrex::Real(1.0);
346  const amrex::Real zlim = dir * most_zeta_lim;
347  auto step = [&] (amrex::Real z, amrex::Real dz) {
348  return (dir > zero) ? amrex::min(z + dz, zlim) : amrex::max(z - dz, zlim);
349  };
350  amrex::Real h = amrex::max(amrex::min(std::abs(ra),
351  amrex::Real(0.1) * amrex::max(amrex::Real(one), std::abs(a))),
352  tol);
353  amrex::Real b = step(a, h);
354  amrex::Real rb = resid(b);
355  // With finite values the doubling step reaches zlim in well under 200 steps (from tol to
356  // 2 most_zeta_lim); the cap only guarantees that this loop ends
357  int nstep = 0;
358  while ((rb > zero) == (ra > zero) && b != zlim && nstep < 200) {
359  a = b;
360  ra = rb;
361  h *= two;
362  b = step(a, h);
363  rb = resid(b);
364  ++nstep;
365  }
366  // No root within the bound: hold zeta there
367  if ((rb > zero) == (ra > zero)) { return zlim; }
368 
369  // Bisect [a, b]
370  int n = 0;
371  while (std::abs(b - a) > tol && n < 60) {
372  const amrex::Real m = myhalf * (a + b);
373  const amrex::Real rm = resid(m);
374  if ((rm > zero) == (ra > zero)) {
375  a = m;
376  ra = rm;
377  } else {
378  b = m;
379  }
380  ++n;
381  }
382  return myhalf * (a + b);
383 }
384 
385 /**
386  * z/L of the prescribed-flux kernels, bounded below by -most_zeta_lim and above by the fold (see
387  * similarity_funs::calc_zeta_fold), where the prescribed stable flux stops having a solution.
388  *
389  * @param[in] zeta z/L from the current u* and flux
390  * @param[in] zeta_fold z/L at the fold
391  * @param[out] at_fold whether the fold bound was applied
392  */
393 AMREX_GPU_HOST_DEVICE
394 AMREX_FORCE_INLINE
396 most_clamp_zeta (amrex::Real zeta, amrex::Real zeta_fold, bool& at_fold)
397 {
398  at_fold = (zeta >= zeta_fold);
399  return amrex::max(amrex::min(zeta, zeta_fold), -most_zeta_lim);
400 }
401 
402 /**
403  * Factors applied to the prescribed sensible and moisture fluxes at the stable fold.
404  */
406 {
407  amrex::Real temp{amrex::Real(1.0)}; ///< factor on surf_temp_flux
408  amrex::Real moist{amrex::Real(1.0)}; ///< factor on surf_moist_flux
409 };
410 
411 /**
412  * Part of a prescribed stable flux the wind can carry at the fold.
413  *
414  * The virtual heat flux is tflux_s + tflux_q, with tflux_s = surf_temp_flux (1 + epsv qv) and
415  * tflux_q = qflux epsv theta. When z/L is held at the fold (see
416  * similarity_funs::calc_zeta_fold), the wind is below the minimum the prescribed flux needs and
417  * carries only -u*^3 thv zeta_fold / (kappa g zref). Only the prescribed components that are
418  * downward drive the fold, so only they are reduced, by a common factor, until the net virtual
419  * flux equals that value. An upward component, or a moisture flux diagnosed from q_surf, is
420  * left unchanged; if no prescribed component is downward, nothing is reduced. The factor is
421  * not taken below 0, so when a diagnosed condensation flux alone is stronger than the fold
422  * value, the downward prescribed components are removed and the net flux remains stronger
423  * than the fold value. Returns 1 for both factors away from the fold.
424  *
425  * @param[in] at_fold whether z/L was held at the fold
426  * @param[in] zeta_fold z/L at the fold
427  * @param[in] ustar friction velocity
428  * @param[in] thv virtual potential temperature at zref
429  * @param[in] tflux_s sensible part of the virtual heat flux
430  * @param[in] tflux_q moisture part of the virtual heat flux
431  * @param[in] spec_qflux whether the moisture flux is prescribed
432  * @param[in] kappa von Karman constant
433  * @param[in] gravity gravitational acceleration
434  * @param[in] zref reference height
435  */
436 AMREX_GPU_HOST_DEVICE
437 AMREX_FORCE_INLINE
440  amrex::Real thv, amrex::Real tflux_s, amrex::Real tflux_q,
441  bool spec_qflux, amrex::Real kappa, amrex::Real gravity,
442  amrex::Real zref)
443 {
444  most_flux_scale scale;
445  const amrex::Real tflux = tflux_s + tflux_q;
446  if (!at_fold || tflux >= zero) { return scale; }
447  const amrex::Real tflux_fold = -ustar * ustar * ustar * thv * zeta_fold / (kappa * gravity * zref);
448  if (tflux_fold <= tflux) { return scale; }
449  const bool down_s = (tflux_s < zero);
450  const bool down_q = (spec_qflux && tflux_q < zero);
451  const amrex::Real down = (down_s ? tflux_s : zero) + (down_q ? tflux_q : zero);
452  if (down >= zero) { return scale; }
453  const amrex::Real r = amrex::max((down + tflux_fold - tflux) / down, amrex::Real(0.0));
454  if (down_s) { scale.temp = r; }
455  if (down_q) { scale.moist = r; }
456  return scale;
457 }
458 
459 /**
460  * Minimum friction velocity [m/s] of the prescribed-flux kernels' outputs (see most_flux_outputs).
461  */
462 inline constexpr amrex::Real most_min_ustar = amrex::Real(0.001);
463 
464 /**
465  * Surface state of a prescribed-flux kernel once its u* iteration has converged.
466  *
467  * Sets t_surf, t_star and q_star (and q_surf when the moisture flux is prescribed). At the
468  * fold the prescribed fluxes are reduced by most_fold_flux_scale and the Obukhov length is set
469  * to the fold value. u* is floored at most_min_ustar here, so every kernel applies the same floor.
470  *
471  * @param[in] mdata MOST data (prescribed fluxes, kappa, gravity)
472  * @param[in] spec_qflux whether the moisture flux is prescribed
473  * @param[in] at_fold whether z/L was held at the fold on the last pass
474  * @param[in] zeta_fold z/L at the fold
475  * @param[in] C ln(zref/z0)
476  * @param[in] psi_h heat stability correction of the last pass
477  * @param[in] ustar friction velocity (floored at most_min_ustar)
478  * @param[in] zref reference height
479  * @param[in] tm potential temperature at zref
480  * @param[in] tvm virtual potential temperature at zref
481  * @param[in] qvm water vapor at zref
482  * @param[in] qflux moisture flux of the last pass
483  * @param[inout] olen Obukhov length
484  * @param[out] t_surf surface potential temperature
485  * @param[out] t_star temperature scale
486  * @param[inout] q_surf surface water vapor (read when the moisture flux is diagnosed)
487  * @param[out] q_star moisture scale
488  */
489 AMREX_GPU_HOST_DEVICE
490 AMREX_FORCE_INLINE
491 void
492 most_flux_outputs (const most_data& mdata, bool spec_qflux, bool at_fold,
493  amrex::Real zeta_fold, amrex::Real C, amrex::Real psi_h,
495  amrex::Real tvm, amrex::Real qvm, amrex::Real qflux,
496  amrex::Real& olen, amrex::Real& t_surf, amrex::Real& t_star,
497  amrex::Real& q_surf, amrex::Real& q_star)
498 {
499  ustar = amrex::max(ustar, amrex::Real(most_min_ustar));
500  const most_flux_scale scale = most_fold_flux_scale(at_fold, zeta_fold, ustar, tvm,
501  mdata.surf_temp_flux * (one + epsv * qvm),
502  qflux * epsv * tm, spec_qflux,
503  mdata.kappa, mdata.gravity, zref);
504  if (at_fold) { olen = zref / zeta_fold; }
505  const amrex::Real CmPsih = most_bracket(C, psi_h);
506  const amrex::Real hflux = scale.temp * mdata.surf_temp_flux;
507  t_surf = hflux * CmPsih / (ustar * mdata.kappa) + tm;
508  t_star = -hflux / ustar;
509  if (spec_qflux) {
510  const amrex::Real mflux = scale.moist * mdata.surf_moist_flux;
511  q_surf = mflux * CmPsih / (ustar * mdata.kappa) + qvm;
512  q_star = -mflux / ustar;
513  } else {
514  q_star = mdata.kappa * (qvm - q_surf) / CmPsih;
515  }
516 }
517 
518 /**
519  * Aitken delta-squared acceleration of the relaxed u* iteration in the prescribed-flux kernels.
520  *
521  * Near the stable fold the slope of the fixed-point map approaches 1 and the plain iteration
522  * needs hundreds of passes. From pass `start` on, every third iterate is replaced by the
523  * Aitken extrapolation of the last three (kept only within a factor of 2 of them), so points
524  * that converge before `start` are unchanged. The map is not stationary when w* or a
525  * u*-dependent z0 also changes from pass to pass, so the kernels do not stop on an
526  * extrapolated pass: the plain pass after it, with zeta and psi from the same u*, decides
527  * convergence.
528  */
530 {
531  /**
532  * Return the iterate to continue from.
533  *
534  * @param[in] u_new iterate after pass `iter`
535  * @param[in] iter number of passes done
536  */
537  AMREX_GPU_HOST_DEVICE
538  AMREX_FORCE_INLINE
540  update (amrex::Real u_new, int iter)
541  {
542  extrapolated = false;
543  if (iter < start) { return u_new; }
544  u0 = u1;
545  u1 = u2;
546  u2 = u_new;
547  if (++n < 3) { return u_new; }
548  const amrex::Real d = u2 - two * u1 + u0;
549  const amrex::Real ua = (d != zero) ? u2 - (u2 - u1) * (u2 - u1) / d : u_new;
550  const amrex::Real lo = myhalf * amrex::min(u0, amrex::min(u1, u2));
551  const amrex::Real hi = two * amrex::max(u0, amrex::max(u1, u2));
552  if (d == zero || !(ua > lo && ua < hi)) {
553  n = 0;
554  return u_new;
555  }
556  // The extrapolated value starts the next triple
557  u2 = ua;
558  n = 1;
559  extrapolated = true;
560  return ua;
561  }
562 
563  int start{25};
564  bool extrapolated{false}; ///< whether the last update returned an extrapolated value
565  int n{0};
569 };
570 
571 
572 /**
573  * Empirical kinematic viscosity [m2/s] formula from Andreas (1989) CRREL Rep.
574  * 89-11, valid between -173 and 277 deg C.
575  *
576  * @param[in] T_degK air temperature in Kelvin
577  */
578 AMREX_GPU_DEVICE
579 AMREX_FORCE_INLINE
582 {
583  amrex::Real TC = T_degK - amrex::Real(273.15);
584  return amrex::Real(1.326e-5)*(one + amrex::Real(6.542e-3)*TC + amrex::Real(8.301e-6)*TC*TC - amrex::Real(4.84e-9)*TC*TC*TC);
585 }
586 
587 /**
588  * Charnock roughness with optional aerodynamically-smooth contribution
589  *
590  * Smooth-flow limit follows z0 = 0.11 nu / u* (Smith 1988; ECMWF; WRF)
591  *
592  * @param[in] ustar friction velocity
593  * @param[in] cnk_a charnock constant
594  * @param[in] nu intrinsic fluid viscosity
595  * @param[in] visc flag to apply smooth flow limit
596  */
597 AMREX_GPU_DEVICE
598 AMREX_FORCE_INLINE
601  amrex::Real Cnk_a,
602  amrex::Real nu,
603  bool visc)
604 {
605  amrex::Real z0 = (Cnk_a / CONST_GRAV) * ustar * ustar;
606  if (visc) {
607  z0 += amrex::Real(0.11) * nu / std::max(ustar, amrex::Real(0.01));
608  }
609  // From Davis et al 2008 MWR
610  return std::min(std::max(z0, amrex::Real(1.27e-7)), amrex::Real(2.85e-3));
611 }
612 
613 /**
614  * Modified Charnock roughness
615  *
616  * Smooth-flow limit is intrinsic.
617  *
618  * @param[in] ustar friction velocity
619  * @param[in] cnk_b modified charnock constant
620  */
621 AMREX_GPU_DEVICE
622 AMREX_FORCE_INLINE
625  amrex::Real Cnk_b)
626 {
627  amrex::Real z0 = std::exp( (amrex::Real(2.7)*ustar - amrex::Real(1.8)/Cnk_b) / (ustar + amrex::Real(0.17)/Cnk_b) );
628  return std::min(std::max(z0, amrex::Real(1.27e-7)), amrex::Real(1.0e-2));
629 }
630 
631 /**
632  * COARE3.0, following WRF phys/module_sf_mynn.F
633  *
634  * Smooth-flow limit follows z0 = 0.11 nu / u* (Smith 1988; ECMWF; WRF)
635  *
636  * @param[in] zref reference height for the wind speed
637  * @param[in] umm horizontal wind speed magnitude at zref
638  * @param[in] ustar friction velocity
639  * @param[in] nu intrinsic fluid viscosity
640  * @param[in] visc flag to apply smooth flow limit
641  */
642 AMREX_GPU_DEVICE
643 AMREX_FORCE_INLINE
646  amrex::Real umm,
648  amrex::Real nu,
649  bool visc)
650 {
651  amrex::Real ws10m = umm*std::log(amrex::Real(10.)/amrex::Real(1e-4)) / std::log(zref/amrex::Real(1e-4));
652  amrex::Real Czc = amrex::Real(0.011) + amrex::Real(0.007)*std::min(std::max((ws10m-amrex::Real(10.))/amrex::Real(8.), amrex::Real(0.0)), amrex::Real(1.0));
653  amrex::Real z0 = (Czc / CONST_GRAV) * ustar * ustar;
654  if (visc) {
655  z0 += amrex::Real(0.11) * nu / std::max(ustar, amrex::Real(0.01));
656  }
657  // From Davis et al 2008 MWR
658  return std::min(std::max(z0, amrex::Real(1.27e-7)), amrex::Real(2.85e-3));
659 }
660 
661 /**
662  * Updated Donelan 2004 as found in AHW
663  *
664  * Matches implementations in WRF phys/physics_mmm/sf_sfclayrev.F90 and
665  * phys/module_sf_mynn.F
666  *
667  * Smooth flow limit is intrinsic in zn2.
668  *
669  * @param[in] ustar friction velocity
670  * @param[in] nu intrinsic fluid viscosity
671  * @param[in] visc flag to apply smooth flow limit
672  */
673 AMREX_GPU_DEVICE
674 AMREX_FORCE_INLINE
677  amrex::Real nu,
678  bool visc)
679 {
680  constexpr amrex::Real ozo{amrex::Real(1.59e-5)};
681  amrex::Real zw = std::min(std::pow(ustar/amrex::Real(1.06), amrex::Real(0.3)),amrex::Real(1.0));
682  amrex::Real zn1 = amrex::Real(0.011)*ustar*ustar/CONST_GRAV + ozo;
683  amrex::Real zn2 = amrex::Real(10.)*std::exp(-amrex::Real(9.5)*std::pow(ustar, -amrex::Real(.3333)));
684  if (visc) {
685  zn2 += amrex::Real(0.11) * nu / std::max(ustar, amrex::Real(0.01));
686  }
687  amrex::Real z0 = (amrex::Real(1.0)-zw) * zn1 + zw * zn2;
688  // From Davis et al 2008 MWR
689  return std::min(std::max(z0, amrex::Real(1.27e-7)), amrex::Real(2.85e-3));
690 }
691 
692 
693 /**
694  * Wave coupled roughness
695  *
696  * Smooth-flow limit follows z0 = 0.11 nu / u* (Smith 1988; ECMWF; WRF)
697  *
698  * @param[in] Hwave wave height
699  * @param[in] Lwave wave length
700  * @param[in] eps avoid divide by zero
701  * @param[in] ustar friction velocity
702  * @param[in] nu intrinsic fluid viscosity
703  * @param[in] visc flag to apply smooth flow limit
704  */
705 AMREX_GPU_DEVICE
706 AMREX_FORCE_INLINE
709  amrex::Real Lwave,
710  amrex::Real eps,
712  amrex::Real nu,
713  bool visc)
714 {
715 
716  amrex::Real z0 = amrex::Real(1200.0) * Hwave * std::pow( Hwave/(Lwave+eps), amrex::Real(4.5) );
717  if (visc) {
718  z0 += amrex::Real(0.11) * nu / std::max(ustar, amrex::Real(0.01));
719  }
720  return std::min(std::max(z0, amrex::Real(1.27e-7)), amrex::Real(1.0e-2));
721 }
722 
723 
724 /// Lower bound of the roughness recovered by Compute_roughness; shared with
725 /// the guard in Compute_roughness so the two cannot drift apart.
727 
728 /**
729  * Bounds on the roughness recovered by Compute_roughness. The inversion is
730  * unbounded above: in weak wind under strong stability (u* from an LSM or bulk
731  * flux large relative to kappa*U, psi_m << 0) it returns z0 >> zref. The
732  * upper bound is above every tabulated land-cover roughness in NoahmpTable.TBL
733  * (Z0MVT <= 1.10 m) and below the 2 m diagnostic height; z0 is also kept below
734  * half of zref so that ln(zref/z0) stays positive.
735  */
736 AMREX_GPU_HOST_DEVICE
737 AMREX_FORCE_INLINE
740 {
741  const amrex::Real z0_lo = inverted_roughness_lo;
742  const amrex::Real z0_hi = amrex::min(amrex::Real(1.5), amrex::Real(0.5) * zref);
743  return amrex::min(amrex::max(z0, z0_lo), z0_hi);
744 }
745 
746 /**
747  * Invert log-law to get roughness
748  *
749  * @param[in] zref reference height for the wind speed
750  * @param[in] Olen Obukhov length
751  * @param[in] umm horizontal wind speed magnitude at zref
752  * @param[in] ustar friction velocity
753  */
754 AMREX_GPU_DEVICE
755 AMREX_FORCE_INLINE
758  amrex::Real Olen,
759  amrex::Real umm,
761 {
762  similarity_funs sfuns;
763  amrex::Real zeta = zref / Olen;
764  amrex::Real psi_m = sfuns.calc_psi_m2(zeta);
765  // z0 = zref / exp(arg) is below the lower bound once arg > log(zref / z0_lo),
766  // so the bound is returned without evaluating exp(arg), which overflows when
767  // u* sits at its floor of machine epsilon (zero stress: bulk_coeff with
768  // Cd = 0, or an LSM that returns none). At or below the threshold exp(arg)
769  // is finite and the inversion runs as before, so no other input changes.
770  const amrex::Real arg = KAPPA*umm/ustar + psi_m;
771  if (arg > std::log(zref / inverted_roughness_lo)) {
773  }
774  amrex::Real z0 = zref / std::exp(arg);
775  return bound_inverted_roughness(z0, zref);
776 }
777 #endif
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
Real z0
Definition: ERF_InitCustomPertVels_ScalarAdvDiff.H:12
constexpr amrex::Real most_min_ustar
Definition: ERF_MOSTUtils.H:462
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real WaveCoupled_roughness(amrex::Real Hwave, amrex::Real Lwave, amrex::Real eps, amrex::Real ustar, amrex::Real nu, bool visc)
Definition: ERF_MOSTUtils.H:708
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real most_rib_zeta_bracketed(const similarity_funs &sfuns, amrex::Real C, amrex::Real Rib, amrex::Real zeta0, amrex::Real tol)
Definition: ERF_MOSTUtils.H:330
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real most_bracket_floor(amrex::Real C)
Definition: ERF_MOSTUtils.H:57
constexpr amrex::Real inverted_roughness_lo
Definition: ERF_MOSTUtils.H:726
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE most_flux_scale most_fold_flux_scale(bool at_fold, amrex::Real zeta_fold, amrex::Real ustar, amrex::Real thv, amrex::Real tflux_s, amrex::Real tflux_q, bool spec_qflux, amrex::Real kappa, amrex::Real gravity, amrex::Real zref)
Definition: ERF_MOSTUtils.H:439
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real most_bracket(amrex::Real C, amrex::Real psi)
Definition: ERF_MOSTUtils.H:255
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real bound_inverted_roughness(amrex::Real z0, amrex::Real zref) noexcept
Definition: ERF_MOSTUtils.H:739
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real COARE3_roughness(amrex::Real zref, amrex::Real umm, amrex::Real ustar, amrex::Real nu, bool visc)
Definition: ERF_MOSTUtils.H:645
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_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real Mod_Charnock_roughness(amrex::Real ustar, amrex::Real Cnk_b)
Definition: ERF_MOSTUtils.H:624
constexpr amrex::Real most_zeta_lim
Definition: ERF_MOSTUtils.H:263
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real most_rib_resid(const similarity_funs &sfuns, amrex::Real C, amrex::Real Rib, amrex::Real zeta)
Definition: ERF_MOSTUtils.H:277
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void most_flux_outputs(const most_data &mdata, bool spec_qflux, bool at_fold, amrex::Real zeta_fold, amrex::Real C, amrex::Real psi_h, amrex::Real ustar, amrex::Real zref, amrex::Real tm, amrex::Real tvm, amrex::Real qvm, amrex::Real qflux, amrex::Real &olen, amrex::Real &t_surf, amrex::Real &t_star, amrex::Real &q_surf, amrex::Real &q_star)
Definition: ERF_MOSTUtils.H:492
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real air_viscosity(amrex::Real T_degK)
Definition: ERF_MOSTUtils.H:581
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool most_rib_zeta_is_root(const similarity_funs &sfuns, amrex::Real C, amrex::Real Rib, amrex::Real zeta, amrex::Real delta)
Definition: ERF_MOSTUtils.H:300
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real most_clamp_zeta(amrex::Real zeta, amrex::Real zeta_fold, bool &at_fold)
Definition: ERF_MOSTUtils.H:396
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real Donelan_roughness(amrex::Real ustar, amrex::Real nu, bool visc)
Definition: ERF_MOSTUtils.H:676
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real Charnock_roughness(amrex::Real ustar, amrex::Real Cnk_a, amrex::Real nu, bool visc)
Definition: ERF_MOSTUtils.H:600
constexpr amrex::Real three
Definition: ERF_NumericalConstants.H:39
constexpr amrex::Real two
Definition: ERF_NumericalConstants.H:38
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:37
constexpr amrex::Real fourth
Definition: ERF_NumericalConstants.H:42
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:36
constexpr amrex::Real myhalf
Definition: ERF_NumericalConstants.H:41
constexpr amrex::Real PI
Definition: ERF_NumericalConstants.H:46
constexpr amrex::Real PIoTwo
Definition: ERF_NumericalConstants.H:47
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ olen
Definition: ERF_SLM.H:104
@ ustar
Definition: ERF_SLM.H:28
@ dz
Definition: ERF_AdvanceWDM6.cpp:273
@ rhi
Definition: ERF_WSM6.H:360
Definition: ERF_MOSTUtils.H:530
amrex::Real u2
Definition: ERF_MOSTUtils.H:568
int start
Definition: ERF_MOSTUtils.H:563
bool extrapolated
whether the last update returned an extrapolated value
Definition: ERF_MOSTUtils.H:564
int n
Definition: ERF_MOSTUtils.H:565
amrex::Real u0
Definition: ERF_MOSTUtils.H:566
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real update(amrex::Real u_new, int iter)
Definition: ERF_MOSTUtils.H:540
amrex::Real u1
Definition: ERF_MOSTUtils.H:567
Definition: ERF_MOSTUtils.H:13
amrex::Real surf_moist_flux
Moisture flux.
Definition: ERF_MOSTUtils.H:19
amrex::Real Cnk_b2
Modified Charnock Eq (4) https://doi.org/amrex::Real(10.1175)/JAMC-D-17-amrex::Real(0137....
Definition: ERF_MOSTUtils.H:23
amrex::Real Cnk_b
Modified Charnock derived quantity.
Definition: ERF_MOSTUtils.H:25
amrex::Real Ch
Definition: ERF_MOSTUtils.H:29
amrex::Real Cnk_d
Modified Charnock Eq (4) https://doi.org/amrex::Real(10.1175)/JAMC-D-17-amrex::Real(0137....
Definition: ERF_MOSTUtils.H:24
amrex::Real kappa
von Karman constant
Definition: ERF_MOSTUtils.H:16
amrex::Real gravity
Acceleration due to gravity (m/s^2)
Definition: ERF_MOSTUtils.H:17
amrex::Real Cnk_a
Standard Charnock constant https://doi.org/amrex::Real(10.1175)/JAMC-D-17-amrex::Real(0137....
Definition: ERF_MOSTUtils.H:21
amrex::Real Cd
Definition: ERF_MOSTUtils.H:28
amrex::Real Cq
Definition: ERF_MOSTUtils.H:30
const amrex::Real Bjr_beta
Definition: ERF_MOSTUtils.H:32
amrex::Real Cnk_b1
Modified Charnock Eq (4) https://doi.org/amrex::Real(10.1175)/JAMC-D-17-amrex::Real(0137....
Definition: ERF_MOSTUtils.H:22
amrex::Real z0_const
Roughness height – default constant value(m)
Definition: ERF_MOSTUtils.H:15
bool visc
Use viscous Charnock formulation.
Definition: ERF_MOSTUtils.H:26
amrex::Real surf_temp_flux
Heat flux TODO: decide whether this is <θ'w'> or <θv'w'> under moist conditions.
Definition: ERF_MOSTUtils.H:18
Definition: ERF_MOSTUtils.H:406
amrex::Real temp
factor on surf_temp_flux
Definition: ERF_MOSTUtils.H:407
amrex::Real moist
factor on surf_moist_flux
Definition: ERF_MOSTUtils.H:408
Definition: ERF_MOSTUtils.H:67
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real calc_phi_h(amrex::Real zeta) const
Definition: ERF_MOSTUtils.H:202
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real calc_psi_m(amrex::Real zeta) const
Definition: ERF_MOSTUtils.H:132
amrex::Real beta_m
Constants from Dyer, BLM, 1974.
Definition: ERF_MOSTUtils.H:233
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real calc_psi_h2(amrex::Real zeta) const
Definition: ERF_MOSTUtils.H:104
amrex::Real beta_h
https://doi.org/amrex::Real(10.1007)/BF00240838
Definition: ERF_MOSTUtils.H:234
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real calc_zeta_fold(amrex::Real C) const
Definition: ERF_MOSTUtils.H:227
amrex::Real gamma_h
Definition: ERF_MOSTUtils.H:236
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real calc_psi_h(amrex::Real zeta) const
Definition: ERF_MOSTUtils.H:151
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real calc_phi_m(amrex::Real zeta) const
Definition: ERF_MOSTUtils.H:182
amrex::Real gamma_m
Definition: ERF_MOSTUtils.H:235
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real calc_psi_m2(amrex::Real zeta) const
Definition: ERF_MOSTUtils.H:79