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 
19 
20  public:
21 
23 
24  // Initializing Perturbation Region
25  // Currently only support perturbation in the x and y direction
26  // This portion of the code is only called once for initialization,
27  // therefore efficiency is not required. Dont spend too much time
28  // here other than to correctly setup the perturbation box region
29  void init_tpi (const int lev,
30  const amrex::IntVect& nx,
31  const amrex::GpuArray<amrex::Real,3> dx,
32  const amrex::BoxArray& ba,
33  const amrex::DistributionMapping& dm,
34  const int ngrow_state,
35  std::string pp_prefix)
36 
37  {
38  // Initialization for some 0 dependent terms
39  tpi_Ti = 0.;
40  tpi_Tinf = 300.;
41 
42  amrex::ParmParse pp(pp_prefix);
43 
44  // Reading inputs, and placing assertion for the perturbation inflow to work
45  pp.queryarr("perturbation_box_dims",tpi_boxDim);
46  pp.queryarr("perturbation_direction",tpi_direction);
47  pp.query("perturbation_layers",tpi_layers);
48  pp.query("perturbation_offset",tpi_offset);
49 
50  pp.query("perturbation_nondimensional",tpi_nonDim);
51  pp.query("perturbation_T_infinity",tpi_Tinf);
52  pp.query("perturbation_T_intensity",tpi_Ti);
53 
54  // Check variables message
55  if (tpi_layers < 0) { amrex::Abort("Please provide a valid perturbation layer value (ie. 3-5)"); }
56  if (tpi_offset < 0) { amrex::Abort("Please provide a valid inflow cell offset value for perturbation region (ie. 0-5)"); }
57  for (int i = 0; i < tpi_boxDim.size(); i++) {
58  if (tpi_boxDim[i] == 0) { amrex::Abort("Please provide valid dimensions for perturbation boxes."); }
59  }
60  if (tpi_nonDim < 0.) { amrex::Abort("Please provide a valid nondimensional number (ie. Ri = 0.042)"); }
61  if (tpi_Tinf < 0.) { amrex::Abort("Please provide a valid ambient temperature value (ie. T_0 = T_infty)"); }
62  if (tpi_Ti < 0.) { amrex::Abort("Please provide a valid temperature intensity value (ie. 0-1.0)"); }
63 
64  // Creating perturbation regions and initializing with generic size. Temporary size for now
65  amrex::Box lo_x_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
66  amrex::Box hi_x_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
67  amrex::Box lo_y_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
68  amrex::Box hi_y_bx(amrex::IntVect(0), amrex::IntVect(1), amrex::IntVect(0));
69 
70  // Create a temporary box list to accumulate all the perturbation regions after box modification
71  amrex::BoxList tmp_bl;
72 
73  // boxSize for individual boxes
74  amrex::IntVect boxSize(tpi_boxDim[0],tpi_boxDim[1],tpi_boxDim[2]);
75 
76  // Starting logic to set the size of the perturbation region(s)
77  //amrex::PrintToFile("BoxPerturbationOutput") << "Setting perturbation region in:";
78  // ***** X-direction perturbation *****
79  if (tpi_direction[0]) { // West
80  lo_x_bx.setSmall(amrex::IntVect(tpi_offset, tpi_direction[1]*tpi_offset, 0));
81  lo_x_bx.setBig(amrex::IntVect((tpi_layers*tpi_boxDim[0]-1)+tpi_offset, nx[1]-(tpi_direction[4]*tpi_offset), nx[2]));
82  //amrex::PrintToFile("BoxPerturbationOutput") << " West face";
83  }
84 
85  if (tpi_direction[3]) { // East
86  hi_x_bx.setSmall(amrex::IntVect(nx[0]-((tpi_layers*tpi_boxDim[0]-1)+tpi_offset), tpi_direction[1]*tpi_offset, 0));
87  hi_x_bx.setBig(amrex::IntVect(nx[0]-tpi_offset, nx[1]-(tpi_direction[4]*tpi_offset), nx[2]));
88  //amrex::PrintToFile("BoxPerturbationOutput") << " East face";
89  }
90 
91  // ***** Y-direction Perturbation *****
92  if (tpi_direction[1]) { // North
93  lo_y_bx.setSmall(amrex::IntVect(tpi_direction[0]*tpi_offset, tpi_offset, 0));
94  lo_y_bx.setBig(amrex::IntVect(nx[0]-tpi_direction[3]*tpi_offset, ((tpi_layers*tpi_boxDim[1])-1)+tpi_offset, nx[2]));
95  //amrex::PrintToFile("BoxPerturbationOutput") << " North face";
96  }
97 
98  if (tpi_direction[4]) { // South
99  hi_y_bx.setSmall(amrex::IntVect(tpi_direction[0]*tpi_offset, nx[1]-((tpi_layers*tpi_boxDim[1]-1)+tpi_offset), 0));
100  hi_y_bx.setBig(amrex::IntVect(nx[0]-tpi_direction[3]*tpi_offset, nx[1]-tpi_offset, nx[2]));
101  //amrex::PrintToFile("BoxPerturbationOutput") << " South face";
102  }
103 
104  if (tpi_direction[2] || tpi_direction[5]) { amrex::Abort("Currently not supporting z-direction flow perturbation"); }
105 
106  // Performing box union for intersecting perturbation regions to avoid overlapping sections (double counting at corners)
107  if (tpi_direction[0] && tpi_direction[1]) { // Reshaping South smallEnd
108  amrex::Box lo_x_lo_y_u = lo_x_bx & lo_y_bx;
109  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)));
110  }
111 
112  if (tpi_direction[3] && tpi_direction[1]) { // Reshaping South bigEnd
113  amrex::Box hi_x_lo_y_u = hi_x_bx & lo_y_bx;
114  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)));
115  }
116 
117  if (tpi_direction[0] && tpi_direction[4]) { // Reshaping North smallEnd
118  amrex::Box lo_x_hi_y_u = lo_x_bx & hi_y_bx;
119  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)));
120  }
121 
122  if (tpi_direction[3] && tpi_direction[4]) { // Reshaping North bigEnd
123  amrex::Box hi_x_hi_y_u = hi_x_bx & hi_y_bx;
124  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)));
125  }
126 
127  // Creating structure box array for conserved quantity
128  if (tpi_direction[0]) { tmp_bl.push_back(lo_x_bx); }
129  if (tpi_direction[1]) { tmp_bl.push_back(lo_y_bx); }
130  if (tpi_direction[3]) { tmp_bl.push_back(hi_x_bx); }
131  if (tpi_direction[4]) { tmp_bl.push_back(hi_y_bx); }
132  //amrex::PrintToFile("BoxPerturbationOutput") << "\nBoxList: " << tmp_bl << "\n";
133  amrex::BoxArray tmp_ba(tmp_bl);
134  tmp_ba.maxSize(boxSize);
135  pb_ba.push_back(tmp_ba);
136 
137  // Initializing mean magnitude vector
138  pb_mag.resize(pb_ba[lev].size(), 0.);
139  pb_netZero.resize(pb_ba[lev].size(), 0.);
140 
141  // Set size of vector and initialize
142  pb_interval.resize(pb_ba[lev].size(), -1.0);
143  pb_local_etime.resize(pb_ba[lev].size(), 0.0);
144  pb_amp.resize(pb_ba[lev].size(), 0.0);
145 
146  // Creating data array for perturbation amplitude storage
147  pb_cell.define(ba, dm, 1, ngrow_state);
148  pb_cell.setVal(0.);
149 
150  // Computing perturbation reference length
151  tpi_Lpb = tpi_boxDim[0]*dx[0];
152  tpi_Wpb = tpi_boxDim[1]*dx[1];
153  tpi_Hpb = tpi_boxDim[2]*dx[2];
155 
156  tpi_pert_adjust = 0.;
157  tpi_net_buoyant = 0.;
158 
159  /*
160  // Function check point message
161  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_box_dims: "
162  << tpi_boxDim[0] << " "
163  << tpi_boxDim[1] << " "
164  << tpi_boxDim[2] << "\n";
165  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_direction: "
166  << tpi_direction[0] << " "
167  << tpi_direction[1] << " "
168  << tpi_direction[2] << "\n\n";
169  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_layers: " << tpi_layers << "\n";
170  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_offset: " << tpi_offset << "\n\n";
171  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_nondimensional: " << tpi_nonDim << "\n";
172  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_T_infinity: " << tpi_Tinf << "\n";
173  amrex::PrintToFile("BoxPerturbationOutput") << "perturbation_T_intensity: " << tpi_Ti << "\n";
174  amrex::PrintToFile("BoxPerturbationOutput") << "Reference length per box = " << tpi_lref << "\n\n";
175  amrex::PrintToFile("BoxPerturbationOutput") << "Turbulent perturbation BoxArray:\n" << pb_ba[lev] << "\n";
176  */
177  }
178 
179 
180  // Perturbation update frequency check.
181  // This function will trigger calc_tpi_meanMag() and calc_tpi_amp(), when
182  // the local elapsed time is greater than the perturbation frequency.
183  // At each timestep a total buoyant force sanity check is made to ensure
184  // that the sum of the buoyant force introduced into the system is net-zero
185  void calc_tpi_update (const int lev,
186  const amrex::Real dt,
187  amrex::MultiFab& mf_xvel,
188  amrex::MultiFab& mf_yvel,
189  amrex::MultiFab& mf_cons)
190  {
191  // Resettubg the net buoyant force value
192  tpi_net_buoyant = 0.;
193 
194  // Setting random number generator for update interval
195  srand( (unsigned) time(NULL) );
196 
197  auto m_ixtype = mf_cons.boxArray().ixType(); // safety step
198 
199  for (int boxIdx = 0; boxIdx < pb_ba[lev].size(); boxIdx++) {
200 
201  // Check if the local elapsed time is greater than the update interval
202  if ( pb_interval[boxIdx] <= pb_local_etime[boxIdx] ) {
203 
204  // Compute mean velocity of each perturbation box
205  calc_tpi_meanMag_perBox(boxIdx, lev, mf_cons, mf_xvel, mf_yvel);
206 
207  // Only the rank owning the box will be able to access the storage location
208  // Done for parallelism to avoid Inf being stored in array
209  if (pb_mag[boxIdx] !=0.) {
210  amrex::Real interval = tpi_lref / pb_mag[boxIdx];
211  pb_interval[boxIdx] = RandomReal(0.9*interval,1.1*interval); // 10% variation
212  }
213 
214  // Trigger amplitude calculation per perturbation box
215  calc_tpi_amp(boxIdx, pb_interval[boxIdx]);
216 
217  // Trigger random amplitude storage per cell within perturbation box
218  pseudoRandomPert(boxIdx, lev, m_ixtype);
219 
220  // Reset local elapsed time
221  pb_local_etime[boxIdx] = 0.;
222  } else {
223  // Increase by timestep of level 0
224  pb_local_etime[boxIdx] += dt;
225  } // if
226 
227  // Per iteration operation of net-zero buoyant force check
228  if (pb_mag[boxIdx] !=0.) netZeroBuoyantAdd(boxIdx, lev);
229  tpi_net_buoyant += pb_netZero[boxIdx];
230  } // for
231 
232  // Normalizing the adjustment based on how many boxes there are
233  // the values within the array is already normalized by the number
234  // of cells within each box
235  tpi_pert_adjust = tpi_net_buoyant / (amrex::Real) pb_ba[lev].size();
236 
237  // Per iteration operation of net-zero buoyant force adjustment
238  for (int boxIdx = 0; boxIdx < pb_ba[lev].size(); boxIdx++) {
239  if (pb_mag[boxIdx] !=0.) netZeroBuoyantAdjust(boxIdx, lev);
240  }
241  }
242 
243  // Applying perturbation amplitude onto source term (Umphrey and Senocak 2016)
244  // This function does per cell application within the box union. Random perturbation
245  // is assigned in calc_tpi_update.
246  void apply_tpi (const int& lev,
247  const amrex::Box& vbx, // box union from upper level
248  const int& comp, // Component to modify
249  const amrex::IndexType& m_ixtype, // IntVect type of src_arr
250  const amrex::Array4<amrex::Real>& src_arr, // Array to apply perturbation
251  const amrex::Array4<amrex::Real const>& pert_cell)
252  {
253  for (int boxIdx = 0; boxIdx < pb_ba[lev].size(); boxIdx++) {
254  amrex::Box pbx = amrex::convert(pb_ba[lev][boxIdx], m_ixtype);
255  amrex::Box ubx = pbx & vbx;
256  if (ubx.ok()) {
257  ParallelFor(ubx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
258  src_arr(i,j,k,comp) += pert_cell(i,j,k);
259 
260  // For box region debug only
261  #ifdef INDEX_PERTURB
262  src_arr(i,j,k,comp) = (amrex::Real) (boxIdx + 5.);
263  #endif
264  });
265  }
266  }
267  }
268 
269  // Perturbation amplitude calculation
270  void calc_tpi_amp (const int& boxIdx,
271  const amrex::Real& interval)
272  {
273  amrex::Real Um = pb_mag[boxIdx];
274  pb_amp[boxIdx] = 0.; // Safety step
275 
276  amrex::Real beta = 1./tpi_Tinf; // Thermal expansion coefficient
277 
278  // Pseudo Random temperature the ignores scale when mechanically tripping turbulence
279  amrex::Real g = CONST_GRAV;
280  if (tpi_Ti > 0.) g = (tpi_nonDim * Um * Um) / (tpi_Ti * tpi_Hpb);
281 
282  // Ma and Senocak (2023) Eq. 8, solving for delta phi
283  pb_amp[boxIdx] = (tpi_nonDim * Um * Um) / (g * beta * tpi_Hpb);
284 
285  if (pt_type == 0) {
286  // Performing this step converts the perturbation proportionality into
287  // the forcing term
288  // Ma & Senocak (2023) Eq. 7
289  pb_amp[boxIdx] /= interval;
290  }
291  }
292 
293  // Assigns pseudo-random (ie. white noise) perturbation to a storage cell, this
294  // value is then held constant for the duration of the update interval and assigned onto
295  // the source term
296  void pseudoRandomPert (const int& boxIdx,
297  const int& lev,
298  const amrex::IndexType& m_ixtype)
299  {
300  // Seed the random generator at 1024UL for regression testing
301  int fix_random_seed = 0;
302  amrex::ParmParse pp("erf"); pp.query("fix_random_seed", fix_random_seed);
303  if (fix_random_seed) {
304  amrex::InitRandom(1024UL);
305  }
306 
307  for (amrex::MFIter mfi(pb_cell,TileNoZ()); mfi.isValid(); ++mfi) {
308  amrex::Box vbx = mfi.validbox();
309  amrex::Box pbx = amrex::convert(pb_ba[lev][boxIdx], m_ixtype);
310  amrex::Box ubx = pbx & vbx;
311  if (ubx.ok()) {
312  amrex::Real amp_copy = pb_amp[boxIdx];
313  const amrex::Array4<amrex::Real>& pert_cell = pb_cell.array(mfi);
314  ParallelForRNG(ubx, [=] AMREX_GPU_DEVICE(int i, int j, int k, const amrex::RandomEngine& engine) noexcept {
315  amrex::Real rand_double = amrex::Random(engine);
316  pert_cell(i,j,k) = (rand_double*2.0 - 1.0) * amp_copy;
317  });
318  }
319  }
320  }
321 
322  // Checks for net-zero buoyant force introduction into the system
323  void netZeroBuoyantAdd (const int& boxIdx,
324  const int& lev)
325  {
326  // Creating local copy of PB box array and magnitude
327  const amrex::BoxArray m_pb_ba = pb_ba[lev];
328  amrex::Real* m_pb_netZero = get_pb_netZero();
329 
330  // Create device array for summation
331  amrex::Vector<amrex::Real> avg_h(1,0.);
332  amrex::Gpu::DeviceVector<amrex::Real> avg_d(1,0.);
333  amrex::Real* avg = avg_d.data();
334 
335  // Iterates through the cells of each box and sum the white noise perturbation
336  for (amrex::MFIter mfi(pb_cell, TileNoZ()) ; mfi.isValid(); ++mfi) {
337  const amrex::Box& vbx = mfi.validbox();
338  amrex::Box pbx = amrex::convert(m_pb_ba[boxIdx], vbx.ixType());
339  amrex::Box ubx = pbx & vbx;
340  if (ubx.ok()) {
341  const amrex::Array4<const amrex::Real>& pert_cell = pb_cell.const_array(mfi);
342  int npts = ubx.numPts();
343  amrex::Real norm = 1.0 / (amrex::Real) npts;
344  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubx, [=]
345  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
346  amrex::Gpu::deviceReduceSum(&avg[0], pert_cell(i,j,k)*norm, handler);
347  });
348  amrex::Gpu::copy(amrex::Gpu::deviceToHost, avg_d.begin(), avg_d.end(), avg_h.begin());
349 
350  // Assigning onto storage array
351  m_pb_netZero[boxIdx] = avg_h[0];
352  }
353  }
354  }
355 
356  // If it's not a net-zero buoyant force, then adjust all cell by a normalized value
357  // to achieve this logic
358  void netZeroBuoyantAdjust (const int& boxIdx,
359  const int& lev)
360  {
361  // Creating local copy of PB box array and magnitude
362  const amrex::BoxArray m_pb_ba = pb_ba[lev];
363  for (amrex::MFIter mfi(pb_cell, TileNoZ()) ; mfi.isValid(); ++mfi) {
364  const amrex::Box& vbx = mfi.validbox();
365  amrex::Box pbx = amrex::convert(m_pb_ba[boxIdx], vbx.ixType());
366  amrex::Box ubx = pbx & vbx;
367  if (ubx.ok()) {
368  const amrex::Real adjust = tpi_pert_adjust;
369  const amrex::Array4<amrex::Real>& pert_cell = pb_cell.array(mfi);
370  ParallelFor(ubx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
371  pert_cell(i,j,k) -= adjust;
372  });
373  }
374  }
375  }
376 
377 // TODO: Test the difference between these two for Source term perturbation
378 #define USE_VOLUME_AVERAGE
379  // Perturbation box mean velocity magnitude calculation
380  // This is pulled into the structure to also utilize during runtime
381  void calc_tpi_meanMag_perBox (const int& boxIdx,
382  const int& lev,
383  amrex::MultiFab& mf_cons,
384  amrex::MultiFab& mf_xvel,
385  amrex::MultiFab& mf_yvel)
386 
387  {
388  // Creating local copy of PB box array and magnitude
389  const amrex::BoxArray m_pb_ba = pb_ba[lev];
390  amrex::Real* m_pb_mag = get_pb_mag();
391  m_pb_mag[boxIdx] = 0.; // Safety step
392 
393  // Storage of averages per PB
394  // Index: 0=u (vol/slab_lo), 1=v (vol/slab_lo)
395  // 2=u (slab_hi), 3=v (slab_hi)
396  int n_avg = 4;
397  amrex::Vector<amrex::Real> avg_h(n_avg,0.);
398  amrex::Gpu::DeviceVector<amrex::Real> avg_d(n_avg,0.);
399  amrex::Real* avg = avg_d.data();
400 
401  // Averaging u & v components in single MFIter
402  for (amrex::MFIter mfi(mf_cons, TileNoZ()); mfi.isValid(); ++mfi) {
403 
404  // CC valid box (inherited from mf_cons)
405  const amrex::Box& vbx = mfi.validbox();
406 
407  // Box logic for u velocity
408  auto ixtype_u = mf_xvel.boxArray().ixType();
409  amrex::Box vbx_u = amrex::convert(vbx,ixtype_u);
410  amrex::Box pbx_u = amrex::convert(m_pb_ba[boxIdx], ixtype_u);
411  amrex::Box ubx_u = pbx_u & vbx_u;
412 
413  // Box logic for v velocity
414  auto ixtype_v = mf_yvel.boxArray().ixType();
415  amrex::Box vbx_v = amrex::convert(vbx,ixtype_v);
416  amrex::Box pbx_v = amrex::convert(m_pb_ba[boxIdx], ixtype_v);
417  amrex::Box ubx_v = pbx_v & vbx_v;
418 
419  // Operation over box union (U)
420  if (ubx_u.ok()) {
421  const amrex::Array4<const amrex::Real>& xvel_arry = mf_xvel.const_array(mfi);
422 
423  #ifdef USE_VOLUME_AVERAGE
424  int npts = ubx_u.numPts();
425  amrex::Real norm = 1.0 / (amrex::Real) npts;
426  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubx_u, [=]
427  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
428  amrex::Gpu::deviceReduceSum(&avg[0], xvel_arry(i,j,k)*norm, handler);
429  });
430  #endif // USE_VOLUME_AVERAGE
431 
432  #ifdef USE_SLAB_AVERAGE
433  amrex::Box ubxSlab_lo = makeSlab(ubx_u,2,ubx_u.smallEnd(2));
434  amrex::Box ubxSlab_hi = makeSlab(ubx_u,2,ubx_u.bigEnd(2));
435  int npts_lo = ubxSlab_lo.numPts();
436  int npts_hi = ubxSlab_hi.numPts();
437  amrex::Real norm_lo = 1.0 / (amrex::Real) npts_lo;
438  amrex::Real norm_hi = 1.0 / (amrex::Real) npts_hi;
439 
440  // Average u in the low slab
441  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubxSlab_lo, [=]
442  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
443  amrex::Gpu::deviceReduceSum(&avg[0], xvel_arry(i,j,k)*norm_lo, handler);
444  });
445 
446  // Average u in the high slab
447  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubxSlab_hi, [=]
448  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
449  amrex::Gpu::deviceReduceSum(&avg[2], xvel_arry(i,j,k)*norm_hi, handler);
450  });
451  #endif // USE_SLAB_AVERAGE
452  } // if
453 
454  // Operation over box union (V)
455  if (ubx_v.ok()) {
456  const amrex::Array4<const amrex::Real>& yvel_arry = mf_yvel.const_array(mfi);
457 
458  #ifdef USE_VOLUME_AVERAGE
459  int npts = ubx_v.numPts();
460  amrex::Real norm = 1.0 / (amrex::Real) npts;
461  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubx_v, [=]
462  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
463  amrex::Gpu::deviceReduceSum(&avg[1], yvel_arry(i,j,k)*norm, handler);
464  });
465  #endif // USE_VOLUME_AVERAGE
466 
467  #ifdef USE_SLAB_AVERAGE
468  amrex::Box ubxSlab_lo = makeSlab(ubx_v,2,ubx_v.smallEnd(2));
469  amrex::Box ubxSlab_hi = makeSlab(ubx_v,2,ubx_v.bigEnd(2));
470  int npts_lo = ubxSlab_lo.numPts();
471  int npts_hi = ubxSlab_hi.numPts();
472  amrex::Real norm_lo = 1.0 / (amrex::Real) npts_lo;
473  amrex::Real norm_hi = 1.0 / (amrex::Real) npts_hi;
474 
475  // Average v in the low slab
476  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubxSlab_lo, [=]
477  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
478  amrex::Gpu::deviceReduceSum(&avg[1], yvel_arry(i,j,k)*norm_lo, handler);
479  });
480 
481  // Average v in the high slab
482  ParallelFor(amrex::Gpu::KernelInfo().setReduction(true), ubxSlab_hi, [=]
483  AMREX_GPU_DEVICE(int i, int j, int k, amrex::Gpu::Handler const& handler) noexcept {
484  amrex::Gpu::deviceReduceSum(&avg[3], yvel_arry(i,j,k)*norm_hi, handler);
485  });
486  #endif // USE_SLAB_AVERAGE
487  } // if
488  } // MFIter
489 
490  // Copy from device back to host
491  amrex::Gpu::copy(amrex::Gpu::deviceToHost, avg_d.begin(), avg_d.end(), avg_h.begin());
492 
493  // Computing the average magnitude within PB
494  #ifdef USE_VOLUME_AVERAGE
495  m_pb_mag[boxIdx] = sqrt(avg_h[0]*avg_h[0] + avg_h[1]*avg_h[1]);
496  #endif
497 
498  #ifdef USE_SLAB_AVERAGE
499  m_pb_mag[boxIdx] = 0.5*( sqrt(avg_h[0]*avg_h[0] + avg_h[1]*avg_h[1])
500  + sqrt(avg_h[2]*avg_h[2] + avg_h[3]*avg_h[3]));
501  #endif
502  }
503 
504  // Quality of life function definitions
505  amrex::Real* get_pb_mag() { return pb_mag.data(); }
506  amrex::Real* get_pb_netZero() { return pb_netZero.data(); }
507 
508  // Output debug message into a file
509  void debug (amrex::Real /*time*/)
510  {
511  /*
512  amrex::PrintToFile("BoxPerturbationOutput") << "#################### PB output at time = "
513  << time << " ####################\n";
514  amrex::PrintToFile("BoxPerturbationOutput") << " Using type: " << pt_type << "\n";
515  amrex::PrintToFile("BoxPerturbationOutput") << " Net: " << tpi_net_buoyant << " Adjust : " << tpi_pert_adjust << "\n";
516  for (int i = 0; i < pb_mag.size(); i++) {
517  amrex::PrintToFile("BoxPerturbationOutput") << "[" << i
518  << "] pb_Umag=" << pb_mag[i]
519  << " | pb_interval=" << pb_interval[i]
520  << " (" << pb_local_etime[i]
521  << ") | pb_amp=" << pb_amp[i] << "\n";
522  }
523  amrex::PrintToFile("BoxPerturbationOutput") << "\n";
524  */
525  }
526 
527  int pt_type; // TODO: May need to pass in solverChoice to replace this
528 
529  // Public data members
530  amrex::Vector<amrex::BoxArray> pb_ba; // PB box array
531  amrex::Vector<amrex::Real> pb_mag; // BP mean magnitude [m/s]
532 
533  // Perturbation amplitude cell storage
534  // This is after random assignment of equation (10) in Ma and Senocak 2023
535  amrex::MultiFab pb_cell;
536 
537  private:
538 
539  // Private data members
540  int tpi_layers; // Number of layers of perturbation boxes
541  int tpi_offset; // Cells to offset the start of the perturbation region
542 
543  amrex::Vector<int> tpi_boxDim; // Dimensions of each perturbation box
544  amrex::Vector<int> tpi_direction; // Direction of the peturbation
545 
546  // Richardson Formulation
547  amrex::Real tpi_nonDim; // Richardson number
548  amrex::Real tpi_Ti; // Temperature intensity value
549  amrex::Real tpi_Tinf; // Reference temperature [K]
550 
551  // Physical dimensions
552  amrex::Real tpi_Hpb; // PB height [m]
553  amrex::Real tpi_Lpb; // PB length [m]
554  amrex::Real tpi_Wpb; // PB width [m]
555  amrex::Real tpi_lref; // PB reference length [m]
556 
557  amrex::Real tpi_net_buoyant; // Perturbation net-zero calculation storage
558  amrex::Real tpi_pert_adjust; // Perturbation adjust for net-zero per cell adjustment
559 
560  // Perturbation data arrays
561  amrex::Vector<amrex::Real> pb_interval; // PB update time [s]
562  amrex::Vector<amrex::Real> pb_local_etime; // PB local elapsed time [s]
563  amrex::Vector<amrex::Real> pb_amp; // PB perturbation amplitude Ri:[K]
564  amrex::Vector<amrex::Real> pb_netZero; // PB array used for net zero sum calculation
565 
566  // Random number generation between range (used for interval calculation)
567  amrex::Real RandomReal (const amrex::Real min, const amrex::Real max)
568  {
569  amrex::Real r = (amrex::Real) rand() / (amrex::Real) RAND_MAX;
570  return min + r * (max - min);
571  }
572 
573 };
574 #endif
constexpr amrex::Real CONST_GRAV
Definition: ERF_Constants.H:21
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real pp(amrex::Real y)
Definition: ERF_MicrophysicsUtils.H:219
AMREX_FORCE_INLINE amrex::IntVect TileNoZ()
Definition: ERF_TileNoZ.H:11
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real avg_h(amrex::Real XXXm, amrex::Real XXXp)
Definition: ERF_EBUtils.H:10
Definition: ERF_TurbPertStruct.H:18
int tpi_layers
Definition: ERF_TurbPertStruct.H:540
void pseudoRandomPert(const int &boxIdx, const int &lev, const amrex::IndexType &m_ixtype)
Definition: ERF_TurbPertStruct.H:296
amrex::Real tpi_Tinf
Definition: ERF_TurbPertStruct.H:549
amrex::Real tpi_Wpb
Definition: ERF_TurbPertStruct.H:554
amrex::Real tpi_Lpb
Definition: ERF_TurbPertStruct.H:553
amrex::Real * get_pb_mag()
Definition: ERF_TurbPertStruct.H:505
amrex::Vector< amrex::Real > pb_local_etime
Definition: ERF_TurbPertStruct.H:562
amrex::Vector< amrex::BoxArray > pb_ba
Definition: ERF_TurbPertStruct.H:530
amrex::Vector< amrex::Real > pb_amp
Definition: ERF_TurbPertStruct.H:563
void netZeroBuoyantAdjust(const int &boxIdx, const int &lev)
Definition: ERF_TurbPertStruct.H:358
void calc_tpi_update(const int lev, const amrex::Real dt, amrex::MultiFab &mf_xvel, amrex::MultiFab &mf_yvel, amrex::MultiFab &mf_cons)
Definition: ERF_TurbPertStruct.H:185
void calc_tpi_amp(const int &boxIdx, const amrex::Real &interval)
Definition: ERF_TurbPertStruct.H:270
amrex::MultiFab pb_cell
Definition: ERF_TurbPertStruct.H:535
amrex::Vector< amrex::Real > pb_netZero
Definition: ERF_TurbPertStruct.H:564
int tpi_offset
Definition: ERF_TurbPertStruct.H:541
amrex::Real tpi_Ti
Definition: ERF_TurbPertStruct.H:548
amrex::Real tpi_pert_adjust
Definition: ERF_TurbPertStruct.H:558
amrex::Vector< amrex::Real > pb_interval
Definition: ERF_TurbPertStruct.H:561
void debug(amrex::Real)
Definition: ERF_TurbPertStruct.H:509
amrex::Real * get_pb_netZero()
Definition: ERF_TurbPertStruct.H:506
amrex::Vector< amrex::Real > pb_mag
Definition: ERF_TurbPertStruct.H:531
amrex::Real tpi_lref
Definition: ERF_TurbPertStruct.H:555
amrex::Vector< int > tpi_boxDim
Definition: ERF_TurbPertStruct.H:543
amrex::Real tpi_nonDim
Definition: ERF_TurbPertStruct.H:547
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:381
void init_tpi(const int lev, const amrex::IntVect &nx, const amrex::GpuArray< amrex::Real, 3 > dx, const amrex::BoxArray &ba, const amrex::DistributionMapping &dm, const int ngrow_state, std::string pp_prefix)
Definition: ERF_TurbPertStruct.H:29
void netZeroBuoyantAdd(const int &boxIdx, const int &lev)
Definition: ERF_TurbPertStruct.H:323
int pt_type
Definition: ERF_TurbPertStruct.H:527
~TurbulentPerturbation()
Definition: ERF_TurbPertStruct.H:22
amrex::Real RandomReal(const amrex::Real min, const amrex::Real max)
Definition: ERF_TurbPertStruct.H:567
amrex::Real tpi_net_buoyant
Definition: ERF_TurbPertStruct.H:557
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:246
amrex::Real tpi_Hpb
Definition: ERF_TurbPertStruct.H:552
amrex::Vector< int > tpi_direction
Definition: ERF_TurbPertStruct.H:544