ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_ComputeDiffusivityMRF.cpp File Reference
#include "ERF_SurfaceLayer.H"
#include "ERF_DirectionSelector.H"
#include "ERF_Diffusion.H"
#include "ERF_Constants.H"
#include "ERF_TurbStruct.H"
#include "ERF_PBLModels.H"
#include "ERF_TileNoZ.H"
#include "ERF_MoistUtils.H"
Include dependency graph for ERF_ComputeDiffusivityMRF.cpp:

Functions

void ComputeDiffusivityMRF (const MultiFab &xvel, const MultiFab &yvel, const MultiFab &cons_in, MultiFab &eddyViscosity, const Geometry &geom, const TurbChoice &turbChoice, std::unique_ptr< SurfaceLayer > &SurfLayer, bool use_terrain_fitted_coords, bool use_moisture, int level, const BCRec *bc_ptr, bool, const std::unique_ptr< MultiFab > &z_phys_nd, const std::unique_ptr< MultiFab > &z_phys_cc, const MoistureComponentIndices &moisture_indices)
 

Function Documentation

◆ ComputeDiffusivityMRF()

void ComputeDiffusivityMRF ( const MultiFab &  xvel,
const MultiFab &  yvel,
const MultiFab &  cons_in,
MultiFab &  eddyViscosity,
const Geometry &  geom,
const TurbChoice turbChoice,
std::unique_ptr< SurfaceLayer > &  SurfLayer,
bool  use_terrain_fitted_coords,
bool  use_moisture,
int  level,
const BCRec *  bc_ptr,
bool  ,
const std::unique_ptr< MultiFab > &  z_phys_nd,
const std::unique_ptr< MultiFab > &  z_phys_cc,
const MoistureComponentIndices moisture_indices 
)
28 {
29  /*
30  ============================================================================
31  Medium-Range Forecast (MRF) Boundary Layer Parameterization Scheme
32  ============================================================================
33 
34  Implementation of the MRF (Medium Range Forecast) boundary layer scheme
35  based on Hong and Pan (1996), with enhanced moisture handling and
36  cloud-aware stability corrections developed for ERF.
37 
38  PRIMARY REFERENCES:
39  -------------------
40  - Hong, S. Y., and H.-L. Pan, 1996: Nonlocal Boundary Layer Vertical
41  Diffusion in a Medium-Range Forecast Model. Monthly Weather Review, 124,
42  2322-2339. https://doi.org/10.1175/1520-0493(1996)124<2322:NBLVDI>2.0.CO;2
43 
44  - WRF Reference Implementation: module_bl_mrf.F
45  https://github.com/wrf-model/WRF/blob/master/phys/module_bl_mrf.F
46 
47  - Hong, S. Y., Y. Noh, and J. Dudhia, 2006: A new vertical diffusion
48  package with an explicit treatment of entrainment processes. Monthly
49  Weather Review, 134, 2318-2341. https://doi.org/10.1175/MWR3250.1
50  (YSU scheme, used for free atmosphere mixing)
51 
52  CORE ALGORITHM (Hong & Pan 1996):
53  ----------------------------------
54  1. Predictor-Corrector Bulk Richardson Number (Rib) PBL Height Diagnosis
55  - Rib = (g*z*(θ_v(z) - θ_v(0))) / (U²*θ_v(0))
56  - h = min(z where Rib > Rib_critical)
57 
58  2. Nonlocal Countergradient Flux Corrections
59  - HGAMT: thermal excess from sensible heat flux
60  - HGAMQ: moisture excess from latent heat flux
61  - VPERT: virtual potential temperature perturbation
62 
63  3. Stability-Dependent Mixing Lengths in PBL
64  - K_m = ρ * w_* * κ * z * (1 - z/h)²
65  - K_t = K_m / Pr_t, K_q = K_m / Pr_q
66 
67  4. Free Atmosphere Mixing via Richardson Number
68  - Uses YSU stability functions (Hong et al. 2006)
69  - Ri-dependent diffusivity above PBL
70 
71  ENHANCEMENTS IN ERF IMPLEMENTATION:
72  -----------------------------------
73 
74  A. Virtual Potential Temperature (θ_v) Treatment
75  - Proper handling of moisture effects on buoyancy
76  - Important for accurate PBL height and convective strength
77  - Not explicitly detailed in Hong & Pan (1996) but standard practice
78 
79  B. Cloud-Aware Stability Function Adjustments (OPTIONAL)
80  - Physically motivated extension beyond Hong & Pan (1996)
81  - Detects clouds via qc + qi > threshold (0.1 g/kg)
82  - In stable layers with clouds:
83  * Reduces stability damping (φ reduced by 10-20%)
84  * Represents suppressed turbulence due to stable stratification
85  - In unstable layers with clouds:
86  * Slightly increases instability enhancement
87  * Represents latent heat release effects
88  - Disabled by default for backward compatibility
89  - Reference: Conceptually similar to WRF's IMVDIF cloud handling
90 
91  C. Explicit Moisture Sign Convention Handling
92  - q_star uses WRF convention: negative for upward flux (evaporation)
93  - HGAMQ calculation: -const_b * u_* * q_* / w_*
94  - Produces positive HGAMQ for unstable (evaporating) conditions
95  - Corrected with MAX(HGAMQ, 0) to prevent negative values
96  - Land/water discrimination: HGAMQ zeroed over water surfaces
97 
98  D. Improved VPERT (Virtual Potential Temperature Perturbation)
99  Formulation
100  - VPERT = max(HGAMT + 0.61*θ*HGAMQ, 0)
101  - Enhancement vs WRF: Does not limit VPERT to GAMCRT
102  - WRF limits VPERT after adding moisture term (VPERT = min(VPERT, GAMCRT))
103  - This is physically incorrect: moisture contribution can exceed GAMCRT
104  - ERF only limits HGAMT (sensible heat), allowing larger VPERT when
105  both sensible and latent heat create strong surface heating
106  - This produces more realistic PBL heights in moist convection
107 
108  E. Prandtl Number with Stability Correction
109  - Pr_t = φ_t/φ_m + const_b * κ * sf
110  - Bounded: 0.5 ≤ Pr_t ≤ 4.0
111  - Consistency with WRF: const_b = 7.8, sf = 0.1
112  - Reference: Hong et al. (2006), Equation A17
113 
114  PARAMETER DEFAULTS (Matching WRF):
115  -----------------------------------
116  - const_b = 7.8 (heat flux weight in countergradient)
117  - sf = 0.1 (surface layer / PBL height ratio in stability functions)
118  - GAMCRT = 3.0 K (maximum heat countergradient)
119  - GAMCRQ = 2.0e-3 kg/kg (maximum moisture countergradient)
120  - Rib_crit = 0.5 (critical bulk Richardson number for PBL height)
121  - Pr_min = 0.5, Pr_max = 4.0 (Prandtl number bounds)
122  - Kmin = 0.1 m²/s, Kmax = 300 m²/s (diffusivity bounds, Hong & Pan 1996)
123 
124  STABILITY FUNCTIONS:
125  --------------------
126  Unstable (L < 0, HOL < 0):
127  phi_m = (1 - 8 * sf * h/L)^(-1/3)
128  phi_t = (1 - 16 * sf * h/L)^(-1/2)
129 
130  Stable (L > 0, HOL > 0):
131  phi_m = phi_t = 1 + 5 * sf * h/L
132 
133  MIXING ABOVE PBL (Free Atmosphere):
134  -----------------------------------
135  Uses YSU scheme (Hong et al. 2006, Appendix A) for Richardson number
136  dependent mixing. This avoids MRF oscillations in stable conditions.
137 
138  Gradient Richardson number: Ri_g = (g/theta_v) * (dtheta_v/dz) / ((du/dz)^2 + (dv/dz)^2)
139 
140  For Ri_g > 0 (stable):
141  f_m = 1 / ((1 + 5*Ri_g)^2)
142  f_t = 1 / ((1 + 5*Ri_g)^2)
143  Pr_t = 1 + 2.1*Ri_g, bounded to [0.25, 4.0]
144 
145  For Ri_g < 0 (unstable):
146  f_m = 1 - 8*Ri_g / (1 + 1.746*sqrt(-Ri_g))
147  f_t = 1 - 8*Ri_g / (1 + 1.286*sqrt(-Ri_g))
148 
149  NUMERICAL CONSIDERATIONS:
150  -------------------------
151  - GPU-optimized with parallel_for loops
152  - Safe bounds on shear and vertical derivatives
153  - Gradient Richardson number limited: -100 <= Ri_g <= 100
154  - Avoids division by zero with minimum shear threshold (1e-10)
155  - theta_v_klo and theta_v guarded against zero/negative values
156 
157  TESTING & VALIDATION:
158  ---------------------
159  - Tested against WRF_ideal unstable/stable ABL cases
160  - Backward compatible when enhancements are disabled
161  - Cloud detection threshold: qc + qi > 1e-4 kg/kg (0.1 g/kg)
162  */
163 
164  // Domain extent in z-dir
165  int klo = geom.Domain().smallEnd(2);
166  int khi = geom.Domain().bigEnd(2);
167 
168 #ifdef _OPENMP
169 #pragma omp parallel if (Gpu::notInLaunchRegion())
170 #endif
171  for (MFIter mfi(eddyViscosity, TileNoZ()); mfi.isValid(); ++mfi) {
172 
173  // Box operated on must span fill domain in z-dir
174  const Box& gbx = mfi.growntilebox(IntVect(1,1,0));
175  AMREX_ALWAYS_ASSERT( gbx.smallEnd(2) == klo &&
176  gbx.bigEnd(2) == khi );
177 
178  // create flattened boxes to store PBL height and related quantities
179  const GeometryData gdata = geom.data();
180  const Box xybx = PerpendicularBox<ZDir>(gbx, IntVect{0, 0, 0});
181 
182  // Pass 1 (predictor): PBLH with base surface temperature, no VPERT
183  // Pass 2 (wstar/VPERT): compute wstar, HGAMT, HGAMQ, VPERT from predictor height
184  // Pass 3 (corrector): PBLH with VPERT-enhanced surface temperature (WRF-consistent)
185  // WRF reference (module_bl_mrf.F lines 813-964):
186  // https://github.com/wrf-model/WRF/blob/master/phys/module_bl_mrf.F#L813-L964
187  FArrayBox pbl_height_predictor(xybx, 1, The_Async_Arena()); // Pass 1: base t_layer_v
188  FArrayBox pbl_height_corrector(xybx, 1, The_Async_Arena()); // Pass 3: VPERT-enhanced
189  IArrayBox pbl_index(xybx, 1, The_Async_Arena());
190  IArrayBox pbl_index_zero_ri(xybx, 1, The_Async_Arena()); // Index for zero-Ri diagnostic pass
191  FArrayBox hgamt_fab(xybx, 1, The_Async_Arena()); // Store HGAMT/h (normalized countergradient)
192  FArrayBox hgamq_fab(xybx, 1, The_Async_Arena()); // Store HGAMQ/h (normalized countergradient)
193  FArrayBox wstar_fab(xybx, 1, The_Async_Arena()); // Convective velocity scale
194  FArrayBox vpert_fab(xybx, 1, The_Async_Arena()); // Virtual temperature perturbation VPERT
195  const auto& pblh_pred_arr = pbl_height_predictor.array(); // predictor (base t_layer_v)
196  const auto& pblh_corr_arr = pbl_height_corrector.array(); // corrector (VPERT-enhanced)
197  const auto& pbli_arr = pbl_index.array();
198  const auto& pbli_zero_arr = pbl_index_zero_ri.array(); // Zero-Ri diagnostic PBL index
199  const auto& hgamt_arr = hgamt_fab.array();
200  const auto& hgamq_arr = hgamq_fab.array();
201  const auto& wstar_arr = wstar_fab.array();
202  const auto& vpert_arr = vpert_fab.array();
203 
204  // Get some data in arrays
205  const auto& cell_data = cons_in.const_array(mfi);
206  const auto& uvel = xvel.const_array(mfi);
207  const auto& vvel = yvel.const_array(mfi);
208 
209  const Real Ribcr = turbChoice.pbl_mrf_Ribcr;
210  const auto& u_star_arr = SurfLayer->get_u_star(level)->const_array(mfi);
211  const auto& t_star_arr = SurfLayer->get_t_star(level)->const_array(mfi);
212  const auto& l_obuk_arr = SurfLayer->get_olen(level)->const_array(mfi);
213  const auto& t10av_arr = SurfLayer->get_mac_avg(level, 2)->const_array(mfi);
214  const auto& q10av_arr = SurfLayer->get_mac_avg(level, 3)->const_array(mfi);
215  const auto& lmask_arr = (SurfLayer->get_lmask(level)) ?
216  SurfLayer->get_lmask(level)->const_array(mfi) :
217  Array4<int>{};
218  // Only retrieve z_phys_nd array if terrain-fitted coordinates are in use
219  const Array4<Real const> z_nd_arr = use_terrain_fitted_coords ? z_phys_nd->array(mfi)
220  : Array4<Real const>{};
221  const PBLDerivativeDzInv_T pbl_derivative_dz_inv{z_phys_cc->const_array(mfi)};
222 
223  //
224  // PASS 1 (PREDICTOR): Compute PBL height using base surface virtual temperature.
225  // No thermal excess (VPERT = 0). Produces pblh_pred_arr and pbli_arr.
226  // WRF reference (module_bl_mrf.F lines 813-842):
227  // https://github.com/wrf-model/WRF/blob/master/phys/module_bl_mrf.F#L813-L842
228  //
229  ParallelFor(xybx, [=] AMREX_GPU_DEVICE(int i, int j, int) noexcept
230  {
231  const Real t_layer = t10av_arr(i, j, 0);
232  const Real moisture_fraction = use_moisture ? q10av_arr(i, j, 0) : Real(0);
233  const Real t_layer_v = t_layer * (one + epsv * moisture_fraction);
234 
235  Real zval, Rib;
236  int kpbl = klo;
237 
238  {
239  zval = (use_terrain_fitted_coords)
240  ? Compute_Zrel_AtCellCenter(i, j, kpbl, z_nd_arr)
241  : (kpbl + myhalf) * gdata.CellSize(2);
242  const Real theta_v = GetThetav(i, j, kpbl, cell_data, moisture_indices);
243  // FIX: guard theta_v_klo against zero to prevent NaN in Rib
244  const Real theta_v_klo = amrex::max(GetThetav(i, j, klo, cell_data, moisture_indices), Real(1.0));
245  const Real ws2_raw = fourth * ( (uvel(i, j, kpbl) + uvel(i + 1, j, kpbl)) *
246  (uvel(i, j, kpbl) + uvel(i + 1, j, kpbl)) +
247  (vvel(i, j, kpbl) + vvel(i, j + 1, kpbl)) *
248  (vvel(i, j, kpbl) + vvel(i, j + 1, kpbl)) );
249  const Real ws2 = amrex::max(ws2_raw, Real(1.0));
250  Rib = CONST_GRAV * zval * (theta_v - t_layer_v) / (ws2 * theta_v_klo);
251  }
252 
253  Real zval0 = zval, Rib0 = Rib;
254  bool above_critical = false;
255  while (!above_critical && ((kpbl + 1) <= khi)) {
256  zval0 = zval;
257  Rib0 = Rib;
258  kpbl += 1;
259 
260  zval = (use_terrain_fitted_coords)
261  ? Compute_Zrel_AtCellCenter(i, j, kpbl, z_nd_arr)
262  : (kpbl + myhalf) * gdata.CellSize(2);
263  const Real theta_v = GetThetav(i, j, kpbl, cell_data, moisture_indices);
264  // FIX: guard theta_v_klo against zero to prevent NaN in Rib
265  const Real theta_v_klo = amrex::max(GetThetav(i, j, klo, cell_data, moisture_indices), Real(1.0));
266  const Real ws2_raw = fourth * ( (uvel(i, j, kpbl) + uvel(i + 1, j, kpbl)) *
267  (uvel(i, j, kpbl) + uvel(i + 1, j, kpbl)) +
268  (vvel(i, j, kpbl) + vvel(i, j + 1, kpbl)) *
269  (vvel(i, j, kpbl) + vvel(i, j + 1, kpbl)) );
270  const Real ws2 = amrex::max(ws2_raw, Real(1.0));
271  Rib = CONST_GRAV * zval * (theta_v - t_layer_v) / (ws2 * theta_v_klo);
272  above_critical = (Rib >= Ribcr);
273  }
274 
275  const Real pblh_emp = (use_terrain_fitted_coords)
276  ? Compute_Zrel_AtCellCenter(i, j, klo, z_nd_arr)
277  : myhalf * gdata.CellSize(2);
278  const Real z_max = (use_terrain_fitted_coords)
279  ? Compute_Zrel_AtCellCenter(i, j, khi, z_nd_arr)
280  : (khi + myhalf) * gdata.CellSize(2);
281  const Real pblh_max = Real(0.9) * z_max;
282  const Real pblh_min = amrex::max(pblh_emp, Real(10.0));
283 
284  if (above_critical) {
285  // FIX: guard against division by zero when Rib == Rib0
286  Real pblh_interp;
287  const Real rib_diff = Rib - Rib0;
288  if (std::abs(rib_diff) > Real(1.0e-10)) {
289  pblh_interp = zval0 + (zval - zval0) / rib_diff * (Ribcr - Rib0);
290  } else {
291  // If Rib not changing, use zval0 as PBL height
292  pblh_interp = zval0;
293  }
294  pblh_pred_arr(i, j, 0) = amrex::max(amrex::min(pblh_interp, pblh_max), pblh_min);
295  pbli_arr(i, j, 0) = kpbl;
296  } else {
297  pblh_pred_arr(i, j, 0) = pblh_min;
298  pbli_arr(i, j, 0) = klo + 1;
299  }
300  });
301 
302  // FIX: guard against null dereference of q_star when use_moisture is false (dry case)
303  // get_q_star can return null pointer when the field is not allocated
304  amrex::MultiFab* q_star_mf = SurfLayer->get_q_star(level);
305  Array4<Real const> q_star_arr = (q_star_mf != nullptr) ? q_star_mf->const_array(mfi)
306  : Array4<Real const>{};
307 
308  const Real const_b = turbChoice.pbl_mrf_const_b;
309  const Real sf = turbChoice.pbl_mrf_sf;
310  constexpr Real prmin = Real(0.5);
311  constexpr Real prmax = Real(4.0);
312  constexpr Real GAMCRT = Real(3.0);
313  constexpr Real GAMCRQ = Real(2.e-3);
314  const bool enable_mrf_countergradient = turbChoice.enable_mrf_countergradient;
315  const bool enable_mrf_unbounded_vpert = turbChoice.enable_mrf_unbounded_vpert;
316 
317  //
318  // PASS 2 (WSTAR / VPERT): Compute wstar, HGAMT, HGAMQ, VPERT using the
319  // predictor PBL height (pblh_pred_arr). VPERT will be fed into the corrector
320  // Rib search in Pass 3 to raise the effective surface temperature.
321  // WRF reference (module_bl_mrf.F lines 857-880):
322  // https://github.com/wrf-model/WRF/blob/master/phys/module_bl_mrf.F#L857-L880
323  //
324  ParallelFor(xybx, [=,zero_d=zero] AMREX_GPU_DEVICE(int i, int j, int) noexcept
325  {
326  const Real t_layer = t10av_arr(i, j, 0);
327  Real obuk_val = l_obuk_arr(i, j, 0);
328  if (std::abs(obuk_val) < amrex::Real(1.0e-10)) {
329  obuk_val = (obuk_val >= zero_d) ? amrex::Real(1.0e-10) : amrex::Real(-1.0e-10);
330  }
331 
332  // HOL computed from predictor height (WRF uses predictor height for wstar/VPERT)
333  const Real HOL = sf * pblh_pred_arr(i, j, 0) / obuk_val;
334  const Real HOL_bounded = amrex::max(amrex::min(HOL, Real(100.0)), Real(-100.0));
335  const Real one_quarter = Real(0.25);
336  const Real phiM = (obuk_val > 0)
337  ? (1 + 5 * HOL_bounded)
338  : std::pow(amrex::max(1 - 16 * HOL_bounded, Real(0.01)), -one_quarter);
339  const Real phiM_safe = amrex::max(phiM, Real(0.01));
340 
341  // wstar = u* / phi_m
342  // Absolute bounds [0.01, 5.0] m/s: prevent division-by-zero (floor) and
343  // free-convection blow-up (ceiling), independent of u* for u*->0 safety.
344  Real wstar = u_star_arr(i, j, 0) / phiM_safe;
345  wstar = amrex::max(wstar, Real(0.01));
346  wstar = amrex::min(wstar, Real(5.0));
347 
348  bool SFCFLG = (obuk_val <= zero_d);
349  const Real HGAMT = (SFCFLG && enable_mrf_countergradient)
350  ? amrex::min(-const_b * u_star_arr(i, j, 0) * t_star_arr(i, j, 0) / wstar, GAMCRT)
351  : zero_d;
352 
353  Real HGAMQ = Real(0);
354  if (SFCFLG && use_moisture && enable_mrf_countergradient) {
355  const Real q_star = q_star_arr(i, j, 0);
356  const Real HGAMQ_calc = -const_b * u_star_arr(i, j, 0) * q_star / wstar;
357  HGAMQ = amrex::max(amrex::min(HGAMQ_calc, GAMCRQ), Real(0));
358 
359  if (lmask_arr) {
360  bool is_land = (lmask_arr(i,j,0) == 1);
361  if (!is_land) HGAMQ = zero_d;
362  }
363 
364  if (moisture_indices.qv >= 0) {
365  Real qv_klo = cell_data(i, j, klo, moisture_indices.qv) / cell_data(i, j, klo, Rho_comp);
366  Real T_klo = getTgivenRandRTh(cell_data(i, j, klo, Rho_comp),
367  cell_data(i, j, klo, RhoTheta_comp), qv_klo);
368  Real p_klo = getPgivenRTh(cell_data(i, j, klo, RhoTheta_comp), qv_klo) * Real(0.01);
369  Real qsat_klo = zero_d;
370  erf_qsatw(T_klo, p_klo, qsat_klo);
371  Real rh_klo = (qsat_klo > Real(1.0e-10)) ? (qv_klo / qsat_klo) : Real(0);
372  if (rh_klo > Real(0.95)) {
373  Real rh_scaling = amrex::max(zero_d, (Real(1) - rh_klo) / Real(0.05));
374  HGAMQ *= rh_scaling;
375  }
376  }
377  }
378 
379  // Compute VPERT = HGAMT + 0.61*theta*HGAMQ (virtual temperature perturbation)
380  // This will be added to the surface temperature in the corrector Rib search.
381  // WRF Reference: module_bl_mrf.F lines 879-880
382  if (pbli_arr(i, j, 0) <= klo + 1 || !enable_mrf_countergradient) {
383  vpert_arr(i, j, 0) = zero_d;
384  } else {
385  const Real VPERT_raw = HGAMT + epsv * t_layer * HGAMQ;
386  const Real VPERT_capped = enable_mrf_unbounded_vpert
387  ? VPERT_raw
388  : amrex::min(VPERT_raw, GAMCRT);
389  vpert_arr(i, j, 0) = amrex::max(VPERT_capped, zero_d);
390  }
391  });
392 
393  //
394  // PASS 3 (CORRECTOR): Recompute PBL height with VPERT-enhanced surface temperature.
395  // theta_s = theta_va + VPERT (Hong & Pan 1996, Eq. 4)
396  // This is the WRF-consistent corrector pass. pblh_corr_arr is used for the
397  // K-profile shape function K = rho*wstar*kappa*z*(1-z/h)^2.
398  // WRF reference (module_bl_mrf.F lines 932-964):
399  // https://github.com/wrf-model/WRF/blob/master/phys/module_bl_mrf.F#L932-L964
400  //
401  ParallelFor(xybx, [=,one_d=one]
402  AMREX_GPU_DEVICE(int i, int j, int) noexcept
403  {
404  const Real t_layer = t10av_arr(i, j, 0);
405  const Real moisture_fraction = use_moisture ? q10av_arr(i, j, 0) : Real(0);
406  // VPERT-enhanced surface virtual temperature (WRF THERMAL variable)
407  const Real t_layer_v = t_layer * (one + epsv * moisture_fraction)
408  + vpert_arr(i, j, 0);
409 
410  Real obuk_val = l_obuk_arr(i, j, 0);
411  if (std::abs(obuk_val) < amrex::Real(1.0e-10)) {
412  obuk_val = (obuk_val >= zero) ? amrex::Real(1.0e-10) : amrex::Real(-1.0e-10);
413  }
414 
415  int kpbl = klo;
416  Real zval, Rib; // FIX: removed uninitialized zval0, Rib0
417  {
418  zval = (use_terrain_fitted_coords)
419  ? Compute_Zrel_AtCellCenter(i, j, kpbl, z_nd_arr)
420  : (kpbl + myhalf) * gdata.CellSize(2);
421  const Real theta_v = GetThetav(i, j, kpbl, cell_data, moisture_indices);
422  // FIX: guard theta_v_klo against zero to prevent NaN in Rib
423  const Real theta_v_klo = amrex::max(GetThetav(i, j, klo, cell_data, moisture_indices), one_d);
424  const Real ws2_raw = fourth * ( (uvel(i, j, kpbl) + uvel(i + 1, j, kpbl)) *
425  (uvel(i, j, kpbl) + uvel(i + 1, j, kpbl)) +
426  (vvel(i, j, kpbl) + vvel(i, j + 1, kpbl)) *
427  (vvel(i, j, kpbl) + vvel(i, j + 1, kpbl)) );
428  const Real ws2 = amrex::max(ws2_raw, one_d);
429  Rib = CONST_GRAV * zval * (theta_v - t_layer_v) / (ws2 * theta_v_klo);
430  }
431  Real zval0 = zval, Rib0 = Rib; // FIX: initialize here, mirrors Pass 1
432 
433  bool above_critical = false;
434  while (!above_critical && ((kpbl + 1) <= khi)) {
435  zval0 = zval;
436  Rib0 = Rib;
437  kpbl += 1;
438 
439  zval = (use_terrain_fitted_coords)
440  ? Compute_Zrel_AtCellCenter(i, j, kpbl, z_nd_arr)
441  : (kpbl + myhalf) * gdata.CellSize(2);
442  const Real theta_v = GetThetav(i, j, kpbl, cell_data, moisture_indices);
443  // FIX: guard theta_v_klo against zero to prevent NaN in Rib
444  const Real theta_v_klo = amrex::max(GetThetav(i, j, klo, cell_data, moisture_indices), one_d);
445  const Real ws2_raw = fourth * ( (uvel(i, j, kpbl) + uvel(i + 1, j, kpbl)) *
446  (uvel(i, j, kpbl) + uvel(i + 1, j, kpbl)) +
447  (vvel(i, j, kpbl) + vvel(i, j + 1, kpbl)) *
448  (vvel(i, j, kpbl) + vvel(i, j + 1, kpbl)) );
449  const Real ws2 = amrex::max(ws2_raw, one_d);
450  Rib = CONST_GRAV * zval * (theta_v - t_layer_v) / (ws2 * theta_v_klo);
451  above_critical = (Rib >= Ribcr);
452  }
453 
454  const Real pblh_emp = (use_terrain_fitted_coords)
455  ? Compute_Zrel_AtCellCenter(i, j, klo, z_nd_arr)
456  : myhalf * gdata.CellSize(2);
457  const Real z_max = (use_terrain_fitted_coords)
458  ? Compute_Zrel_AtCellCenter(i, j, khi, z_nd_arr)
459  : (khi + myhalf) * gdata.CellSize(2);
460  const Real pblh_max = Real(0.9) * z_max;
461  const Real pblh_min = amrex::max(pblh_emp, Real(10.0));
462 
463  if (above_critical) {
464  // FIX: guard against division by zero when Rib == Rib0
465  Real pblh_interp;
466  const Real rib_diff = Rib - Rib0;
467  if (std::abs(rib_diff) > Real(1.0e-10)) {
468  pblh_interp = zval0 + (zval - zval0) / rib_diff * (Ribcr - Rib0);
469  } else {
470  // If Rib not changing, use zval0 as PBL height
471  pblh_interp = zval0;
472  }
473  pblh_corr_arr(i, j, 0) = amrex::max(amrex::min(pblh_interp, pblh_max), pblh_min);
474  pbli_arr(i, j, 0) = kpbl;
475  } else {
476  pblh_corr_arr(i, j, 0) = pblh_min;
477  pbli_arr(i, j, 0) = klo + 1;
478  }
479  });
480 
481  //
482  // PASS 4 (WSTAR RECOMPUTE): Recompute wstar, HGAMT, HGAMQ using the corrected
483  // PBL height (pblh_corr_arr) to ensure internal consistency between the K-profile
484  // amplitude and the countergradient fluxes. HOL = sf*h/L uses pblh_corr_arr.
485  // WRF reference (module_bl_mrf.F lines 857-880):
486  // https://github.com/wrf-model/WRF/blob/master/phys/module_bl_mrf.F#L857-L880
487  //
488  ParallelFor(xybx, [=] AMREX_GPU_DEVICE(int i, int j, int) noexcept
489  {
490  //const Real t_layer = t10av_arr(i, j, 0);
491  Real obuk_val = l_obuk_arr(i, j, 0);
492  if (std::abs(obuk_val) < amrex::Real(1.0e-10)) {
493  obuk_val = (obuk_val >= Real(0)) ? amrex::Real(1.0e-10) : amrex::Real(-1.0e-10);
494  }
495 
496  // HOL now uses corrected PBLH for full internal consistency
497  const Real HOL = sf * pblh_corr_arr(i, j, 0) / obuk_val;
498  const Real HOL_bounded = amrex::max(amrex::min(HOL, Real(100.0)), Real(-100.0));
499  const Real one_quarter = Real(0.25);
500  const Real phiM = (obuk_val > 0)
501  ? (1 + 5 * HOL_bounded)
502  : std::pow(amrex::max(1 - 16 * HOL_bounded, Real(0.01)), -one_quarter);
503  const Real phiM_safe = amrex::max(phiM, Real(0.01));
504 
505  // Absolute bounds [0.01, 5.0] m/s
506  Real wstar = u_star_arr(i, j, 0) / phiM_safe;
507  wstar = amrex::max(wstar, Real(0.01));
508  wstar = amrex::min(wstar, Real(5.0));
509  wstar_arr(i, j, 0) = wstar;
510 
511  bool SFCFLG = (obuk_val <= Real(0));
512  const Real HGAMT = (SFCFLG && enable_mrf_countergradient)
513  ? amrex::min(-const_b * u_star_arr(i, j, 0) * t_star_arr(i, j, 0) / wstar, GAMCRT)
514  : Real(0);
515 
516  Real HGAMQ = Real(0);
517  if (SFCFLG && use_moisture && enable_mrf_countergradient) {
518  const Real q_star = q_star_arr(i, j, 0);
519  const Real HGAMQ_calc = -const_b * u_star_arr(i, j, 0) * q_star / wstar;
520  HGAMQ = amrex::max(amrex::min(HGAMQ_calc, GAMCRQ), Real(0));
521 
522  if (lmask_arr) {
523  bool is_land = (lmask_arr(i,j,0) == 1);
524  if (!is_land) HGAMQ = Real(0);
525  }
526 
527  if (moisture_indices.qv >= 0) {
528  Real qv_klo = cell_data(i, j, klo, moisture_indices.qv) / cell_data(i, j, klo, Rho_comp);
529  Real T_klo = getTgivenRandRTh(cell_data(i, j, klo, Rho_comp),
530  cell_data(i, j, klo, RhoTheta_comp), qv_klo);
531  Real p_klo = getPgivenRTh(cell_data(i, j, klo, RhoTheta_comp), qv_klo) * Real(0.01);
532  Real qsat_klo = Real(0);
533  erf_qsatw(T_klo, p_klo, qsat_klo);
534  Real rh_klo = (qsat_klo > Real(1.0e-10)) ? (qv_klo / qsat_klo) : Real(0);
535  if (rh_klo > Real(0.95)) {
536  Real rh_scaling = amrex::max(Real(0), (Real(1) - rh_klo) / Real(0.05));
537  HGAMQ *= rh_scaling;
538  }
539  }
540  }
541 
542  if (pbli_arr(i, j, 0) <= klo + 1) {
543  hgamt_arr(i, j, 0) = Real(0);
544  hgamq_arr(i, j, 0) = Real(0);
545  } else {
546  const Real pblh = pblh_corr_arr(i, j, 0);
547  // FIX: guard against division by zero when pblh is extremely small
548  if (pblh > Real(1.0e-10)) {
549  hgamt_arr(i, j, 0) = (enable_mrf_countergradient) ? HGAMT / pblh : Real(0);
550  hgamq_arr(i, j, 0) = (enable_mrf_countergradient && use_moisture) ? HGAMQ / pblh : Real(0);
551  } else {
552  hgamt_arr(i, j, 0) = Real(0);
553  hgamq_arr(i, j, 0) = Real(0);
554  }
555  }
556  });
557 
558  //
559  // PASS 5 (ZERO-RI): Diagnostic PBL height with Ribcr=0, VPERT-enhanced surface temp.
560  // Used optionally (pbl_mrf_use_zero_ri_extent) to extend the nonlocal mixing region.
561  // WRF reference (module_bl_mrf.F lines 932-964):
562  // https://github.com/wrf-model/WRF/blob/master/phys/module_bl_mrf.F#L932-L964
563  //
564  constexpr Real Ribcr_zero = Real(0);
565  ParallelFor(xybx, [=,one_d=one]
566  AMREX_GPU_DEVICE(int i, int j, int) noexcept
567  {
568  const Real t_layer = t10av_arr(i, j, 0);
569  const Real moisture_fraction = use_moisture ? q10av_arr(i, j, 0) : zero;
570  const Real t_layer_v = t_layer * (one + epsv * moisture_fraction);
571  const Real t_layer_v_enhanced = t_layer_v + vpert_arr(i, j, 0);
572 
573  int kpbl_zero = klo;
574  Real zval_zero, Rib_zero;
575  {
576  zval_zero = (use_terrain_fitted_coords)
577  ? Compute_Zrel_AtCellCenter(i, j, kpbl_zero, z_nd_arr)
578  : (kpbl_zero + myhalf) * gdata.CellSize(2);
579  const Real theta_v = GetThetav(i, j, kpbl_zero, cell_data, moisture_indices);
580  // FIX: guard theta_v_klo against zero to prevent NaN in Rib
581  const Real theta_v_klo = amrex::max(GetThetav(i, j, klo, cell_data, moisture_indices), one_d);
582  const Real ws2_raw = fourth * ( (uvel(i, j, kpbl_zero) + uvel(i + 1, j, kpbl_zero)) *
583  (uvel(i, j, kpbl_zero) + uvel(i + 1, j, kpbl_zero)) +
584  (vvel(i, j, kpbl_zero) + vvel(i, j + 1, kpbl_zero)) *
585  (vvel(i, j, kpbl_zero) + vvel(i, j + 1, kpbl_zero)) );
586  const Real ws2 = amrex::max(ws2_raw, one_d);
587  Rib_zero = CONST_GRAV * zval_zero * (theta_v - t_layer_v_enhanced) / (ws2 * theta_v_klo);
588  }
589 
590  bool above_critical_zero = false;
591  while (!above_critical_zero && ((kpbl_zero + 1) <= khi)) {
592  kpbl_zero += 1;
593  zval_zero = (use_terrain_fitted_coords)
594  ? Compute_Zrel_AtCellCenter(i, j, kpbl_zero, z_nd_arr)
595  : (kpbl_zero + myhalf) * gdata.CellSize(2);
596  const Real theta_v = GetThetav(i, j, kpbl_zero, cell_data, moisture_indices);
597  // FIX: guard theta_v_klo against zero to prevent NaN in Rib
598  const Real theta_v_klo = amrex::max(GetThetav(i, j, klo, cell_data, moisture_indices), one_d);
599  const Real ws2_raw = fourth * ( (uvel(i, j, kpbl_zero) + uvel(i + 1, j, kpbl_zero)) *
600  (uvel(i, j, kpbl_zero) + uvel(i + 1, j, kpbl_zero)) +
601  (vvel(i, j, kpbl_zero) + vvel(i, j + 1, kpbl_zero)) *
602  (vvel(i, j, kpbl_zero) + vvel(i, j + 1, kpbl_zero)) );
603  const Real ws2 = amrex::max(ws2_raw, one_d);
604  Rib_zero = CONST_GRAV * zval_zero * (theta_v - t_layer_v_enhanced) / (ws2 * theta_v_klo);
605  above_critical_zero = (Rib_zero >= Ribcr_zero);
606  }
607 
608  if (above_critical_zero) {
609  pbli_zero_arr(i, j, 0) = kpbl_zero;
610  } else {
611  pbli_zero_arr(i, j, 0) = klo + 1;
612  }
613  });
614 
615  // -- Compute diffusion coefficients --
616 
617  const Array4<Real>& K_turb = eddyViscosity.array(mfi);
618 
619  bool c_ext_dir_on_zlo = ((bc_ptr[BCVars::cons_bc].lo(2) == ERFBCType::ext_dir));
620  bool c_ext_dir_on_zhi = ((bc_ptr[BCVars::cons_bc].hi(2) == ERFBCType::ext_dir));
621  bool u_ext_dir_on_zlo = ((bc_ptr[BCVars::xvel_bc].lo(2) == ERFBCType::ext_dir));
622  bool u_ext_dir_on_zhi = ((bc_ptr[BCVars::xvel_bc].hi(2) == ERFBCType::ext_dir));
623  bool v_ext_dir_on_zlo = ((bc_ptr[BCVars::yvel_bc].lo(2) == ERFBCType::ext_dir));
624  bool v_ext_dir_on_zhi = ((bc_ptr[BCVars::yvel_bc].hi(2) == ERFBCType::ext_dir));
625 
626  const auto& dxInv = geom.InvCellSizeArray();
627  const Real dz_inv = geom.InvCellSize(2);
628  const int izmin = geom.Domain().smallEnd(2);
629  const int izmax = geom.Domain().bigEnd(2);
630 
631  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
632  {
633  Real obuk_val = l_obuk_arr(i, j, 0);
634  if (std::abs(obuk_val) < amrex::Real(1.0e-10)) {
635  obuk_val = (obuk_val >= Real(0)) ? amrex::Real(1.0e-10) : amrex::Real(-1.0e-10);
636  }
637 
638  const Real zval = (use_terrain_fitted_coords)
639  ? Compute_Zrel_AtCellCenter(i, j, k, z_nd_arr)
640  : (k + myhalf) * gdata.CellSize(2);
641  const Real rho = cell_data(i, j, k, Rho_comp);
642  // FIX: skip ghost cells with uninitialized (zero) density — prevents
643  // division-by-zero inside GetThetav at lateral ghost cells of gbx
644  if (rho <= Real(0)) {
645  K_turb(i, j, k, EddyDiff::Mom_v) = Real(0);
646  K_turb(i, j, k, EddyDiff::Theta_v) = Real(0);
647  K_turb(i, j, k, EddyDiff::Q_v) = Real(0);
648  K_turb(i, j, k, EddyDiff::HGAMT_v) = Real(0);
649  K_turb(i, j, k, EddyDiff::HGAMQ_v) = Real(0);
650  K_turb(i, j, k, EddyDiff::Turb_lengthscale) = Real(0);
651  return;
652  }
653  const Real met_h_zeta = (use_terrain_fitted_coords)
654  ? Compute_h_zeta_AtCellCenter(i, j, k, dxInv, z_nd_arr) : Real(1);
655  const Real dz_terrain = met_h_zeta / dz_inv;
656 
657  constexpr Real qc_threshold = Real(1.0e-4); // Cloud water/ice threshold (kg/kg)
658  Real qc_mix = Real(0);
659  Real qi_mix = Real(0);
660  if (use_moisture) {
661  if (moisture_indices.qc >= 0) {
662  qc_mix = cell_data(i, j, k, moisture_indices.qc) / rho;
663  }
664  if (moisture_indices.qi >= 0) {
665  qi_mix = cell_data(i, j, k, moisture_indices.qi) / rho;
666  }
667  }
668  const Real total_qcloud = qc_mix + qi_mix;
669  const bool has_cloud = turbChoice.enable_mrf_cloud_adjustment && (total_qcloud > qc_threshold);
670 
671  const int pbli_extent = turbChoice.pbl_mrf_use_zero_ri_extent ? pbli_zero_arr(i, j, 0) : pbli_arr(i, j, 0);
672 
673  if (k < pbli_extent) {
674  bool SFCFLG = (obuk_val <= Real(0));
675 
676  const Real HOL = sf * pblh_corr_arr(i, j, 0) / obuk_val;
677  const Real HOL_bounded = amrex::max(amrex::min(HOL, Real(100.0)), Real(-100.0));
678 
679  const Real one_quarter = Real(0.25);
680  const Real phiM = (obuk_val > 0)
681  ? (1 + 5 * HOL_bounded)
682  : std::pow(amrex::max(1 - 16 * HOL_bounded, Real(0.01)), -one_quarter);
683  const Real phit = (obuk_val > 0)
684  ? (1 + 5 * HOL_bounded)
685  : std::pow(amrex::max(1 - 16 * HOL_bounded, Real(0.01)), -Real(0.5));
686 
687  Real phit_cloud = phit;
688  Real phiM_cloud = phiM;
689  if (has_cloud && obuk_val > Real(0)) {
690  Real reduction_factor = Real(1) - Real(0.15) * amrex::min(total_qcloud / qc_threshold, Real(1));
691  phiM_cloud = Real(1) + Real(5.0) * reduction_factor * sf * pblh_corr_arr(i, j, 0) / obuk_val;
692  phit_cloud = Real(1) + Real(5.0) * reduction_factor * sf * pblh_corr_arr(i, j, 0) / obuk_val;
693  } else if (has_cloud && obuk_val <= Real(0)) {
694  Real cloud_boost = Real(1.0) + Real(0.05) * amrex::min(total_qcloud / qc_threshold, Real(1));
695  phiM_cloud = std::pow(amrex::max(Real(1) - Real(16.0) * HOL_bounded / cloud_boost, Real(0.01)), -one_quarter);
696  phit_cloud = std::pow(amrex::max(Real(1) - Real(16.0) * HOL_bounded / cloud_boost, Real(0.01)), -Real(0.5));
697  }
698 
699  const Real phiM_eff = phiM_cloud;
700  const Real phit_eff = phit_cloud;
701 
702  Real Prt_base = phit_eff / phiM_eff;
703  const Real Prt = amrex::min(amrex::max(Prt_base + const_b * KAPPA * sf, prmin), prmax);
704 
705  const Real wstar = wstar_arr(i, j, 0);
706 
707  if (SFCFLG) {
708  // K-profile: K = rho * wstar * kappa * zrel * (1 - zrel/pblh_rel)^2
709  // WRF Reference: module_bl_mrf.F L976-978
710  const Real z_sfc = (use_terrain_fitted_coords)
711  ? Compute_Zrel_AtCellCenter(i, j, klo, z_nd_arr)
712  : Real(0);
713  const Real zrel = zval - z_sfc;
714  const Real pblh = pblh_corr_arr(i, j, 0);
715  const Real pblh_rel = pblh - z_sfc;
716  const Real zfac = amrex::max(Real(1) - zrel / pblh_rel, Real(1.0e-8));
717 
718  K_turb(i, j, k, EddyDiff::Mom_v) = rho * wstar * KAPPA * zrel * zfac * zfac;
719  K_turb(i, j, k, EddyDiff::Theta_v) = K_turb(i, j, k, EddyDiff::Mom_v) / Prt;
720 
722  Real Prq_base = phit_eff / phiM_eff;
723  const Real Prq = amrex::min(amrex::max(Prq_base + const_b * KAPPA * sf, prmin), prmax);
724  K_turb(i, j, k, EddyDiff::Q_v) = K_turb(i, j, k, EddyDiff::Mom_v) / Prq;
725  } else {
726  K_turb(i, j, k, EddyDiff::Q_v) = K_turb(i, j, k, EddyDiff::Theta_v);
727  }
728  } else {
729  const Real lambda = Real(150.0);
730  const Real lscale = (KAPPA * zval * lambda) / (KAPPA * zval + lambda);
731  Real dthetadz, dudz, dvdz;
732  ComputeVerticalDerivativesPBL(i, j, k, uvel, vvel, cell_data, izmin, izmax, pbl_derivative_dz_inv(i,j,k),
733  c_ext_dir_on_zlo, c_ext_dir_on_zhi, u_ext_dir_on_zlo,
734  u_ext_dir_on_zhi, v_ext_dir_on_zlo, v_ext_dir_on_zhi, dthetadz,
735  dudz, dvdz, moisture_indices);
736 
737  const Real dudz_safe = (k < izmax) ? dudz : Real(0);
738  const Real dvdz_safe = (k < izmax) ? dvdz : Real(0);
739  const Real wind_shear = dudz_safe * dudz_safe + dvdz_safe * dvdz_safe;
740  const Real wind_shear_safe = std::max(wind_shear, Real(1.0e-8));
741 
742  // FIX: guard theta_v against zero/negative in grad_Ri denominator
743  const Real theta_v = amrex::max(GetThetav(i, j, k, cell_data, moisture_indices), Real(1.0));
744  const Real dtheta_v_dz = dthetadz;
745 
746  Real grad_Ri = CONST_GRAV / theta_v * dtheta_v_dz / wind_shear_safe;
747  grad_Ri = std::max(std::min(grad_Ri, Real(100.0)), -Real(100.0));
748 
749  const Real grad_Ri_safe = amrex::max(grad_Ri, -Real(100.0));
750  Real Pr_rich = Real(1) + Real(2.1) * grad_Ri;
751  const Real fm = (grad_Ri_safe > 0)
752  ? Real(1) / ((Real(1) + Real(5.0) * grad_Ri_safe) * (Real(1) + Real(5.0) * grad_Ri_safe))
753  : 1 - 8 * grad_Ri_safe / (1 + Real(1.746) * std::sqrt(amrex::max(-grad_Ri_safe, Real(0))));
754  const Real ft = (grad_Ri_safe > 0)
755  ? Real(1) / ((Real(1) + Real(5.0) * grad_Ri_safe) * (Real(1) + Real(5.0) * grad_Ri_safe))
756  : 1 - 8 * grad_Ri_safe / (1 + Real(1.286) * std::sqrt(amrex::max(-grad_Ri_safe, Real(0))));
757  const Real rl2wsp = rho * lscale * lscale * std::sqrt(wind_shear);
758 
759  Pr_rich = std::max(amrex::Real(0.25), std::min(Pr_rich, Real(4.0)));
760 
761  K_turb(i, j, k, EddyDiff::Mom_v) = rl2wsp * fm;
762  K_turb(i, j, k, EddyDiff::Theta_v) = rl2wsp * ft;
764  K_turb(i, j, k, EddyDiff::Q_v) = rl2wsp * ft;
765  } else {
766  K_turb(i, j, k, EddyDiff::Q_v) = K_turb(i, j, k, EddyDiff::Theta_v);
767  }
768  }
769  } else if (k >= pbli_extent) {
770  const Real lambda = Real(150.0);
771  const Real lscale = (KAPPA * zval * lambda) / (KAPPA * zval + lambda);
772  Real dthetadz, dudz, dvdz;
773  ComputeVerticalDerivativesPBL(i, j, k, uvel, vvel, cell_data, izmin, izmax, pbl_derivative_dz_inv(i,j,k),
774  c_ext_dir_on_zlo, c_ext_dir_on_zhi, u_ext_dir_on_zlo,
775  u_ext_dir_on_zhi, v_ext_dir_on_zlo, v_ext_dir_on_zhi, dthetadz,
776  dudz, dvdz, moisture_indices);
777 
778  const Real dudz_safe = (k < izmax) ? dudz : Real(0);
779  const Real dvdz_safe = (k < izmax) ? dvdz : Real(0);
780  const Real wind_shear = dudz_safe * dudz_safe + dvdz_safe * dvdz_safe;
781  const Real wind_shear_safe = std::max(wind_shear, Real(1.0e-8));
782 
783  // FIX: guard theta_v against zero/negative in grad_Ri denominator
784  const Real theta_v = amrex::max(GetThetav(i, j, k, cell_data, moisture_indices), Real(1.0));
785  const Real dtheta_v_dz = dthetadz;
786 
787  Real grad_Ri = CONST_GRAV / theta_v * dtheta_v_dz / wind_shear_safe;
788  grad_Ri = std::max(std::min(grad_Ri, Real(100.0)), -Real(100.0));
789 
790  const Real grad_Ri_safe = amrex::max(grad_Ri, -Real(100.0));
791  Real Pr = Real(1) + Real(2.1) * grad_Ri;
792  const Real fm = (grad_Ri_safe > 0)
793  ? Real(1) / ((Real(1) + Real(5.0) * grad_Ri_safe) * (Real(1) + Real(5.0) * grad_Ri_safe))
794  : 1 - 8 * grad_Ri_safe / (1 + Real(1.746) * std::sqrt(amrex::max(-grad_Ri_safe, Real(0))));
795  const Real ft = (grad_Ri_safe > 0)
796  ? Real(1) / ((Real(1) + Real(5.0) * grad_Ri_safe) * (Real(1) + Real(5.0) * grad_Ri_safe))
797  : 1 - 8 * grad_Ri_safe / (1 + Real(1.286) * std::sqrt(amrex::max(-grad_Ri_safe, Real(0))));
798  const Real rl2wsp = rho * lscale * lscale * std::sqrt(wind_shear);
799 
800  Pr = std::max(amrex::Real(0.25), std::min(Pr, Real(4.0)));
801 
802  K_turb(i, j, k, EddyDiff::Mom_v) = rl2wsp * fm;
803  K_turb(i, j, k, EddyDiff::Theta_v) = rl2wsp * ft;
805  K_turb(i, j, k, EddyDiff::Q_v) = rl2wsp * ft;
806  } else {
807  K_turb(i, j, k, EddyDiff::Q_v) = K_turb(i, j, k, EddyDiff::Theta_v);
808  }
809  }
810 
811  // Limit diffusion coefficients to physical bounds
812  // Hong & Pan (1996): Kmin=0.1, Kmax=300 m^2/s (module_bl_mrf.F lines 1014-1025)
813  // Hong et al. (2006): Kmin=ckz*dz*rho, Kmax=1000 m^2/s (high-res option)
814  Real rhoKmin, rhoKmax;
816  constexpr Real ckz = Real(0.001);
817  constexpr Real Kmax = Real(1000.0);
818  rhoKmin = ckz * dz_terrain * rho;
819  rhoKmax = rho * Kmax;
820  } else {
821  constexpr Real Kmin = Real(0.1);
822  constexpr Real Kmax = Real(300.0);
823  rhoKmin = rho * Kmin;
824  rhoKmax = rho * Kmax;
825  }
826 
827  K_turb(i, j, k, EddyDiff::Mom_v) = std::max(
828  std::min(K_turb(i, j, k, EddyDiff::Mom_v), rhoKmax), rhoKmin);
829  K_turb(i, j, k, EddyDiff::Theta_v) = std::max(
830  std::min(K_turb(i, j, k, EddyDiff::Theta_v), rhoKmax), rhoKmin);
831  K_turb(i, j, k, EddyDiff::Q_v) = std::max(
832  std::min(K_turb(i, j, k, EddyDiff::Q_v), rhoKmax), rhoKmin);
833  K_turb(i, j, k, EddyDiff::Turb_lengthscale) = pblh_corr_arr(i, j, 0);
834 
835  if (k < pbli_extent) {
836  K_turb(i, j, k, EddyDiff::HGAMT_v) = hgamt_arr(i, j, 0);
837  K_turb(i, j, k, EddyDiff::HGAMQ_v) = hgamq_arr(i, j, 0);
838  } else {
839  K_turb(i, j, k, EddyDiff::HGAMT_v) = Real(0);
840  K_turb(i, j, k, EddyDiff::HGAMQ_v) = Real(0);
841  }
842  });
843 
844  // FOEXTRAP top and bottom ghost cells
845  ParallelFor(xybx, [=] AMREX_GPU_DEVICE(int i, int j, int ) noexcept
846  {
847  K_turb(i, j, klo-1, EddyDiff::Mom_v ) = K_turb(i, j, klo, EddyDiff::Mom_v );
848  K_turb(i, j, klo-1, EddyDiff::Theta_v) = K_turb(i, j, klo, EddyDiff::Theta_v);
849  K_turb(i, j, klo-1, EddyDiff::Q_v ) = K_turb(i, j, klo, EddyDiff::Q_v );
850  K_turb(i, j, klo-1, EddyDiff::HGAMT_v) = K_turb(i, j, klo, EddyDiff::HGAMT_v);
851  K_turb(i, j, klo-1, EddyDiff::HGAMQ_v) = K_turb(i, j, klo, EddyDiff::HGAMQ_v);
852  K_turb(i, j, klo-1, EddyDiff::Turb_lengthscale) = K_turb(i, j, klo, EddyDiff::Turb_lengthscale);
853  K_turb(i, j, khi+1, EddyDiff::Mom_v ) = K_turb(i, j, khi, EddyDiff::Mom_v );
854  K_turb(i, j, khi+1, EddyDiff::Theta_v) = K_turb(i, j, khi, EddyDiff::Theta_v);
855  K_turb(i, j, khi+1, EddyDiff::Q_v ) = K_turb(i, j, khi, EddyDiff::Q_v );
856  K_turb(i, j, khi+1, EddyDiff::HGAMT_v) = K_turb(i, j, khi, EddyDiff::HGAMT_v);
857  K_turb(i, j, khi+1, EddyDiff::HGAMQ_v) = K_turb(i, j, khi, EddyDiff::HGAMQ_v);
858  K_turb(i, j, khi+1, EddyDiff::Turb_lengthscale) = K_turb(i, j, khi, EddyDiff::Turb_lengthscale);
859  });
860  }// mfi
861 }
constexpr amrex::Real epsv
Definition: ERF_Constants.H:53
constexpr amrex::Real KAPPA
Definition: ERF_Constants.H:63
constexpr amrex::Real one
Definition: ERF_Constants.H:9
constexpr amrex::Real fourth
Definition: ERF_Constants.H:14
constexpr amrex::Real zero
Definition: ERF_Constants.H:8
constexpr amrex::Real myhalf
Definition: ERF_Constants.H:13
constexpr amrex::Real CONST_GRAV
Definition: ERF_Constants.H:64
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real getTgivenRandRTh(const amrex::Real rho, const amrex::Real rhotheta, const amrex::Real qv=amrex::Real(0))
Definition: ERF_EOS.H:46
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real getPgivenRTh(const amrex::Real rhotheta, const amrex::Real qv=amrex::Real(0))
Definition: ERF_EOS.H:81
#define Rho_comp
Definition: ERF_IndexDefines.H:36
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:37
amrex::GpuArray< Real, AMREX_SPACEDIM > dxInv
Definition: ERF_InitCustomPertVels_ParticleTests.H:17
const bool use_moisture
Definition: ERF_InitCustomPert_Bomex.H:14
const int khi
Definition: ERF_InitCustomPert_Bubble.H:21
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
rho
Definition: ERF_InitCustomPert_Bubble.H:107
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void erf_qsatw(amrex::Real t, amrex::Real p, amrex::Real &qsatw)
Definition: ERF_MicrophysicsUtils.H:228
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real GetThetav(const int &i, const int &j, const int &k, const amrex::Array4< amrex::Real const > &cell_data, const MoistureComponentIndices &moisture_indices)
Definition: ERF_MoistUtils.H:72
ParallelFor(grown_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_GPU_DEVICE AMREX_FORCE_INLINE void ComputeVerticalDerivativesPBL(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 int izmin, const int izmax, const PBLDerivativeDzInv &dz_inv, const bool c_ext_dir_on_zlo, const bool c_ext_dir_on_zhi, const bool u_ext_dir_on_zlo, const bool u_ext_dir_on_zhi, const bool v_ext_dir_on_zlo, const bool v_ext_dir_on_zhi, amrex::Real &dthetadz, amrex::Real &dudz, amrex::Real &dvdz, const MoistureComponentIndices &moisture_indices)
Definition: ERF_PBLModels.H:254
TurbChoice turbChoice
Definition: ERF_SetupVertDiff.H:6
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_FORCE_INLINE AMREX_GPU_DEVICE amrex::Real Compute_h_zeta_AtCellCenter(const int &i, const int &j, const int &k, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &cellSizeInv, const amrex::Array4< const amrex::Real > &z_nd)
Definition: ERF_TerrainMetrics.H:55
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real Compute_Zrel_AtCellCenter(const int &i, const int &j, const int &k, const amrex::Array4< const amrex::Real > &z_nd)
Definition: ERF_TerrainMetrics.H:389
AMREX_FORCE_INLINE amrex::IntVect TileNoZ()
Definition: ERF_TileNoZ.H:11
@ yvel_bc
Definition: ERF_IndexDefines.H:103
@ cons_bc
Definition: ERF_IndexDefines.H:86
@ xvel_bc
Definition: ERF_IndexDefines.H:102
@ ext_dir
Definition: ERF_IndexDefines.H:248
@ Theta_v
Definition: ERF_IndexDefines.H:211
@ Turb_lengthscale
Definition: ERF_IndexDefines.H:215
@ Q_v
Definition: ERF_IndexDefines.H:214
@ Mom_v
Definition: ERF_IndexDefines.H:210
@ HGAMQ_v
Definition: ERF_IndexDefines.H:217
@ HGAMT_v
Definition: ERF_IndexDefines.H:216
@ xvel
Definition: ERF_IndexDefines.H:176
@ yvel
Definition: ERF_IndexDefines.H:177
int qi
Definition: ERF_DataStruct.H:109
int qv
Definition: ERF_DataStruct.H:107
int qc
Definition: ERF_DataStruct.H:108
Definition: ERF_PBLModels.H:416
bool pbl_mrf_use_zero_ri_extent
Definition: ERF_TurbStruct.H:636
amrex::Real pbl_mrf_const_b
Definition: ERF_TurbStruct.H:629
bool enable_mrf_unbounded_vpert
Definition: ERF_TurbStruct.H:635
amrex::Real pbl_mrf_Ribcr
Definition: ERF_TurbStruct.H:628
amrex::Real pbl_mrf_sf
Definition: ERF_TurbStruct.H:630
bool enable_mrf_countergradient
Definition: ERF_TurbStruct.H:632
bool mrf_moistvars
Definition: ERF_TurbStruct.H:631
bool enable_mrf_cloud_adjustment
Definition: ERF_TurbStruct.H:633
bool pbl_mrf_highres_bounds
Definition: ERF_TurbStruct.H:634

Referenced by ComputeTurbulentViscosity().

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