ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_MakeSources.cpp File Reference
#include <AMReX_MultiFab.H>
#include "ERF_Constants.H"
#include <AMReX_ArrayLim.H>
#include <AMReX_BCRec.H>
#include <AMReX_TableData.H>
#include <AMReX_GpuContainers.H>
#include <ERF_ImmersedForcing.H>
#include <ERF_NumericalDiffusion.H>
#include <ERF_PlaneAverage.H>
#include <ERF_SrcHeaders.H>
#include <ERF_CanopyTurbulence.H>
#include <ERF_TI_slow_headers.H>
#include <ERF_MOSTStress.H>
Include dependency graph for ERF_MakeSources.cpp:

Functions

void make_sources (int level, int, double dt, double time_d, const Vector< MultiFab > &S_data, const MultiFab &S_prim, MultiFab &source, const MultiFab &base_state, const MultiFab *z_phys_cc, const MultiFab &xvel, const MultiFab &yvel, const MultiFab &zvel, const MultiFab *qheating_rates, MultiFab *terrain_blank, const Geometry geom, const SolverChoice &solverChoice, Vector< std::unique_ptr< MultiFab >> &mapfac, const MultiFab *rhotheta_src, const MultiFab *rhoqt_src, const Real *dptr_wbar_sub, const Vector< Real * > d_rayleigh_ptrs_at_lev, const Real *d_sinesq_at_lev, TurbulentPerturbation &turbPert, const Table1D< Real > r_plane_avg, const Table1D< Real > t_plane_avg, const MultiFab *forest_drag, const MultiFab *cons_old, bool is_slow_step, MultiFab *ib_wall_hfx, MultiFab *ib_wall_ktarget)
 

Function Documentation

◆ make_sources()

void make_sources ( int  level,
int  ,
double  dt,
double  time_d,
const Vector< MultiFab > &  S_data,
const MultiFab &  S_prim,
MultiFab &  source,
const MultiFab &  base_state,
const MultiFab *  z_phys_cc,
const MultiFab &  xvel,
const MultiFab &  yvel,
const MultiFab &  zvel,
const MultiFab *  qheating_rates,
MultiFab *  terrain_blank,
const Geometry  geom,
const SolverChoice &  solverChoice,
Vector< std::unique_ptr< MultiFab >> &  mapfac,
const MultiFab *  rhotheta_src,
const MultiFab *  rhoqt_src,
const Real *  dptr_wbar_sub,
const Vector< Real * >  d_rayleigh_ptrs_at_lev,
const Real *  d_sinesq_at_lev,
TurbulentPerturbation &  turbPert,
const Table1D< Real >  r_plane_avg,
const Table1D< Real >  t_plane_avg,
const MultiFab *  forest_drag,
const MultiFab *  cons_old,
bool  is_slow_step,
MultiFab *  ib_wall_hfx,
MultiFab *  ib_wall_ktarget 
)

Function for computing the slow RHS for the evolution equations for the density, potential temperature and momentum.

Parameters
[in]levellevel of resolution
[in]nrkwhich RK stage
[in]dtslow time step
[in]S_datacurrent solution
[in]S_primprimitive variables (i.e. conserved variables divided by density)
[in]sourcesource terms for conserved variables
[in]geomContainer for geometric information
[in]solverChoiceContainer for solver parameters
[in]mapfacmap factors
[in]dptr_rhotheta_srccustom temperature source term
[in]dptr_rhoqt_srccustom moisture source term
[in]dptr_wbar_subsubsidence source term
[in]d_rayleigh_ptrs_at_levVector of {strength of Rayleigh damping, reference value of theta} used to define Rayleigh damping
[in]d_sinesq_at_levsin( (pi/2) (z-z_t)/(damping depth)) at cell centers
[in]forest_dragforest drag field C_d LAD, or null without a forest
[in]cons_oldconserved state at the start of the step, read by the canopy TKE source
69 {
70  BL_PROFILE_REGION("erf_make_sources()");
71 
72  Real time = static_cast<Real>(time_d);
73 
74  // *****************************************************************************
75  // Initialize source to zero since we re-compute it every RK stage
76  // *****************************************************************************
77  if (is_slow_step) {
78  source.setVal(0.);
79  } else {
80  source.setVal(0.0,Rho_comp,2);
81  }
82 
83  const bool l_use_ndiff = solverChoice.use_num_diff;
84 
85  TurbChoice tc = solverChoice.turbChoice[level];
86  const bool l_use_KE = tc.use_tke;
87  const bool l_diff_KE = tc.diffuse_tke_3D;
88 
89  const Box& domain = geom.Domain();
90 
91  const GpuArray<Real, AMREX_SPACEDIM> dxInv = geom.InvCellSizeArray();
92  const GpuArray<Real, AMREX_SPACEDIM> dx = geom.CellSizeArray();
93 
94  MultiFab r_hse (base_state, make_alias, BaseState::r0_comp , 1);
95  MultiFab th_hse (base_state, make_alias, BaseState::th0_comp , 1);
96  MultiFab qv_hse (base_state, make_alias, BaseState::qv0_comp , 1);
97 
98  Real* thetabar = d_rayleigh_ptrs_at_lev[Rayleigh::thetabar];
99 
100  // flags to apply certain source terms in substep call only
101  bool use_Rayleigh_fast = ( (solverChoice.dampingChoice.rayleigh_damping_type == RayleighDampingType::FastExplicit) ||
103  bool use_ImmersedForcing_fast = solverChoice.immersed_forcing_substep;
104 
105  // flag for a moisture model
106  bool has_moisture = (solverChoice.moisture_type != MoistureType::None);
107 
108  // Canopy terms of the k-eqn RANS model (erf.forest_canopy_tke); check_config
109  // has already required a forest and kEqn on every level
110  const bool use_canopy_tke = is_slow_step && solverChoice.canopyTurb.enabled;
111  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(!use_canopy_tke || (forest_drag && cons_old),
112  "erf.forest_canopy_tke needs the forest drag field and the old state in make_sources");
113 
114  // *****************************************************************************
115  // Planar averages for subsidence terms and immersed forcing
116  // *****************************************************************************
117  Table1D<Real> dptr_r_plane, dptr_t_plane, dptr_qv_plane, dptr_qc_plane;
118  Table1D<Real> dptr_r_plane_if, dptr_t_plane_if; // Separate tables for immersed forcing
119  TableData<Real, 1> r_plane_tab, t_plane_tab, qv_plane_tab, qc_plane_tab;
120 
121  bool use_immersed_forcing = (solverChoice.terrain_type == TerrainType::ImmersedForcing ||
122  solverChoice.buildings_type == BuildingsType::ImmersedForcing);
123 
124  // Use passed-in planar averages for immersed forcing (computed once in ERF_AdvanceDycore.cpp)
125  AMREX_ALWAYS_ASSERT(!use_immersed_forcing || (r_plane_avg && t_plane_avg));
126  if (use_immersed_forcing && r_plane_avg && t_plane_avg) {
127  // Use pre-computed values from persistent storage (already in Table1D format)
128  dptr_r_plane_if = r_plane_avg;
129  dptr_t_plane_if = t_plane_avg;
130  }
131 
132  // Compute planar averages if needed for subsidence
133  bool compute_averages = (is_slow_step && dptr_wbar_sub);
134 
135  if (compute_averages)
136  {
137  // The plane averaging operates at fixed k-index (not fixed height), which is
138  // acceptable for immersed forcing but not correct for subsidence with variable dz
139  AMREX_ALWAYS_ASSERT(solverChoice.mesh_type != MeshType::VariableDz);
140 
141  //
142  // The call to "compute_averages" currently does all the components in one call
143  // We can then extract each component separately with the "line_average" call
144  //
145  // We need just one ghost cell in the vertical
146  //
147  IntVect ng_c(S_data[IntVars::cons].nGrowVect()); ng_c[2] = 1;
148  //
149  // With no moisture we only (rho) and (rho theta); with moisture we also do qv and qc
150  // We use the alias here to control ncomp inside the PlaneAverage
151  //
152  int ncomp = (!has_moisture) ? 2 : RhoQ2_comp+1;
153  MultiFab cons(S_data[IntVars::cons], make_alias, 0, ncomp);
154 
155  PlaneAverage cons_ave(&cons, geom, solverChoice.ave_plane, ng_c);
156  cons_ave.compute_averages(ZDir(), cons_ave.field());
157 
158  int ncell = cons_ave.ncell_line();
159 
160  Gpu::HostVector< Real> r_plane_h(ncell);
161  Gpu::DeviceVector< Real> r_plane_d(ncell);
162 
163  Gpu::HostVector< Real> t_plane_h(ncell);
164  Gpu::DeviceVector< Real> t_plane_d(ncell);
165 
166  cons_ave.line_average(Rho_comp , r_plane_h);
167  cons_ave.line_average(RhoTheta_comp, t_plane_h);
168 
169  Gpu::copyAsync(Gpu::hostToDevice, r_plane_h.begin(), r_plane_h.end(), r_plane_d.begin());
170  Gpu::copyAsync(Gpu::hostToDevice, t_plane_h.begin(), t_plane_h.end(), t_plane_d.begin());
171 
172  Real* dptr_r = r_plane_d.data();
173  Real* dptr_t = t_plane_d.data();
174 
175  Box tdomain = domain; tdomain.grow(2,ng_c[2]);
176  r_plane_tab.resize({tdomain.smallEnd(2)}, {tdomain.bigEnd(2)});
177  t_plane_tab.resize({tdomain.smallEnd(2)}, {tdomain.bigEnd(2)});
178 
179  int offset = ng_c[2];
180 
181  dptr_r_plane = r_plane_tab.table();
182  dptr_t_plane = t_plane_tab.table();
183  ParallelFor(ncell, [=] AMREX_GPU_DEVICE (int k) noexcept
184  {
185  dptr_r_plane(k-offset) = dptr_r[k];
186  dptr_t_plane(k-offset) = dptr_t[k];
187  });
188 
189  if (has_moisture)
190  {
191  Gpu::HostVector< Real> qv_plane_h(ncell), qc_plane_h(ncell);
192  Gpu::DeviceVector<Real> qv_plane_d(ncell), qc_plane_d(ncell);
193 
194  // Water vapor
195  cons_ave.line_average(RhoQ1_comp, qv_plane_h);
196  Gpu::copyAsync(Gpu::hostToDevice, qv_plane_h.begin(), qv_plane_h.end(), qv_plane_d.begin());
197 
198  // Cloud water
199  cons_ave.line_average(RhoQ2_comp, qc_plane_h);
200  Gpu::copyAsync(Gpu::hostToDevice, qc_plane_h.begin(), qc_plane_h.end(), qc_plane_d.begin());
201 
202  Real* dptr_qv = qv_plane_d.data();
203  Real* dptr_qc = qc_plane_d.data();
204 
205  qv_plane_tab.resize({tdomain.smallEnd(2)}, {tdomain.bigEnd(2)});
206  qc_plane_tab.resize({tdomain.smallEnd(2)}, {tdomain.bigEnd(2)});
207 
208  dptr_qv_plane = qv_plane_tab.table();
209  dptr_qc_plane = qc_plane_tab.table();
210  ParallelFor(ncell, [=] AMREX_GPU_DEVICE (int k) noexcept
211  {
212  dptr_qv_plane(k-offset) = dptr_qv[k];
213  dptr_qc_plane(k-offset) = dptr_qc[k];
214  });
215  }
216  }
217 
218  // *****************************************************************************
219  // Vertical extent used by the four stream radiation approximation
220  // *****************************************************************************
221  // NOTE: The fluxes live on w-faces
222  //
223  // NOTE: There is deliberately no scratch storage for the Q integral or the
224  // radiative flux here. Those used to be two nk-length device vectors
225  // shared by every (i,j) thread of the 2-D ParallelFor below, so all the
226  // columns raced on the same buffers (issue 3714). The kernel now keeps
227  // the running integral and the lower-face flux in thread-private scalars.
228  int klo = domain.smallEnd(2);
229  int khi = domain.bigEnd(2);
230 
231  // *****************************************************************************
232  // Define source term for cell-centered conserved variables, from
233  // 1. user-defined source terms for (rho theta) and (rho q_t)
234  // 2. radiation for (rho theta)
235  // 3. Rayleigh damping for (rho theta)
236  // 4. custom forcing for (rho theta) and (rho Q1)
237  // 5. custom subsidence for (rho theta) and (rho Q1)
238  // 6. numerical diffusion for (rho theta)
239  // 7. sponging
240  // 8. turbulent perturbation
241  // 9. nudging towards input sounding values (only for theta)
242  // 9b. canopy wake production and short-circuit dissipation for (rho KE)
243  // 10a. Immersed forcing for terrain
244  // 10b. Immersed forcing for buildings
245  // Real(11.) Four stream radiation source for (rho theta)
246  // *****************************************************************************
247 
248  // ***********************************************************************************************
249  // Add remaining source terms
250  // ***********************************************************************************************
251 #ifdef _OPENMP
252 #pragma omp parallel if (Gpu::notInLaunchRegion())
253 #endif
254  {
255  for ( MFIter mfi(S_data[IntVars::cons],TileNoZ()); mfi.isValid(); ++mfi)
256  {
257  Box bx = mfi.tilebox();
258 
259  const Array4<const Real>& cell_data = S_data[IntVars::cons].array(mfi);
260  const Array4<const Real>& cell_prim = S_prim.array(mfi);
261  const Array4<Real> & cell_src = source.array(mfi);
262 
263  const Array4<const Real>& r0 = r_hse.const_array(mfi);
264  const Array4<const Real>& th0 = th_hse.const_array(mfi);
265  const Array4<const Real>& qv0 = qv_hse.const_array(mfi);
266 
267  const Array4<const Real>& z_cc_arr = z_phys_cc->const_array(mfi);
268 
269  const Array4<const Real>& t_blank_arr = (terrain_blank) ? terrain_blank->const_array(mfi) :
270  Array4<const Real>{};
271 
272 
273 
274  // *************************************************************************************
275  // 2. Add radiation source terms to (rho theta)
276  // *************************************************************************************
277  // Every erf.radiation_model (RRTMGP, Simple, TwoStream) writes the
278  // same 2-component (SW, LW) qheating_rates MultiFab (see
279  // Source/ERF_MakeNewArrays.cpp and
280  // Source/Radiation/TwoStream/ERF_TwoStreamRadiation.cpp), so the
281  // injection formula is the same either way. The nullptr check is
282  // defensive: qheating_rates is only allocated when at least one
283  // solver is active, and this can be reached before that allocation
284  // during early init.
285  //
286  // Temporal consistency is guaranteed by where advance_radiation() sits
287  // and by the is_slow_step gating:
288  // 1. qheating_rates[lev] contains heating rates computed from the old
289  // state (t^n) at the beginning of the slow step (called in
290  // ERF::Advance before dycore, see ERF_AdvanceRadiation.cpp).
291  // 2. This source term is ONLY added when is_slow_step==true, ensuring
292  // it is computed once per slow step and NOT repeated in fast
293  // substeps.
294  // 3. The resulting radiative tendency is consistent with the old-state
295  // atmosphere throughout all fast substeps of the current slow step,
296  // providing a single radiative "kick" per slow step.
297  // 4. No adaptation or re-evaluation of radiation occurs within a slow
298  // step; the heating field is frozen at the beginning of the slow
299  // step and applies uniformly to all fast substeps.
300  if (solverChoice.rad_type != RadiationType::None &&
301  is_slow_step && qheating_rates != nullptr) {
302  auto const& qheating_arr = qheating_rates->const_array(mfi);
303  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
304  {
305  // Short-wavelength and long-wavelength radiation source terms
306  // Computed once per slow step from the old state (t^n)
307  cell_src(i,j,k,RhoTheta_comp) += cell_data(i,j,k,Rho_comp) * ( qheating_arr(i,j,k,0) + qheating_arr(i,j,k,1) );
308  });
309  }
310 
311  // *************************************************************************************
312  // 3. Add Rayleigh damping for (rho theta)
313  // *************************************************************************************
314  Real dampcoef = solverChoice.dampingChoice.rayleigh_dampcoef;
315 
316  if (solverChoice.dampingChoice.rayleigh_damp_T) {
317  if ((is_slow_step && !use_Rayleigh_fast) || (!is_slow_step && use_Rayleigh_fast)) {
318  int n = RhoTheta_comp;
319  int nr = Rho_comp;
320  int np = PrimTheta_comp;
321 
322  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
323  {
324  Real theta = cell_prim(i,j,k,np);
325  Real sinesq = d_sinesq_at_lev[k];
326  cell_src(i, j, k, n) -= dampcoef*sinesq * (theta - thetabar[k]) * cell_data(i,j,k,nr);
327  });
328  }
329  }
330 
331  // *************************************************************************************
332  // 4. Add custom forcing for (rho theta)
333  // *************************************************************************************
334  if (solverChoice.custom_rhotheta_forcing && is_slow_step) {
335  const int n = RhoTheta_comp;
336  auto const& rhotheta_src_arr = rhotheta_src->const_array(mfi);
337  if (solverChoice.spatial_rhotheta_forcing)
338  {
339  if (solverChoice.custom_forcing_prim_vars) {
340  const int nr = Rho_comp;
341  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
342  {
343  cell_src(i, j, k, n) += cell_data(i,j,k,nr) * rhotheta_src_arr(i, j, k);
344  });
345  } else {
346  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
347  {
348  cell_src(i, j, k, n) += rhotheta_src_arr(i, j, k);
349  });
350  }
351  } else {
352  if (solverChoice.custom_forcing_prim_vars) {
353  const int nr = Rho_comp;
354  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
355  {
356  cell_src(i, j, k, n) += cell_data(i,j,k,nr) * rhotheta_src_arr(0, 0, k);
357  });
358  } else {
359  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
360  {
361  cell_src(i, j, k, n) += rhotheta_src_arr(0, 0, k);
362  });
363  }
364  }
365  }
366 
367  // *************************************************************************************
368  // 4. Add custom forcing for RhoQ1
369  // *************************************************************************************
370  if (solverChoice.custom_moisture_forcing && is_slow_step) {
371  const int n = RhoQ1_comp;
372  auto const& rhoqt_src_arr = rhoqt_src->const_array(mfi);
373  if (solverChoice.spatial_moisture_forcing)
374  {
375  if (solverChoice.custom_forcing_prim_vars) {
376  const int nr = Rho_comp;
377  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
378  {
379  cell_src(i, j, k, n) += cell_data(i,j,k,nr) * rhoqt_src_arr(i, j, k);
380  });
381  } else {
382  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
383  {
384  cell_src(i, j, k, n) += rhoqt_src_arr(i, j, k);
385  });
386  }
387  } else {
388  if (solverChoice.custom_forcing_prim_vars) {
389  const int nr = Rho_comp;
390  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
391  {
392  cell_src(i, j, k, n) += cell_data(i,j,k,nr) * rhoqt_src_arr(0, 0, k);
393  });
394  } else {
395  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
396  {
397  cell_src(i, j, k, n) += rhoqt_src_arr(0, 0, k);
398  });
399  }
400  }
401  }
402 
403  // *************************************************************************************
404  // 5. Add custom subsidence for (rho theta)
405  // *************************************************************************************
406  if (solverChoice.custom_w_subsidence && is_slow_step && solverChoice.do_theta_advection) {
407  const int n = RhoTheta_comp;
408  // -w dtheta/dz is a tendency of theta whichever way the custom forcing is
409  // specified, so it is weighted by the cell density to give one of rho*theta
410  const int nr = Rho_comp;
411  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
412  {
413  Real dzInv = (z_cc_arr) ? one/ (z_cc_arr(i,j,k+1) - z_cc_arr(i,j,k-1)) : myhalf*dxInv[2];
414  Real T_hi = dptr_t_plane(k+1) / dptr_r_plane(k+1);
415  Real T_lo = dptr_t_plane(k-1) / dptr_r_plane(k-1);
416  Real wbar_cc = myhalf * (dptr_wbar_sub[k] + dptr_wbar_sub[k+1]);
417  cell_src(i, j, k, n) -= cell_data(i,j,k,nr) * wbar_cc * (T_hi - T_lo) * dzInv;
418  });
419  }
420 
421  // *************************************************************************************
422  // 5. Add custom subsidence for RhoQ1 and RhoQ2
423  // *************************************************************************************
424  if (solverChoice.custom_w_subsidence && (solverChoice.moisture_type != MoistureType::None) && is_slow_step) {
425  const int nv = RhoQ1_comp;
426  // -w dq/dz is a tendency of the mixing ratios whichever way the custom forcing is
427  // specified, so it is weighted by the cell density to give one of rho*q
428  const int nr = Rho_comp;
429  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
430  {
431  Real dzInv = (z_cc_arr) ? one/ (z_cc_arr(i,j,k+1) - z_cc_arr(i,j,k-1)) : myhalf*dxInv[2];
432  Real Qv_hi = dptr_qv_plane(k+1) / dptr_r_plane(k+1);
433  Real Qv_lo = dptr_qv_plane(k-1) / dptr_r_plane(k-1);
434  Real Qc_hi = dptr_qc_plane(k+1) / dptr_r_plane(k+1);
435  Real Qc_lo = dptr_qc_plane(k-1) / dptr_r_plane(k-1);
436  Real wbar_cc = myhalf * (dptr_wbar_sub[k] + dptr_wbar_sub[k+1]);
437  cell_src(i, j, k, nv ) -= cell_data(i,j,k,nr) * wbar_cc * (Qv_hi - Qv_lo) * dzInv;
438  cell_src(i, j, k, nv+1) -= cell_data(i,j,k,nr) * wbar_cc * (Qc_hi - Qc_lo) * dzInv;
439  });
440  }
441 
442  // *************************************************************************************
443  // 6. Add numerical diffusion for rho and (rho theta)
444  // *************************************************************************************
445  if (l_use_ndiff && is_slow_step)
446  {
447  const Array4<const Real>& mf_mx = mapfac[MapFacType::m_x]->const_array(mfi);
448  const Array4<const Real>& mf_my = mapfac[MapFacType::m_y]->const_array(mfi);
449 
450  // Rho is a special case
451  NumericalDiffusion_Scal(bx, 0, 1, dt, solverChoice.num_diff_coeff,
452  cell_data, cell_data, cell_src, mf_mx, mf_my);
453 
454  // Other scalars proceed as normal
455  NumericalDiffusion_Scal(bx, 1, 1, dt, solverChoice.num_diff_coeff,
456  cell_prim, cell_data, cell_src, mf_mx, mf_my);
457 
458 
459  if (l_use_KE && l_diff_KE) {
460  NumericalDiffusion_Scal(bx, RhoKE_comp, 1, dt, solverChoice.num_diff_coeff,
461  cell_prim, cell_data, cell_src, mf_mx, mf_my);
462  }
463 
465  cell_prim, cell_data, cell_src, mf_mx, mf_my);
466  }
467 
468  // *************************************************************************************
469  // 7. Add sponging
470  // *************************************************************************************
471  if ( (solverChoice.spongeChoice.sponge_type != SpongeType::None) && is_slow_step) {
472  const int n_qstate = S_data[IntVars::cons].nComp() - (NDRY + NSCALARS);
473  ApplySpongeZoneBCsForCC(solverChoice.spongeChoice, geom, bx, cell_src, cell_data, r0, th0, qv0, z_cc_arr, n_qstate);
474  }
475 
476  // *************************************************************************************
477  // 8. Add perturbation
478  // *************************************************************************************
479  if (solverChoice.use_source_perturbation(level) && is_slow_step) {
480  auto m_ixtype = S_data[IntVars::cons].boxArray().ixType(); // Conserved term
481  const amrex::Array4<const amrex::Real>& pert_cell = turbPert.pb_cell[level].const_array(mfi);
482  turbPert.apply_tpi(level, bx, RhoTheta_comp, m_ixtype, cell_src, pert_cell); // Applied as source term
483  }
484 
485  // *************************************************************************************
486  // 9b. Add the canopy wake production and short-circuit dissipation to (rho KE)
487  // *************************************************************************************
488  if (use_canopy_tke) {
489  AddCanopyTKESources(bx, cell_src, cell_data, cons_old->const_array(mfi),
490  xvel.const_array(mfi), yvel.const_array(mfi), zvel.const_array(mfi),
491  forest_drag->const_array(mfi), t_blank_arr,
492  solverChoice.canopyTurb, static_cast<Real>(dt));
493  }
494 
495  // *************************************************************************************
496  // 10a. Add immersed source terms for terrain
497  // *************************************************************************************
498  if (solverChoice.terrain_type == TerrainType::ImmersedForcing &&
499  ((is_slow_step && !use_ImmersedForcing_fast) || (!is_slow_step && use_ImmersedForcing_fast))) {
500 
501  const Array4<const Real>& u = xvel.array(mfi);
502  const Array4<const Real>& v = yvel.array(mfi);
503 
504  AMREX_ALWAYS_ASSERT(dptr_r_plane_if && dptr_t_plane_if); // Tables must be filled before immersed forcing
505  if (solverChoice.if_fraction_stress) {
506  const Array4<Real> wall_hfx = (ib_wall_hfx) ? ib_wall_hfx->array(mfi) : Array4<Real>{};
507  const Array4<Real> wall_kt = (ib_wall_ktarget) ? ib_wall_ktarget->array(mfi) : Array4<Real>{};
508  const auto& tc_lev = solverChoice.turbChoice[level];
509  const bool wall_tke = solverChoice.if_wall_tke && (tc_lev.rans_type == RANSType::kEqn);
510  ImmersedForcingTerrain_Scalar_FractionStress(bx, u, v, cell_data, t_blank_arr, cell_src, wall_hfx, wall_kt,
511  geom, solverChoice, dptr_r_plane_if, dptr_t_plane_if,
512  static_cast<Real>(dt), static_cast<Real>(time),
513  wall_tke, tc_lev.Cmu0);
514  } else {
515  ImmersedForcingTerrain_Scalar(bx, u, v, cell_data, t_blank_arr, z_cc_arr,
516  cell_src, geom, solverChoice, dptr_r_plane_if, dptr_t_plane_if, time);
517  }
518  }
519 
520  // *************************************************************************************
521  // 10b. Add immersed source terms for buildings
522  // *************************************************************************************
523  if (solverChoice.use_building_immersed_forcing(geom) &&
524  ((is_slow_step && !use_ImmersedForcing_fast) || (!is_slow_step && use_ImmersedForcing_fast))) {
525 
526  const Array4<const Real>& u = xvel.array(mfi);
527  const Array4<const Real>& v = yvel.array(mfi);
528  const Array4<const Real>& w = zvel.array(mfi);
529 
530  AMREX_ALWAYS_ASSERT(dptr_r_plane_if && dptr_t_plane_if); // Tables must be filled before immersed forcing
531  ImmersedForcingBuildings_Scalar(bx, u, v, w, cell_data, t_blank_arr, z_cc_arr,
532  cell_src, geom, solverChoice, dptr_r_plane_if, dptr_t_plane_if, time);
533  }
534 
535  // *************************************************************************************
536  // Real(11.) Add 4 stream radiation src to RhoTheta
537  // *************************************************************************************
538  if (solverChoice.four_stream_radiation && has_moisture && is_slow_step)
539  {
540  AMREX_ALWAYS_ASSERT((bx.smallEnd(2) == klo) && (bx.bigEnd(2) == khi));
541  Real D = Real(3.75e-6); // [s^-1]
542  Real F0 = 70; // [W/m^2]
543  Real F1 = 22; // [W/m^2]
544  Real krad = 85; // [m^2 kg^-1]
545  Real qt_i = Real(0.008);
546 
547  Box xybx = makeSlab(bx,2,klo);
548  ParallelFor(xybx, [=]
549  AMREX_GPU_DEVICE(int i, int j, int /*k*/) noexcept
550  {
551  // Pass 1: total Q integral through the column (also find "i" values).
552  // NOTE: the running integral is kept in a thread-private scalar rather
553  // than a shared nk-length buffer, and is simply re-formed in the
554  // second pass below. The accumulation order is unchanged, so the
555  // values are identical to the old per-level array.
556  Real q_int_inf = zero;
557  // Height of the ground (the bottom face). zi is an absolute height; the
558  // zi factor in the flux below is the inversion's height above the ground.
559  Real z_sfc = myhalf * (z_cc_arr(i,j,klo) + z_cc_arr(i,j,klo-1));
560  Real zi = myhalf * (z_cc_arr(i,j,khi) + z_cc_arr(i,j,khi-1));
561  Real rhoi = myhalf * (cell_data(i,j,khi,Rho_comp) + cell_data(i,j,khi-1,Rho_comp));
562  for (int k(klo+1); k<=khi+1; ++k) {
563  // Average to w-faces when looping w-faces
564  Real dz = (z_cc_arr) ? myhalf * (z_cc_arr(i,j,k) - z_cc_arr(i,j,k-2)) : dx[2];
565  q_int_inf += krad * cell_data(i,j,k-1,Rho_comp) * cell_data(i,j,k-1,RhoQ2_comp) * dz;
566  Real qt_hi = cell_data(i,j,k ,RhoQ1_comp) + cell_data(i,j,k ,RhoQ2_comp);
567  Real qt_lo = cell_data(i,j,k-1,RhoQ1_comp) + cell_data(i,j,k-1,RhoQ2_comp);
568  if ( (qt_lo > qt_i) && (qt_hi < qt_i) ) {
569  zi = myhalf * (z_cc_arr(i,j,k) + z_cc_arr(i,j,k-1));
570  rhoi = myhalf * (cell_data(i,j,k,Rho_comp) + cell_data(i,j,k-1,Rho_comp));
571  }
572  }
573 
574  // Pass 2: re-form the integral level by level, decompose it into the
575  // w-face flux, and difference the flux to get the heating. The flux on
576  // the lower face is carried across iterations so each face is evaluated
577  // once, exactly as when both faces were read from the shared array.
578  Real q_int = zero; // face klo
579  Real z_lo = z_sfc;
580  Real flux_lo = F1*std::exp(-q_int) + F0*std::exp(-(q_int_inf - q_int));
581  if (z_lo > zi) {
582  flux_lo += rhoi * Cp_d * D * ( std::pow(z_lo-zi,Real(4.)/three)/Real(4.) + (zi-z_sfc)*std::pow(z_lo-zi,one/three) ) ;
583  }
584 
585  for (int k(klo); k<=khi; ++k) {
586  // Advance the integral from face k to face k+1
587  Real dz = (z_cc_arr) ? myhalf * (z_cc_arr(i,j,k+1) - z_cc_arr(i,j,k-1)) : dx[2];
588  q_int += krad * cell_data(i,j,k,Rho_comp) * cell_data(i,j,k,RhoQ2_comp) * dz;
589 
590  Real z_hi = myhalf * (z_cc_arr(i,j,k+1) + z_cc_arr(i,j,k));
591  Real flux_hi = F1*std::exp(-q_int) + F0*std::exp(-(q_int_inf - q_int));
592  if (z_hi > zi) {
593  flux_hi += rhoi * Cp_d * D * ( std::pow(z_hi-zi,Real(4.)/three)/Real(4.) + (zi-z_sfc)*std::pow(z_hi-zi,one/three) ) ;
594  }
595 
596  // Average to w-faces when looping CC
597  Real dzInv = (z_cc_arr) ? one/ (myhalf * (z_cc_arr(i,j,k+1) - z_cc_arr(i,j,k-1))) : dxInv[2];
598  // NOTE: Fnet = Up - Dn (all fluxes are up here)
599  // dT/dt = dF/dz * (1/(-rho*Cp))
600  Real dTdt = (flux_hi - flux_lo) * dzInv / (-cell_data(i,j,k,Rho_comp)*Cp_d);
601  Real qv = cell_data(i,j,k,RhoQ1_comp)/cell_data(i,j,k,Rho_comp);
602  Real iexner = one/getExnergivenRTh(cell_data(i,j,k,RhoTheta_comp), RdoCp, qv);
603  // Convert dT/dt to dTheta/dt and multiply rho
604  cell_src(i,j,k,RhoTheta_comp) += cell_data(i,j,k,Rho_comp) * dTdt * iexner;
605 
606  flux_lo = flux_hi;
607  }
608  });
609  }
610  } // mfi
611  } // OMP
612 }
void ApplySpongeZoneBCsForCC(const SpongeChoice &spongeChoice, const Geometry geom, const Box &bx, const Array4< Real > &cell_rhs, const Array4< const Real > &cell_data, const Array4< const Real > &r0, const Array4< const Real > &th0, const Array4< const Real > &qv0, const Array4< const Real > &z_phys_cc, int n_qstate)
Apply sponge zone damping to cell-centered state variables.
Definition: ERF_ApplySpongeZoneBCs.cpp:21
AMREX_INLINE void AddCanopyTKESources(const amrex::Box &bx, const amrex::Array4< amrex::Real > &cell_src, const amrex::Array4< const amrex::Real > &cell_data, const amrex::Array4< const amrex::Real > &cell_old, const amrex::Array4< const amrex::Real > &u, const amrex::Array4< const amrex::Real > &v, const amrex::Array4< const amrex::Real > &w, const amrex::Array4< const amrex::Real > &f_arr, const amrex::Array4< const amrex::Real > &t_blank_arr, const CanopyTurbulence &canopy, amrex::Real dt)
Definition: ERF_CanopyTurbulence.H:128
constexpr amrex::Real Cp_d
Definition: ERF_Constants.H:51
constexpr amrex::Real RdoCp
Definition: ERF_Constants.H:57
@ thetabar
Definition: ERF_DataStruct.H:207
@ m_y
Definition: ERF_DataStruct.H:39
@ m_x
Definition: ERF_DataStruct.H:38
DirectionSelector< 2 > ZDir
Definition: ERF_DirectionSelector.H:55
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real getExnergivenRTh(const amrex::Real rhotheta, const amrex::Real rdOcp, const amrex::Real qv=amrex::Real(0))
Definition: ERF_EOS.H:156
void ImmersedForcingTerrain_Scalar(const Box &bx, const Array4< const Real > &u, const Array4< const Real > &v, const Array4< const Real > &cell_data, const Array4< const Real > &t_blank_arr, const Array4< const Real > &z_cc_arr, const Array4< Real > &cell_src, const Geometry &geom, const SolverChoice &solverChoice, const Table1D< Real > &r_avg, const Table1D< Real > &t_avg, const Real time)
Definition: ERF_ImmersedForcing.cpp:1217
void ImmersedForcingTerrain_Scalar_FractionStress(const Box &bx, const Array4< const Real > &u, const Array4< const Real > &v, const Array4< const Real > &cell_data, const Array4< const Real > &t_blank_arr, const Array4< Real > &cell_src, const Array4< Real > &wall_hfx, const Array4< Real > &wall_ktarget, const Geometry &geom, const SolverChoice &solverChoice, const Table1D< Real > &r_avg, const Table1D< Real > &t_avg, const Real dt, const Real time, const bool wall_tke, const Real Cmu0)
Definition: ERF_ImmersedForcing.cpp:497
void ImmersedForcingBuildings_Scalar(const Box &bx, const Array4< const Real > &u, const Array4< const Real > &v, const Array4< const Real > &w, const Array4< const Real > &cell_data, const Array4< const Real > &t_blank_arr, const Array4< const Real > &z_cc_arr, const Array4< Real > &cell_src, const Geometry &geom, const SolverChoice &solverChoice, const Table1D< Real > &r_avg, const Table1D< Real > &t_avg, const Real time)
Definition: ERF_ImmersedForcing.cpp:1363
#define RhoScalar_comp
Definition: ERF_IndexDefines.H:57
#define Rho_comp
Definition: ERF_IndexDefines.H:53
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:54
#define NDRY
Definition: ERF_IndexDefines.H:15
#define RhoQ2_comp
Definition: ERF_IndexDefines.H:67
#define NSCALARS
Definition: ERF_IndexDefines.H:26
#define PrimTheta_comp
Definition: ERF_IndexDefines.H:79
#define RhoQ1_comp
Definition: ERF_IndexDefines.H:66
#define RhoKE_comp
Definition: ERF_IndexDefines.H:55
amrex::GpuArray< Real, AMREX_SPACEDIM > dxInv
Definition: ERF_InitCustomPertVels_ParticleTests.H:17
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")
const int khi
Definition: ERF_InitCustomPert_Bubble.H:25
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
const int klo
Definition: ERF_InitCustomPert_KE.H:9
ParallelFor(fab_box, [=] AMREX_GPU_DEVICE(int i, int j, int k) { qrcuten_arr(i, j, k)=Real(0);qscuten_arr(i, j, k)=Real(0);qicuten_arr(i, j, k)=Real(0);})
constexpr amrex::Real three
Definition: ERF_NumericalConstants.H:39
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:37
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:36
constexpr amrex::Real myhalf
Definition: ERF_NumericalConstants.H:41
void NumericalDiffusion_Scal(const Box &bx, const int start_comp, const int num_comp, const double dt, const Real num_diff_coeff, const Array4< const Real > &prim_data, const Array4< const Real > &cell_data, const Array4< Real > &rhs, const Array4< const Real > &mfx_arr, const Array4< const Real > &mfy_arr)
Definition: ERF_NumericalDiffusion.cpp:18
Real w
Definition: ERF_Plotfile2DInterpolator.cpp:23
AMREX_FORCE_INLINE IntVect offset(const int face_dir, const int normal)
Definition: ERF_ReadBndryPlanes.cpp:36
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_FORCE_INLINE amrex::IntVect TileNoZ()
Definition: ERF_TileNoZ.H:11
Definition: ERF_PlaneAverage.H:14
@ qv0_comp
Definition: ERF_IndexDefines.H:101
@ th0_comp
Definition: ERF_IndexDefines.H:100
@ r0_comp
Definition: ERF_IndexDefines.H:97
@ cons
Definition: ERF_IndexDefines.H:272
@ None
Definition: ERF_LSM_Types.H:6
@ theta
Definition: ERF_Kessler.H:26
@ qv
Definition: ERF_Kessler.H:31
@ nr
Definition: ERF_Morrison.H:48
@ xvel
Definition: ERF_IndexDefines.H:255
@ cons
Definition: ERF_IndexDefines.H:254
@ zvel
Definition: ERF_IndexDefines.H:257
@ yvel
Definition: ERF_IndexDefines.H:256
@ dz
Definition: ERF_AdvanceWDM6.cpp:273
@ zi
Definition: ERF_AdvanceWDM6.cpp:277
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
real(c_double), private rhoi
Definition: ERF_module_mp_morr_two_moment.F90:188
real(kind=kind_phys), parameter, private r0
Definition: ERF_module_mp_wdm6.F90:75
bool enabled
erf.forest_canopy_tke: add the canopy terms to the k-eqn RANS model
Definition: ERF_CanopyTurbStruct.H:18
RayleighDampingType rayleigh_damping_type
Selected Rayleigh damping time-integration treatment.
Definition: ERF_DampingStruct.H:111
amrex::Real rayleigh_dampcoef
Rayleigh damping inverse time scale [1/s].
Definition: ERF_DampingStruct.H:98
bool rayleigh_damp_T
Whether Rayleigh damping is applied to potential temperature.
Definition: ERF_DampingStruct.H:97
amrex::Vector< TurbChoice > turbChoice
Turbulence options for each AMR level.
Definition: ERF_DataStruct.H:2620
bool if_fraction_stress
erf.if_wall_form = fraction_stress: the wall stress of ERF_ImmersedWallCell.H in the wall cell of eac...
Definition: ERF_DataStruct.H:2714
MoistureType moisture_type
Moisture or microphysics model.
Definition: ERF_DataStruct.H:2942
int ave_plane
Averaging plane index used by diagnostics.
Definition: ERF_DataStruct.H:2990
bool use_building_immersed_forcing(const amrex::Geometry &geom) const noexcept
Definition: ERF_DataStruct.H:2808
amrex::Real num_diff_coeff
Numerical diffusion coefficient after input scaling.
Definition: ERF_DataStruct.H:2939
bool do_theta_advection
Whether custom vertical subsidence is applied to rho-theta.
Definition: ERF_DataStruct.H:2753
bool spatial_moisture_forcing
Whether spatially varying moisture forcing is enabled.
Definition: ERF_DataStruct.H:2758
static MeshType mesh_type
Vertical mesh representation.
Definition: ERF_DataStruct.H:2604
SpongeChoice spongeChoice
Sponge-layer options.
Definition: ERF_DataStruct.H:2619
bool four_stream_radiation
Whether the four-stream radiation approximation is enabled.
Definition: ERF_DataStruct.H:2692
DampingChoice dampingChoice
Damping-related options.
Definition: ERF_DataStruct.H:2618
bool if_wall_tke
Hold the k-eqn TKE of the fraction-stress wall cell at its flat-ground wall value (ib_wall::wall_tke)...
Definition: ERF_DataStruct.H:2716
static TerrainType terrain_type
Terrain or immersed-boundary representation.
Definition: ERF_DataStruct.H:2589
CanopyTurbulence canopyTurb
Definition: ERF_DataStruct.H:3040
static BuildingsType buildings_type
Building representation.
Definition: ERF_DataStruct.H:2595
bool custom_rhotheta_forcing
Whether custom rho-theta forcing is enabled.
Definition: ERF_DataStruct.H:2750
bool spatial_rhotheta_forcing
Whether spatially varying rho-theta forcing is enabled.
Definition: ERF_DataStruct.H:2757
bool use_source_perturbation(int lev) const
Query whether source-term turbulent perturbations are enabled on a level.
Definition: ERF_DataStruct.H:2845
bool custom_w_subsidence
Whether custom vertical subsidence is enabled.
Definition: ERF_DataStruct.H:2752
bool custom_moisture_forcing
Whether custom moisture forcing is enabled.
Definition: ERF_DataStruct.H:2751
RadiationType rad_type
Radiation model.
Definition: ERF_DataStruct.H:2956
bool immersed_forcing_substep
Whether immersed-forcing source terms are applied only during substeps.
Definition: ERF_DataStruct.H:2695
bool custom_forcing_prim_vars
Whether custom forcing operates on primitive variables.
Definition: ERF_DataStruct.H:2756
bool use_num_diff
Whether sixth-order numerical diffusion is enabled.
Definition: ERF_DataStruct.H:2938
static SpongeType sponge_type
Selected sponge damping model.
Definition: ERF_SpongeStruct.H:100
Definition: ERF_TurbStruct.H:119
bool diffuse_tke_3D
Whether three-dimensional numerical diffusion is applied to TKE/QKE.
Definition: ERF_TurbStruct.H:1067
bool use_tke
Whether any TKE or QKE closure is active.
Definition: ERF_TurbStruct.H:983
void apply_tpi(const int &lev, const amrex::Box &vbx, const int &comp, const amrex::IndexType &m_ixtype, const amrex::Array4< amrex::Real > &src_arr, const amrex::Array4< amrex::Real const > &pert_cell, const amrex::Real scale=one)
Apply stored turbulent perturbations to a source or state array.
Definition: ERF_TurbPertStruct.H:620
amrex::Vector< amrex::MultiFab > pb_cell
Per-cell perturbation amplitude storage.
Definition: ERF_TurbPertStruct.H:926
Here is the call graph for this function: