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 <AMReX_MultiFabUtil.H>
6 #include <ERF_TileNoZ.H>
7 #include <time.h>
8 /**
9  * Container holding quantities related to turbulent perturbation parameters
10  */
11 
12 /* The general rule of thumb is to create a perturbation box size of 1/8th of the boundary layer height.
13  The boundary layer height can't be the height of the domain. The length and width of the box should
14  be twice the height of the box. If meandering flow is present, the width of the box should be take
15  the angle of the inflow into consideration.
16 */
17 
18 AMREX_ENUM(PerturbationType,
19  Source, Direct, CPM, CPM_W, None
20 );
21 
23 
24  public:
25 
27 
28  void init_tpi_type (const int lev,
29  const PerturbationType& pert_type,
30  const int max_level)
31  {
32  if (pt_type.size() < max_level + 1) {
33  pt_type.resize(max_level + 1, -1);
34  }
35 
36  if (pert_type == PerturbationType::Source) {
37  pt_type[lev] = 0;
38  } else if (pert_type == PerturbationType::Direct) {
39  pt_type[lev] = 1;
40  } else if (pert_type == PerturbationType::CPM) {
41  pt_type[lev] = 2;
42  } else if (pert_type == PerturbationType::CPM_W) {
43  pt_type[lev] = 3;
44  } else {
45  pt_type[lev] = -1;
46  }
47  }
48 
49  // Initializing Perturbation Region
50  // Currently only support perturbation in the x and y direction
51  // This portion of the code is only called once for initialization,
52  // therefore efficiency is not required. Dont spend too much time
53  // here other than to correctly setup the perturbation box region
54  void init_tpi (const int lev,
55  const amrex::Vector<amrex::BoxArray>& subdomains_lev,
56  const amrex::GpuArray<amrex::Real,3> dx,
57  const amrex::BoxArray& ba,
58  const amrex::DistributionMapping& dm,
59  const int ngrow_state,
60  std::string pp_prefix,
61  const amrex::Vector<amrex::IntVect> refRatio,
62  const int max_level)
63 
64  {
65  ref_ratio = refRatio;
66 
67  amrex::ParmParse pp(pp_prefix);
68 
69  // Reading inputs, and placing assertion for the perturbation inflow to work
70  pp.getarr("perturbation_box_dims",tpi_boxDim);
71  pp.getarr("perturbation_direction",tpi_direction);
72  pp.get("perturbation_layers",tpi_layers);
73  pp.get("perturbation_offset",tpi_offset);
74 
75  tpi_nonDim = zero;
76  pp.query("perturbation_nondimensional",tpi_nonDim);
77 
78  tpi_Tinf = amrex::Real(300.);
79  pp.query("perturbation_T_infinity",tpi_Tinf);
80 
81  tpi_Ti = zero;
82  pp.query("perturbation_T_intensity",tpi_Ti);
83 
84  input_Ug = zero;
85  pp.query("perturbation_Ug",input_Ug);
86 
87  input_w_amp = zero;
88  pp.query("perturbation_w_amp",input_w_amp);
89 
90  perturbation_klo = 0;
91  bool have_klo = pp.query("perturbation_klo", perturbation_klo);
92 
93  perturbation_khi = 0;
94  bool have_khi = pp.query("perturbation_khi", perturbation_khi);
95 
96  // Check variables message
97  if (tpi_offset < 0) { amrex::Abort("Please provide a valid inflow cell offset value for perturbation region (ie. 0-5)"); }
98  if (tpi_layers < 0) { amrex::Abort("Please provide a valid perturbation layer value (ie. 3-5)"); }
99  if (tpi_nonDim < zero) { amrex::Abort("Please provide a valid nondimensional number (ie. Ri = amrex::Real(0.042))"); }
100  for (int i = 0; i < tpi_boxDim.size(); i++) {
101  if (tpi_boxDim[i] < 3) { amrex::Abort("Please provide valid dimensions for perturbation boxes."); }
102  }
103  if (input_Ug < zero) { amrex::Abort("Please provide a valid geostrophic wind speed (ie. Ug = amrex::Real(10.0) m/s)"); }
104  if (tpi_Tinf < zero) { amrex::Abort("Please provide a valid ambient temperature value (ie. T_0 = T_infty)"); }
105  if (tpi_Ti < zero) { amrex::Abort("Please provide a valid temperature intensity value (ie. 0-one)"); }
106 
107  // Create a temporary box list to accumulate all the perturbation regions after box modification
108  amrex::BoxList tmp_bl;
109 
110  // boxSize for individual boxes
111  amrex::IntVect boxSize(tpi_boxDim[0],tpi_boxDim[1],tpi_boxDim[2]);
112 
113  if (tpi_direction[2] || tpi_direction[5]) { amrex::Abort("Currently not supporting z-direction flow perturbation"); }
114 
115  for (int isub = 0; isub < subdomains_lev.size(); ++isub) {
116  const amrex::BoxArray& subdomain = subdomains_lev[isub];
117  amrex::Box subdomain_box(subdomain.minimalBox());
118 
119  if (subdomain_box.numPts() != subdomain.numPts()) {
120  amrex::Abort("Turbulent perturbations require rectangular subdomains. "
121  "Level " + std::to_string(lev) +
122  ", subdomain " + std::to_string(isub) +
123  " is not a rectangular region fully covered by grids.");
124  }
125 
126  const amrex::IntVect& valid_box_lo = subdomain_box.smallEnd();
127  const amrex::IntVect& valid_box_hi = subdomain_box.bigEnd();
128 
129  // default perturbation region to be the entire z extent if the user does not specify bounds
130  if (!have_klo) { perturbation_klo = valid_box_lo[2]; }
131  if (!have_khi) { perturbation_khi = valid_box_hi[2]; }
132 
133  // Creating perturbation regions and initializing with generic size.
134  amrex::Box lo_x_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
135  amrex::Box hi_x_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
136  amrex::Box lo_y_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
137  amrex::Box hi_y_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
138 
139  // Starting logic to set the size of the perturbation region(s)
140  //amrex::PrintToFile("BoxPerturbationOutput") << "Setting perturbation region in:";
141  // ***** X-direction perturbation *****
142  if (tpi_direction[0]) { // West
143  lo_x_bx.setSmall(amrex::IntVect(valid_box_lo[0]+tpi_offset, valid_box_lo[1]+tpi_direction[1]*tpi_offset, perturbation_klo));
144  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));
145  amrex::PrintToFile("BoxPerturbationOutput") << " West face";
146  }
147 
148  if (tpi_direction[3]) { // East
149  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));
150  hi_x_bx.setBig(amrex::IntVect(valid_box_hi[0]-tpi_offset, valid_box_hi[1]-(tpi_direction[4]*tpi_offset), perturbation_khi));
151  amrex::PrintToFile("BoxPerturbationOutput") << " East face";
152  }
153 
154  // ***** Y-direction Perturbation *****
155  if (tpi_direction[1]) { // North
156  lo_y_bx.setSmall(amrex::IntVect(valid_box_lo[0]+tpi_direction[0]*tpi_offset, valid_box_lo[1]+tpi_offset, perturbation_klo));
157  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));
158  amrex::PrintToFile("BoxPerturbationOutput") << " North face";
159  }
160 
161  if (tpi_direction[4]) { // South
162  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));
163  hi_y_bx.setBig(amrex::IntVect(valid_box_hi[0]-tpi_direction[3]*tpi_offset, valid_box_hi[1]-tpi_offset, perturbation_khi));
164  amrex::PrintToFile("BoxPerturbationOutput") << " South face";
165  }
166 
167  // Performing box union for intersecting perturbation regions to avoid overlapping sections (double counting at corners)
168  if (tpi_direction[0] && tpi_direction[1]) { // Reshaping South smallEnd
169  amrex::Box lo_x_lo_y_u = lo_x_bx & lo_y_bx;
170  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)));
171  }
172 
173  if (tpi_direction[3] && tpi_direction[1]) { // Reshaping South bigEnd
174  amrex::Box hi_x_lo_y_u = hi_x_bx & lo_y_bx;
175  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)));
176  }
177 
178  if (tpi_direction[0] && tpi_direction[4]) { // Reshaping North smallEnd
179  amrex::Box lo_x_hi_y_u = lo_x_bx & hi_y_bx;
180  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)));
181  }
182 
183  if (tpi_direction[3] && tpi_direction[4]) { // Reshaping North bigEnd
184  amrex::Box hi_x_hi_y_u = hi_x_bx & hi_y_bx;
185  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)));
186  }
187 
188  // Creating structure box array for conserved quantity
189  if (tpi_direction[0]) { tmp_bl.push_back(lo_x_bx); }
190  if (tpi_direction[1]) { tmp_bl.push_back(lo_y_bx); }
191  if (tpi_direction[3]) { tmp_bl.push_back(hi_x_bx); }
192  if (tpi_direction[4]) { tmp_bl.push_back(hi_y_bx); }
193  }
194 
195  //amrex::PrintToFile("BoxPerturbationOutput") << "\nBoxList: " << tmp_bl << "\n";
196  amrex::BoxArray tmp_ba(tmp_bl);
197  tmp_ba.maxSize(boxSize);
198 
199  const int num_levels = max_level + 1;
200  if (pb_ba.size() < num_levels) {
201  pb_ba.resize(num_levels);
202  pb_mag.resize(num_levels);
203  pb_dir.resize(num_levels);
204  pb_netZero.resize(num_levels);
205  pb_interval.resize(num_levels);
206  pb_local_etime.resize(num_levels);
207  pb_amp.resize(num_levels);
208  pb_cell.resize(num_levels);
209  tpi_Lpb.resize(num_levels);
210  tpi_Wpb.resize(num_levels);
211  tpi_Hpb.resize(num_levels);
212  tpi_lref.resize(num_levels);
213  }
214 
215  pb_ba[lev] = tmp_ba;
216 
217  // Initializing mean magnitude and direction vectors
218  pb_mag[lev].resize(pb_ba[lev].size(), zero);
219  pb_dir[lev].resize(pb_ba[lev].size(), zero);
220  pb_netZero[lev].resize(pb_ba[lev].size(), zero);
221 
222  // Set size of vector and initialize
223  pb_interval[lev].resize(pb_ba[lev].size(), -one);
224  pb_local_etime[lev].resize(pb_ba[lev].size(), zero);
225  pb_amp[lev].resize(pb_ba[lev].size(), zero);
226 
227  // Creating data array for perturbation amplitude storage
228  if (pt_type[lev] == 3) { // CPM_W converts to the k-face ba.
229  pb_cell[lev].define(convert(ba, amrex::IntVect(0,0,1)), dm, 1, ngrow_state);
230  } else {
231  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...
232  }
233  pb_cell[lev].setVal(0.);
234 
235  // Computing perturbation reference length
236  tpi_Lpb[lev] = tpi_boxDim[0]*dx[0];
237  tpi_Wpb[lev] = tpi_boxDim[1]*dx[1];
238  tpi_Hpb[lev] = tpi_boxDim[2]*dx[2];
239  tpi_lref[lev] = std::sqrt(tpi_Lpb[lev]*tpi_Lpb[lev] + tpi_Wpb[lev]*tpi_Wpb[lev]);
240 
243 
244  /*
245  // Function check point message
246  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_box_dims: "
247  << tpi_boxDim[0] << " "
248  << tpi_boxDim[1] << " "
249  << tpi_boxDim[2] << "\n";
250  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_direction: "
251  << tpi_direction[0] << " "
252  << tpi_direction[1] << " "
253  << tpi_direction[2] << "\n\n";
254  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_layers: " << tpi_layers << "\n";
255  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_offset: " << tpi_offset << "\n\n";
256  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_nondimensional: " << tpi_nonDim << "\n";
257  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_T_infinity: " << tpi_Tinf << "\n";
258  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_T_intensity: " << tpi_Ti << "\n";
259  amrex::PrintToFile("BoxPerturbationOutput") << "Reference length per box = " << tpi_lref[lev] << "\n\n";
260  amrex::PrintToFile("BoxPerturbationOutput") << "Turbulent perturbation BoxArray:\n" << pb_ba[lev] << "\n";
261  */
262  }
263 
264 
265  // Perturbation update frequency check.
266  // This function will trigger calc_tpi_meanMag() and calc_tpi_amp(), when
267  // the local elapsed time is greater than the perturbation frequency.
268  // At each timestep a total buoyant force sanity check is made to ensure
269  // that the sum of the buoyant force introduced into the system is net-zero
270  void calc_tpi_update (const int lev,
271  const double dt,
272  amrex::MultiFab& mf_xvel,
273  amrex::MultiFab& mf_yvel,
274  amrex::MultiFab& mf_cons)
275  {
276  // Resetting the net buoyant force value
278 
279  // Setting random number generator for update interval
280  srand( (unsigned) time(NULL) );
281 
282  auto m_ixtype = mf_cons.boxArray().ixType(); // safety step
283  if (pt_type[lev] == 3) { m_ixtype = amrex::IndexType(amrex::IntVect(0,0,1)); }
284 
285  // Seed the random generator at 1024UL for regression testing
286  int fix_random_seed = 0;
287  amrex::ParmParse pp("erf");
288  pp.query("fix_random_seed", fix_random_seed);
289  if (fix_random_seed) {
290  // We need this one for the ParalleForRNG used in calc_tpi
291  amrex::InitRandom(1024UL, amrex::ParallelDescriptor::NProcs(), 1024UL);
292 
293  // We need this one for the RandomReal below
294  srand(1024UL);
295  }
296 
297  for (int boxIdx = 0; boxIdx < pb_ba[lev].size(); boxIdx++) {
298 
299  bool update_box = true; // initialize flag
300  // Check if the local elapsed time is greater than the update interval and don't go into boxes the rank doesn't own
301  if (pt_type[lev] == 2 || pt_type[lev] == 3) {
302  // for CPM, perturbation boxes/cells refresh after boxes have advected box width/length * num_layers (advective time scale)
303  update_box = ((pb_local_etime[lev][boxIdx] >= pb_interval[lev][boxIdx]*tpi_layers) && pb_local_etime[lev][boxIdx] != -one);
304  } else {
305  update_box = ( pb_local_etime[lev][boxIdx] >= pb_interval[lev][boxIdx] && pb_local_etime[lev][boxIdx] != -one );
306  }
307  if ( update_box ) {
308 
309  // Compute mean velocity of each perturbation box
310  calc_tpi_meanMag_perBox(boxIdx, lev, mf_cons, mf_xvel, mf_yvel);
311 
312  // Only the rank owning the box will be able to access the storage location
313  // Done for parallelism to avoid Inf being stored in array
314  if (pb_mag[lev][boxIdx] !=zero) {
315  double interval = 0.0;
316  if (pt_type[lev] == 2 || pt_type[lev] == 3) {
317  // Wind direction correction for angled wind
318  amrex::Real wind_direction = pb_dir[lev][boxIdx];
319  if (wind_direction > PI / 4) { wind_direction = PI / 2 - wind_direction; }
320  // CPM only cares about the side length of a box, min call maintains flexibility.
321  interval = one / std::cos(wind_direction) * std::min(tpi_Lpb[lev], tpi_Wpb[lev]) / pb_mag[lev][boxIdx];
322  } else {
323  interval = tpi_lref[lev] / pb_mag[lev][boxIdx];
324  }
325  pb_interval[lev][boxIdx] = RandomReal(static_cast<amrex::Real>(0.9*interval),
326  static_cast<amrex::Real>(1.1*interval)); // 10% variation
327 
328  // Reset local elapsed time
329  pb_local_etime[lev][boxIdx] = zero;
330  } else {
331  // this box is not on this rank, we shouldn't enter it again.
332  // Technically, all boxes are looped through on the very first step of a simulation and this is when this is set
333  pb_local_etime[lev][boxIdx] = -one;
334  }
335 
336  // Trigger amplitude calculation per perturbation box
337  calc_tpi_amp(lev, boxIdx, pb_interval[lev][boxIdx]);
338 
339  // Trigger random amplitude storage per cell within perturbation box
340  pseudoRandomPert(boxIdx, lev, m_ixtype);
341 
342  } else {
343  // set perturbation amplitudes to 0 for CPM. A little inefficient but leverages as much as existing code as possible.
344  if (pt_type[lev] == 2 || pt_type[lev] == 3) { zero_amp(boxIdx, lev, m_ixtype); }
345 
346  // Increase by timestep of level 0 (but only if the box is owned by this rank)
347  if (pb_local_etime[lev][boxIdx] != -one) { pb_local_etime[lev][boxIdx] += dt; }
348  } // if
349 
350  if (pt_type[lev] < 2) { // box perturbation method only
351  // Per iteration operation of net-zero buoyant force check
352  if (pb_mag[lev][boxIdx] !=zero) netZeroBuoyantAdd(boxIdx, lev);
353  tpi_net_buoyant += pb_netZero[lev][boxIdx];
354  }
355  } // for
356 
357  if (pt_type[lev] < 2) { // box perturbation method only
358  // Normalizing the adjustment based on how many boxes there are
359  // the values within the array is already normalized by the number
360  // of cells within each box
362 
363  // Per iteration operation of net-zero buoyant force adjustment
364  for (int boxIdx = 0; boxIdx < pb_ba[lev].size(); boxIdx++) {
365  if (pb_mag[lev][boxIdx] !=zero) netZeroBuoyantAdjust(boxIdx, lev);
366  }
367  }
368  }
369 
370  // Applying perturbation amplitude onto source term (Umphrey and Senocak 2016)
371  // This function does per cell application within the box union. Random perturbation
372  // is assigned in calc_tpi_update.
373  void apply_tpi (const int& lev,
374  const amrex::Box& vbx, // box union from upper level
375  const int& comp, // Component to modify
376  const amrex::IndexType& m_ixtype, // IntVect type of src_arr
377  const amrex::Array4<amrex::Real>& src_arr, // Array to apply perturbation
378  const amrex::Array4<amrex::Real const>& pert_cell)
379  {
380  for (int boxIdx = 0; boxIdx < pb_ba[lev].size(); boxIdx++) {
381  amrex::Box pbx = amrex::convert(pb_ba[lev][boxIdx], m_ixtype);
382  if (pt_type[lev] == 3) { pbx.setBig(2, pbx.bigEnd(2) - 1); } // prevent double counting after converting to k-faces
383  amrex::Box ubx = pbx & vbx;
384  if (ubx.ok()) {
385  if (comp == -1) { // vertical velocity perturbations
386  ParallelFor(ubx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
387  src_arr(i,j,k) += pert_cell(i,j,k);
388  });
389  } else {
390  ParallelFor(ubx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
391  src_arr(i,j,k,comp) += pert_cell(i,j,k);
392 
393  // For box region debug only
394  #ifdef INDEX_PERTURB
395  src_arr(i,j,k,comp) = (amrex::Real) (boxIdx + 5.);
396  #endif
397  });
398  }
399  }
400  }
401  }
402 
403  // Perturbation amplitude calculation
404  void calc_tpi_amp (const int& lev,
405  const int& boxIdx,
406  const double& interval)
407  {
408  pb_amp[lev][boxIdx] = zero; // Safety step
409  if (pt_type[lev] == 2) { // CPM
410  amrex::Real cp = 1004; // specific heat of air [J/(kg K)]
411  amrex::Real Ec = amrex::Real(0.2); // Eckert number
412  pb_amp[lev][boxIdx] = (input_Ug * input_Ug) / (Ec * cp);
413  } else if (pt_type[lev] == 3) {
414  pb_amp[lev][boxIdx] = input_w_amp;
415  } else { // box perturbation
416  amrex::Real Um = pb_mag[lev][boxIdx];
417  amrex::Real beta = one/tpi_Tinf; // Thermal expansion coefficient
418 
419  // Pseudo Random temperature the ignores scale when mechanically tripping turbulence
421  // get total refinement ratio on each level
422  int total_ref_ratio = 1;
423  for (int level = lev; level >= 1; level--) {
424  total_ref_ratio *= ref_ratio[level-1][2];
425  }
426  // calculation needs to be scale-aware since the formulation relies on the physical size of the box
427  if (tpi_Ti > zero) g = (tpi_nonDim * Um * Um) / (tpi_Ti * tpi_Hpb[lev]) * 1 / total_ref_ratio;
428 
429  // Ma and Senocak (2023) Eq. 8, solving for delta phi
430  pb_amp[lev][boxIdx] = (tpi_nonDim * Um * Um) / (g * beta * tpi_Hpb[lev]) * 1 / total_ref_ratio;
431 
432  if (pt_type[lev] == 0) {
433  // Performing this step converts the perturbation proportionality into
434  // the forcing term
435  // Ma & Senocak (2023) Eq. 7
436  pb_amp[lev][boxIdx] /= static_cast<amrex::Real>(interval);
437  }
438  }
439  }
440 
441  // Assigns pseudo-random (ie. white noise) perturbation to a storage cell, this
442  // value is then held constant for the duration of the update interval and assigned onto
443  // the source term
444  void pseudoRandomPert (const int& boxIdx,
445  const int& lev,
446  const amrex::IndexType& m_ixtype)
447  {
448  for (amrex::MFIter mfi(pb_cell[lev],TileNoZ()); mfi.isValid(); ++mfi) {
449  amrex::Box vbx = mfi.validbox();
450  amrex::Box pbx = amrex::convert(pb_ba[lev][boxIdx], m_ixtype);
451  if (pt_type[lev] == 3) { pbx.setBig(2, pbx.bigEnd(2) - 1); } // prevent double counting after converting to k-faces
452  amrex::Box ubx = pbx & vbx;
453  if (ubx.ok()) {
454  amrex::Real amp_copy = pb_amp[lev][boxIdx];
455  const amrex::Array4<amrex::Real>& pert_cell = pb_cell[lev].array(mfi);
456 
457  if (pt_type[lev] == 2 || pt_type[lev] == 3) { // CPM
458  amrex::Real rand_number_const = RandomReal(-one, one);
459  ParallelFor(ubx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
460  pert_cell(i,j,k) = rand_number_const * amp_copy;
461  });
462  } else {
463  ParallelForRNG(ubx, [=] AMREX_GPU_DEVICE(int i, int j, int k, const amrex::RandomEngine& engine) noexcept {
464  amrex::Real rand_double = amrex::Random(engine);
465  pert_cell(i,j,k) = (rand_double*two - one) * amp_copy;
466  });
467  }
468  }
469  }
470  }
471 
472  // convert pert_cell value to be 0 so that perturbation amplitudes do not compound for CPM
473  void zero_amp (const int& boxIdx,
474  const int& lev,
475  const amrex::IndexType& m_ixtype)
476  {
477 
478  for (amrex::MFIter mfi(pb_cell[lev],TileNoZ()); mfi.isValid(); ++mfi) {
479  amrex::Box vbx = mfi.validbox();
480  amrex::Box pbx = amrex::convert(pb_ba[lev][boxIdx], m_ixtype);
481  if (pt_type[lev] == 3) { pbx.setBig(2, pbx.bigEnd(2) - 1); } // prevent double counting after converting to k-faces
482  amrex::Box ubx = pbx & vbx;
483  if (ubx.ok()) {
484  const amrex::Array4<amrex::Real>& pert_cell = pb_cell[lev].array(mfi);
485  ParallelFor(ubx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
486  pert_cell(i,j,k) = zero;
487  });
488  }
489  }
490  }
491 
492  // Checks for net-zero buoyant force introduction into the system
493  void netZeroBuoyantAdd (const int& boxIdx,
494  const int& lev)
495  {
496  // Creating local copy of PB box array and magnitude
497  const amrex::BoxArray m_pb_ba = pb_ba[lev];
498  amrex::Real* m_pb_netZero = pb_netZero[lev].data();
499 
500  // Create device array for summation
501  amrex::Vector<amrex::Real> avg_h(1,zero);
502  amrex::Gpu::DeviceVector<amrex::Real> avg_d(1,zero);
503  amrex::Real* avg = avg_d.data();
504 
505  // Iterates through the cells of each box and sum the white noise perturbation
506  for (amrex::MFIter mfi(pb_cell[lev], TileNoZ()) ; mfi.isValid(); ++mfi) {
507  const amrex::Box& vbx = mfi.validbox();
508  amrex::Box pbx = amrex::convert(m_pb_ba[boxIdx], vbx.ixType());
509  amrex::Box ubx = pbx & vbx;
510  if (ubx.ok()) {
511  const amrex::Array4<const amrex::Real>& pert_cell = pb_cell[lev].const_array(mfi);
512  amrex::Real norm = one / static_cast<amrex::Real>(ubx.numPts());
513  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubx, [=]
514  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
515  amrex::Gpu::deviceReduceSum(&avg[0], pert_cell(i,j,k)*norm, handler);
516  });
517  amrex::Gpu::copy(amrex::Gpu::deviceToHost, avg_d.begin(), avg_d.end(), avg_h.begin());
518 
519  // Assigning onto storage array
520  m_pb_netZero[boxIdx] = avg_h[0];
521  }
522  }
523  }
524 
525  // If it's not a net-zero buoyant force, then adjust all cell by a normalized value
526  // to achieve this logic
527  void netZeroBuoyantAdjust (const int& boxIdx,
528  const int& lev)
529  {
530  // Creating local copy of PB box array and magnitude
531  const amrex::BoxArray m_pb_ba = pb_ba[lev];
532  for (amrex::MFIter mfi(pb_cell[lev], TileNoZ()) ; mfi.isValid(); ++mfi) {
533  const amrex::Box& vbx = mfi.validbox();
534  amrex::Box pbx = amrex::convert(m_pb_ba[boxIdx], vbx.ixType());
535  amrex::Box ubx = pbx & vbx;
536  if (ubx.ok()) {
537  const amrex::Real adjust = tpi_pert_adjust;
538  const amrex::Array4<amrex::Real>& pert_cell = pb_cell[lev].array(mfi);
539  ParallelFor(ubx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
540  pert_cell(i,j,k) -= adjust;
541  });
542  }
543  }
544  }
545 
546 // TODO: Test the difference between these two for Source term perturbation
547 #define USE_VOLUME_AVERAGE
548  // Perturbation box mean velocity magnitude calculation
549  // This is pulled into the structure to also utilize during runtime
550  void calc_tpi_meanMag_perBox (const int& boxIdx,
551  const int& lev,
552  amrex::MultiFab& mf_cons,
553  amrex::MultiFab& mf_xvel,
554  amrex::MultiFab& mf_yvel)
555 
556  {
557  // Creating local copy of PB box array and magnitude
558  const amrex::BoxArray m_pb_ba = pb_ba[lev];
559  amrex::Real* m_pb_mag = pb_mag[lev].data();
560  amrex::Real* m_pb_dir = pb_dir[lev].data();
561  m_pb_mag[boxIdx] = zero; // Safety step
562  m_pb_dir[boxIdx] = zero; // Safety step
563 
564  // Storage of averages per PB
565  // Index: 0=u (vol/slab_lo), 1=v (vol/slab_lo)
566  // 2=u (slab_hi), 3=v (slab_hi)
567  int n_avg = 4;
568  amrex::Vector<amrex::Real> avg_h(n_avg,zero);
569  amrex::Gpu::DeviceVector<amrex::Real> avg_d(n_avg,zero);
570  amrex::Real* avg = avg_d.data();
571 
572  // Averaging u & v components in single MFIter
573  for (amrex::MFIter mfi(mf_cons, TileNoZ()); mfi.isValid(); ++mfi) {
574 
575  // CC valid box (inherited from mf_cons)
576  const amrex::Box& vbx = mfi.validbox();
577 
578  // Box logic for u velocity
579  auto ixtype_u = mf_xvel.boxArray().ixType();
580  amrex::Box vbx_u = amrex::convert(vbx,ixtype_u);
581  amrex::Box pbx_u = amrex::convert(m_pb_ba[boxIdx], ixtype_u);
582  amrex::Box ubx_u = pbx_u & vbx_u;
583 
584  // Box logic for v velocity
585  auto ixtype_v = mf_yvel.boxArray().ixType();
586  amrex::Box vbx_v = amrex::convert(vbx,ixtype_v);
587  amrex::Box pbx_v = amrex::convert(m_pb_ba[boxIdx], ixtype_v);
588  amrex::Box ubx_v = pbx_v & vbx_v;
589 
590  // Operation over box union (U)
591  if (ubx_u.ok()) {
592  const amrex::Array4<const amrex::Real>& xvel_arry = mf_xvel.const_array(mfi);
593 
594  #ifdef USE_VOLUME_AVERAGE
595  amrex::Real norm = one / static_cast<amrex::Real>(ubx_u.numPts());
596  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubx_u, [=]
597  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
598  amrex::Gpu::deviceReduceSum(&avg[0], xvel_arry(i,j,k)*norm, handler);
599  });
600  #endif // USE_VOLUME_AVERAGE
601 
602  #ifdef USE_SLAB_AVERAGE
603  amrex::Box ubxSlab_lo = makeSlab(ubx_u,2,ubx_u.smallEnd(2));
604  amrex::Box ubxSlab_hi = makeSlab(ubx_u,2,ubx_u.bigEnd(2));
605  amrex::Real norm_lo = one / static_cast<amrex::Real>(ubxSlab_lo.numPts());
606  amrex::Real norm_hi = one / static_cast<amrex::Real>(ubxSlab_hi.numPts());
607 
608  // Average u in the low slab
609  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubxSlab_lo, [=]
610  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
611  amrex::Gpu::deviceReduceSum(&avg[0], xvel_arry(i,j,k)*norm_lo, handler);
612  });
613 
614  // Average u in the high slab
615  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubxSlab_hi, [=]
616  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
617  amrex::Gpu::deviceReduceSum(&avg[2], xvel_arry(i,j,k)*norm_hi, handler);
618  });
619  #endif // USE_SLAB_AVERAGE
620  } // if
621 
622  // Operation over box union (V)
623  if (ubx_v.ok()) {
624  const amrex::Array4<const amrex::Real>& yvel_arry = mf_yvel.const_array(mfi);
625 
626  #ifdef USE_VOLUME_AVERAGE
627  amrex::Real norm = one / static_cast<amrex::Real>(ubx_v.numPts());
628  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubx_v, [=]
629  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
630  amrex::Gpu::deviceReduceSum(&avg[1], yvel_arry(i,j,k)*norm, handler);
631  });
632  #endif // USE_VOLUME_AVERAGE
633 
634  #ifdef USE_SLAB_AVERAGE
635  amrex::Box ubxSlab_lo = makeSlab(ubx_v,2,ubx_v.smallEnd(2));
636  amrex::Box ubxSlab_hi = makeSlab(ubx_v,2,ubx_v.bigEnd(2));
637  amrex::Real norm_lo = one / static_cast<amrex::Real>(ubxSlab_lo.numPts());
638  amrex::Real norm_hi = one / static_cast<amrex::Real>(ubxSlab_hi.numPts());
639 
640  // Average v in the low slab
641  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubxSlab_lo, [=]
642  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
643  amrex::Gpu::deviceReduceSum(&avg[1], yvel_arry(i,j,k)*norm_lo, handler);
644  });
645 
646  // Average v in the high slab
647  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubxSlab_hi, [=]
648  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
649  amrex::Gpu::deviceReduceSum(&avg[3], yvel_arry(i,j,k)*norm_hi, handler);
650  });
651  #endif // USE_SLAB_AVERAGE
652  } // if
653  } // MFIter
654 
655  // Copy from device back to host
656  amrex::Gpu::copy(amrex::Gpu::deviceToHost, avg_d.begin(), avg_d.end(), avg_h.begin());
657 
658  // Computing the average magnitude within PB
659  #ifdef USE_VOLUME_AVERAGE
660  m_pb_mag[boxIdx] = std::sqrt(avg_h[0]*avg_h[0] + avg_h[1]*avg_h[1]);
661  m_pb_dir[boxIdx] = std::atan(std::abs(avg_h[0]) / std::abs(avg_h[1]+std::numeric_limits<amrex::Real>::epsilon()));
662  #endif
663 
664  #ifdef USE_SLAB_AVERAGE
665  m_pb_mag[boxIdx] = myhalf*( std::sqrt(avg_h[0]*avg_h[0] + avg_h[1]*avg_h[1])
666  + std::sqrt(avg_h[2]*avg_h[2] + avg_h[3]*avg_h[3]));
667  m_pb_dir[boxIdx] = std::atan(std::abs(myhalf*(avg_h[0]+avg_h[2])) / std::abs(myhalf*(avg_h[1]+avg_h[3])+std::numeric_limits<amrex::Real>::epsilon()));
668  #endif
669  }
670 
671  // Output debug message into a file
672  void debug (double /*time*/)
673  {
674  /*
675  amrex::PrintToFile("BoxPerturbationOutput") << "#################### PB output at time = "
676  << time << " ####################\n";
677  amrex::PrintToFile("BoxPerturbationOutput") << " Using type: " << pt_type << "\n";
678  amrex::PrintToFile("BoxPerturbationOutput") << " Net: " << tpi_net_buoyant << " Adjust : " << tpi_pert_adjust << "\n";
679  for (int i = 0; i < pb_mag.size(); i++) {
680  amrex::PrintToFile("BoxPerturbationOutput") << "[" << i
681  << "] pb_Umag=" << pb_mag[i]
682  << " | pb_interval=" << pb_interval[i]
683  << " (" << pb_local_etime[i]
684  << ") | pb_amp=" << pb_amp[i] << "\n";
685  }
686  amrex::PrintToFile("BoxPerturbationOutput") << "\n";
687  */
688  }
689 
690  amrex::Vector<int> pt_type;
691 
692  // Public data members
693  amrex::Vector<amrex::BoxArray> pb_ba; // PB box array
694  amrex::Vector<amrex::Vector<amrex::Real>> pb_mag; // BP mean magnitude [m/s]
695  amrex::Vector<amrex::Vector<amrex::Real>> pb_dir; // BP mean direction [deg]
696 
697  // Perturbation amplitude cell storage
698  // This is after random assignment of equation (10) in Ma and Senocak 2023
699  amrex::Vector<amrex::MultiFab> pb_cell;
700 
701  private:
702 
703  // Private data members
704  int tpi_layers; // Number of layers of perturbation boxes
705  int tpi_offset; // Cells to offset the start of the perturbation region
706 
707  amrex::Vector<int> tpi_boxDim; // Dimensions of each perturbation box
708  amrex::Vector<int> tpi_direction; // Direction of the perturbation
709 
710  // Richardson Formulation
711  amrex::Real tpi_nonDim; // Richardson number
712  amrex::Real tpi_Ti; // Temperature intensity value
713  amrex::Real tpi_Tinf; // Reference temperature [K]
714 
715  // Physical dimensions
716  amrex::Vector<amrex::Real> tpi_Hpb; // PB height [m]
717  amrex::Vector<amrex::Real> tpi_Lpb; // PB length [m]
718  amrex::Vector<amrex::Real> tpi_Wpb; // PB width [m]
719  amrex::Vector<amrex::Real> tpi_lref; // PB reference length [m]
720 
721  amrex::Real tpi_net_buoyant; // Perturbation net-zero calculation storage
722  amrex::Real tpi_pert_adjust; // Perturbation adjust for net-zero per cell adjustment
723 
724  amrex::Vector<amrex::IntVect> ref_ratio; // ref_ratio for multilevel run
725  amrex::Real input_Ug; // input geostrophic wind speed to scale perturbations when using CPM
726  amrex::Real input_w_amp; // input vertical velocity amplitude for w
727  int perturbation_klo; // upper bound in Nz for perturbations
728  int perturbation_khi; // lower bound in Nz for perturbations
729 
730  // Perturbation data arrays
731  amrex::Vector<amrex::Vector<double>> pb_interval; // PB update time [s]
732  amrex::Vector<amrex::Vector<double>> pb_local_etime; // PB local elapsed time [s]
733  amrex::Vector<amrex::Vector<amrex::Real>> pb_amp; // PB perturbation amplitude Ri:[K]
734  amrex::Vector<amrex::Vector<amrex::Real>> pb_netZero; // PB array used for net zero sum calculation
735 
736  // Random number generation between range (used for interval calculation)
738  {
739  amrex::Real r = (amrex::Real) rand() / (amrex::Real) RAND_MAX;
740  return min + r * (max - min);
741  }
742 
743 };
744 #endif
constexpr amrex::Real two
Definition: ERF_Constants.H:10
constexpr amrex::Real one
Definition: ERF_Constants.H:9
constexpr amrex::Real zero
Definition: ERF_Constants.H:8
constexpr amrex::Real myhalf
Definition: ERF_Constants.H:13
constexpr amrex::Real PI
Definition: ERF_Constants.H:42
constexpr amrex::Real CONST_GRAV
Definition: ERF_Constants.H:64
ParmParse pp("prob")
ParallelForRNG(bx, [=] AMREX_GPU_DEVICE(int i, int j, int k, const amrex::RandomEngine &engine) noexcept { const Real x=prob_lo_x+(i+myhalf) *dx;const Real y=prob_lo_y+(j+myhalf) *dy;const Real z=z_cc(i, j, k);const Real r=std::sqrt((x-xc) *(x-xc)+(y-yc) *(y-yc)+(z-zc) *(z-zc));if((z<=pert_ref_height) &&(T_0_Pert_Mag !=amrex::Real(0))) { Real rand_double=amrex::Random(engine);state_pert(i, j, k, RhoTheta_comp)=(rand_double *amrex::Real(2) - amrex::Real(1)) *T_0_Pert_Mag;if(!pert_rhotheta) { state_pert(i, j, k, RhoTheta_comp) *=r_hse(i, j, k);} } state_pert(i, j, k, RhoScalar_comp)=A_0 *std::exp(-amrex::Real(10.) *r *r);if(state_pert.nComp() > RhoKE_comp) { if(rhoKE_0 > 0) { state_pert(i, j, k, RhoKE_comp)=rhoKE_0;} else { state_pert(i, j, k, RhoKE_comp)=r_hse(i, j, k) *KE_0;} if(KE_decay_height > 0) { state_pert(i, j, k, RhoKE_comp) *=amrex::max(std::pow(1 - amrex::min(z/KE_decay_height, amrex::Real(1)), KE_decay_order), Real(1e-12));} } })
const Real dx
Definition: ERF_InitCustomPert_ABL.H:23
amrex::Real beta
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:10
ParallelFor(grown_box, [=] AMREX_GPU_DEVICE(int i, int j, int k) { qrcuten_arr(i, j, k)=Real(0);qscuten_arr(i, j, k)=Real(0);qicuten_arr(i, j, k)=Real(0);})
amrex::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)
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:22
amrex::Vector< amrex::MultiFab > pb_cell
Definition: ERF_TurbPertStruct.H:699
void calc_tpi_update(const int lev, const double dt, amrex::MultiFab &mf_xvel, amrex::MultiFab &mf_yvel, amrex::MultiFab &mf_cons)
Definition: ERF_TurbPertStruct.H:270
void calc_tpi_amp(const int &lev, const int &boxIdx, const double &interval)
Definition: ERF_TurbPertStruct.H:404
amrex::Real input_w_amp
Definition: ERF_TurbPertStruct.H:726
int tpi_layers
Definition: ERF_TurbPertStruct.H:704
void pseudoRandomPert(const int &boxIdx, const int &lev, const amrex::IndexType &m_ixtype)
Definition: ERF_TurbPertStruct.H:444
amrex::Real tpi_Tinf
Definition: ERF_TurbPertStruct.H:713
amrex::Vector< amrex::IntVect > ref_ratio
Definition: ERF_TurbPertStruct.H:724
amrex::Real input_Ug
Definition: ERF_TurbPertStruct.H:725
amrex::Vector< amrex::BoxArray > pb_ba
Definition: ERF_TurbPertStruct.H:693
void netZeroBuoyantAdjust(const int &boxIdx, const int &lev)
Definition: ERF_TurbPertStruct.H:527
amrex::Vector< amrex::Vector< amrex::Real > > pb_mag
Definition: ERF_TurbPertStruct.H:694
amrex::Vector< amrex::Real > tpi_lref
Definition: ERF_TurbPertStruct.H:719
int perturbation_khi
Definition: ERF_TurbPertStruct.H:728
amrex::Vector< int > pt_type
Definition: ERF_TurbPertStruct.H:690
int tpi_offset
Definition: ERF_TurbPertStruct.H:705
amrex::Vector< amrex::Real > tpi_Hpb
Definition: ERF_TurbPertStruct.H:716
amrex::Vector< amrex::Vector< double > > pb_interval
Definition: ERF_TurbPertStruct.H:731
void init_tpi_type(const int lev, const PerturbationType &pert_type, const int max_level)
Definition: ERF_TurbPertStruct.H:28
amrex::Vector< amrex::Vector< amrex::Real > > pb_netZero
Definition: ERF_TurbPertStruct.H:734
void debug(double)
Definition: ERF_TurbPertStruct.H:672
amrex::Real tpi_Ti
Definition: ERF_TurbPertStruct.H:712
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)
Definition: ERF_TurbPertStruct.H:54
amrex::Real tpi_pert_adjust
Definition: ERF_TurbPertStruct.H:722
amrex::Vector< amrex::Vector< amrex::Real > > pb_dir
Definition: ERF_TurbPertStruct.H:695
void zero_amp(const int &boxIdx, const int &lev, const amrex::IndexType &m_ixtype)
Definition: ERF_TurbPertStruct.H:473
amrex::Vector< int > tpi_boxDim
Definition: ERF_TurbPertStruct.H:707
amrex::Vector< amrex::Real > tpi_Wpb
Definition: ERF_TurbPertStruct.H:718
amrex::Real tpi_nonDim
Definition: ERF_TurbPertStruct.H:711
void calc_tpi_meanMag_perBox(const int &boxIdx, const int &lev, amrex::MultiFab &mf_cons, amrex::MultiFab &mf_xvel, amrex::MultiFab &mf_yvel)
Definition: ERF_TurbPertStruct.H:550
void netZeroBuoyantAdd(const int &boxIdx, const int &lev)
Definition: ERF_TurbPertStruct.H:493
~TurbulentPerturbation()
Definition: ERF_TurbPertStruct.H:26
amrex::Real RandomReal(const amrex::Real min, const amrex::Real max)
Definition: ERF_TurbPertStruct.H:737
amrex::Vector< amrex::Real > tpi_Lpb
Definition: ERF_TurbPertStruct.H:717
int perturbation_klo
Definition: ERF_TurbPertStruct.H:727
amrex::Real tpi_net_buoyant
Definition: ERF_TurbPertStruct.H:721
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)
Definition: ERF_TurbPertStruct.H:373
amrex::Vector< amrex::Vector< double > > pb_local_etime
Definition: ERF_TurbPertStruct.H:732
amrex::Vector< int > tpi_direction
Definition: ERF_TurbPertStruct.H:708
amrex::Vector< amrex::Vector< amrex::Real > > pb_amp
Definition: ERF_TurbPertStruct.H:733