ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_TerrainMetrics.cpp File Reference
#include <ERF_TerrainMetrics.H>
#include <ERF_Utils.H>
#include <AMReX_ParmParse.H>
#include <ERF_Constants.H>
#include <ERF_Interpolation_1D.H>
#include <cmath>
Include dependency graph for ERF_TerrainMetrics.cpp:

Functions

void init_zlevels (Vector< Vector< Real >> &zlevels_stag, Vector< Vector< Real >> &stretched_dz_h, Vector< Gpu::DeviceVector< Real >> &stretched_dz_d, Vector< Geometry > const &geom, Vector< IntVect > const &ref_ratio, const Real grid_stretching_ratio, const Real zsurf, const Real dz0)
 
void init_terrain_grid (int lev, const Geometry &geom, MultiFab &z_phys_nd, Vector< Real > const &z_levels_h, GpuArray< ERF_BC, AMREX_SPACEDIM *2 > &phys_bc_type)
 
void init_which_terrain_grid (int lev, Geometry const &geom, MultiFab &z_phys_nd, Vector< Real > const &z_levels_h)
 
void make_J (const Geometry &geom, MultiFab &z_phys_nd, MultiFab &detJ_cc)
 
void make_areas (const Geometry &geom, MultiFab &z_phys_nd, MultiFab &ax, MultiFab &ay, MultiFab &az)
 
void make_zcc (const Geometry &geom, MultiFab &z_phys_nd, MultiFab &z_phys_cc)
 

Function Documentation

◆ init_terrain_grid()

void init_terrain_grid ( int  lev,
const Geometry &  geom,
MultiFab &  z_phys_nd,
Vector< Real > const &  z_levels_h,
GpuArray< ERF_BC, AMREX_SPACEDIM *2 > &  phys_bc_type 
)

Computation of the terrain grid from BTF, STF, or Sullivan TF model

121 {
122  const Box& domain = geom.Domain();
123 
124  int domlo_z = domain.smallEnd(2);
125  int domhi_z = domain.bigEnd(2) + 1;
126 
127  // ****************************************************************************
128 
129  if (lev == 0) {
130  BoxArray ba(z_phys_nd.boxArray());
131  bool all_boxes_touch_bottom = true;
132  for (int i = 0; i < ba.size(); i++) {
133  if (ba[i].smallEnd(2) != domlo_z) {
134  all_boxes_touch_bottom = false;
135  }
136  }
137 
138  if (all_boxes_touch_bottom) {
139  init_which_terrain_grid(lev, geom, z_phys_nd, z_levels_h);
140  } else {
141 
142  BoxArray ba_new(domain);
143  ChopGrids2D(ba_new, domain, ParallelDescriptor::NProcs());
144 
145  DistributionMapping dm_new(ba_new);
146  ba_new.surroundingNodes();
147 
148  MultiFab z_phys_nd_new(ba_new, dm_new, 1, z_phys_nd.nGrowVect());
149 
150  z_phys_nd_new.ParallelCopy(z_phys_nd,0,0,1,z_phys_nd.nGrowVect(),z_phys_nd.nGrowVect());
151 
152  init_which_terrain_grid(lev, geom, z_phys_nd_new, z_levels_h);
153 
154  z_phys_nd.ParallelCopy(z_phys_nd_new,0,0,1,z_phys_nd.nGrowVect(),z_phys_nd.nGrowVect());
155  }
156  } else { // lev > 0
157  init_which_terrain_grid(lev, geom, z_phys_nd, z_levels_h);
158  }
159 
160  //
161  // Fill ghost layers and corners (including periodic) -- no matter what level
162  //
163  z_phys_nd.FillBoundary(geom.periodicity());
164 
165  if (phys_bc_type[Orientation(0,Orientation::low )] == ERF_BC::symmetry ||
166  phys_bc_type[Orientation(0,Orientation::high)] == ERF_BC::symmetry ||
167  phys_bc_type[Orientation(1,Orientation::low )] == ERF_BC::symmetry ||
168  phys_bc_type[Orientation(1,Orientation::high)] == ERF_BC::symmetry) {
169 
170  const auto& dom_lo = lbound(convert(domain,IntVect(1,1,1)));
171  const auto& dom_hi = ubound(convert(domain,IntVect(1,1,1)));
172 
173  for (MFIter mfi(z_phys_nd,true); mfi.isValid(); ++mfi) {
174  const Box& bx = mfi.growntilebox();
175  const Array4< Real> z_nd_arr = z_phys_nd.array(mfi);
176  if (phys_bc_type[Orientation(0,Orientation::low)] == ERF_BC::symmetry && bx.smallEnd(0) == dom_lo.x) {
177  ParallelFor(makeSlab(bx,0,1), [=] AMREX_GPU_DEVICE (int , int j, int k)
178  {
179  z_nd_arr(dom_lo.x-1,j,k) = z_nd_arr(dom_lo.x+1,j,k);
180  });
181  }
182  if (phys_bc_type[Orientation(0,Orientation::high)] == ERF_BC::symmetry && bx.bigEnd(0) == dom_hi.x) {
183  ParallelFor(makeSlab(bx,0,1), [=] AMREX_GPU_DEVICE (int , int j, int k)
184  {
185  z_nd_arr(dom_hi.x+1,j,k) = z_nd_arr(dom_hi.x-1,j,k);
186  });
187  }
188  if (phys_bc_type[Orientation(1,Orientation::low)] == ERF_BC::symmetry && bx.smallEnd(1) == dom_lo.y) {
189  ParallelFor(makeSlab(bx,1,1), [=] AMREX_GPU_DEVICE (int i, int , int k)
190  {
191  z_nd_arr(i,dom_lo.y-1,k) = z_nd_arr(i,dom_lo.y+1,k);
192  });
193  }
194  if (phys_bc_type[Orientation(1,Orientation::high)] == ERF_BC::symmetry && bx.bigEnd(1) == dom_hi.y) {
195  ParallelFor(makeSlab(bx,1,1), [=] AMREX_GPU_DEVICE (int i, int , int k)
196  {
197  z_nd_arr(i,dom_hi.y+1,k) = z_nd_arr(i,dom_hi.y-1,k);
198  });
199  }
200  }
201  }
202 
203  //********************************************************************************
204  // Populate domain boundary cells in z-direction
205  //********************************************************************************
206  int ngrow = z_phys_nd.nGrow();
207 
208  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
209  {
210  // Only set values above top of domain if this box reaches that far
211  Box nd_bx = mfi.tilebox();
212 
213  // Note that domhi_z is already nodal in the z-direction
214  if (nd_bx.bigEnd(2) >= domhi_z) {
215  // Grown box with no z range
216  Box xybx = mfi.growntilebox(ngrow);
217  xybx.setRange(2,0);
218  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
219 
220  // Extrapolate top layer
221  ParallelFor(xybx, [=] AMREX_GPU_DEVICE (int i, int j, int ) {
222  z_arr(i,j,domhi_z+1) = 2.0*z_arr(i,j,domhi_z) - z_arr(i,j,domhi_z-1);
223  });
224  }
225  }
226 
227  /*
228  // Debug
229  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
230  {
231  Box gbx = mfi.growntilebox(ngrow);
232  gbx.setRange(2,domlo_z,domhi_z+1);
233 
234  Array4<Real> z_arr = z_phys_nd.array(mfi);
235 
236  int rank = 0;
237  Print(rank) << "Debugging init_terrain_grid" << "\n";
238  Print(rank) << gbx << "\n";
239 
240  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) {
241  Print(rank) << IntVect(i,j,k) << "\n";
242  Print(rank) << z_arr(i,j,k) << "\n";
243  Print(rank) << "\n";
244  });
245  Print(rank) << "Cleared..." << "\n";
246  }
247  */
248 } // init_terrain_grid
void ChopGrids2D(BoxArray &ba, const Box &domain, int target_size)
Definition: ERF_ChopGrids.cpp:21
void init_which_terrain_grid(int lev, Geometry const &geom, MultiFab &z_phys_nd, Vector< Real > const &z_levels_h)
Definition: ERF_TerrainMetrics.cpp:251

Referenced by ERF::init_zphys(), and ERF::remake_zphys().

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

◆ init_which_terrain_grid()

void init_which_terrain_grid ( int  lev,
Geometry const &  geom,
MultiFab &  z_phys_nd,
Vector< Real > const &  z_levels_h 
)
253 {
254  // User-selected method from inputs file (BTF default)
255  ParmParse pp("erf");
256  int terrain_smoothing = 0;
257  pp.query("terrain_smoothing", terrain_smoothing);
258 
259  if (lev > 0 && terrain_smoothing != 0) {
260  Abort("Must use terrain_smoothing = 0 when doing multilevel");
261  }
262 
263  // Number of ghost cells
264  int ngrow = z_phys_nd.nGrow();
265  IntVect ngrowVect = z_phys_nd.nGrowVect();
266 
267  const Box& domain = geom.Domain();
268  int domlo_x = domain.smallEnd(0); int domhi_x = domain.bigEnd(0) + 1;
269  int domlo_y = domain.smallEnd(1); int domhi_y = domain.bigEnd(1) + 1;
270  int domlo_z = domain.smallEnd(2); int domhi_z = domain.bigEnd(2) + 1;
271 
272  int imin = domlo_x; // if (geom.isPeriodic(0)) imin -= z_phys_nd.nGrowVect()[0];
273  int jmin = domlo_y; // if (geom.isPeriodic(1)) jmin -= z_phys_nd.nGrowVect()[1];
274 
275  int imax = domhi_x; // if (geom.isPeriodic(0)) imax += z_phys_nd.nGrowVect()[0];
276  int jmax = domhi_y; // if (geom.isPeriodic(1)) jmax += z_phys_nd.nGrowVect()[1];
277 
278  int nz = z_levels_h.size();
279  Real z_top = z_levels_h[nz-1];
280 
281  Gpu::DeviceVector<Real> z_levels_d;
282  z_levels_d.resize(nz);
283  Gpu::copy(Gpu::hostToDevice, z_levels_h.begin(), z_levels_h.end(), z_levels_d.begin());
284 
285  switch(terrain_smoothing) {
286  case 0: // BTF Method
287  {
288  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
289  {
290  // Note that this box is nodal because it is based on z_phys_nd
291  const Box& bx = mfi.validbox();
292 
293  int k0 = bx.smallEnd()[2];
294 
295  // Grown box with corrected ghost cells at top
296  Box gbx = mfi.growntilebox(ngrowVect);
297 
298  if (bx.smallEnd(2) == domlo_z) {
299  gbx.setSmall(2,domlo_z);
300  } else {
301  gbx.growLo(2,-1);
302  }
303 
304  // Note that we don't overwrite the values at the high end of the box
305  // regardless of whether the box reaches the top of the domain or not.
306  // In the case of lev > 0, this ensures that the fine nodes at the top
307  // of a fine box are those that are interpolated from the coarse grid
308  if (bx.bigEnd(2) == domhi_z) {
309  gbx.setBig(2,domhi_z);
310  } else {
311  gbx.growHi(2,-1);
312  if (gbx.bigEnd(2) > domhi_z) {
313  gbx.setBig(2,domhi_z);
314  }
315  }
316 
317  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
318  auto const& z_lev = z_levels_d.data();
319 
320  //
321  // Vertical grid stretching using BTF model from p2163 of Klemp2011
322  // z_levels are only defined from k = dom_lo to dom_hi (nodal)
323  //
324  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
325  {
326  int ii = amrex::max(amrex::min(i,imax),imin);
327  int jj = amrex::max(amrex::min(j,jmax),jmin);
328 
329  //
330  // Start with flat z_lev set either with uniform cell size or specified z_levels
331  // If k0 = 0 then z_arr at k0 has already been filled from the terrain data
332  // If k0 > 0 then z_arr at k0 has already been filled from interpolation
333  //
334  Real z = z_lev[k];
335  Real z_sfc = z_arr(ii,jj,k0);
336  Real z_lev_sfc = z_lev[k0];
337 
338  z_arr(i,j,k) = ( (z_sfc - z_lev_sfc) * z_top +
339  (z_top - z_sfc ) * z ) / (z_top - z_lev_sfc);
340  });
341  } // mfi
342 
343  z_phys_nd.FillBoundary(geom.periodicity());
344 
345  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
346  {
347  // Note that this box is nodal because it is based on z_phys_nd
348  const Box& bx = mfi.validbox();
349  Box gbx = mfi.growntilebox(ngrowVect);
350 
351  int k0 = bx.smallEnd()[2];
352 
353  if (k0 == 0)
354  {
355  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
356 
357  // Fill lateral boundaries below the bottom surface
358  ParallelFor(makeSlab(gbx,2,0), [=] AMREX_GPU_DEVICE (int i, int j, int)
359  {
360  z_arr(i,j,-1) = 2.0*z_arr(i,j,0) - z_arr(i,j,1);
361  });
362  }
363  } // mfi
364  break;
365  } // case 0
366 
367  case 1: // STF Method
368  {
369  // Get Multifab spanning domain with 1 level of ghost cells
370  MultiFab h_mf( z_phys_nd.boxArray(), z_phys_nd.DistributionMap(), 1, ngrow+1);
371  MultiFab h_mf_old(z_phys_nd.boxArray(), z_phys_nd.DistributionMap(), 1, ngrow+1);
372 
373  // Save max height for smoothing
374  Real h_m;
375 
376  // Create 2D MF without allocation
377  MultiFab mf2d;
378  {
379  BoxList bl2d = h_mf.boxArray().boxList();
380  for (auto& b : bl2d) {
381  b.setRange(2,0);
382  }
383  BoxArray ba2d(std::move(bl2d));
384  mf2d = MultiFab(ba2d, h_mf.DistributionMap(), 1, ngrow, MFInfo().SetAlloc(false));
385  }
386 
387  // Get MultiArray4s from the multifabs
388  MultiArray4<Real> const& ma_h_s = h_mf.arrays();
389  MultiArray4<Real> const& ma_h_s_old = h_mf_old.arrays();
390  MultiArray4<Real> const& ma_z_phys = z_phys_nd.arrays();
391 
392  // Bottom boundary
393  int k0 = domlo_z;
394 
395  // Get max value
396  h_m = ParReduce(TypeList<ReduceOpMax>{}, TypeList<Real>{}, mf2d, IntVect(ngrow,ngrow,0),
397  [=] AMREX_GPU_DEVICE (int box_no, int i, int j, int) noexcept
398  -> GpuTuple<Real>
399  {
400  // Get Array4s
401  const auto & h = ma_h_s[box_no];
402  const auto & z_arr = ma_z_phys[box_no];
403 
404  int ii = amrex::max(amrex::min(i,imax),imin);
405  int jj = amrex::max(amrex::min(j,jmax),jmin);
406 
407  // Fill the lateral boundaries
408  z_arr(i,j,k0) = z_arr(ii,jj,k0);
409 
410  // Populate h with terrain
411  h(i,j,k0) = z_arr(i,j,k0);
412 
413  // Return height for max
414  return { z_arr(i,j,k0) };
415  });
416 
417  if (h_m < std::numeric_limits<Real>::epsilon()) h_m = 1e-16;
418 
419  // Fill ghost cells (neglects domain boundary if not periodic)
420  h_mf.FillBoundary(geom.periodicity());
421 
422  // Make h_mf copy for old values
423  MultiFab::Copy(h_mf_old, h_mf,0,0,1,h_mf_old.nGrow());
424 
425  // Minimum allowed fractional grid spacing
426  Real gamma_m = 0.5;
427  pp.query("terrain_gamma_m", gamma_m);
428  Real z_H = 2.44*h_m/(1-gamma_m); // Klemp2011 Eqn. 11
429 
430  // Populate h_mf at k>0 with h_s, solving in ordered 2D slices
431  for (int k = domlo_z+1; k <= domhi_z; k++) // skip terrain level
432  {
433  auto const& z_lev_h = z_levels_h.data();
434 
435  Real zz = z_lev_h[k];
436  Real zz_minus = z_lev_h[k-1];
437 
438  // Hybrid attenuation profile, Klemp2011 Eqn. 9
439  Real A;
440  Real foo = cos((PI/2)*(zz/z_H));
441  if(zz < z_H) { A = foo*foo*foo*foo*foo*foo; } // A controls rate of return to atm
442  else { A = 0; }
443  Real foo_minus = cos((PI/2)*(zz_minus/z_H));
444  Real A_minus;
445  if(zz_minus < z_H) { A_minus = foo_minus*foo_minus*foo_minus*foo_minus*foo_minus*foo_minus; } // A controls rate of return to atm
446  else { A_minus = 0; }
447 
448  unsigned maxIter = 50; // M_k in paper
449  unsigned iter = 0;
450  Real threshold = gamma_m;
451  Real diff = 1.e20;
452  while (iter < maxIter && diff > threshold)
453  {
454 
455  diff = ParReduce(TypeList<ReduceOpMin>{}, TypeList<Real>{}, mf2d, IntVect(ngrow,ngrow,0),
456  [=] AMREX_GPU_DEVICE (int box_no, int i, int j, int) noexcept
457  -> GpuTuple<Real>
458  {
459  const auto & h_s = ma_h_s[box_no];
460  const auto & h_s_old = ma_h_s_old[box_no];
461 
462  Real beta_k = 0.2*std::min(zz/(2*h_m),1.0); //smoothing coefficient (Eqn. 8)
463 
464  // Clip indices for ghost-cells
465  int ii = amrex::min(amrex::max(i,domlo_x),domhi_x);
466  int jj = amrex::min(amrex::max(j,domlo_y),domhi_y);
467 
468  if (iter == 0) {
469  h_s(i,j,k) = h_s_old(i,j,k-1) + beta_k*(h_s_old(ii+1,jj ,k-1)
470  + h_s_old(ii-1,jj ,k-1)
471  + h_s_old(ii ,jj+1,k-1)
472  + h_s_old(ii ,jj-1,k-1) - 4*h_s_old(ii,jj,k-1));
473  }
474  else {
475  h_s(i,j,k) = h_s_old(i,j,k ) + beta_k*(h_s_old(ii+1,jj ,k )
476  + h_s_old(ii-1,jj ,k )
477  + h_s_old(ii ,jj+1,k )
478  + h_s_old(ii ,jj-1,k ) - 4*h_s_old(ii,jj,k ));
479  }
480 
481  // Minimum vertical grid spacing condition (Klemp2011 Eqn. 7)
482  return { (zz + A * h_s(i,j,k) - (zz_minus + A_minus * h_s(i,j,k-1))) / (zz - zz_minus) };
483 
484  }); //ParReduce
485 
486  MultiFab::Copy(h_mf_old, h_mf,0,0,1,h_mf_old.nGrow());
487 
488  ParallelDescriptor::ReduceRealMin(diff);
489 
490  iter++;
491 
492  //fill ghost points
493  h_mf_old.FillBoundary(geom.periodicity());
494 
495  } //while
496 
497  auto const& z_lev_d = z_levels_d.data();
498 
499  //Populate z_phys_nd by solving z_arr(i,j,k) = z + A*h_s(i,j,k)
500  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
501  {
502  // Grown box with no z range
503  Box xybx = mfi.growntilebox(ngrow);
504  xybx.setRange(2,0);
505 
506  Array4<Real> const& h_s = h_mf_old.array(mfi);
507  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
508 
509  ParallelFor(xybx, [=] AMREX_GPU_DEVICE (int i, int j, int) {
510 
511  // Location of nodes
512  Real z = z_lev_d[k];
513 
514  // STF model from p2164 of Klemp2011 (Eqn. 4)
515  z_arr(i,j,k) = z + A*h_s(i,j,k);
516 
517  // Fill below the bottom surface
518  if (k == 1) {
519  z_arr(i,j,k0-1) = 2.0*z_arr(i,j,k0) - z_arr(i,j,k);
520  }
521  });
522  } // mfi
523  } // k
524 
525  Gpu::streamSynchronize();
526 
527  break;
528  } // case 1
529 
530  case 2: // Sullivan TF Method
531  {
532  int k0 = 0;
533 
534  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
535  {
536  // Grown box with corrected ghost cells at top
537  Box gbx = mfi.growntilebox(ngrow);
538  gbx.setRange(2,domlo_z,domhi_z+1);
539 
540  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
541  auto const& z_lev = z_levels_d.data();
542 
543  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
544  {
545  // Vertical grid stretching
546  Real z = z_lev[k];
547 
548  int ii = amrex::max(amrex::min(i,imax),imin);
549  int jj = amrex::max(amrex::min(j,jmax),jmin);
550 
551  // Fill levels using model from Sullivan et. al. 2014
552  int omega = 3; //Used to adjust how rapidly grid lines level out. omega=1 is BTF!
553  z_arr(i,j,k) = z + (std::pow((1. - (z/z_top)),omega) * z_arr(ii,jj,k0));
554 
555  // Fill lateral boundaries and below the bottom surface
556  if (k == k0) {
557  z_arr(i,j,k0 ) = z_arr(ii,jj,k0);
558  }
559  if (k == 1) {
560  z_arr(i,j,k0-1) = 2.0*z_arr(ii,jj,k0) - z_arr(i,j,k);
561  }
562  });
563  } // mfi
564  break;
565  } // case 2
566 
567  case 3: // Debugging Test Method -- applies Sullivan TF starting at k = 1 so that domain does not change size
568  {
569  int k0 = 0;
570 
571  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
572  {
573  // Grown box with corrected ghost cells at top
574  Box gbx = mfi.growntilebox(ngrow);
575  gbx.setRange(2,domlo_z,domhi_z+1);
576 
577  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
578  auto const& z_lev = z_levels_d.data();
579 
580  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
581  {
582  // Vertical grid stretching
583  Real z = z_lev[k];
584 
585  int ii = amrex::max(amrex::min(i,imax),imin);
586  int jj = amrex::max(amrex::min(j,jmax),jmin);
587 
588  // Fill values outside the lateral boundaries and below the bottom surface (necessary if init_type = "real")
589  if (k == k0+1) {
590  z_arr(i,j,k) = z + z_arr(ii,jj,k0);
591  } else {
592  // Fill levels using model from Sullivan et. al. 2014
593  int omega = 3; //Used to adjust how rapidly grid lines level out. omega=1 is BTF!
594  z_arr(i,j,k) = z + (std::pow((1. - (z/z_top)),omega) * z_arr(ii,jj,k0));
595  }
596  });
597  gbx.setBig(2,0);
598  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
599  {
600  z_arr(i,j,k ) = 0.0;
601  z_arr(i,j,k-1) = -z_arr(i,j,k+1);
602  });
603  } // mfi
604  break;
605  } // case 3
606  } //switch
607 }
constexpr amrex::Real PI
Definition: ERF_Constants.H:6
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real pp(amrex::Real y)
Definition: ERF_MicrophysicsUtils.H:219
@ omega
Definition: ERF_SAM.H:49

Referenced by init_terrain_grid().

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

◆ init_zlevels()

void init_zlevels ( Vector< Vector< Real >> &  zlevels_stag,
Vector< Vector< Real >> &  stretched_dz_h,
Vector< Gpu::DeviceVector< Real >> &  stretched_dz_d,
Vector< Geometry > const &  geom,
Vector< IntVect > const &  ref_ratio,
const Real  grid_stretching_ratio,
const Real  zsurf,
const Real  dz0 
)
19 {
20  int max_level = zlevels_stag.size()-1;
21 
22  for (int lev = 0; lev <= max_level; lev++)
23  {
24  auto dx = geom[lev].CellSizeArray();
25  const Box& domain = geom[lev].Domain();
26  int nz = domain.length(2)+1; // staggered
27 
28  zlevels_stag[lev].resize(nz);
29 
30  stretched_dz_h[lev].resize(domain.length(2));
31 
32  if (grid_stretching_ratio == 0) {
33  // This is the default for z_levels
34  for (int k = 0; k < nz; k++)
35  {
36  zlevels_stag[lev][k] = k * dx[2];
37  }
38  for (int k = 0; k < nz-1; k++)
39  {
40  stretched_dz_h[lev][k] = dx[2];
41  }
42  } else if (lev == 0) {
43  // Create stretched grid based on initial dz and stretching ratio
44  zlevels_stag[lev][0] = zsurf;
45  Real dz = dz0;
46  stretched_dz_h[lev][0] = dz0;
47  Print() << "Stretched grid levels at level : " << lev << " is " << zsurf;
48  for (int k = 1; k < nz; k++)
49  {
50  zlevels_stag[lev][k] = zlevels_stag[lev][k-1] + dz;
51  if (k < nz-1) {
52  stretched_dz_h[lev][k] = dz;
53  }
54  Print() << " " << zlevels_stag[lev][k];
55  dz *= grid_stretching_ratio;
56  }
57  Print() << std::endl;
58  } else if (lev > 0) {
59  int rr = ref_ratio[lev-1][2];
60  expand_and_interpolate_1d(zlevels_stag[lev], zlevels_stag[lev-1], rr, false);
61  for (int k = 0; k < nz-1; k++)
62  {
63  stretched_dz_h[lev][k] = (zlevels_stag[lev][k+1] - zlevels_stag[lev][k]);
64  }
65  }
66  }
67 
68  // Try reading in terrain_z_levels, which allows arbitrarily spaced grid
69  // levels to be specified and will take precedence over the
70  // grid_stretching_ratio parameter
71  ParmParse pp("erf");
72  int n_zlevels = pp.countval("terrain_z_levels");
73  if (n_zlevels > 0)
74  {
75  int nz = geom[0].Domain().length(2)+1; // staggered
76  if (n_zlevels != nz) {
77  Print() << "You supplied " << n_zlevels << " staggered terrain_z_levels " << std::endl;
78  Print() << "but n_cell+1 in the z-direction is " << nz << std::endl;
79  Abort("You must specify a z_level for every value of k");
80  }
81 
82  if (grid_stretching_ratio > 0) {
83  Print() << "Note: Found terrain_z_levels, ignoring grid_stretching_ratio" << std::endl;
84  }
85 
86  pp.getarr("terrain_z_levels", zlevels_stag[0], 0, nz);
87 
88  // These levels should range from 0 at the surface to the height of the
89  // top of model domain (see the coordinate surface height, zeta, in
90  // Klemp 2011)
91  AMREX_ALWAYS_ASSERT(zlevels_stag[0][0] == 0);
92 
93  for (int lev = 1; lev <= max_level; lev++) {
94  int rr = ref_ratio[lev-1][2];
95  expand_and_interpolate_1d(zlevels_stag[lev], zlevels_stag[lev-1], rr, false);
96  }
97 
98  for (int lev = 0; lev <= max_level; lev++) {
99  int nz_zlevs = zlevels_stag[lev].size();
100  for (int k = 0; k < nz_zlevs-1; k++)
101  {
102  stretched_dz_h[lev][k] = (zlevels_stag[lev][k+1] - zlevels_stag[lev][k]);
103  }
104  }
105  }
106 
107  for (int lev = 0; lev <= max_level; lev++) {
108  stretched_dz_d[lev].resize(stretched_dz_h[lev].size());
109  Gpu::copy(Gpu::hostToDevice, stretched_dz_h[lev].begin(), stretched_dz_h[lev].end(), stretched_dz_d[lev].begin());
110  }
111 }
AMREX_FORCE_INLINE void expand_and_interpolate_1d(amrex::Vector< amrex::Real > &znew, const amrex::Vector< amrex::Real > &zorig, int refine_fac, bool destag=false)
Definition: ERF_Interpolation_1D.H:85

Referenced by ERF::ERF_shared().

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

◆ make_areas()

void make_areas ( const Geometry &  geom,
MultiFab &  z_phys_nd,
MultiFab &  ax,
MultiFab &  ay,
MultiFab &  az 
)

Computation of area fractions on faces

654 {
655  const auto* dx = geom.CellSize();
656  Real dzInv = 1.0/dx[2];
657 
658  // Domain valid box (z_nd is nodal)
659  const Box& domain = geom.Domain();
660  int domlo_z = domain.smallEnd(2);
661 
662  // The z-faces are always full when using terrain-fitted coordinates
663  az.setVal(1.0);
664 
665  //
666  // x-areas
667  //
668 #ifdef _OPENMP
669 #pragma omp parallel if (Gpu::notInLaunchRegion())
670 #endif
671  for ( MFIter mfi(ax, TilingIfNotGPU()); mfi.isValid(); ++mfi )
672  {
673  Box gbx = mfi.growntilebox(ax.nGrow());
674  if (gbx.smallEnd(2) < domlo_z) {
675  gbx.setSmall(2,domlo_z);
676  }
677 
678  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
679  Array4<Real > ax_arr = ax.array(mfi);
680  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
681  ax_arr(i, j, k) = .5 * dzInv * (
682  z_nd(i,j,k+1) + z_nd(i,j+1,k+1) - z_nd(i,j,k) - z_nd(i,j+1,k));
683  });
684  }
685 
686  //
687  // y-areas
688  //
689 #ifdef _OPENMP
690 #pragma omp parallel if (Gpu::notInLaunchRegion())
691 #endif
692  for ( MFIter mfi(ay, TilingIfNotGPU()); mfi.isValid(); ++mfi )
693  {
694  Box gbx = mfi.growntilebox(ay.nGrow());
695  if (gbx.smallEnd(2) < domlo_z) {
696  gbx.setSmall(2,domlo_z);
697  }
698 
699  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
700  Array4<Real > ay_arr = ay.array(mfi);
701  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
702  ay_arr(i, j, k) = .5 * dzInv * (
703  z_nd(i,j,k+1) + z_nd(i+1,j,k+1) - z_nd(i,j,k) - z_nd(i+1,j,k));
704  });
705  }
706 
707  ax.FillBoundary(geom.periodicity());
708  ay.FillBoundary(geom.periodicity());
709  az.FillBoundary(geom.periodicity());
710 }

Referenced by ERF::update_terrain_arrays().

Here is the caller graph for this function:

◆ make_J()

void make_J ( const Geometry &  geom,
MultiFab &  z_phys_nd,
MultiFab &  detJ_cc 
)

Computation of detJ at cell-center

616 {
617  const auto *dx = geom.CellSize();
618  Real dzInv = 1.0/dx[2];
619 
620  // Domain valid box (z_nd is nodal)
621  const Box& domain = geom.Domain();
622  int domlo_z = domain.smallEnd(2);
623 
624  // Number of ghost cells
625  int ngrow= detJ_cc.nGrow();
626 
627 #ifdef _OPENMP
628 #pragma omp parallel if (Gpu::notInLaunchRegion())
629 #endif
630  for ( MFIter mfi(detJ_cc, TilingIfNotGPU()); mfi.isValid(); ++mfi )
631  {
632  Box gbx = mfi.growntilebox(ngrow);
633  if (gbx.smallEnd(2) < domlo_z) {
634  gbx.setSmall(2,domlo_z);
635  }
636  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
637  Array4<Real > detJ = detJ_cc.array(mfi);
638  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
639  detJ(i, j, k) = .25 * dzInv * (
640  z_nd(i,j,k+1) + z_nd(i+1,j,k+1) + z_nd(i,j+1,k+1) + z_nd(i+1,j+1,k+1)
641  -z_nd(i,j,k ) - z_nd(i+1,j,k ) - z_nd(i,j+1,k ) - z_nd(i+1,j+1,k ) );
642  });
643  }
644  detJ_cc.FillBoundary(geom.periodicity());
645 }

Referenced by ERF::update_terrain_arrays().

Here is the caller graph for this function:

◆ make_zcc()

void make_zcc ( const Geometry &  geom,
MultiFab &  z_phys_nd,
MultiFab &  z_phys_cc 
)

Computation of z_phys at cell-center

719 {
720  // Domain valid box (z_nd is nodal)
721  const Box& domain = geom.Domain();
722  int domlo_z = domain.smallEnd(2);
723 
724  // Number of ghost cells
725  int ngrow= z_phys_cc.nGrow();
726 
727 #ifdef _OPENMP
728 #pragma omp parallel if (Gpu::notInLaunchRegion())
729 #endif
730  for ( MFIter mfi(z_phys_cc, TilingIfNotGPU()); mfi.isValid(); ++mfi )
731  {
732  Box gbx = mfi.growntilebox(ngrow);
733  if (gbx.smallEnd(2) < domlo_z) {
734  gbx.setSmall(2,domlo_z);
735  }
736  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
737  Array4<Real > z_cc = z_phys_cc.array(mfi);
738  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
739  z_cc(i, j, k) = .125 * ( z_nd(i,j,k ) + z_nd(i+1,j,k ) + z_nd(i,j+1,k ) + z_nd(i+1,j+1,k )
740  +z_nd(i,j,k+1) + z_nd(i+1,j,k+1) + z_nd(i,j+1,k+1) + z_nd(i+1,j+1,k+1) );
741  });
742  }
743  z_phys_cc.FillBoundary(geom.periodicity());
744 }

Referenced by ERF::post_timestep(), and ERF::update_terrain_arrays().

Here is the caller graph for this function: