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 make_terrain_fitted_coords (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_which_terrain_grid()

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

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

632 {
633  const auto* dx = geom.CellSize();
634  Real dzInv = 1.0/dx[2];
635 
636  // Domain valid box (z_nd is nodal)
637  const Box& domain = geom.Domain();
638  int domlo_z = domain.smallEnd(2);
639 
640  // The z-faces are always full when using terrain-fitted coordinates
641  az.setVal(1.0);
642 
643  //
644  // x-areas
645  //
646 #ifdef _OPENMP
647 #pragma omp parallel if (Gpu::notInLaunchRegion())
648 #endif
649  for ( MFIter mfi(ax, TilingIfNotGPU()); mfi.isValid(); ++mfi )
650  {
651  Box gbx = mfi.growntilebox(ax.nGrow());
652  if (gbx.smallEnd(2) < domlo_z) {
653  gbx.setSmall(2,domlo_z);
654  }
655 
656  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
657  Array4<Real > ax_arr = ax.array(mfi);
658  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
659  ax_arr(i, j, k) = .5 * dzInv * (
660  z_nd(i,j,k+1) + z_nd(i,j+1,k+1) - z_nd(i,j,k) - z_nd(i,j+1,k));
661  });
662  }
663 
664  //
665  // y-areas
666  //
667 #ifdef _OPENMP
668 #pragma omp parallel if (Gpu::notInLaunchRegion())
669 #endif
670  for ( MFIter mfi(ay, TilingIfNotGPU()); mfi.isValid(); ++mfi )
671  {
672  Box gbx = mfi.growntilebox(ay.nGrow());
673  if (gbx.smallEnd(2) < domlo_z) {
674  gbx.setSmall(2,domlo_z);
675  }
676 
677  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
678  Array4<Real > ay_arr = ay.array(mfi);
679  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
680  ay_arr(i, j, k) = .5 * dzInv * (
681  z_nd(i,j,k+1) + z_nd(i+1,j,k+1) - z_nd(i,j,k) - z_nd(i+1,j,k));
682  });
683  }
684 
685  ax.FillBoundary(geom.periodicity());
686  ay.FillBoundary(geom.periodicity());
687  az.FillBoundary(geom.periodicity());
688 }

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

594 {
595  const auto *dx = geom.CellSize();
596  Real dzInv = 1.0/dx[2];
597 
598  // Domain valid box (z_nd is nodal)
599  const Box& domain = geom.Domain();
600  int domlo_z = domain.smallEnd(2);
601 
602  // Number of ghost cells
603  int ngrow= detJ_cc.nGrow();
604 
605 #ifdef _OPENMP
606 #pragma omp parallel if (Gpu::notInLaunchRegion())
607 #endif
608  for ( MFIter mfi(detJ_cc, TilingIfNotGPU()); mfi.isValid(); ++mfi )
609  {
610  Box gbx = mfi.growntilebox(ngrow);
611  if (gbx.smallEnd(2) < domlo_z) {
612  gbx.setSmall(2,domlo_z);
613  }
614  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
615  Array4<Real > detJ = detJ_cc.array(mfi);
616  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
617  detJ(i, j, k) = .25 * dzInv * (
618  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)
619  -z_nd(i,j,k ) - z_nd(i+1,j,k ) - z_nd(i,j+1,k ) - z_nd(i+1,j+1,k ) );
620  });
621  }
622  detJ_cc.FillBoundary(geom.periodicity());
623 }

Referenced by ERF::update_terrain_arrays().

Here is the caller graph for this function:

◆ make_terrain_fitted_coords()

void make_terrain_fitted_coords ( 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 } // make_terrain_fitted_coords
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:229

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:

◆ make_zcc()

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

Computation of z_phys at cell-center

697 {
698  // Domain valid box (z_nd is nodal)
699  const Box& domain = geom.Domain();
700  int domlo_z = domain.smallEnd(2);
701 
702  // Number of ghost cells
703  int ngrow= z_phys_cc.nGrow();
704 
705 #ifdef _OPENMP
706 #pragma omp parallel if (Gpu::notInLaunchRegion())
707 #endif
708  for ( MFIter mfi(z_phys_cc, TilingIfNotGPU()); mfi.isValid(); ++mfi )
709  {
710  Box gbx = mfi.growntilebox(ngrow);
711  if (gbx.smallEnd(2) < domlo_z) {
712  gbx.setSmall(2,domlo_z);
713  }
714  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
715  Array4<Real > z_cc = z_phys_cc.array(mfi);
716  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
717  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 )
718  +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) );
719  });
720  }
721  z_phys_cc.FillBoundary(geom.periodicity());
722 }

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

Here is the caller graph for this function: