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_default_zphys (int, const Geometry &geom, MultiFab &z_phys_nd, MultiFab &z_phys_cc, Real z_offset)
 
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, FineTerrain fine_terrain, MultiFab const *z_phys_interp)
 
FineTerrain which_fine_terrain ()
 
void init_fine_terrain_grid (int lev, Geometry const &geom, MultiFab &z_phys_nd, Vector< Real > const &z_levels_h, FineTerrain fine_terrain, MultiFab const *z_phys_interp)
 
void init_which_terrain_grid (int lev, Geometry const &geom, MultiFab &z_phys_nd, Vector< Real > const &z_levels_h, FineTerrain fine_terrain, MultiFab const *z_phys_interp)
 
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)
 
Real get_dzmin_terrain (MultiFab &z_phys_nd)
 

Function Documentation

◆ get_dzmin_terrain()

Real get_dzmin_terrain ( MultiFab &  z_phys_nd)

Computation min dz at cell-center

833 {
834  auto const& ma_z_nd_arr = z_phys_nd.const_arrays();
835  GpuTuple<Real> min = ParReduce(TypeList<ReduceOpMin>{},
836  TypeList<Real>{},
837  z_phys_nd, IntVect(0),
838  [=] AMREX_GPU_DEVICE (int box_no, int i, int j, int k) noexcept
839  -> GpuTuple<Real>
840  {
841  amrex::Real dz = Compute_Z_AtWFace(i,j,k+1,ma_z_nd_arr[box_no]) -
842  Compute_Z_AtWFace(i,j,k ,ma_z_nd_arr[box_no]);
843  return { dz };
844  });
845  Real r = (get<0>(min) + std::numeric_limits<amrex::Real>::epsilon());
846  ParallelDescriptor::ReduceRealMin(r);
847  return r;
848 }
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real Compute_Z_AtWFace(const int &i, const int &j, const int &k, const amrex::Array4< const amrex::Real > &z_nd)
Definition: ERF_TerrainMetrics.H:718
@ dz
Definition: ERF_AdvanceWDM6.cpp:270
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12

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

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

◆ init_default_zphys()

void init_default_zphys ( int  ,
const Geometry &  geom,
MultiFab &  z_phys_nd,
MultiFab &  z_phys_cc,
Real  z_offset 
)

Define a default z_phys so we have it even if a completely regular grid This will be over-written if we use z_levels, or grid stretching, or terrain-fitted grids

20 {
21  const auto& dx = geom.CellSize();
22  Real dz = dx[2];
23 
24  for (MFIter mfi(z_phys_nd,true); mfi.isValid(); ++mfi)
25  {
26  const Box& bx = mfi.growntilebox();
27  const Array4< Real> z_nd_arr = z_phys_nd.array(mfi);
28  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
29  {
30  z_nd_arr(i,j,k) = k * dz - z_offset;
31  });
32  }
33 
34  for (MFIter mfi(z_phys_cc,true); mfi.isValid(); ++mfi)
35  {
36  const Box& bx = mfi.growntilebox();
37  const Array4< Real> z_cc_arr = z_phys_cc.array(mfi);
38  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
39  {
40  z_cc_arr(i,j,k) = (k + myhalf) * dz - z_offset;
41  });
42  }
43 }
constexpr amrex::Real myhalf
Definition: ERF_Constants.H:13
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
ParallelFor(fab_box, [=] AMREX_GPU_DEVICE(int i, int j, int k) { qrcuten_arr(i, j, k)=Real(0);qscuten_arr(i, j, k)=Real(0);qicuten_arr(i, j, k)=Real(0);})

Referenced by ERF::init_stuff().

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

◆ init_fine_terrain_grid()

void init_fine_terrain_grid ( int  lev,
Geometry const &  geom,
MultiFab &  z_phys_nd,
Vector< Real > const &  z_levels_h,
FineTerrain  fine_terrain,
MultiFab const *  z_phys_interp 
)

Finish the terrain-fitted mesh on a fine level built from the coarse level.

On entry z_phys_nd holds the mesh interpolated from the coarse level in its valid region, with – for Transform – the fine-resolution terrain already written into the k=0 slab. The nodes outside the domain are still bogus_large_value, because neither the interpolation nor make_terrain_fitted_coords()'s setDomainBndry() call gave them a usable value, so we fill them here the same way the BTF branch does.

205 {
206  AMREX_ALWAYS_ASSERT(lev > 0);
207  AMREX_ALWAYS_ASSERT(fine_terrain != FineTerrain::None);
208 
209  const Box& domain = geom.Domain();
210  int domlo_x = domain.smallEnd(0); int domhi_x = domain.bigEnd(0) + 1;
211  int domlo_y = domain.smallEnd(1); int domhi_y = domain.bigEnd(1) + 1;
212  int domlo_z = domain.smallEnd(2);
213 
214  IntVect ngrowVect = z_phys_nd.nGrowVect();
215 
216  if (fine_terrain == FineTerrain::Transform) {
217  AMREX_ALWAYS_ASSERT(z_phys_interp != nullptr);
218 
219  const BoxArray& ba = z_phys_nd.boxArray();
220 
221  //
222  // The correction is anchored at the surface, so a box that does not reach the
223  // surface would keep the uncorrected interpolated mesh and would not join the
224  // box below it. Say so rather than silently building a tangled mesh.
225  //
226  for (int i = 0; i < ba.size(); i++) {
227  if (ba[i].smallEnd(2) != domlo_z) {
228  Abort("erf.amr_terrain_refinement = transform requires that every box on a fine level reach the surface: increase amr.max_grid_size_z or use erf.amr_terrain_refinement = interpolate");
229  }
230  }
231 
232  //
233  // Decay over the height of the fine grids as a whole and never over the height
234  // of an individual box, so that the mesh does not depend on the decomposition.
235  //
236  int khi_lev = ba.minimalBox().bigEnd(2);
237  Real z_lev_top = z_levels_h[khi_lev];
238  Real z_lev_bot = z_levels_h[domlo_z];
239  AMREX_ALWAYS_ASSERT(z_lev_top > z_lev_bot);
240 
241  Gpu::DeviceVector<Real> z_levels_d(z_levels_h.size());
242  Gpu::copy(Gpu::hostToDevice, z_levels_h.begin(), z_levels_h.end(), z_levels_d.begin());
243  auto const& z_lev = z_levels_d.data();
244 
245  for (MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi)
246  {
247  const Box& bx = mfi.tilebox();
248 
249  Array4<Real > const& z_arr = z_phys_nd.array(mfi);
250  Array4<Real const> const& z_interp = z_phys_interp->const_array(mfi);
251 
252  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
253  {
254  if (k > domlo_z && k <= khi_lev) {
255  // How much taller the fine terrain is than the interpolated surface
256  Real delta_terrain = z_arr(i,j,domlo_z) - z_interp(i,j,domlo_z);
257 
258  Real decay = (z_lev_top - z_lev[k]) / (z_lev_top - z_lev_bot);
259  decay = amrex::max(Real(0.0), amrex::min(Real(1.0), decay));
260 
261  z_arr(i,j,k) = z_interp(i,j,k) + decay * delta_terrain;
262  }
263  });
264  } // mfi
265 
266  // The loop above only covers the valid region, so bring the ghost nodes inside
267  // the domain up to date before the fill below reads them
268  z_phys_nd.FillBoundary(geom.periodicity());
269  }
270 
271  //
272  // Extend the mesh to the nodes outside the lateral domain boundary by copying the
273  // nearest column inside it, which is the value the BTF branch gives those nodes.
274  // Like that branch we clamp in a periodic direction too, even though FillBoundary
275  // below will replace what we write there with the periodic image: that way every
276  // node we read here is one inside the domain, whose value we know is already set.
277  //
278  int imin = domlo_x; int imax = domhi_x;
279  int jmin = domlo_y; int jmax = domhi_y;
280 
281  for (MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi)
282  {
283  Box gbx = mfi.growntilebox(ngrowVect);
284  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
285 
286  // Note that we only write nodes outside the domain and only read nodes inside
287  // it, so no node is both read and written here.
288  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
289  {
290  int ii = amrex::max(amrex::min(i,imax),imin);
291  int jj = amrex::max(amrex::min(j,jmax),jmin);
292  if (ii != i || jj != j) {
293  z_arr(i,j,k) = z_arr(ii,jj,k);
294  }
295  });
296  } // mfi
297 
298  z_phys_nd.FillBoundary(geom.periodicity());
299 
300  for (MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi)
301  {
302  const Box& bx = mfi.validbox();
303  Box gbx = mfi.growntilebox(ngrowVect);
304 
305  if (bx.smallEnd(2) == domlo_z)
306  {
307  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
308 
309  // Fill lateral boundaries below the bottom surface
310  ParallelFor(makeSlab(gbx,2,domlo_z), [=] AMREX_GPU_DEVICE (int i, int j, int)
311  {
312  z_arr(i,j,domlo_z-1) = two*z_arr(i,j,domlo_z) - z_arr(i,j,domlo_z+1);
313  });
314  }
315  } // mfi
316 } // init_fine_terrain_grid
constexpr amrex::Real two
Definition: ERF_Constants.H:10
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
@ Transform
add the fine terrain as a correction that decays with height
@ None
caller has not prepared a fine mesh from the coarse level

Referenced by init_which_terrain_grid().

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,
FineTerrain  fine_terrain,
MultiFab const *  z_phys_interp 
)

Initialize the terrain grid using the selected model (BTF, STF, or Sullivan TF).

Parameters
[in]levLevel in the AMR hierarchy.
[in]geomGeometry defining the domain.
[out]z_phys_ndMultiFab to be filled with nodal physical heights.
[in]z_levels_hReference z-levels for grid stretching.
333 {
334  // User-selected method from inputs file (BTF default)
335  ParmParse pp("erf");
336  int terrain_smoothing = 0;
337  pp.queryAdd("terrain_smoothing", terrain_smoothing);
338 
339  //
340  // The STF and Sullivan TF transformations need the full column from the surface to
341  // the domain top, so on a fine level we build the mesh from the one interpolated
342  // from the coarse level instead. Only a caller that has done that interpolation
343  // (and told us so) may take this path; everyone else still aborts, as before.
344  //
345  if (lev > 0 && terrain_smoothing != 0) {
346  if (fine_terrain == FineTerrain::None) {
347  Abort("Must use terrain_smoothing = 0 when doing multilevel with this initialization");
348  }
349  init_fine_terrain_grid(lev, geom, z_phys_nd, z_levels_h, fine_terrain, z_phys_interp);
350  return;
351  }
352 
353  // Number of ghost cells
354  int ngrow = z_phys_nd.nGrow();
355  IntVect ngrowVect = z_phys_nd.nGrowVect();
356 
357  const Box& domain = geom.Domain();
358  int domlo_x = domain.smallEnd(0); int domhi_x = domain.bigEnd(0) + 1;
359  int domlo_y = domain.smallEnd(1); int domhi_y = domain.bigEnd(1) + 1;
360  int domlo_z = domain.smallEnd(2); int domhi_z = domain.bigEnd(2) + 1;
361 
362  int imin = domlo_x; // if (geom.isPeriodic(0)) imin -= z_phys_nd.nGrowVect()[0];
363  int jmin = domlo_y; // if (geom.isPeriodic(1)) jmin -= z_phys_nd.nGrowVect()[1];
364 
365  int imax = domhi_x; // if (geom.isPeriodic(0)) imax += z_phys_nd.nGrowVect()[0];
366  int jmax = domhi_y; // if (geom.isPeriodic(1)) jmax += z_phys_nd.nGrowVect()[1];
367 
368  int nz = static_cast<int>(z_levels_h.size());
369  Real z_top = z_levels_h[nz-1];
370 
371  Gpu::DeviceVector<Real> z_levels_d;
372  z_levels_d.resize(nz);
373  Gpu::copy(Gpu::hostToDevice, z_levels_h.begin(), z_levels_h.end(), z_levels_d.begin());
374 
375  switch(terrain_smoothing) {
376  case 0: // BTF Method
377  {
378  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
379  {
380  // Note that this box is nodal because it is based on z_phys_nd
381  const Box& bx = mfi.validbox();
382 
383  int k0 = bx.smallEnd()[2];
384 
385  // Grown box with corrected ghost cells at top
386  Box gbx = mfi.growntilebox(ngrowVect);
387 
388  if (bx.smallEnd(2) == domlo_z) {
389  gbx.setSmall(2,domlo_z);
390  } else {
391  gbx.growLo(2,-1);
392  }
393 
394  // Note that we don't overwrite the values at the high end of the box
395  // regardless of whether the box reaches the top of the domain or not.
396  // In the case of lev > 0, this ensures that the fine nodes at the top
397  // of a fine box are those that are interpolated from the coarse grid
398  if (bx.bigEnd(2) == domhi_z) {
399  gbx.setBig(2,domhi_z);
400  } else {
401  gbx.growHi(2,-1);
402  if (gbx.bigEnd(2) > domhi_z) {
403  gbx.setBig(2,domhi_z);
404  }
405  }
406 
407  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
408  auto const& z_lev = z_levels_d.data();
409 
410  //
411  // Vertical grid stretching using BTF model from p2163 of Klemp2011
412  // z_levels are only defined from k = dom_lo to dom_hi (nodal)
413  //
414  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
415  {
416  int ii = amrex::max(amrex::min(i,imax),imin);
417  int jj = amrex::max(amrex::min(j,jmax),jmin);
418 
419  //
420  // Start with flat z_lev set either with uniform cell size or specified z_levels
421  // If k0 = 0 then z_arr at k0 has already been filled from the terrain data
422  // If k0 > 0 then z_arr at k0 has already been filled from interpolation
423  //
424  Real z = z_lev[k];
425  Real z_sfc = z_arr(ii,jj,k0);
426  Real z_lev_sfc = z_lev[k0];
427 
428  z_arr(i,j,k) = ( (z_sfc - z_lev_sfc) * z_top +
429  (z_top - z_sfc ) * z ) / (z_top - z_lev_sfc);
430  });
431  } // mfi
432 
433  z_phys_nd.FillBoundary(geom.periodicity());
434 
435  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
436  {
437  // Note that this box is nodal because it is based on z_phys_nd
438  const Box& bx = mfi.validbox();
439  Box gbx = mfi.growntilebox(ngrowVect);
440 
441  int k0 = bx.smallEnd()[2];
442 
443  if (k0 == 0)
444  {
445  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
446 
447  // Fill lateral boundaries below the bottom surface
448  ParallelFor(makeSlab(gbx,2,0), [=] AMREX_GPU_DEVICE (int i, int j, int)
449  {
450  z_arr(i,j,-1) = two*z_arr(i,j,0) - z_arr(i,j,1);
451  });
452  }
453  } // mfi
454  break;
455  } // case 0
456 
457  case 1: // STF Method
458  {
459  // Get MultiFab spanning domain with 1 level of ghost cells
460  MultiFab h_mf( z_phys_nd.boxArray(), z_phys_nd.DistributionMap(), 1, ngrow+1);
461  MultiFab h_mf_old(z_phys_nd.boxArray(), z_phys_nd.DistributionMap(), 1, ngrow+1);
462 
463  // Save max height for smoothing
464  Real h_m;
465 
466  // Create 2D MF without allocation
467  MultiFab mf2d;
468  {
469  BoxList bl2d = h_mf.boxArray().boxList();
470  for (auto& b : bl2d) { b.setRange(2,b.smallEnd(2)); }
471  BoxArray ba2d(std::move(bl2d));
472  mf2d = MultiFab(ba2d, h_mf.DistributionMap(), 1, ngrow, MFInfo().SetAlloc(false));
473  }
474 
475  // Get MultiArray4s from the multifabs
476  MultiArray4<Real> const& ma_h_s = h_mf.arrays();
477  MultiArray4<Real> const& ma_h_s_old = h_mf_old.arrays();
478  MultiArray4<Real> const& ma_z_phys = z_phys_nd.arrays();
479 
480  // Bottom boundary
481  int k0 = domlo_z;
482 
483  // Get max value
484  h_m = ParReduce(TypeList<ReduceOpMax>{}, TypeList<Real>{}, mf2d, IntVect(ngrow,ngrow,0),
485  [=] AMREX_GPU_DEVICE (int box_no, int i, int j, int) noexcept
486  -> GpuTuple<Real>
487  {
488  // Get Array4s
489  const auto & h = ma_h_s[box_no];
490  const auto & z_arr = ma_z_phys[box_no];
491 
492  int ii = amrex::max(amrex::min(i,imax),imin);
493  int jj = amrex::max(amrex::min(j,jmax),jmin);
494 
495  // Fill the lateral boundaries
496  z_arr(i,j,k0) = z_arr(ii,jj,k0);
497 
498  // Populate h with terrain
499  h(i,j,k0) = z_arr(i,j,k0);
500 
501  // Return height for max
502  return { z_arr(i,j,k0) };
503  });
504  amrex::ParallelDescriptor::ReduceRealMax(h_m);
505 
506  if (h_m < std::numeric_limits<Real>::epsilon()) h_m = Real(1e-16);
507 
508  // Fill ghost cells (neglects domain boundary if not periodic)
509  h_mf.FillBoundary(geom.periodicity());
510 
511  // Make h_mf copy for old values
512  MultiFab::Copy(h_mf_old, h_mf,0,0,1,h_mf_old.nGrow());
513 
514  // Minimum allowed fractional grid spacing
515  Real gamma_m = myhalf;
516  pp.queryAdd("terrain_gamma_m", gamma_m);
517  Real z_H = Real(2.44)*h_m/(1-gamma_m); // Klemp2011 Eqn. 11
518 
519  // Populate h_mf at k>0 with h_s, solving in ordered 2D slices
520  for (int k = domlo_z+1; k <= domhi_z; k++) // skip terrain level
521  {
522  auto const& z_lev_h = z_levels_h.data();
523 
524  Real zz = z_lev_h[k];
525  Real zz_minus = z_lev_h[k-1];
526 
527  // Hybrid attenuation profile, Klemp2011 Eqn. 9
528  Real A;
529  Real foo = std::cos((PI/2)*(zz/z_H));
530  if(zz < z_H) { A = foo*foo*foo*foo*foo*foo; } // A controls rate of return to atm
531  else { A = 0; }
532  Real foo_minus = std::cos((PI/2)*(zz_minus/z_H));
533  Real A_minus;
534  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
535  else { A_minus = 0; }
536 
537  unsigned maxIter = 50; // M_k in paper
538  unsigned iter = 0;
539  Real threshold = gamma_m;
540  Real diff = bogus_large_value;
541  while (iter < maxIter && diff > threshold)
542  {
543 
544  diff = ParReduce(TypeList<ReduceOpMin>{}, TypeList<Real>{}, mf2d, IntVect(ngrow,ngrow,0),
545  [=] AMREX_GPU_DEVICE (int box_no, int i, int j, int) noexcept
546  -> GpuTuple<Real>
547  {
548  const auto & h_s = ma_h_s[box_no];
549  const auto & h_s_old = ma_h_s_old[box_no];
550 
551  Real beta_k = Real(0.2)*std::min(zz/(2*h_m),Real(1.0)); //smoothing coefficient (Eqn. 8)
552 
553  // Clip indices for ghost-cells
554  int ii = amrex::min(amrex::max(i,domlo_x),domhi_x);
555  int jj = amrex::min(amrex::max(j,domlo_y),domhi_y);
556 
557  if (iter == 0) {
558  h_s(i,j,k) = h_s_old(i,j,k-1) + beta_k*(h_s_old(ii+1,jj ,k-1)
559  + h_s_old(ii-1,jj ,k-1)
560  + h_s_old(ii ,jj+1,k-1)
561  + h_s_old(ii ,jj-1,k-1) - 4*h_s_old(ii,jj,k-1));
562  }
563  else {
564  h_s(i,j,k) = h_s_old(i,j,k ) + beta_k*(h_s_old(ii+1,jj ,k )
565  + h_s_old(ii-1,jj ,k )
566  + h_s_old(ii ,jj+1,k )
567  + h_s_old(ii ,jj-1,k ) - 4*h_s_old(ii,jj,k ));
568  }
569 
570  // Minimum vertical grid spacing condition (Klemp2011 Eqn. 7)
571  return { (zz + A * h_s(i,j,k) - (zz_minus + A_minus * h_s(i,j,k-1))) / (zz - zz_minus) };
572 
573  }); //ParReduce
574 
575  MultiFab::Copy(h_mf_old, h_mf,0,0,1,h_mf_old.nGrow());
576 
577  ParallelDescriptor::ReduceRealMin(diff);
578 
579  iter++;
580 
581  // Fill ghost points
582  h_mf_old.FillBoundary(geom.periodicity());
583 
584  } // while
585 
586  auto const& z_lev_d = z_levels_d.data();
587 
588  // Populate z_phys_nd by solving z_arr(i,j,k) = z + A*h_s(i,j,k)
589  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
590  {
591  // Grown box with no z range
592  Box xybx = mfi.growntilebox(ngrow);
593  xybx.setRange(2,0);
594 
595  Array4<Real> const& h_s = h_mf_old.array(mfi);
596  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
597 
598  ParallelFor(xybx, [=] AMREX_GPU_DEVICE (int i, int j, int) {
599 
600  // Location of nodes
601  Real z = z_lev_d[k];
602 
603  // STF model from p2164 of Klemp2011 (Eqn. 4)
604  z_arr(i,j,k) = z + A*h_s(i,j,k);
605 
606  // Fill below the bottom surface
607  if (k == 1) {
608  z_arr(i,j,k0-1) = two*z_arr(i,j,k0) - z_arr(i,j,k);
609  }
610  });
611  } // mfi
612  } // k
613 
614  Gpu::streamSynchronize();
615 
616  break;
617  } // case 1
618 
619  case 2: // Sullivan TF Method
620  {
621  int k0 = 0;
622 
623  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
624  {
625  // Grown box with corrected ghost cells at top
626  Box gbx = mfi.growntilebox(ngrow);
627  gbx.setRange(2,domlo_z,domhi_z+1);
628 
629  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
630  auto const& z_lev = z_levels_d.data();
631 
632  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
633  {
634  // Vertical grid stretching
635  Real z = z_lev[k];
636 
637  int ii = amrex::max(amrex::min(i,imax),imin);
638  int jj = amrex::max(amrex::min(j,jmax),jmin);
639 
640  // Fill levels using model from Sullivan et. al. 2014
641  int omega = 3; //Used to adjust how rapidly grid lines level out. omega=1 is BTF!
642  z_arr(i,j,k) = z + static_cast<Real>(std::pow((one - (z/z_top)),omega) * z_arr(ii,jj,k0));
643 
644  // Fill lateral boundaries and below the bottom surface
645  if (k == k0) {
646  z_arr(i,j,k0 ) = z_arr(ii,jj,k0);
647  }
648  if (k == 1) {
649  z_arr(i,j,k0-1) = two*z_arr(ii,jj,k0) - z_arr(i,j,k);
650  }
651  });
652  } // mfi
653  break;
654  } // case 2
655 
656  case 3: // Debugging Test Method -- applies Sullivan TF starting at k = 1 so that domain does not change size
657  {
658  int k0 = 0;
659 
660  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
661  {
662  // Grown box with corrected ghost cells at top
663  Box gbx = mfi.growntilebox(ngrow);
664  gbx.setRange(2,domlo_z,domhi_z+1);
665 
666  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
667  auto const& z_lev = z_levels_d.data();
668 
669  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
670  {
671  // Vertical grid stretching
672  Real z = z_lev[k];
673 
674  int ii = amrex::max(amrex::min(i,imax),imin);
675  int jj = amrex::max(amrex::min(j,jmax),jmin);
676 
677  // Fill values outside the lateral boundaries and below the bottom surface (necessary if init_type = "WRFInput")
678  if (k == k0+1) {
679  z_arr(i,j,k) = z + z_arr(ii,jj,k0);
680  } else {
681  // Fill levels using model from Sullivan et. al. 2014
682  int omega = 3; //Used to adjust how rapidly grid lines level out. omega=1 is BTF!
683  z_arr(i,j,k) = z + static_cast<Real>(std::pow((one - (z/z_top)),omega) * z_arr(ii,jj,k0));
684  }
685  });
686  gbx.setBig(2,0);
687  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
688  {
689  z_arr(i,j,k ) = zero;
690  z_arr(i,j,k-1) = -z_arr(i,j,k+1);
691  });
692  } // mfi
693  break;
694  } // case 3
695  } //switch
696 }
constexpr amrex::Real bogus_large_value
Definition: ERF_Constants.H:26
constexpr amrex::Real one
Definition: ERF_Constants.H:9
constexpr amrex::Real zero
Definition: ERF_Constants.H:8
constexpr amrex::Real PI
Definition: ERF_Constants.H:42
ParmParse pp("prob")
void init_fine_terrain_grid(int lev, Geometry const &geom, MultiFab &z_phys_nd, Vector< Real > const &z_levels_h, FineTerrain fine_terrain, MultiFab const *z_phys_interp)
Definition: ERF_TerrainMetrics.cpp:199
@ omega
Definition: ERF_Morrison.H:54

Referenced by make_terrain_fitted_coords().

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

745 {
746  const auto* dx = geom.CellSize();
747  Real dzInv = one/dx[2];
748 
749  // Domain valid box (z_nd is nodal)
750  const Box& domain = geom.Domain();
751  int domlo_z = domain.smallEnd(2);
752 
753  // The z-faces are always full when using terrain-fitted coordinates
754  az.setVal(1.0);
755 
756  //
757  // x-areas
758  //
759 #ifdef _OPENMP
760 #pragma omp parallel if (Gpu::notInLaunchRegion())
761 #endif
762  for ( MFIter mfi(ax, TilingIfNotGPU()); mfi.isValid(); ++mfi )
763  {
764  Box gbx = mfi.growntilebox(ax.nGrow());
765  if (gbx.smallEnd(2) < domlo_z) {
766  gbx.setSmall(2,domlo_z);
767  }
768 
769  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
770  Array4<Real > ax_arr = ax.array(mfi);
771  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
772  ax_arr(i, j, k) = Real(.5) * dzInv * (
773  z_nd(i,j,k+1) + z_nd(i,j+1,k+1) - z_nd(i,j,k) - z_nd(i,j+1,k));
774  });
775  }
776 
777  //
778  // y-areas
779  //
780 #ifdef _OPENMP
781 #pragma omp parallel if (Gpu::notInLaunchRegion())
782 #endif
783  for ( MFIter mfi(ay, TilingIfNotGPU()); mfi.isValid(); ++mfi )
784  {
785  Box gbx = mfi.growntilebox(ay.nGrow());
786  if (gbx.smallEnd(2) < domlo_z) {
787  gbx.setSmall(2,domlo_z);
788  }
789 
790  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
791  Array4<Real > ay_arr = ay.array(mfi);
792  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
793  ay_arr(i, j, k) = Real(.5) * dzInv * (
794  z_nd(i,j,k+1) + z_nd(i+1,j,k+1) - z_nd(i,j,k) - z_nd(i+1,j,k));
795  });
796  }
797 
798  ax.FillBoundary(geom.periodicity());
799  ay.FillBoundary(geom.periodicity());
800  az.FillBoundary(geom.periodicity());
801 }

Referenced by ERF::update_terrain_arrays().

Here is the call graph for this function:
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

705 {
706  const auto *dx = geom.CellSize();
707  Real dzInv = one/dx[2];
708 
709  // Domain valid box (z_nd is nodal)
710  const Box& domain = geom.Domain();
711  int domlo_z = domain.smallEnd(2);
712 
713  // Number of ghost cells
714  int ngrow= detJ_cc.nGrow();
715 
716 #ifdef _OPENMP
717 #pragma omp parallel if (Gpu::notInLaunchRegion())
718 #endif
719  for ( MFIter mfi(detJ_cc, TilingIfNotGPU()); mfi.isValid(); ++mfi )
720  {
721  Box gbx = mfi.growntilebox(ngrow);
722  if (gbx.smallEnd(2) < domlo_z) {
723  gbx.setSmall(2,domlo_z);
724  }
725  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
726  Array4<Real > detJ = detJ_cc.array(mfi);
727  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
728  detJ(i, j, k) = Real(.25) * dzInv * (
729  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)
730  -z_nd(i,j,k ) - z_nd(i+1,j,k ) - z_nd(i,j+1,k ) - z_nd(i+1,j+1,k ) );
731  });
732  }
733  detJ_cc.FillBoundary(geom.periodicity());
734 }

Referenced by ERF::update_terrain_arrays().

Here is the call graph for this function:
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,
FineTerrain  fine_terrain,
MultiFab const *  z_phys_interp 
)

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

57 {
58  const Box& domain = geom.Domain();
59 
60  int domlo_z = domain.smallEnd(2);
61  int domhi_z = domain.bigEnd(2) + 1;
62 
63  // Just in case ...
64  z_phys_nd.setDomainBndry(bogus_large_value,0,1,geom);
65 
66  // ****************************************************************************
67 
68  if (lev == 0) {
69  BoxArray ba(z_phys_nd.boxArray());
70  bool all_boxes_touch_bottom = true;
71  for (int i = 0; i < ba.size(); i++) {
72  if (ba[i].smallEnd(2) != domlo_z) {
73  all_boxes_touch_bottom = false;
74  }
75  }
76 
77  if (all_boxes_touch_bottom) {
78  init_which_terrain_grid(lev, geom, z_phys_nd, z_levels_h, fine_terrain, z_phys_interp);
79  } else {
80 
81  BoxArray ba_new(domain);
82  ChopGrids2D(ba_new, domain, ParallelDescriptor::NProcs());
83 
84  DistributionMapping dm_new(ba_new);
85  ba_new.surroundingNodes();
86 
87  MultiFab z_phys_nd_new(ba_new, dm_new, 1, z_phys_nd.nGrowVect());
88 
89  z_phys_nd_new.ParallelCopy(z_phys_nd,0,0,1,z_phys_nd.nGrowVect(),z_phys_nd.nGrowVect());
90 
91  init_which_terrain_grid(lev, geom, z_phys_nd_new, z_levels_h, fine_terrain, z_phys_interp);
92 
93  z_phys_nd.ParallelCopy(z_phys_nd_new,0,0,1,z_phys_nd.nGrowVect(),z_phys_nd.nGrowVect());
94  }
95  } else { // lev > 0
96  init_which_terrain_grid(lev, geom, z_phys_nd, z_levels_h, fine_terrain, z_phys_interp);
97  }
98 
99  //
100  // Fill ghost layers and corners (including periodic) -- no matter what level
101  //
102  z_phys_nd.FillBoundary(geom.periodicity());
103 
104  if (phys_bc_type[Orientation(0,Orientation::low )] == ERF_BC::symmetry ||
105  phys_bc_type[Orientation(0,Orientation::high)] == ERF_BC::symmetry ||
106  phys_bc_type[Orientation(1,Orientation::low )] == ERF_BC::symmetry ||
107  phys_bc_type[Orientation(1,Orientation::high)] == ERF_BC::symmetry) {
108 
109  const auto& dom_lo = lbound(convert(domain,IntVect(1,1,1)));
110  const auto& dom_hi = ubound(convert(domain,IntVect(1,1,1)));
111 
112  for (MFIter mfi(z_phys_nd,true); mfi.isValid(); ++mfi) {
113  const Box& bx = mfi.growntilebox();
114  const Array4< Real> z_nd_arr = z_phys_nd.array(mfi);
115  if (phys_bc_type[Orientation(0,Orientation::low)] == ERF_BC::symmetry && bx.smallEnd(0) == dom_lo.x) {
116  ParallelFor(makeSlab(bx,0,1), [=] AMREX_GPU_DEVICE (int , int j, int k)
117  {
118  z_nd_arr(dom_lo.x-1,j,k) = z_nd_arr(dom_lo.x+1,j,k);
119  });
120  }
121  if (phys_bc_type[Orientation(0,Orientation::high)] == ERF_BC::symmetry && bx.bigEnd(0) == dom_hi.x) {
122  ParallelFor(makeSlab(bx,0,1), [=] AMREX_GPU_DEVICE (int , int j, int k)
123  {
124  z_nd_arr(dom_hi.x+1,j,k) = z_nd_arr(dom_hi.x-1,j,k);
125  });
126  }
127  if (phys_bc_type[Orientation(1,Orientation::low)] == ERF_BC::symmetry && bx.smallEnd(1) == dom_lo.y) {
128  ParallelFor(makeSlab(bx,1,1), [=] AMREX_GPU_DEVICE (int i, int , int k)
129  {
130  z_nd_arr(i,dom_lo.y-1,k) = z_nd_arr(i,dom_lo.y+1,k);
131  });
132  }
133  if (phys_bc_type[Orientation(1,Orientation::high)] == ERF_BC::symmetry && bx.bigEnd(1) == dom_hi.y) {
134  ParallelFor(makeSlab(bx,1,1), [=] AMREX_GPU_DEVICE (int i, int , int k)
135  {
136  z_nd_arr(i,dom_hi.y+1,k) = z_nd_arr(i,dom_hi.y-1,k);
137  });
138  }
139  }
140  }
141 
142  //********************************************************************************
143  // Populate domain boundary cells in z-direction
144  //********************************************************************************
145  int ngrow = z_phys_nd.nGrow();
146 
147  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
148  {
149  // Only set values above top of domain if this box reaches that far
150  Box nd_bx = mfi.tilebox();
151 
152  // Note that domhi_z is already nodal in the z-direction
153  if (nd_bx.bigEnd(2) >= domhi_z) {
154  // Grown box with no z range
155  Box bx_zhi = mfi.growntilebox(ngrow);
156  bx_zhi.setSmall(2,domhi_z+1);
157  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
158 
159  // Extrapolate top layer
160  ParallelFor(bx_zhi, [=] AMREX_GPU_DEVICE (int i, int j, int k) {
161  z_arr(i,j,k) = z_arr(i,j,domhi_z)
162  + (k-domhi_z) * (z_arr(i,j,domhi_z) - z_arr(i,j,domhi_z-1));
163  });
164  }
165  }
166 } // make_terrain_fitted_coords
void ChopGrids2D(BoxArray &ba, const Box &domain, int target_size)
Definition: ERF_ChopGrids.cpp:35
void init_which_terrain_grid(int lev, Geometry const &geom, MultiFab &z_phys_nd, Vector< Real > const &z_levels_h, FineTerrain fine_terrain, MultiFab const *z_phys_interp)
Definition: ERF_TerrainMetrics.cpp:327

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

810 {
811 #ifdef _OPENMP
812 #pragma omp parallel if (Gpu::notInLaunchRegion())
813 #endif
814  for ( MFIter mfi(z_phys_cc, TilingIfNotGPU()); mfi.isValid(); ++mfi )
815  {
816  Box gbx = mfi.growntilebox();
817  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
818  Array4<Real > z_cc = z_phys_cc.array(mfi);
819  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
820  z_cc(i, j, k) = Real(.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 )
821  +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) );
822  });
823  }
824  z_phys_cc.FillBoundary(geom.periodicity());
825 }

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

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

◆ which_fine_terrain()

FineTerrain which_fine_terrain ( )

Translate erf.amr_terrain_refinement into a FineTerrain.

173 {
174  ParmParse pp("erf");
175  std::string amr_terrain_refinement = "interpolate";
176  pp.query("amr_terrain_refinement", amr_terrain_refinement);
177 
178  if (amr_terrain_refinement == "interpolate") {
180  } else if (amr_terrain_refinement == "transform") {
181  return FineTerrain::Transform;
182  }
183 
184  Abort("erf.amr_terrain_refinement = " + amr_terrain_refinement +
185  " is not recognized; it must be \"interpolate\" or \"transform\"");
187 }
@ Interpolate
use the coordinates interpolated from the coarse level as-is

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: