ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_TurbPertStruct.H
Go to the documentation of this file.
1 #ifndef ERF_TURB_PERT_STRUCT_H_
2 #define ERF_TURB_PERT_STRUCT_H_
3 
4 #include <ERF_DataStruct.H>
5 #include "ERF_Constants.H"
6 #include <AMReX_MultiFabUtil.H>
7 #include <ERF_TileNoZ.H>
8 #include <ERF_HashRNG.H>
9 #include <cstdint>
10 /**
11  * Container holding quantities related to turbulent perturbation parameters
12  */
13 
14 /* The general rule of thumb is to create a perturbation box size of 1/8th of the boundary layer height.
15  The boundary layer height can't be the height of the domain. The length and width of the box should
16  be twice the height of the box. If meandering flow is present, the width of the box should be take
17  the angle of the inflow into consideration.
18 */
19 
20 AMREX_ENUM(PerturbationType,
21  Source, Direct, CPM, CPM_W, None
22 );
23 
24 // One lateral edge of a rectangular nest. Faces are xlo, ylo, xhi, yhi.
26  amrex::Box region;
27  int face = 0;
30  amrex::Real Ug = zero; // CPM wind scale sampled above the PBL
31  amrex::Real height = zero; // perturbation ceiling, meters above local terrain
32 
33  bool inflow () const {
34  const amrex::Real normal = (face % 2 == 0) ? u : v;
35  return height > zero && ((face < 2) ? normal > zero : normal < zero);
36  }
37 };
38 
39 /**
40  * @brief Runtime state and operations for turbulent perturbation forcing.
41  */
43 
44  public:
45 
46  /**
47  * @brief Destroy turbulent perturbation state.
48  */
50 
51  /**
52  * @brief Store the integer perturbation-type selector for one AMR level.
53  * @param lev AMR level index.
54  * @param pert_type Perturbation type selected for this level.
55  * @param max_level Maximum AMR level configured for the run.
56  */
57  void init_tpi_type (const int lev,
58  const PerturbationType& pert_type,
59  const int max_level)
60  {
61  if (pt_type.size() < max_level + 1) {
62  pt_type.resize(max_level + 1, -1);
63  }
64 
65  if (pert_type == PerturbationType::Source) {
66  pt_type[lev] = 0;
67  } else if (pert_type == PerturbationType::Direct) {
68  pt_type[lev] = 1;
69  } else if (pert_type == PerturbationType::CPM) {
70  pt_type[lev] = 2;
71  } else if (pert_type == PerturbationType::CPM_W) {
72  pt_type[lev] = 3;
73  } else {
74  pt_type[lev] = -1;
75  }
76  }
77 
78  /**
79  * @brief Initialize turbulent perturbation regions and per-box storage.
80  * @param lev AMR level index.
81  * @param subdomains_lev Rectangular level subdomains used to define perturbation regions.
82  * @param dx Cell spacing for this level.
83  * @param ba State BoxArray used for perturbation-cell storage.
84  * @param dm DistributionMapping for perturbation-cell storage.
85  * @param ngrow_state Number of ghost cells for perturbation-cell storage.
86  * @param pp_prefix ParmParse prefix for the ERF input namespace.
87  * @param refRatio Refinement ratios between AMR levels.
88  * @param max_level Maximum AMR level configured for the run.
89  * @param automatic Select runtime inflow/height masks driven by the parent.
90  */
91  void init_tpi (const int lev,
92  const amrex::Vector<amrex::BoxArray>& subdomains_lev,
93  const amrex::GpuArray<amrex::Real,3> dx,
94  const amrex::BoxArray& ba,
95  const amrex::DistributionMapping& dm,
96  const int ngrow_state,
97  std::string pp_prefix,
98  const amrex::Vector<amrex::IntVect> refRatio,
99  const int max_level,
100  const bool automatic = false)
101 
102  {
103  ref_ratio = refRatio;
104  auto_mode = automatic;
105 
106  amrex::ParmParse pp(pp_prefix);
107 
108  uniform_mode = true;
109  pp.queryAdd("perturbation_uniform_refresh", uniform_mode);
111  pp.queryAdd("perturbation_refresh_height", uniform_height);
113  pp.queryAdd("perturbation_refresh_wind_period", uniform_wind_period);
114  if (uniform_refresh_enabled(lev) &&
115  (!(uniform_height > zero) || !std::isfinite(uniform_height) ||
116  !(uniform_wind_period > zero) || !std::isfinite(uniform_wind_period))) {
117  amrex::Abort("Uniform CPM refresh requires positive, finite "
118  "perturbation_refresh_height and perturbation_refresh_wind_period");
119  }
120 
121  // Reading inputs, and placing assertion for the perturbation inflow to work
122  pp.getarr("perturbation_box_dims",tpi_boxDim);
123  if (auto_mode) {
124  tpi_direction = {1, 1, 0, 1, 1, 0};
125  } else {
126  pp.getarr("perturbation_direction",tpi_direction);
127  }
128  if (tpi_boxDim.size() != 3) {
129  amrex::Abort("perturbation_box_dims must have 3 entries (Nx Ny Nz)");
130  }
131  if (tpi_direction.size() != 6) {
132  amrex::Abort("perturbation_direction must have 6 entries (west south bottom east north top)");
133  }
134  pp.get("perturbation_layers",tpi_layers);
135  pp.get("perturbation_offset",tpi_offset);
136 
137  tpi_nonDim = zero;
138  pp.queryAdd("perturbation_nondimensional",tpi_nonDim);
139 
140  tpi_Tinf = amrex::Real(300.);
141  pp.queryAdd("perturbation_T_infinity",tpi_Tinf);
142 
143  tpi_Ti = zero;
144  pp.queryAdd("perturbation_T_intensity",tpi_Ti);
145 
146  input_Ug = zero;
147  pp.queryAdd("perturbation_Ug",input_Ug);
148 
149  input_w_amp = zero;
150  pp.queryAdd("perturbation_w_amp",input_w_amp);
151 
152  // Seed of the perturbation's random numbers, read the way erf.deterministic_ic_pert reads
153  // it. The numbers are hashed from the global cell index (or the perturbation-box index) and
154  // the box's refresh count, so they do not depend on the domain decomposition (#4317).
155  {
156  int fix_random_seed = 0;
157  long random_seed = -1;
158  amrex::ParmParse pp_erf("erf");
159  pp_erf.query("fix_random_seed", fix_random_seed);
160  pp_erf.query("random_seed", random_seed);
161  tpi_seed = fix_random_seed ? std::uint64_t(1024)
162  : ((random_seed >= 0) ? static_cast<std::uint64_t>(random_seed) : std::uint64_t(0));
163  }
164 
165  // Negative sentinels rather than the queryAdd return values: k = 0 is a legal
166  // bound, so the natural default cannot double as "unset", and the return value
167  // only reports whether the key existed before the call. init_tpi runs once per
168  // level, so testing the return would make every level above 0 see "specified"
169  // with the value level 0 planted, skipping the valid_box_lo/hi defaults below.
170  perturbation_klo = -1;
171  pp.queryAdd("perturbation_klo", perturbation_klo);
172  const bool have_klo = (perturbation_klo >= 0);
173 
174  perturbation_khi = -1;
175  pp.queryAdd("perturbation_khi", perturbation_khi);
176  const bool have_khi = (perturbation_khi >= 0);
177 
178  // Check variables message
179  if (tpi_offset < 0) { amrex::Abort("Please provide a valid inflow cell offset value for perturbation region (ie. 0-5)"); }
180  if (tpi_layers < 0) { amrex::Abort("Please provide a valid perturbation layer value (ie. 3-5)"); }
181  if ((auto_mode || uniform_refresh_enabled(lev)) && tpi_layers == 0) {
182  amrex::Abort("Automatic perturbations and uniform CPM refresh require perturbation_layers > 0");
183  }
184  if (tpi_nonDim < zero) { amrex::Abort("Please provide a valid nondimensional number (ie. Ri = amrex::Real(0.042))"); }
185  for (int i = 0; i < tpi_boxDim.size(); i++) {
186  if (tpi_boxDim[i] < 3) { amrex::Abort("Please provide valid dimensions for perturbation boxes."); }
187  }
188  if (input_Ug < zero) { amrex::Abort("Please provide a valid geostrophic wind speed (ie. Ug = amrex::Real(10.0) m/s)"); }
189  if (tpi_Tinf < zero) { amrex::Abort("Please provide a valid ambient temperature value (ie. T_0 = T_infty)"); }
190  if (tpi_Ti < zero) { amrex::Abort("Please provide a valid temperature intensity value (ie. 0-one)"); }
191 
192  // Create a temporary box list to accumulate all the perturbation regions after box modification
193  amrex::BoxList tmp_bl;
194 
195  // boxSize for individual boxes
196  amrex::IntVect boxSize(tpi_boxDim[0],tpi_boxDim[1],tpi_boxDim[2]);
197 
198  if (tpi_direction[2] || tpi_direction[5]) { amrex::Abort("Currently not supporting z-direction flow perturbation"); }
199 
200  for (int isub = 0; isub < subdomains_lev.size(); ++isub) {
201  const amrex::BoxArray& subdomain = subdomains_lev[isub];
202  amrex::Box subdomain_box(subdomain.minimalBox());
203 
204  if (subdomain_box.numPts() != subdomain.numPts()) {
205  amrex::Abort("Turbulent perturbations require rectangular subdomains. "
206  "Level " + std::to_string(lev) +
207  ", subdomain " + std::to_string(isub) +
208  " is not a rectangular region fully covered by grids.");
209  }
210 
211  const amrex::IntVect& valid_box_lo = subdomain_box.smallEnd();
212  const amrex::IntVect& valid_box_hi = subdomain_box.bigEnd();
213 
214  if (auto_mode) {
215  // Allocate all possible edges once. A mask chooses inflow and height
216  // at runtime, including corners when either adjoining edge is inflow.
217  for (int face = 0; face < 4; ++face) {
218  const amrex::Box region = automatic_face_box(subdomain_box, face);
219  if (region.ok()) { tmp_bl.push_back(region); }
220  }
221  continue;
222  }
223 
224  // default perturbation region to be the entire z extent if the user does not specify bounds
225  if (!have_klo) { perturbation_klo = valid_box_lo[2]; }
226  if (!have_khi) { perturbation_khi = valid_box_hi[2]; }
227 
228  // Creating perturbation regions and initializing with generic size.
229  amrex::Box lo_x_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
230  amrex::Box hi_x_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
231  amrex::Box lo_y_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
232  amrex::Box hi_y_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
233 
234  // Starting logic to set the size of the perturbation region(s)
235  //amrex::PrintToFile("BoxPerturbationOutput") << "Setting perturbation region in:";
236  // ***** X-direction perturbation *****
237  if (tpi_direction[0]) { // West
238  lo_x_bx.setSmall(amrex::IntVect(valid_box_lo[0]+tpi_offset, valid_box_lo[1]+tpi_direction[1]*tpi_offset, perturbation_klo));
239  lo_x_bx.setBig (amrex::IntVect(valid_box_lo[0]+(tpi_layers*tpi_boxDim[0]-1)+tpi_offset, valid_box_hi[1]-(tpi_direction[4]*tpi_offset), perturbation_khi));
240  amrex::PrintToFile("BoxPerturbationOutput") << " West face";
241  }
242 
243  if (tpi_direction[3]) { // East
244  hi_x_bx.setSmall(amrex::IntVect(valid_box_hi[0]-((tpi_layers*tpi_boxDim[0]-1)+tpi_offset), valid_box_lo[1]+tpi_direction[1]*tpi_offset, perturbation_klo));
245  hi_x_bx.setBig(amrex::IntVect(valid_box_hi[0]-tpi_offset, valid_box_hi[1]-(tpi_direction[4]*tpi_offset), perturbation_khi));
246  amrex::PrintToFile("BoxPerturbationOutput") << " East face";
247  }
248 
249  // ***** Y-direction Perturbation *****
250  if (tpi_direction[1]) { // North
251  lo_y_bx.setSmall(amrex::IntVect(valid_box_lo[0]+tpi_direction[0]*tpi_offset, valid_box_lo[1]+tpi_offset, perturbation_klo));
252  lo_y_bx.setBig(amrex::IntVect(valid_box_hi[0]-tpi_direction[3]*tpi_offset, valid_box_lo[1]+((tpi_layers*tpi_boxDim[1])-1)+tpi_offset, perturbation_khi));
253  amrex::PrintToFile("BoxPerturbationOutput") << " North face";
254  }
255 
256  if (tpi_direction[4]) { // South
257  hi_y_bx.setSmall(amrex::IntVect(valid_box_lo[0]+tpi_direction[0]*tpi_offset, valid_box_hi[1]-((tpi_layers*tpi_boxDim[1]-1)+tpi_offset), perturbation_klo));
258  hi_y_bx.setBig(amrex::IntVect(valid_box_hi[0]-tpi_direction[3]*tpi_offset, valid_box_hi[1]-tpi_offset, perturbation_khi));
259  amrex::PrintToFile("BoxPerturbationOutput") << " South face";
260  }
261 
262  // Performing box union for intersecting perturbation regions to avoid overlapping sections (double counting at corners)
263  if (tpi_direction[0] && tpi_direction[1]) { // Reshaping South smallEnd
264  amrex::Box lo_x_lo_y_u = lo_x_bx & lo_y_bx;
265  lo_y_bx.setSmall(amrex::IntVect(lo_x_lo_y_u.bigEnd(0)+1, lo_x_lo_y_u.smallEnd(1), lo_x_lo_y_u.smallEnd(2)));
266  }
267 
268  if (tpi_direction[3] && tpi_direction[1]) { // Reshaping South bigEnd
269  amrex::Box hi_x_lo_y_u = hi_x_bx & lo_y_bx;
270  lo_y_bx.setBig(amrex::IntVect(hi_x_lo_y_u.smallEnd(0)-1, hi_x_lo_y_u.bigEnd(1), hi_x_lo_y_u.bigEnd(2)));
271  }
272 
273  if (tpi_direction[0] && tpi_direction[4]) { // Reshaping North smallEnd
274  amrex::Box lo_x_hi_y_u = lo_x_bx & hi_y_bx;
275  hi_y_bx.setSmall(amrex::IntVect(lo_x_hi_y_u.bigEnd(0)+1, lo_x_hi_y_u.smallEnd(1), lo_x_hi_y_u.smallEnd(2)));
276  }
277 
278  if (tpi_direction[3] && tpi_direction[4]) { // Reshaping North bigEnd
279  amrex::Box hi_x_hi_y_u = hi_x_bx & hi_y_bx;
280  hi_y_bx.setBig(amrex::IntVect(hi_x_hi_y_u.smallEnd(0)-1, hi_x_hi_y_u.bigEnd(1), hi_x_hi_y_u.bigEnd(2)));
281  }
282 
283  // Creating structure box array for conserved quantity
284  if (tpi_direction[0]) { tmp_bl.push_back(lo_x_bx); }
285  if (tpi_direction[1]) { tmp_bl.push_back(lo_y_bx); }
286  if (tpi_direction[3]) { tmp_bl.push_back(hi_x_bx); }
287  if (tpi_direction[4]) { tmp_bl.push_back(hi_y_bx); }
288  }
289 
290  //amrex::PrintToFile("BoxPerturbationOutput") << "\nBoxList: " << tmp_bl << "\n";
291  amrex::BoxArray tmp_ba(tmp_bl);
292  if (auto_mode) { tmp_ba.removeOverlap(); }
293  tmp_ba.maxSize(boxSize);
294  if (auto_mode && tmp_ba.empty()) {
295  amrex::Abort("Automatic perturbation strips are empty; reduce perturbation_offset");
296  }
297 
298  const int num_levels = max_level + 1;
299  if (pb_ba.size() < num_levels) {
300  pb_ba.resize(num_levels);
301  pb_mag.resize(num_levels);
302  pb_Ug.resize(num_levels);
303  pb_dir.resize(num_levels);
304  pb_netZero.resize(num_levels);
305  pb_interval.resize(num_levels);
306  pb_local_etime.resize(num_levels);
307  pb_amp.resize(num_levels);
308  pb_nupdate.resize(num_levels);
309  pb_cell.resize(num_levels);
310  tpi_Lpb.resize(num_levels);
311  tpi_Wpb.resize(num_levels);
312  tpi_Hpb.resize(num_levels);
313  tpi_lref.resize(num_levels);
314  auto_mask.resize(num_levels);
315  auto_next_update.resize(num_levels, -1.0);
316  auto_ready.resize(num_levels, 0);
317  uniform_clock.resize(num_levels);
318  }
319  uniform_clock[lev] = UniformClock{};
320 
321  pb_ba[lev] = tmp_ba;
322 
323  // Initializing mean magnitude and direction vectors
324  pb_mag[lev].resize(pb_ba[lev].size(), zero);
325  pb_Ug[lev].resize(pb_ba[lev].size(), zero);
326  pb_dir[lev].resize(pb_ba[lev].size(), zero);
327  pb_netZero[lev].resize(pb_ba[lev].size(), zero);
328 
329  // Set size of vector and initialize
330  pb_interval[lev].resize(pb_ba[lev].size(), -one);
331  pb_local_etime[lev].resize(pb_ba[lev].size(), zero);
332  pb_amp[lev].resize(pb_ba[lev].size(), zero);
333  pb_nupdate[lev].resize(pb_ba[lev].size(), 0);
334 
335  // Creating data array for perturbation amplitude storage
336  if (pt_type[lev] == 3) { // CPM_W converts to the k-face ba.
337  pb_cell[lev].define(convert(ba, amrex::IntVect(0,0,1)), dm, 1, ngrow_state);
338  } else {
339  pb_cell[lev].define(ba, dm, 1, ngrow_state); // this is the only place ba is used. Maybe we can print here to determine what's valid...
340  }
341  pb_cell[lev].setVal(0.);
342  if (auto_mode) {
343  auto_mask[lev].define(pb_cell[lev].boxArray(), dm, 1, 0);
344  auto_mask[lev].setVal(zero);
345  auto_next_update[lev] = -1.0;
346  auto_ready[lev] = 0;
347  // Regridding can leave vectors at the same size; resize alone would retain
348  // clocks/amplitudes belonging to a different set of boxes.
349  pb_interval[lev].assign(pb_ba[lev].size(), -one);
350  pb_local_etime[lev].assign(pb_ba[lev].size(), zero);
351  pb_nupdate[lev].assign(pb_ba[lev].size(), 0);
352  }
353 
354  // Computing perturbation reference length
355  tpi_Lpb[lev] = tpi_boxDim[0]*dx[0];
356  tpi_Wpb[lev] = tpi_boxDim[1]*dx[1];
357  tpi_Hpb[lev] = tpi_boxDim[2]*dx[2];
358  tpi_lref[lev] = std::sqrt(tpi_Lpb[lev]*tpi_Lpb[lev] + tpi_Wpb[lev]*tpi_Wpb[lev]);
359 
362 
363  /*
364  // Function check point message
365  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_box_dims: "
366  << tpi_boxDim[0] << " "
367  << tpi_boxDim[1] << " "
368  << tpi_boxDim[2] << "\n";
369  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_direction: "
370  << tpi_direction[0] << " "
371  << tpi_direction[1] << " "
372  << tpi_direction[2] << "\n\n";
373  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_layers: " << tpi_layers << "\n";
374  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_offset: " << tpi_offset << "\n\n";
375  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_nondimensional: " << tpi_nonDim << "\n";
376  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_T_infinity: " << tpi_Tinf << "\n";
377  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_T_intensity: " << tpi_Ti << "\n";
378  amrex::PrintToFile("BoxPerturbationOutput") << "Reference length per box = " << tpi_lref[lev] << "\n\n";
379  amrex::PrintToFile("BoxPerturbationOutput") << "Turbulent perturbation BoxArray:\n" << pb_ba[lev] << "\n";
380  */
381  }
382 
383 
384  // Geometry of a candidate edge strip, clipped to the nest interior.
385  amrex::Box automatic_face_box (const amrex::Box& subdomain, int face) const
386  {
387  amrex::Box region = subdomain;
388  region.grow(0, -tpi_offset);
389  region.grow(1, -tpi_offset);
390  const int dir = face % 2;
391  const int width = tpi_layers * tpi_boxDim[dir];
392  if (face < 2) {
393  region.setBig(dir, std::min(region.bigEnd(dir), region.smallEnd(dir)+width-1));
394  } else {
395  region.setSmall(dir, std::max(region.smallEnd(dir), region.bigEnd(dir)-width+1));
396  }
397  return region;
398  }
399 
400  bool automatic_update_due (int lev, double time) const
401  {
402  return !auto_ready[lev] || time >= auto_next_update[lev];
403  }
404 
405  bool uniform_refresh_enabled (int lev) const
406  {
407  return uniform_mode && (pt_type[lev] == 2 || pt_type[lev] == 3);
408  }
409 
410  bool uniform_wind_update_due (int lev, double time) const
411  {
412  return time >= uniform_clock[lev].next_wind_update;
413  }
414 
417 
418  bool uniform_sampling_face (int face) const
419  {
420  return tpi_direction[face < 2 ? face : face+1] != 0;
421  }
422 
423  // speed is max(|mean u|, |mean v|) over the sampled edges. This is the
424  // existing CPM speed*cos(angle) correction, with one clock for the level.
425  void set_uniform_advection (int lev, double time, amrex::Real speed, double wind_period)
426  {
428  AMREX_ALWAYS_ASSERT(speed >= zero && std::isfinite(speed));
429  auto& clock = uniform_clock[lev];
430  clock.interval = speed > zero
431  ? double(tpi_layers) * double(std::min(tpi_Lpb[lev], tpi_Wpb[lev])) / double(speed)
432  : 0.0;
433  clock.next_refresh = time;
434  clock.next_wind_update = time + wind_period;
435  clock.apply = false;
436  }
437 
438  double uniform_refresh_interval (int lev) const { return uniform_clock[lev].interval; }
439 
440  // Called once per level step. No box scans or field clears between events;
441  // apply_tpi also checks this step's flag so stored increments cannot repeat.
442  bool calc_tpi_uniform_update (int lev, double time)
443  {
445  auto& clock = uniform_clock[lev];
446  clock.apply = false;
447  if ((auto_mode && !auto_ready[lev]) || clock.interval <= 0.0 ||
448  time < clock.next_refresh || pb_ba[lev].empty()) { return false; }
449 
450  clock.next_refresh = time + clock.interval;
451  clock.apply = true;
452  pb_cell[lev].setVal(zero);
453  const auto m_ixtype = pb_cell[lev].ixType();
454  for (int b = 0; b < pb_ba[lev].size(); ++b) {
455  pb_interval[lev][b] = clock.interval / double(tpi_layers);
456  pb_local_etime[lev][b] = zero;
457  calc_tpi_amp(lev, b, pb_interval[lev][b]);
458  pseudoRandomPert(b, lev, m_ixtype);
459  ++pb_nupdate[lev][b];
460  }
461  return true;
462  }
463 
464  // Refresh the parent controls without changing the perturbation boxes or
465  // their random refresh counts. z_ground is a nodal bottom slab per grid box.
466  void configure_automatic (int lev, double time, double period,
467  const amrex::Vector<AutomaticPerturbationFace>& faces,
468  const amrex::Geometry& geom,
469  const amrex::MultiFab* z_cc,
470  const amrex::MultiFab* z_nd,
471  const amrex::MultiFab* z_ground)
472  {
473  auto_mask[lev].setVal(zero);
474  pb_cell[lev].setVal(zero);
475  pb_mag[lev].assign(pb_ba[lev].size(), zero);
476  pb_Ug[lev].assign(pb_ba[lev].size(), zero);
477  pb_dir[lev].assign(pb_ba[lev].size(), zero);
478  const bool on_w_faces = (pt_type[lev] == 3);
479  const bool terrain = (z_cc != nullptr);
480  AMREX_ALWAYS_ASSERT(!terrain || (z_nd && z_ground));
481  const int klo = geom.Domain().smallEnd(2);
482  const auto dx = geom.CellSizeArray();
483  const auto problo = geom.ProbLoArray();
484  for (const auto& face : faces) {
485  if (!face.inflow()) { continue; }
486  const amrex::Real speed = std::hypot(face.u, face.v);
487  for (const auto& isect : pb_ba[lev].intersections(face.region)) {
488  const int b = isect.first;
489  pb_Ug[lev][b] = std::max(pb_Ug[lev][b], face.Ug);
490  if (speed > pb_mag[lev][b]) {
491  pb_mag[lev][b] = speed;
492  pb_dir[lev][b] = std::atan2(std::abs(face.u), std::abs(face.v));
493  }
494  }
495  const amrex::Real height = face.height;
496  const amrex::Box region = amrex::convert(face.region, auto_mask[lev].ixType());
497  for (amrex::MFIter mfi(auto_mask[lev]); mfi.isValid(); ++mfi) {
498  const amrex::Box bx = mfi.validbox() & region;
499  if (!bx.ok()) { continue; }
500  const auto mask = auto_mask[lev].array(mfi);
501  const auto cc = terrain ? z_cc->const_array(mfi) : amrex::Array4<const amrex::Real>{};
502  const auto nd = terrain ? z_nd->const_array(mfi) : amrex::Array4<const amrex::Real>{};
503  const auto ground = terrain ? z_ground->const_array(mfi) : amrex::Array4<const amrex::Real>{};
504  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
505  amrex::Real z = problo[2] + (k-klo + (on_w_faces ? zero : myhalf))*dx[2];
506  amrex::Real zs = problo[2];
507  if (terrain) {
508  zs = fourth*(ground(i,j,klo) + ground(i+1,j,klo) +
509  ground(i,j+1,klo) + ground(i+1,j+1,klo));
510  z = on_w_faces ? fourth*(nd(i,j,k) + nd(i+1,j,k) +
511  nd(i,j+1,k) + nd(i+1,j+1,k))
512  : cc(i,j,k);
513  }
514  if (z > zs && z-zs <= height) { mask(i,j,k) = one; }
515  });
516  }
517  }
518  // Reconsider even boxes that previously had calm winds. They must be able
519  // to start again after a later parent update changes the inflow.
520  pb_interval[lev].assign(pb_ba[lev].size(), -one);
521  pb_local_etime[lev].assign(pb_ba[lev].size(), zero);
522  auto_ready[lev] = 1;
523  auto_next_update[lev] = time + period;
524  }
525 
526  /**
527  * @brief Update perturbation amplitudes and intervals when each box is ready.
528  * @param lev AMR level index.
529  * @param dt Time step for elapsed-time accumulation.
530  * @param mf_xvel X-velocity field used to compute box mean velocities.
531  * @param mf_yvel Y-velocity field used to compute box mean velocities.
532  * @param mf_cons Conserved state used for iteration layout.
533  */
534  void calc_tpi_update (const int lev,
535  const double dt,
536  amrex::MultiFab& mf_xvel,
537  amrex::MultiFab& mf_yvel,
538  amrex::MultiFab& mf_cons)
539  {
540  auto m_ixtype = mf_cons.boxArray().ixType(); // safety step
541  if (pt_type[lev] == 3) { m_ixtype = amrex::IndexType(amrex::IntVect(0,0,1)); }
542 
543  const int nboxes = pb_ba[lev].size();
544  if (nboxes == 0 || (auto_mode && !auto_ready[lev])) { return; }
545 
546  // Which boxes refresh this step. pb_interval and pb_local_etime hold the same values on
547  // every rank, so every rank makes the same choice.
548  amrex::Vector<int> update_box(nboxes, 0);
549  for (int boxIdx = 0; boxIdx < nboxes; boxIdx++) {
550  if (pt_type[lev] == 2 || pt_type[lev] == 3) {
551  // for CPM, perturbation boxes/cells refresh after boxes have advected box width/length * num_layers (advective time scale)
552  update_box[boxIdx] = ((pb_local_etime[lev][boxIdx] >= pb_interval[lev][boxIdx]*tpi_layers) && pb_local_etime[lev][boxIdx] != -one);
553  } else {
554  update_box[boxIdx] = ( pb_local_etime[lev][boxIdx] >= pb_interval[lev][boxIdx] && pb_local_etime[lev][boxIdx] != -one );
555  }
556  }
557 
558  // Compute mean velocity of each refreshing perturbation box
559  if (!auto_mode) { calc_tpi_meanMag(lev, update_box, mf_cons, mf_xvel, mf_yvel); }
560 
561  for (int boxIdx = 0; boxIdx < nboxes; boxIdx++) {
562  if ( update_box[boxIdx] ) {
563 
564  if (pb_mag[lev][boxIdx] !=zero) {
565  double interval = 0.0;
566  if (pt_type[lev] == 2 || pt_type[lev] == 3) {
567  // Wind direction correction for angled wind
568  amrex::Real wind_direction = pb_dir[lev][boxIdx];
569  if (wind_direction > PI / 4) { wind_direction = PI / 2 - wind_direction; }
570  // CPM only cares about the side length of a box, min call maintains flexibility.
571  interval = one / std::cos(wind_direction) * std::min(tpi_Lpb[lev], tpi_Wpb[lev]) / pb_mag[lev][boxIdx];
572  } else {
573  interval = tpi_lref[lev] / pb_mag[lev][boxIdx];
574  }
575  // 10% variation
576  const amrex::Real r = erf_hash_rng::hash_uniform(boxIdx, pb_nupdate[lev][boxIdx], 0,
578  pb_interval[lev][boxIdx] = (amrex::Real(0.9) + amrex::Real(0.2)*r) * interval;
579 
580  // Reset local elapsed time
581  pb_local_etime[lev][boxIdx] = zero;
582  } else {
583  // No mean wind through this box, so no advective time scale: it is not refreshed again
584  pb_local_etime[lev][boxIdx] = -one;
585  }
586 
587  // Trigger amplitude calculation per perturbation box
588  calc_tpi_amp(lev, boxIdx, pb_interval[lev][boxIdx]);
589 
590  // Trigger random amplitude storage per cell within perturbation box
591  pseudoRandomPert(boxIdx, lev, m_ixtype);
592 
593  pb_nupdate[lev][boxIdx]++;
594 
595  } else {
596  // set perturbation amplitudes to 0 for CPM. A little inefficient but leverages as much as existing code as possible.
597  if (pt_type[lev] == 2 || pt_type[lev] == 3) { zero_amp(boxIdx, lev, m_ixtype); }
598 
599  // Increase by timestep of level 0
600  if (pb_local_etime[lev][boxIdx] != -one) { pb_local_etime[lev][boxIdx] += dt; }
601  } // if
602  } // for
603 
604  if (pt_type[lev] < 2) { // box perturbation method only
605  netZeroBuoyant(lev);
606  }
607  }
608 
609  /**
610  * @brief Apply stored turbulent perturbations to a source or state array.
611  * @param lev AMR level index.
612  * @param vbx Valid box over which perturbations may be applied.
613  * @param comp Component to modify, or -1 for vertical velocity perturbations.
614  * @param m_ixtype Index type of the destination array.
615  * @param src_arr Destination array receiving perturbation increments.
616  * @param pert_cell Stored perturbation amplitudes.
617  * @param scale Factor applied to the stored amplitudes (the time step for the direct
618  * perturbation, whose stored amplitudes are rates).
619  */
620  void apply_tpi (const int& lev,
621  const amrex::Box& vbx, // box union from upper level
622  const int& comp, // Component to modify
623  const amrex::IndexType& m_ixtype, // IntVect type of src_arr
624  const amrex::Array4<amrex::Real>& src_arr, // Array to apply perturbation
625  const amrex::Array4<amrex::Real const>& pert_cell,
626  const amrex::Real scale = one)
627  {
628  if (uniform_refresh_enabled(lev) && !uniform_clock[lev].apply) { return; }
629  for (int boxIdx = 0; boxIdx < pb_ba[lev].size(); boxIdx++) {
630  amrex::Box pbx = amrex::convert(pb_ba[lev][boxIdx], m_ixtype);
631  if (pt_type[lev] == 3) { pbx.setBig(2, pbx.bigEnd(2) - 1); } // prevent double counting after converting to k-faces
632  amrex::Box ubx = pbx & vbx;
633  if (ubx.ok()) {
634  if (comp == -1) { // vertical velocity perturbations
635  ParallelFor(ubx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
636  src_arr(i,j,k) += scale * pert_cell(i,j,k);
637  });
638  } else {
639  ParallelFor(ubx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
640  src_arr(i,j,k,comp) += scale * pert_cell(i,j,k);
641 
642  // For box region debug only
643  #ifdef INDEX_PERTURB
644  src_arr(i,j,k,comp) = (amrex::Real) (boxIdx + 5.);
645  #endif
646  });
647  }
648  }
649  }
650  }
651 
652  /**
653  * @brief Compute the perturbation amplitude for one perturbation box.
654  * @param lev AMR level index.
655  * @param boxIdx Perturbation-box index on the level.
656  * @param interval Current perturbation update interval [s].
657  */
658  void calc_tpi_amp (const int& lev,
659  const int& boxIdx,
660  const double& interval)
661  {
662  pb_amp[lev][boxIdx] = zero; // Safety step
663  if (pt_type[lev] == 2) { // CPM
664  constexpr amrex::Real cp = amrex::Real(1004); // specific heat of air [J/(kg K)]
665  constexpr amrex::Real Ec = amrex::Real(0.2); // Eckert number
666  const amrex::Real Ug = auto_mode ? pb_Ug[lev][boxIdx] : input_Ug;
667  pb_amp[lev][boxIdx] = (Ug * Ug) / (Ec * cp);
668  } else if (pt_type[lev] == 3) {
669  pb_amp[lev][boxIdx] = input_w_amp;
670  } else { // box perturbation
671  amrex::Real Um = pb_mag[lev][boxIdx];
672 
673  // No mean wind: nothing to trip, and with tpi_Ti > 0 the amplitude below would be
674  // 0/0. The box is not refreshed again, so a zero amplitude also clears the
675  // perturbation it held
676  if (Um == zero) { return; }
677 
678  amrex::Real beta = one/tpi_Tinf; // Thermal expansion coefficient
679 
680  // Pseudo Random temperature the ignores scale when mechanically tripping turbulence
682  // get total refinement ratio on each level
683  int total_ref_ratio = 1;
684  for (int level = lev; level >= 1; level--) {
685  total_ref_ratio *= ref_ratio[level-1][2];
686  }
687  // calculation needs to be scale-aware since the formulation relies on the physical size of the box
688  if (tpi_Ti > zero) g = (tpi_nonDim * Um * Um) / (tpi_Ti * tpi_Hpb[lev]) * 1 / total_ref_ratio;
689 
690  // Ma and Senocak (2023) Eq. 8, solving for delta phi
691  pb_amp[lev][boxIdx] = (tpi_nonDim * Um * Um) / (g * beta * tpi_Hpb[lev]) * 1 / total_ref_ratio;
692 
693  // Divide by the interval to get a forcing rate [K/s], Ma & Senocak (2023) Eq. 7.
694  // The source perturbation adds this rate as a source term; the direct
695  // perturbation adds it times the time step to the state at every step. Either
696  // way the total over one refresh interval is delta phi, even if dt changes
697  pb_amp[lev][boxIdx] /= static_cast<amrex::Real>(interval);
698  }
699  }
700 
701  /**
702  * @brief Assign pseudo-random perturbations to cells in one perturbation box.
703  * @param boxIdx Perturbation-box index on the level.
704  * @param lev AMR level index.
705  * @param m_ixtype Index type used to align perturbation storage.
706  */
707  void pseudoRandomPert (const int& boxIdx,
708  const int& lev,
709  const amrex::IndexType& m_ixtype)
710  {
711  for (amrex::MFIter mfi(pb_cell[lev],TileNoZ()); mfi.isValid(); ++mfi) {
712  amrex::Box vbx = mfi.tilebox();
713  amrex::Box pbx = amrex::convert(pb_ba[lev][boxIdx], m_ixtype);
714  if (pt_type[lev] == 3) { pbx.setBig(2, pbx.bigEnd(2) - 1); } // prevent double counting after converting to k-faces
715  amrex::Box ubx = pbx & vbx;
716  if (ubx.ok()) {
717  amrex::Real amp_copy = pb_amp[lev][boxIdx];
718  const amrex::Array4<amrex::Real>& pert_cell = pb_cell[lev].array(mfi);
719  const bool use_mask = auto_mode;
720  const auto mask = use_mask ? auto_mask[lev].const_array(mfi)
721  : amrex::Array4<const amrex::Real>{};
722 
723  if (pt_type[lev] == 2 || pt_type[lev] == 3) { // CPM: one value per perturbation box
724  const amrex::Real rand_number_const = erf_hash_rng::hash_symmetric(
725  boxIdx, pb_nupdate[lev][boxIdx], 0, hash_comp_cpm, lev, tpi_seed);
726  ParallelFor(ubx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
727  pert_cell(i,j,k) = (use_mask && mask(i,j,k) == zero) ? zero : rand_number_const * amp_copy;
728  });
729  } else { // one value per cell, keyed by its global index and the box's refresh count
730  const std::uint64_t cell_seed = erf_hash_rng::splitmix64(
731  tpi_seed ^ erf_hash_rng::splitmix64(static_cast<std::uint64_t>(pb_nupdate[lev][boxIdx])));
732  ParallelFor(ubx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
733  pert_cell(i,j,k) = (use_mask && mask(i,j,k) == zero) ? zero
734  : erf_hash_rng::hash_symmetric(i, j, k, hash_comp_cell, lev, cell_seed) * amp_copy;
735  });
736  }
737  }
738  }
739  }
740 
741  /**
742  * @brief Reset CPM perturbation amplitudes in one perturbation box to zero.
743  * @param boxIdx Perturbation-box index on the level.
744  * @param lev AMR level index.
745  * @param m_ixtype Index type used to align perturbation storage.
746  */
747  void zero_amp (const int& boxIdx,
748  const int& lev,
749  const amrex::IndexType& m_ixtype)
750  {
751 
752  for (amrex::MFIter mfi(pb_cell[lev],TileNoZ()); mfi.isValid(); ++mfi) {
753  amrex::Box vbx = mfi.tilebox();
754  amrex::Box pbx = amrex::convert(pb_ba[lev][boxIdx], m_ixtype);
755  if (pt_type[lev] == 3) { pbx.setBig(2, pbx.bigEnd(2) - 1); } // prevent double counting after converting to k-faces
756  amrex::Box ubx = pbx & vbx;
757  if (ubx.ok()) {
758  const amrex::Array4<amrex::Real>& pert_cell = pb_cell[lev].array(mfi);
759  ParallelFor(ubx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
760  pert_cell(i,j,k) = zero;
761  });
762  }
763  }
764  }
765 
766  /**
767  * @brief Remove the mean of the box-perturbation field, so the net buoyant forcing is zero.
768  *
769  * Each perturbation box's mean is summed over every grid box it overlaps and over all
770  * ranks, so the correction does not depend on the domain decomposition.
771  * @param lev AMR level index.
772  */
773  void netZeroBuoyant (const int& lev)
774  {
775  const int nboxes = pb_ba[lev].size();
776  if (nboxes == 0) { return; }
777 
778  // Sum and cell count of each perturbation box, then one reduction over the ranks
779  amrex::Vector<amrex::Real> sums(2*nboxes, zero);
780  for (amrex::MFIter mfi(pb_cell[lev]); mfi.isValid(); ++mfi) {
781  const amrex::Box& vbx = mfi.validbox();
782  const amrex::Array4<const amrex::Real>& pert_cell = pb_cell[lev].const_array(mfi);
783  for (const auto& isect : pb_ba[lev].intersections(vbx)) {
784  sums[2*isect.first ] += box_sum(pert_cell, isect.second);
785  sums[2*isect.first+1] += auto_mode
786  ? box_sum(auto_mask[lev].const_array(mfi), isect.second)
787  : static_cast<amrex::Real>(isect.second.numPts());
788  }
789  }
790  amrex::ParallelAllReduce::Sum(sums.data(), static_cast<int>(sums.size()),
791  amrex::ParallelContext::CommunicatorSub());
792 
793  // Normalizing the adjustment based on how many boxes there are; each box's
794  // value is already normalized by the number of cells within it
796  for (int boxIdx = 0; boxIdx < nboxes; boxIdx++) {
797  pb_netZero[lev][boxIdx] = (sums[2*boxIdx+1] > zero) ? sums[2*boxIdx] / sums[2*boxIdx+1] : zero;
798  tpi_net_buoyant += pb_netZero[lev][boxIdx];
799  }
800  tpi_pert_adjust = tpi_net_buoyant / static_cast<amrex::Real>(nboxes);
801  if (auto_mode) {
802  amrex::Real sum = zero, count = zero;
803  for (int b = 0; b < nboxes; ++b) { sum += sums[2*b]; count += sums[2*b+1]; }
804  tpi_pert_adjust = count > zero ? sum / count : zero;
805  }
806 
807  const amrex::Real adjust = tpi_pert_adjust;
808  for (amrex::MFIter mfi(pb_cell[lev]); mfi.isValid(); ++mfi) {
809  const amrex::Box& vbx = mfi.validbox();
810  const amrex::Array4<amrex::Real>& pert_cell = pb_cell[lev].array(mfi);
811  const bool use_mask = auto_mode;
812  const auto mask = use_mask ? auto_mask[lev].const_array(mfi)
813  : amrex::Array4<const amrex::Real>{};
814  for (const auto& isect : pb_ba[lev].intersections(vbx)) {
815  ParallelFor(isect.second, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
816  if (!use_mask || mask(i,j,k) > zero) { pert_cell(i,j,k) -= adjust; }
817  });
818  }
819  }
820  }
821 
822  /**
823  * @brief Compute the mean horizontal velocity magnitude and direction of the refreshing perturbation boxes.
824  *
825  * The velocities are summed over every grid box a perturbation box overlaps, each face once,
826  * and over all ranks, so every rank gets the same mean whatever the domain decomposition.
827  * @param lev AMR level index.
828  * @param update_box Nonzero for the perturbation boxes that refresh this step.
829  * @param mf_cons Conserved state used for iteration layout.
830  * @param mf_xvel X-velocity field.
831  * @param mf_yvel Y-velocity field.
832  */
833  void calc_tpi_meanMag (const int& lev,
834  const amrex::Vector<int>& update_box,
835  amrex::MultiFab& mf_cons,
836  amrex::MultiFab& mf_xvel,
837  amrex::MultiFab& mf_yvel)
838  {
839  const amrex::BoxArray& m_pb_ba = pb_ba[lev];
840  const int nboxes = m_pb_ba.size();
841 
842  // Per box: sum of u, number of u faces, sum of v, number of v faces
843  amrex::Vector<amrex::Real> sums(4*nboxes, zero);
844  for (amrex::MFIter mfi(mf_cons); mfi.isValid(); ++mfi) {
845  const amrex::Box& vbx = mfi.validbox(); // cell centered
846  const amrex::Array4<const amrex::Real>& xvel_arry = mf_xvel.const_array(mfi);
847  const amrex::Array4<const amrex::Real>& yvel_arry = mf_yvel.const_array(mfi);
848  for (const auto& isect : m_pb_ba.intersections(vbx)) {
849  const int boxIdx = isect.first;
850  if (!update_box[boxIdx]) { continue; }
851  const amrex::Box& pbx = m_pb_ba[boxIdx];
852 
853  // The faces of the overlap's cells; the high face only where the overlap ends
854  // at the perturbation box's high side, so a face shared by two grid boxes counts once
855  amrex::Box ubx_u = amrex::surroundingNodes(isect.second, 0);
856  if (isect.second.bigEnd(0) < pbx.bigEnd(0)) { ubx_u.growHi(0, -1); }
857  amrex::Box ubx_v = amrex::surroundingNodes(isect.second, 1);
858  if (isect.second.bigEnd(1) < pbx.bigEnd(1)) { ubx_v.growHi(1, -1); }
859 
860  sums[4*boxIdx ] += box_sum(xvel_arry, ubx_u);
861  sums[4*boxIdx+1] += static_cast<amrex::Real>(ubx_u.numPts());
862  sums[4*boxIdx+2] += box_sum(yvel_arry, ubx_v);
863  sums[4*boxIdx+3] += static_cast<amrex::Real>(ubx_v.numPts());
864  }
865  }
866  amrex::ParallelAllReduce::Sum(sums.data(), static_cast<int>(sums.size()),
867  amrex::ParallelContext::CommunicatorSub());
868 
869  for (int boxIdx = 0; boxIdx < nboxes; boxIdx++) {
870  if (!update_box[boxIdx]) { continue; }
871  const amrex::Real u_avg = (sums[4*boxIdx+1] > zero) ? sums[4*boxIdx ] / sums[4*boxIdx+1] : zero;
872  const amrex::Real v_avg = (sums[4*boxIdx+3] > zero) ? sums[4*boxIdx+2] / sums[4*boxIdx+3] : zero;
873  pb_mag[lev][boxIdx] = std::sqrt(u_avg*u_avg + v_avg*v_avg);
874  pb_dir[lev][boxIdx] = std::atan(std::abs(u_avg) / std::abs(v_avg+std::numeric_limits<amrex::Real>::epsilon()));
875  }
876  }
877 
878  /**
879  * @brief Sum an array over a box.
880  * @param arr Array to sum.
881  * @param bx Box to sum over.
882  * @return Sum of arr over bx.
883  */
884  static amrex::Real box_sum (const amrex::Array4<const amrex::Real>& arr, const amrex::Box& bx)
885  {
886  amrex::ReduceOps<amrex::ReduceOpSum> reduce_op;
887  amrex::ReduceData<amrex::Real> reduce_data(reduce_op);
888  using ReduceTuple = typename decltype(reduce_data)::Type;
889  reduce_op.eval(bx, reduce_data, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept -> ReduceTuple
890  {
891  return {arr(i,j,k)};
892  });
893  return amrex::get<0>(reduce_data.value(reduce_op));
894  }
895 
896  /**
897  * @brief Write perturbation debug information when debug output is enabled.
898  */
899  void debug (double /*time*/)
900  {
901  /*
902  amrex::PrintToFile("BoxPerturbationOutput") << "#################### PB output at time = "
903  << time << " ####################\n";
904  amrex::PrintToFile("BoxPerturbationOutput") << " Using type: " << pt_type << "\n";
905  amrex::PrintToFile("BoxPerturbationOutput") << " Net: " << tpi_net_buoyant << " Adjust : " << tpi_pert_adjust << "\n";
906  for (int i = 0; i < pb_mag.size(); i++) {
907  amrex::PrintToFile("BoxPerturbationOutput") << "[" << i
908  << "] pb_Umag=" << pb_mag[i]
909  << " | pb_interval=" << pb_interval[i]
910  << " (" << pb_local_etime[i]
911  << ") | pb_amp=" << pb_amp[i] << "\n";
912  }
913  amrex::PrintToFile("BoxPerturbationOutput") << "\n";
914  */
915  }
916 
917  amrex::Vector<int> pt_type;
918 
919  // Public data members
920  amrex::Vector<amrex::BoxArray> pb_ba; // PB box array
921  amrex::Vector<amrex::Vector<amrex::Real>> pb_mag; // BP mean magnitude [m/s]
922  amrex::Vector<amrex::Vector<amrex::Real>> pb_dir; // BP mean direction [deg]
923 
924  // Perturbation amplitude cell storage
925  // This is after random assignment of equation (10) in Ma and Senocak 2023
926  amrex::Vector<amrex::MultiFab> pb_cell;
927 
928  private:
929 
930  struct UniformClock {
931  double interval = 0.0;
932  double next_refresh = -1.0;
933  double next_wind_update = -1.0;
934  bool apply = false;
935  };
936  bool uniform_mode{true};
939  amrex::Vector<UniformClock> uniform_clock;
940 
941  bool auto_mode{false};
942  amrex::Vector<amrex::Vector<amrex::Real>> pb_Ug; // CPM amplitude scale; independent of refresh winds
943  amrex::Vector<amrex::MultiFab> auto_mask;
944  amrex::Vector<double> auto_next_update;
945  // Integer flags avoid std::vector<bool>'s proxy references in amrex::Vector.
946  amrex::Vector<int> auto_ready;
947 
948  // Private data members
949  int tpi_layers; // Number of layers of perturbation boxes
950  int tpi_offset; // Cells to offset the start of the perturbation region
951 
952  amrex::Vector<int> tpi_boxDim; // Dimensions of each perturbation box
953  amrex::Vector<int> tpi_direction; // Direction of the perturbation
954 
955  // Richardson Formulation
956  amrex::Real tpi_nonDim; // Richardson number
957  amrex::Real tpi_Ti; // Temperature intensity value
958  amrex::Real tpi_Tinf; // Reference temperature [K]
959 
960  // Physical dimensions
961  amrex::Vector<amrex::Real> tpi_Hpb; // PB height [m]
962  amrex::Vector<amrex::Real> tpi_Lpb; // PB length [m]
963  amrex::Vector<amrex::Real> tpi_Wpb; // PB width [m]
964  amrex::Vector<amrex::Real> tpi_lref; // PB reference length [m]
965 
966  amrex::Real tpi_net_buoyant; // Perturbation net-zero calculation storage
967  amrex::Real tpi_pert_adjust; // Perturbation adjust for net-zero per cell adjustment
968 
969  amrex::Vector<amrex::IntVect> ref_ratio; // ref_ratio for multilevel run
970  amrex::Real input_Ug; // input geostrophic wind speed to scale perturbations when using CPM
971  amrex::Real input_w_amp; // input vertical velocity amplitude for w
972  int perturbation_klo; // upper bound in Nz for perturbations
973  int perturbation_khi; // lower bound in Nz for perturbations
974 
975  // Perturbation data arrays
976  amrex::Vector<amrex::Vector<double>> pb_interval; // PB update time [s]
977  amrex::Vector<amrex::Vector<double>> pb_local_etime; // PB local elapsed time [s]
978  amrex::Vector<amrex::Vector<amrex::Real>> pb_amp; // PB perturbation amplitude Ri:[K]
979  amrex::Vector<amrex::Vector<amrex::Real>> pb_netZero; // PB array used for net zero sum calculation
980  amrex::Vector<amrex::Vector<int>> pb_nupdate; // PB number of refreshes so far
981 
982  // Random numbers
983  std::uint64_t tpi_seed = 0; // seed of the hashed random numbers
984  static constexpr int hash_comp_cell = 0; // hash salts of the three uses
985  static constexpr int hash_comp_cpm = 1;
986  static constexpr int hash_comp_interval = 2;
987 
988 };
989 
990 /**
991  * @var TurbulentPerturbation::pt_type
992  * @brief Integer perturbation type for each AMR level.
993  * @var TurbulentPerturbation::pb_ba
994  * @brief Perturbation-box BoxArray for each AMR level.
995  * @var TurbulentPerturbation::pb_mag
996  * @brief Mean velocity magnitude for each perturbation box [m/s].
997  * @var TurbulentPerturbation::pb_dir
998  * @brief Mean velocity direction for each perturbation box.
999  * @var TurbulentPerturbation::pb_cell
1000  * @brief Per-cell perturbation amplitude storage.
1001  * @var TurbulentPerturbation::tpi_layers
1002  * @brief Number of layers of perturbation boxes.
1003  * @var TurbulentPerturbation::tpi_offset
1004  * @brief Cell offset for the start of the perturbation region.
1005  * @var TurbulentPerturbation::tpi_boxDim
1006  * @brief Dimensions of each perturbation box.
1007  * @var TurbulentPerturbation::tpi_direction
1008  * @brief Boundary directions where perturbations are applied.
1009  * @var TurbulentPerturbation::tpi_nonDim
1010  * @brief Nondimensional number used by the perturbation formulation.
1011  * @var TurbulentPerturbation::tpi_Ti
1012  * @brief Temperature intensity used by the perturbation formulation.
1013  * @var TurbulentPerturbation::tpi_Tinf
1014  * @brief Reference temperature used by the perturbation formulation [K].
1015  * @var TurbulentPerturbation::tpi_Hpb
1016  * @brief Perturbation-box height for each AMR level [m].
1017  * @var TurbulentPerturbation::tpi_Lpb
1018  * @brief Perturbation-box length for each AMR level [m].
1019  * @var TurbulentPerturbation::tpi_Wpb
1020  * @brief Perturbation-box width for each AMR level [m].
1021  * @var TurbulentPerturbation::tpi_lref
1022  * @brief Perturbation-box reference length for each AMR level [m].
1023  * @var TurbulentPerturbation::tpi_net_buoyant
1024  * @brief Accumulated net buoyant perturbation used for correction.
1025  * @var TurbulentPerturbation::tpi_pert_adjust
1026  * @brief Per-cell perturbation adjustment used for net-zero buoyancy.
1027  * @var TurbulentPerturbation::ref_ratio
1028  * @brief Refinement ratios used by multilevel perturbation scaling.
1029  * @var TurbulentPerturbation::input_Ug
1030  * @brief Input geostrophic wind speed used to scale CPM perturbations.
1031  * @var TurbulentPerturbation::input_w_amp
1032  * @brief Input vertical velocity perturbation amplitude.
1033  * @var TurbulentPerturbation::perturbation_klo
1034  * @brief Lower vertical index bound for perturbations.
1035  * @var TurbulentPerturbation::perturbation_khi
1036  * @brief Upper vertical index bound for perturbations.
1037  * @var TurbulentPerturbation::pb_interval
1038  * @brief Perturbation update interval for each box [s].
1039  * @var TurbulentPerturbation::pb_local_etime
1040  * @brief Local elapsed time for each perturbation box [s].
1041  * @var TurbulentPerturbation::pb_amp
1042  * @brief Perturbation amplitude for each perturbation box.
1043  * @var TurbulentPerturbation::pb_netZero
1044  * @brief Per-box storage used for net-zero buoyancy calculation.
1045  * @var TurbulentPerturbation::pb_nupdate
1046  * @brief Number of times each perturbation box has been refreshed; keys its random numbers.
1047  * @var TurbulentPerturbation::tpi_seed
1048  * @brief Seed of the hashed random numbers (1024 with erf.fix_random_seed, else erf.random_seed, else 0).
1049  * @var TurbulentPerturbation::hash_comp_cell
1050  * @brief Hash salt of the per-cell perturbation values.
1051  * @var TurbulentPerturbation::hash_comp_cpm
1052  * @brief Hash salt of the per-box CPM values.
1053  * @var TurbulentPerturbation::hash_comp_interval
1054  * @brief Hash salt of the refresh-interval variation.
1055  */
1056 #endif
constexpr amrex::Real CONST_GRAV
Definition: ERF_Constants.H:72
auto problo
Definition: ERF_InitCustomPertVels_ABL.H:36
long random_seed
Definition: ERF_InitCustomPert_ABL.H:6
int fix_random_seed
Definition: ERF_InitCustomPert_ABL.H:5
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
ParmParse pp("prob")
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
const int klo
Definition: ERF_InitCustomPert_KE.H:9
Real height
Definition: ERF_InitCustomPert_SquallLine.H:37
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 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
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_FORCE_INLINE amrex::IntVect TileNoZ()
Definition: ERF_TileNoZ.H:11
AMREX_ENUM(PerturbationType, Source, Direct, CPM, CPM_W, None)
@ None
Definition: ERF_LSM_Types.H:6
@ beta
Definition: ERF_SLM.H:152
void apply(const amrex::Box &bx, const amrex::Box &domain, const int quantity, const int flux_comp, const amrex::Array4< const amrex::Real > &state, const amrex::Array4< const amrex::Real > &prim, const amrex::Array4< const amrex::Real > &base_state, const amrex::Array4< const amrex::Real > &u, const amrex::Array4< const amrex::Real > &v, const amrex::Array4< const amrex::Real > &w, const amrex::Array4< amrex::Real > &rhs, const amrex::Array4< amrex::Real > &xflux, const amrex::Array4< amrex::Real > &yflux, const amrex::Array4< amrex::Real > &zflux, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &dx_inv, const erf_wall_thermodynamics::Boundary &walls, const amrex::Real alpha_T, const amrex::Real alpha_C, const amrex::Real rdOcp, const bool cloudy=false)
Definition: ERF_CloudChamberWallFlux.H:1037
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real hash_symmetric(int i, int j, int k, int comp, int lev, std::uint64_t seed) noexcept
Definition: ERF_HashRNG.H:91
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE std::uint64_t splitmix64(std::uint64_t x) noexcept
Definition: ERF_HashRNG.H:19
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real hash_uniform(int i, int j, int k, int comp, int lev, std::uint64_t seed) noexcept
Definition: ERF_HashRNG.H:68
real(c_double), parameter g
Definition: ERF_module_model_constants.F90:19
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12
real(c_double), parameter cp
Definition: ERF_module_model_constants.F90:22
integer, private isub
Definition: ERF_module_mp_morr_two_moment.F90:164
Definition: ERF_TurbPertStruct.H:25
int face
Definition: ERF_TurbPertStruct.H:27
amrex::Real Ug
Definition: ERF_TurbPertStruct.H:30
amrex::Real height
Definition: ERF_TurbPertStruct.H:31
amrex::Real u
Definition: ERF_TurbPertStruct.H:28
amrex::Box region
Definition: ERF_TurbPertStruct.H:26
amrex::Real v
Definition: ERF_TurbPertStruct.H:29
bool inflow() const
Definition: ERF_TurbPertStruct.H:33
Definition: ERF_TurbPertStruct.H:930
double next_refresh
Definition: ERF_TurbPertStruct.H:932
double interval
Definition: ERF_TurbPertStruct.H:931
bool apply
Definition: ERF_TurbPertStruct.H:934
double next_wind_update
Definition: ERF_TurbPertStruct.H:933
Runtime state and operations for turbulent perturbation forcing.
Definition: ERF_TurbPertStruct.H:42
amrex::Real tpi_Tinf
Reference temperature used by the perturbation formulation [K].
Definition: ERF_TurbPertStruct.H:958
int tpi_layers
Number of layers of perturbation boxes.
Definition: ERF_TurbPertStruct.H:949
amrex::Vector< amrex::Real > tpi_Wpb
Perturbation-box width for each AMR level [m].
Definition: ERF_TurbPertStruct.H:963
amrex::Real input_w_amp
Input vertical velocity perturbation amplitude.
Definition: ERF_TurbPertStruct.H:971
void calc_tpi_update(const int lev, const double dt, amrex::MultiFab &mf_xvel, amrex::MultiFab &mf_yvel, amrex::MultiFab &mf_cons)
Update perturbation amplitudes and intervals when each box is ready.
Definition: ERF_TurbPertStruct.H:534
void calc_tpi_amp(const int &lev, const int &boxIdx, const double &interval)
Compute the perturbation amplitude for one perturbation box.
Definition: ERF_TurbPertStruct.H:658
amrex::Box automatic_face_box(const amrex::Box &subdomain, int face) const
Definition: ERF_TurbPertStruct.H:385
bool uniform_wind_update_due(int lev, double time) const
Definition: ERF_TurbPertStruct.H:410
void pseudoRandomPert(const int &boxIdx, const int &lev, const amrex::IndexType &m_ixtype)
Assign pseudo-random perturbations to cells in one perturbation box.
Definition: ERF_TurbPertStruct.H:707
amrex::Real tpi_pert_adjust
Per-cell perturbation adjustment used for net-zero buoyancy.
Definition: ERF_TurbPertStruct.H:967
amrex::Vector< int > tpi_direction
Boundary directions where perturbations are applied.
Definition: ERF_TurbPertStruct.H:953
int perturbation_klo
Lower vertical index bound for perturbations.
Definition: ERF_TurbPertStruct.H:972
amrex::Vector< amrex::Vector< amrex::Real > > pb_Ug
Definition: ERF_TurbPertStruct.H:942
void set_uniform_advection(int lev, double time, amrex::Real speed, double wind_period)
Definition: ERF_TurbPertStruct.H:425
void netZeroBuoyant(const int &lev)
Remove the mean of the box-perturbation field, so the net buoyant forcing is zero.
Definition: ERF_TurbPertStruct.H:773
int tpi_offset
Cell offset for the start of the perturbation region.
Definition: ERF_TurbPertStruct.H:950
static constexpr int hash_comp_interval
Hash salt of the refresh-interval variation.
Definition: ERF_TurbPertStruct.H:986
void init_tpi(const int lev, const amrex::Vector< amrex::BoxArray > &subdomains_lev, const amrex::GpuArray< amrex::Real, 3 > dx, const amrex::BoxArray &ba, const amrex::DistributionMapping &dm, const int ngrow_state, std::string pp_prefix, const amrex::Vector< amrex::IntVect > refRatio, const int max_level, const bool automatic=false)
Initialize turbulent perturbation regions and per-box storage.
Definition: ERF_TurbPertStruct.H:91
bool uniform_refresh_enabled(int lev) const
Definition: ERF_TurbPertStruct.H:405
static constexpr int hash_comp_cpm
Hash salt of the per-box CPM values.
Definition: ERF_TurbPertStruct.H:985
amrex::Vector< amrex::Real > tpi_Lpb
Perturbation-box length for each AMR level [m].
Definition: ERF_TurbPertStruct.H:962
amrex::Real uniform_sampling_height() const
Definition: ERF_TurbPertStruct.H:415
amrex::Vector< amrex::Vector< amrex::Real > > pb_netZero
Per-box storage used for net-zero buoyancy calculation.
Definition: ERF_TurbPertStruct.H:979
bool auto_mode
Definition: ERF_TurbPertStruct.H:941
void configure_automatic(int lev, double time, double period, const amrex::Vector< AutomaticPerturbationFace > &faces, const amrex::Geometry &geom, const amrex::MultiFab *z_cc, const amrex::MultiFab *z_nd, const amrex::MultiFab *z_ground)
Definition: ERF_TurbPertStruct.H:466
bool uniform_sampling_face(int face) const
Definition: ERF_TurbPertStruct.H:418
amrex::Vector< UniformClock > uniform_clock
Definition: ERF_TurbPertStruct.H:939
amrex::Real uniform_wind_period
Definition: ERF_TurbPertStruct.H:938
amrex::Real uniform_height
Definition: ERF_TurbPertStruct.H:937
amrex::Vector< amrex::Vector< amrex::Real > > pb_mag
Mean velocity magnitude for each perturbation box [m/s].
Definition: ERF_TurbPertStruct.H:921
amrex::Real tpi_net_buoyant
Accumulated net buoyant perturbation used for correction.
Definition: ERF_TurbPertStruct.H:966
amrex::Vector< amrex::Vector< amrex::Real > > pb_dir
Mean velocity direction for each perturbation box.
Definition: ERF_TurbPertStruct.H:922
static constexpr int hash_comp_cell
Hash salt of the per-cell perturbation values.
Definition: ERF_TurbPertStruct.H:984
amrex::Vector< amrex::Vector< int > > pb_nupdate
Number of times each perturbation box has been refreshed; keys its random numbers.
Definition: ERF_TurbPertStruct.H:980
amrex::Vector< int > auto_ready
Definition: ERF_TurbPertStruct.H:946
amrex::Real input_Ug
Input geostrophic wind speed used to scale CPM perturbations.
Definition: ERF_TurbPertStruct.H:970
void calc_tpi_meanMag(const int &lev, const amrex::Vector< int > &update_box, amrex::MultiFab &mf_cons, amrex::MultiFab &mf_xvel, amrex::MultiFab &mf_yvel)
Compute the mean horizontal velocity magnitude and direction of the refreshing perturbation boxes.
Definition: ERF_TurbPertStruct.H:833
amrex::Vector< int > tpi_boxDim
Dimensions of each perturbation box.
Definition: ERF_TurbPertStruct.H:952
amrex::Vector< amrex::Vector< double > > pb_interval
Perturbation update interval for each box [s].
Definition: ERF_TurbPertStruct.H:976
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
void init_tpi_type(const int lev, const PerturbationType &pert_type, const int max_level)
Store the integer perturbation-type selector for one AMR level.
Definition: ERF_TurbPertStruct.H:57
void debug(double)
Write perturbation debug information when debug output is enabled.
Definition: ERF_TurbPertStruct.H:899
amrex::Vector< amrex::Vector< double > > pb_local_etime
Local elapsed time for each perturbation box [s].
Definition: ERF_TurbPertStruct.H:977
amrex::Vector< amrex::Real > tpi_lref
Perturbation-box reference length for each AMR level [m].
Definition: ERF_TurbPertStruct.H:964
double uniform_refresh_interval(int lev) const
Definition: ERF_TurbPertStruct.H:438
amrex::Vector< amrex::MultiFab > pb_cell
Per-cell perturbation amplitude storage.
Definition: ERF_TurbPertStruct.H:926
void zero_amp(const int &boxIdx, const int &lev, const amrex::IndexType &m_ixtype)
Reset CPM perturbation amplitudes in one perturbation box to zero.
Definition: ERF_TurbPertStruct.H:747
amrex::Vector< amrex::IntVect > ref_ratio
Refinement ratios used by multilevel perturbation scaling.
Definition: ERF_TurbPertStruct.H:969
static amrex::Real box_sum(const amrex::Array4< const amrex::Real > &arr, const amrex::Box &bx)
Sum an array over a box.
Definition: ERF_TurbPertStruct.H:884
bool automatic_update_due(int lev, double time) const
Definition: ERF_TurbPertStruct.H:400
int perturbation_khi
Upper vertical index bound for perturbations.
Definition: ERF_TurbPertStruct.H:973
amrex::Real tpi_Ti
Temperature intensity used by the perturbation formulation.
Definition: ERF_TurbPertStruct.H:957
bool uniform_mode
Definition: ERF_TurbPertStruct.H:936
amrex::Real uniform_sampling_period() const
Definition: ERF_TurbPertStruct.H:416
amrex::Vector< amrex::BoxArray > pb_ba
Perturbation-box BoxArray for each AMR level.
Definition: ERF_TurbPertStruct.H:920
~TurbulentPerturbation()
Destroy turbulent perturbation state.
Definition: ERF_TurbPertStruct.H:49
amrex::Vector< double > auto_next_update
Definition: ERF_TurbPertStruct.H:944
amrex::Vector< int > pt_type
Integer perturbation type for each AMR level.
Definition: ERF_TurbPertStruct.H:917
amrex::Vector< amrex::Vector< amrex::Real > > pb_amp
Perturbation amplitude for each perturbation box.
Definition: ERF_TurbPertStruct.H:978
amrex::Vector< amrex::MultiFab > auto_mask
Definition: ERF_TurbPertStruct.H:943
amrex::Real tpi_nonDim
Nondimensional number used by the perturbation formulation.
Definition: ERF_TurbPertStruct.H:956
std::uint64_t tpi_seed
Seed of the hashed random numbers (1024 with erf.fix_random_seed, else erf.random_seed,...
Definition: ERF_TurbPertStruct.H:983
bool calc_tpi_uniform_update(int lev, double time)
Definition: ERF_TurbPertStruct.H:442
amrex::Vector< amrex::Real > tpi_Hpb
Perturbation-box height for each AMR level [m].
Definition: ERF_TurbPertStruct.H:961