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 <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <map>
#include <numeric>
#include <utility>
#include <vector>
Include dependency graph for ERF_TerrainMetrics.cpp:

Functions

void validate_flat_terrain (int lev, const MultiFab &z_phys_nd, const Vector< Real > &z_levels_h)
 
void init_default_zphys (int, const Geometry &geom, MultiFab &z_phys_nd, MultiFab &z_phys_cc, Real z_offset)
 
BoxArray join_boxes_stacked_in_z (const BoxArray &ba)
 
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

1178 {
1179  auto const& ma_z_nd_arr = z_phys_nd.const_arrays();
1180  GpuTuple<Real> min = ParReduce(TypeList<ReduceOpMin>{},
1181  TypeList<Real>{},
1182  z_phys_nd, IntVect(0),
1183  [=] AMREX_GPU_DEVICE (int box_no, int i, int j, int k) noexcept
1184  -> GpuTuple<Real>
1185  {
1186  amrex::Real dz = Compute_Z_AtWFace(i,j,k+1,ma_z_nd_arr[box_no]) -
1187  Compute_Z_AtWFace(i,j,k ,ma_z_nd_arr[box_no]);
1188  return { dz };
1189  });
1190  Real r = (get<0>(min) + std::numeric_limits<amrex::Real>::epsilon());
1191  ParallelDescriptor::ReduceRealMin(r);
1192  return r;
1193 }
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_GPU_HOST_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:736
@ dz
Definition: ERF_AdvanceWDM6.cpp:272
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

65 {
66  const auto& dx = geom.CellSize();
67  Real dz = dx[2];
68 
69  for (MFIter mfi(z_phys_nd,true); mfi.isValid(); ++mfi)
70  {
71  const Box& bx = mfi.growntilebox();
72  const Array4< Real> z_nd_arr = z_phys_nd.array(mfi);
73  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
74  {
75  z_nd_arr(i,j,k) = k * dz - z_offset;
76  });
77  }
78 
79  for (MFIter mfi(z_phys_cc,true); mfi.isValid(); ++mfi)
80  {
81  const Box& bx = mfi.growntilebox();
82  const Array4< Real> z_cc_arr = z_phys_cc.array(mfi);
83  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
84  {
85  z_cc_arr(i,j,k) = (k + myhalf) * dz - z_offset;
86  });
87  }
88 }
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);})
constexpr amrex::Real myhalf
Definition: ERF_NumericalConstants.H:41

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.

484 {
485  AMREX_ALWAYS_ASSERT(lev > 0);
486  AMREX_ALWAYS_ASSERT(fine_terrain != FineTerrain::None);
487 
488  const Box& domain = geom.Domain();
489  int domlo_x = domain.smallEnd(0); int domhi_x = domain.bigEnd(0) + 1;
490  int domlo_y = domain.smallEnd(1); int domhi_y = domain.bigEnd(1) + 1;
491  int domlo_z = domain.smallEnd(2);
492 
493  IntVect ngrowVect = z_phys_nd.nGrowVect();
494 
495  if (fine_terrain == FineTerrain::Transform) {
496  AMREX_ALWAYS_ASSERT(z_phys_interp != nullptr);
497 
498  const BoxArray& ba = z_phys_nd.boxArray();
499 
500  //
501  // The correction is anchored at the surface, so a box that does not reach the
502  // surface would keep the uncorrected interpolated mesh and would not join the
503  // box below it. Say so rather than silently building a tangled mesh.
504  //
505  for (int i = 0; i < ba.size(); i++) {
506  if (ba[i].smallEnd(2) != domlo_z) {
507  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");
508  }
509  }
510 
511  //
512  // Decay over the height of the fine grids as a whole and never over the height
513  // of an individual box, so that the mesh does not depend on the decomposition.
514  //
515  int khi_lev = ba.minimalBox().bigEnd(2);
516  Real z_lev_top = z_levels_h[khi_lev];
517  Real z_lev_bot = z_levels_h[domlo_z];
518  AMREX_ALWAYS_ASSERT(z_lev_top > z_lev_bot);
519 
520  Gpu::DeviceVector<Real> z_levels_d(z_levels_h.size());
521  Gpu::copy(Gpu::hostToDevice, z_levels_h.begin(), z_levels_h.end(), z_levels_d.begin());
522  auto const& z_lev = z_levels_d.data();
523 
524  //
525  // Correct the ghost nodes along with the valid ones. On a fine level whose
526  // grids do not cover the domain, the lateral ghost nodes have no valid owner
527  // anywhere, so the FillBoundary below cannot bring them up to date: left
528  // uncorrected they would keep the interpolated mesh above the surface while
529  // their k=0 node already holds the fine terrain, and the first layer of such a
530  // column inverts wherever the fine terrain stands more than a layer thickness
531  // above the interpolated one. Both fields the correction reads are defined on
532  // those nodes -- but only inside the domain, so stop there and leave the nodes
533  // outside it to the clamp below, which is what gives them a mesh at all.
534  //
535  const Box domain_nd = convert(domain, IntVect(1,1,1));
536 
537  for (MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi)
538  {
539  const Box bx = mfi.growntilebox(ngrowVect) & domain_nd;
540 
541  Array4<Real > const& z_arr = z_phys_nd.array(mfi);
542  Array4<Real const> const& z_interp = z_phys_interp->const_array(mfi);
543 
544  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
545  {
546  if (k > domlo_z && k <= khi_lev) {
547  // How much taller the fine terrain is than the interpolated surface
548  Real delta_terrain = z_arr(i,j,domlo_z) - z_interp(i,j,domlo_z);
549 
550  Real decay = (z_lev_top - z_lev[k]) / (z_lev_top - z_lev_bot);
551  decay = amrex::max(Real(0.0), amrex::min(Real(1.0), decay));
552 
553  z_arr(i,j,k) = z_interp(i,j,k) + decay * delta_terrain;
554  }
555  });
556  } // mfi
557 
558  // Give every ghost node that does have a valid owner that owner's value, rather
559  // than the one this box computed for itself, before the fill below reads them
560  z_phys_nd.FillBoundary(geom.periodicity());
561  }
562 
563  //
564  // Extend the mesh to the nodes outside the lateral domain boundary by copying the
565  // nearest column inside it, which is the value the BTF branch gives those nodes.
566  // Like that branch we clamp in a periodic direction too, even though FillBoundary
567  // below will replace what we write there with the periodic image: that way every
568  // node we read here is one inside the domain, whose value we know is already set.
569  //
570  int imin = domlo_x; int imax = domhi_x;
571  int jmin = domlo_y; int jmax = domhi_y;
572 
573  for (MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi)
574  {
575  Box gbx = mfi.growntilebox(ngrowVect);
576  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
577 
578  // Note that we only write nodes outside the domain and only read nodes inside
579  // it, so no node is both read and written here.
580  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
581  {
582  int ii = amrex::max(amrex::min(i,imax),imin);
583  int jj = amrex::max(amrex::min(j,jmax),jmin);
584  if (ii != i || jj != j) {
585  z_arr(i,j,k) = z_arr(ii,jj,k);
586  }
587  });
588  } // mfi
589 
590  z_phys_nd.FillBoundary(geom.periodicity());
591 
592  for (MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi)
593  {
594  const Box& bx = mfi.validbox();
595  Box gbx = mfi.growntilebox(ngrowVect);
596 
597  if (bx.smallEnd(2) == domlo_z)
598  {
599  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
600 
601  // Fill lateral boundaries below the bottom surface
602  ParallelFor(makeSlab(gbx,2,domlo_z), [=] AMREX_GPU_DEVICE (int i, int j, int)
603  {
604  z_arr(i,j,domlo_z-1) = two*z_arr(i,j,domlo_z) - z_arr(i,j,domlo_z+1);
605  });
606  }
607  } // mfi
608 } // init_fine_terrain_grid
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
constexpr amrex::Real two
Definition: ERF_NumericalConstants.H:38
@ 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.
625 {
626  // User-selected method from inputs file (BTF default)
627  ParmParse pp("erf");
628  int terrain_smoothing = 0;
629  pp.queryAdd("terrain_smoothing", terrain_smoothing);
630 
631  //
632  // The STF and Sullivan TF transformations need the full column from the surface to
633  // the domain top, so on a fine level we build the mesh from the one interpolated
634  // from the coarse level instead. Only a caller that has done that interpolation
635  // (and told us so) may take this path; everyone else still aborts.
636  //
637  if (lev > 0 && terrain_smoothing != 0) {
638  if (fine_terrain == FineTerrain::None) {
639  Abort("terrain_smoothing = " + std::to_string(terrain_smoothing) +
640  " on level > 0 requires setting erf.amr_terrain_refinement (transform or interpolate)");
641  }
642  init_fine_terrain_grid(lev, geom, z_phys_nd, z_levels_h, fine_terrain, z_phys_interp);
643  return;
644  }
645 
646  // Number of ghost cells
647  int ngrow = z_phys_nd.nGrow();
648  IntVect ngrowVect = z_phys_nd.nGrowVect();
649 
650  const Box& domain = geom.Domain();
651  int domlo_x = domain.smallEnd(0); int domhi_x = domain.bigEnd(0) + 1;
652  int domlo_y = domain.smallEnd(1); int domhi_y = domain.bigEnd(1) + 1;
653  int domlo_z = domain.smallEnd(2); int domhi_z = domain.bigEnd(2) + 1;
654 
655  int imin = domlo_x; // if (geom.isPeriodic(0)) imin -= z_phys_nd.nGrowVect()[0];
656  int jmin = domlo_y; // if (geom.isPeriodic(1)) jmin -= z_phys_nd.nGrowVect()[1];
657 
658  int imax = domhi_x; // if (geom.isPeriodic(0)) imax += z_phys_nd.nGrowVect()[0];
659  int jmax = domhi_y; // if (geom.isPeriodic(1)) jmax += z_phys_nd.nGrowVect()[1];
660 
661  int nz = static_cast<int>(z_levels_h.size());
662  Real z_top = z_levels_h[nz-1];
663 
664  Gpu::DeviceVector<Real> z_levels_d;
665  z_levels_d.resize(nz);
666  Gpu::copy(Gpu::hostToDevice, z_levels_h.begin(), z_levels_h.end(), z_levels_d.begin());
667 
668  switch(terrain_smoothing) {
669  case 0: // BTF Method
670  {
671  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
672  {
673  // Note that this box is nodal because it is based on z_phys_nd
674  const Box& bx = mfi.validbox();
675 
676  int k0 = bx.smallEnd()[2];
677 
678  // Grown box with corrected ghost cells at top
679  Box gbx = mfi.growntilebox(ngrowVect);
680 
681  if (bx.smallEnd(2) == domlo_z) {
682  gbx.setSmall(2,domlo_z);
683  } else {
684  gbx.growLo(2,-1);
685  }
686 
687  // Note that we don't overwrite the values at the high end of the box
688  // regardless of whether the box reaches the top of the domain or not.
689  // In the case of lev > 0, this ensures that the fine nodes at the top
690  // of a fine box are those that are interpolated from the coarse grid
691  if (bx.bigEnd(2) == domhi_z) {
692  gbx.setBig(2,domhi_z);
693  } else {
694  gbx.growHi(2,-1);
695  if (gbx.bigEnd(2) > domhi_z) {
696  gbx.setBig(2,domhi_z);
697  }
698  }
699 
700  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
701  auto const& z_lev = z_levels_d.data();
702 
703  //
704  // Vertical grid stretching using BTF model from p2163 of Klemp2011
705  // z_levels are only defined from k = dom_lo to dom_hi (nodal)
706  //
707  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
708  {
709  int ii = amrex::max(amrex::min(i,imax),imin);
710  int jj = amrex::max(amrex::min(j,jmax),jmin);
711 
712  //
713  // Start with flat z_lev set either with uniform cell size or specified z_levels
714  // If k0 = 0 then z_arr at k0 has already been filled from the terrain data
715  // If k0 > 0 then z_arr at k0 has already been filled from interpolation
716  //
717  Real z = z_lev[k];
718  Real z_sfc = z_arr(ii,jj,k0);
719  Real z_lev_sfc = z_lev[k0];
720 
721  z_arr(i,j,k) = ( (z_sfc - z_lev_sfc) * z_top +
722  (z_top - z_sfc ) * z ) / (z_top - z_lev_sfc);
723  });
724  } // mfi
725 
726  z_phys_nd.FillBoundary(geom.periodicity());
727 
728  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
729  {
730  // Note that this box is nodal because it is based on z_phys_nd
731  const Box& bx = mfi.validbox();
732  Box gbx = mfi.growntilebox(ngrowVect);
733 
734  int k0 = bx.smallEnd()[2];
735 
736  if (k0 == 0)
737  {
738  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
739 
740  // Fill lateral boundaries below the bottom surface
741  ParallelFor(makeSlab(gbx,2,0), [=] AMREX_GPU_DEVICE (int i, int j, int)
742  {
743  z_arr(i,j,-1) = two*z_arr(i,j,0) - z_arr(i,j,1);
744  });
745  }
746  } // mfi
747  break;
748  } // case 0
749 
750  case 1: // STF Method
751  {
752  // Get MultiFab spanning domain with 1 level of ghost cells
753  MultiFab h_mf( z_phys_nd.boxArray(), z_phys_nd.DistributionMap(), 1, ngrow+1);
754  MultiFab h_mf_old(z_phys_nd.boxArray(), z_phys_nd.DistributionMap(), 1, ngrow+1);
755 
756  // Save max height for smoothing
757  Real h_m;
758 
759  // Create 2D MF without allocation
760  MultiFab mf2d;
761  {
762  BoxList bl2d = h_mf.boxArray().boxList();
763  for (auto& b : bl2d) { b.setRange(2,b.smallEnd(2)); }
764  BoxArray ba2d(std::move(bl2d));
765  mf2d = MultiFab(ba2d, h_mf.DistributionMap(), 1, ngrow, MFInfo().SetAlloc(false));
766  }
767 
768  // Get MultiArray4s from the multifabs
769  MultiArray4<Real> const& ma_h_s = h_mf.arrays();
770  MultiArray4<Real> const& ma_h_s_old = h_mf_old.arrays();
771  MultiArray4<Real> const& ma_z_phys = z_phys_nd.arrays();
772 
773  // Bottom boundary
774  int k0 = domlo_z;
775 
776  // Get max value
777  h_m = ParReduce(TypeList<ReduceOpMax>{}, TypeList<Real>{}, mf2d, IntVect(ngrow,ngrow,0),
778  [=] AMREX_GPU_DEVICE (int box_no, int i, int j, int) noexcept
779  -> GpuTuple<Real>
780  {
781  // Get Array4s
782  const auto & h = ma_h_s[box_no];
783  const auto & z_arr = ma_z_phys[box_no];
784 
785  int ii = amrex::max(amrex::min(i,imax),imin);
786  int jj = amrex::max(amrex::min(j,jmax),jmin);
787 
788  // Fill the lateral boundaries
789  z_arr(i,j,k0) = z_arr(ii,jj,k0);
790 
791  // Populate h with terrain
792  h(i,j,k0) = z_arr(i,j,k0);
793 
794  // Return height for max
795  return { z_arr(i,j,k0) };
796  });
797  amrex::ParallelDescriptor::ReduceRealMax(h_m);
798 
799  if (h_m < std::numeric_limits<Real>::epsilon()) h_m = Real(1e-16);
800 
801  // Fill ghost cells (neglects domain boundary if not periodic)
802  h_mf.FillBoundary(geom.periodicity());
803 
804  // Make h_mf copy for old values
805  MultiFab::Copy(h_mf_old, h_mf,0,0,1,h_mf_old.nGrow());
806 
807  // Minimum allowed fractional grid spacing
808  Real gamma_m = myhalf;
809  pp.queryAdd("terrain_gamma_m", gamma_m);
810  Real z_H = Real(2.44)*h_m/(1-gamma_m); // Klemp2011 Eqn. 11
811 
812  // Levels at which the minimum-thickness criterion forced us to stop smoothing
813  // early, reported once below: a terrain that needs this at many levels is at the
814  // edge of what STF can represent on this vertical grid.
815  int n_capped = 0;
816 
817  // Populate h_mf at k>0 with h_s, solving in ordered 2D slices
818  for (int k = domlo_z+1; k <= domhi_z; k++) // skip terrain level
819  {
820  auto const& z_lev_h = z_levels_h.data();
821 
822  Real zz = z_lev_h[k];
823  Real zz_minus = z_lev_h[k-1];
824 
825  // Hybrid attenuation profile, Klemp2011 Eqn. 9
826  Real A;
827  Real foo = std::cos((PI/2)*(zz/z_H));
828  if(zz < z_H) { A = foo*foo*foo*foo*foo*foo; } // A controls rate of return to atm
829  else { A = 0; }
830  Real foo_minus = std::cos((PI/2)*(zz_minus/z_H));
831  Real A_minus;
832  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
833  else { A_minus = 0; }
834 
835  unsigned maxIter = 50; // M_k in paper
836  unsigned iter = 0;
837  Real threshold = gamma_m;
838  Real diff = bogus_large_value;
839  while (iter < maxIter)
840  {
841 
842  diff = ParReduce(TypeList<ReduceOpMin>{}, TypeList<Real>{}, mf2d, IntVect(ngrow,ngrow,0),
843  [=] AMREX_GPU_DEVICE (int box_no, int i, int j, int) noexcept
844  -> GpuTuple<Real>
845  {
846  const auto & h_s = ma_h_s[box_no];
847  const auto & h_s_old = ma_h_s_old[box_no];
848 
849  Real beta_k = Real(0.2)*std::min(zz/(2*h_m),Real(1.0)); //smoothing coefficient (Eqn. 8)
850 
851  // Clip indices for ghost-cells
852  int ii = amrex::min(amrex::max(i,domlo_x),domhi_x);
853  int jj = amrex::min(amrex::max(j,domlo_y),domhi_y);
854 
855  if (iter == 0) {
856  h_s(i,j,k) = h_s_old(i,j,k-1) + beta_k*(h_s_old(ii+1,jj ,k-1)
857  + h_s_old(ii-1,jj ,k-1)
858  + h_s_old(ii ,jj+1,k-1)
859  + h_s_old(ii ,jj-1,k-1) - 4*h_s_old(ii,jj,k-1));
860  }
861  else {
862  h_s(i,j,k) = h_s_old(i,j,k ) + beta_k*(h_s_old(ii+1,jj ,k )
863  + h_s_old(ii-1,jj ,k )
864  + h_s_old(ii ,jj+1,k )
865  + h_s_old(ii ,jj-1,k ) - 4*h_s_old(ii,jj,k ));
866  }
867 
868  // Minimum vertical grid spacing condition (Klemp2011 Eqn. 7)
869  return { (zz + A * h_s(i,j,k) - (zz_minus + A_minus * h_s(i,j,k-1))) / (zz - zz_minus) };
870 
871  }); //ParReduce
872 
873  ParallelDescriptor::ReduceRealMin(diff);
874 
875  //
876  // Each pass smooths h_s at THIS level while the level below is already
877  // fixed, so over a peak it always thins the layer: the passes have to
878  // stop before Eqn. 7 is violated, not after. Measuring only once a pass
879  // has been committed is not enough -- nothing then stops the pass that
880  // breaks the criterion from being the one we keep, which is how a column
881  // over steep terrain ended up at 0.21 of its nominal layer thickness
882  // against gamma_m = 0.5. Reject that pass and restore what preceded it.
883  //
884  // At iter 0 there is no earlier pass at this level to fall back to, so
885  // carry the level below up unsmoothed instead. That leaves only the
886  // A-decay term, whose worst case is exactly what z_H = 2.44 h_m/(1-gamma_m)
887  // was chosen to bound: max|dA/dz| = 2.439/z_H, so h_m max|dA/dz| dz equals
888  // (1-gamma_m) dz and the thickness ratio lands on gamma_m.
889  //
890  if (diff < threshold) {
891  ++n_capped;
892  if (iter == 0) {
893  for ( MFIter mfi(h_mf, TilingIfNotGPU()); mfi.isValid(); ++mfi )
894  {
895  Box xybx = mfi.growntilebox(ngrow);
896  xybx.setRange(2,0);
897  Array4<Real> const& h_s = h_mf.array(mfi);
898  ParallelFor(xybx, [=] AMREX_GPU_DEVICE (int i, int j, int) noexcept
899  {
900  h_s(i,j,k) = h_s(i,j,k-1);
901  });
902  }
903  MultiFab::Copy(h_mf_old, h_mf,0,0,1,h_mf_old.nGrow());
904  h_mf_old.FillBoundary(geom.periodicity());
905  } else {
906  // h_mf_old still holds the last pass that satisfied the criterion
907  MultiFab::Copy(h_mf, h_mf_old,0,0,1,h_mf.nGrow());
908  }
909  break;
910  }
911 
912  MultiFab::Copy(h_mf_old, h_mf,0,0,1,h_mf_old.nGrow());
913 
914  iter++;
915 
916  // Fill ghost points
917  h_mf_old.FillBoundary(geom.periodicity());
918 
919  } // while
920 
921  auto const& z_lev_d = z_levels_d.data();
922 
923  // Populate z_phys_nd by solving z_arr(i,j,k) = z + A*h_s(i,j,k)
924  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
925  {
926  // Grown box with no z range
927  Box xybx = mfi.growntilebox(ngrow);
928  xybx.setRange(2,0);
929 
930  Array4<Real> const& h_s = h_mf_old.array(mfi);
931  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
932 
933  ParallelFor(xybx, [=] AMREX_GPU_DEVICE (int i, int j, int) {
934 
935  // Location of nodes
936  Real z = z_lev_d[k];
937 
938  // STF model from p2164 of Klemp2011 (Eqn. 4)
939  z_arr(i,j,k) = z + A*h_s(i,j,k);
940 
941  // Fill below the bottom surface
942  if (k == 1) {
943  z_arr(i,j,k0-1) = two*z_arr(i,j,k0) - z_arr(i,j,k);
944  }
945  });
946  } // mfi
947  } // k
948 
949  if (n_capped > 0) {
950  amrex::Print() << "NOTE: the smoothed terrain-following grid at level " << lev
951  << " hit its minimum layer-thickness floor (erf.terrain_gamma_m = "
952  << gamma_m << ") at " << n_capped << " of "
953  << (domhi_z - domlo_z) << " levels, so the terrain smoothing was "
954  << "capped there. Raising erf.terrain_gamma_m, coarsening the "
955  << "vertical grid or using erf.terrain_smoothing = 0 would give "
956  << "this terrain more room.\n";
957  }
958 
959  Gpu::streamSynchronize();
960 
961  break;
962  } // case 1
963 
964  case 2: // Sullivan TF Method
965  {
966  int k0 = 0;
967 
968  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
969  {
970  // Grown box with corrected ghost cells at top
971  Box gbx = mfi.growntilebox(ngrow);
972  gbx.setRange(2,domlo_z,domhi_z+1);
973 
974  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
975  auto const& z_lev = z_levels_d.data();
976 
977  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
978  {
979  // Vertical grid stretching
980  Real z = z_lev[k];
981 
982  int ii = amrex::max(amrex::min(i,imax),imin);
983  int jj = amrex::max(amrex::min(j,jmax),jmin);
984 
985  // Fill levels using model from Sullivan et. al. 2014
986  int omega = 3; //Used to adjust how rapidly grid lines level out. omega=1 is BTF!
987  z_arr(i,j,k) = z + static_cast<Real>(std::pow((one - (z/z_top)),omega) * z_arr(ii,jj,k0));
988 
989  // Fill lateral boundaries and below the bottom surface
990  if (k == k0) {
991  z_arr(i,j,k0 ) = z_arr(ii,jj,k0);
992  }
993  if (k == 1) {
994  z_arr(i,j,k0-1) = two*z_arr(ii,jj,k0) - z_arr(i,j,k);
995  }
996  });
997  } // mfi
998  break;
999  } // case 2
1000 
1001  case 3: // Debugging Test Method -- applies Sullivan TF starting at k = 1 so that domain does not change size
1002  {
1003  int k0 = 0;
1004 
1005  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
1006  {
1007  // Grown box with corrected ghost cells at top
1008  Box gbx = mfi.growntilebox(ngrow);
1009  gbx.setRange(2,domlo_z,domhi_z+1);
1010 
1011  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
1012  auto const& z_lev = z_levels_d.data();
1013 
1014  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1015  {
1016  // Vertical grid stretching
1017  Real z = z_lev[k];
1018 
1019  int ii = amrex::max(amrex::min(i,imax),imin);
1020  int jj = amrex::max(amrex::min(j,jmax),jmin);
1021 
1022  // Fill values outside the lateral boundaries and below the bottom surface (necessary if init_type = "WRFInput")
1023  if (k == k0+1) {
1024  z_arr(i,j,k) = z + z_arr(ii,jj,k0);
1025  } else {
1026  // Fill levels using model from Sullivan et. al. 2014
1027  int omega = 3; //Used to adjust how rapidly grid lines level out. omega=1 is BTF!
1028  z_arr(i,j,k) = z + static_cast<Real>(std::pow((one - (z/z_top)),omega) * z_arr(ii,jj,k0));
1029  }
1030  });
1031  gbx.setBig(2,0);
1032  ParallelFor(gbx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
1033  {
1034  z_arr(i,j,k ) = zero;
1035  z_arr(i,j,k-1) = -z_arr(i,j,k+1);
1036  });
1037  } // mfi
1038  break;
1039  } // case 3
1040  } //switch
1041 }
constexpr amrex::Real bogus_large_value
Definition: ERF_Constants.H:19
ParmParse pp("prob")
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:37
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:36
constexpr amrex::Real PI
Definition: ERF_NumericalConstants.H:46
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:478
@ omega
Definition: ERF_Morrison.H:55
constexpr int A
Definition: ERF_TwoStreamColumn.H:603

Referenced by make_terrain_fitted_coords().

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

◆ join_boxes_stacked_in_z()

BoxArray join_boxes_stacked_in_z ( const BoxArray &  ba)

The cells of a (cell-centered) BoxArray as boxes that are never stacked on each other.

Wherever a box of ba sits on the top face of another box of ba, the run of cells in z that the two belong to becomes a single box. Boxes stacked with different lateral extents are first cut at each other's lateral edges; the joined pieces are then merged in x and y and chopped to no more than the largest lateral size of the boxes they came from. Returns ba itself when no two boxes of ba are stacked.

101 {
102  AMREX_ALWAYS_ASSERT(ba.ixType().cellCentered());
103 
104  const int nboxes = static_cast<int>(ba.size());
105 
106  // Gather the boxes into stacks: link every box to the boxes that sit on its top face
107  std::vector<int> stack_of(nboxes);
108  std::iota(stack_of.begin(), stack_of.end(), 0);
109  auto find_stack = [&stack_of] (int i) {
110  while (stack_of[i] != i) {
111  stack_of[i] = stack_of[stack_of[i]];
112  i = stack_of[i];
113  }
114  return i;
115  };
116 
117  bool any_stacked = false;
118  for (int i = 0; i < nboxes; ++i) {
119  Box above(ba[i]);
120  above.setRange(2, ba[i].bigEnd(2)+1);
121  for (const auto& isect : ba.intersections(above)) {
122  stack_of[find_stack(isect.first)] = find_stack(i);
123  any_stacked = true;
124  }
125  }
126  if (!any_stacked) { return ba; }
127 
128  // std::map keeps the order, and so the joined boxes, the same on every rank
129  std::map<int, std::vector<Box>> stacks;
130  for (int i = 0; i < nboxes; ++i) {
131  stacks[find_stack(i)].push_back(ba[i]);
132  }
133 
134  BoxList bl_joined;
135  for (const auto& stack : stacks)
136  {
137  const std::vector<Box>& boxes = stack.second;
138  if (boxes.size() == 1) {
139  bl_joined.push_back(boxes[0]);
140  continue;
141  }
142 
143  // Cut the stack at every lateral edge of its boxes
144  std::array<std::vector<int>,2> cuts;
145  IntVect max_size(1);
146  for (const auto& b : boxes) {
147  for (int idim = 0; idim < 2; ++idim) {
148  cuts[idim].push_back(b.smallEnd(idim));
149  cuts[idim].push_back(b.bigEnd(idim)+1);
150  max_size[idim] = std::max(max_size[idim], b.length(idim));
151  }
152  }
153  for (auto& c : cuts) {
154  std::sort(c.begin(), c.end());
155  c.erase(std::unique(c.begin(), c.end()), c.end());
156  }
157  auto cut_index = [&cuts] (int idim, int coord) {
158  return static_cast<int>(std::lower_bound(cuts[idim].begin(), cuts[idim].end(), coord)
159  - cuts[idim].begin());
160  };
161 
162  // The z extents of the boxes over each piece, keyed by the piece's cut indices
163  std::map<std::pair<int,int>, std::vector<std::pair<int,int>>> zruns_of_piece;
164  for (const auto& b : boxes) {
165  for (int ix = cut_index(0, b.smallEnd(0)); ix < cut_index(0, b.bigEnd(0)+1); ++ix) {
166  for (int iy = cut_index(1, b.smallEnd(1)); iy < cut_index(1, b.bigEnd(1)+1); ++iy) {
167  zruns_of_piece[std::make_pair(ix,iy)].emplace_back(b.smallEnd(2), b.bigEnd(2));
168  }
169  }
170  }
171 
172  // Over each piece, join the extents that touch into one box per run of cells
173  BoxList bl_stack;
174  for (auto& piece : zruns_of_piece) {
175  const int ix = piece.first.first;
176  const int iy = piece.first.second;
177  auto& zruns = piece.second;
178  std::sort(zruns.begin(), zruns.end());
179  std::size_t n_first = 0;
180  for (std::size_t n = 1; n <= zruns.size(); ++n) {
181  if (n == zruns.size() || zruns[n].first != zruns[n-1].second+1) {
182  bl_stack.push_back(Box(IntVect(cuts[0][ix], cuts[1][iy], zruns[n_first].first),
183  IntVect(cuts[0][ix+1]-1, cuts[1][iy+1]-1, zruns[n-1].second)));
184  n_first = n;
185  }
186  }
187  }
188 
189  // Merging in x and y joins pieces with the same z extent, so it cannot stack them again
190  bl_stack.simplify();
191  max_size[2] = bl_stack.minimalBox().length(2);
192  bl_stack.maxSize(max_size);
193  bl_joined.join(bl_stack);
194  }
195 
196  return BoxArray(std::move(bl_joined));
197 }

Referenced by SurfaceLayer::define_pblh_columns().

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

1090 {
1091  const auto* dx = geom.CellSize();
1092  Real dzInv = one/dx[2];
1093 
1094  // Domain valid box (z_nd is nodal)
1095  const Box& domain = geom.Domain();
1096  int domlo_z = domain.smallEnd(2);
1097 
1098  // The z-faces are always full when using terrain-fitted coordinates
1099  az.setVal(1.0);
1100 
1101  //
1102  // x-areas
1103  //
1104 #ifdef _OPENMP
1105 #pragma omp parallel if (Gpu::notInLaunchRegion())
1106 #endif
1107  for ( MFIter mfi(ax, TilingIfNotGPU()); mfi.isValid(); ++mfi )
1108  {
1109  Box gbx = mfi.growntilebox(ax.nGrow());
1110  if (gbx.smallEnd(2) < domlo_z) {
1111  gbx.setSmall(2,domlo_z);
1112  }
1113 
1114  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
1115  Array4<Real > ax_arr = ax.array(mfi);
1116  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
1117  ax_arr(i, j, k) = Real(.5) * dzInv * (
1118  z_nd(i,j,k+1) + z_nd(i,j+1,k+1) - z_nd(i,j,k) - z_nd(i,j+1,k));
1119  });
1120  }
1121 
1122  //
1123  // y-areas
1124  //
1125 #ifdef _OPENMP
1126 #pragma omp parallel if (Gpu::notInLaunchRegion())
1127 #endif
1128  for ( MFIter mfi(ay, TilingIfNotGPU()); mfi.isValid(); ++mfi )
1129  {
1130  Box gbx = mfi.growntilebox(ay.nGrow());
1131  if (gbx.smallEnd(2) < domlo_z) {
1132  gbx.setSmall(2,domlo_z);
1133  }
1134 
1135  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
1136  Array4<Real > ay_arr = ay.array(mfi);
1137  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
1138  ay_arr(i, j, k) = Real(.5) * dzInv * (
1139  z_nd(i,j,k+1) + z_nd(i+1,j,k+1) - z_nd(i,j,k) - z_nd(i+1,j,k));
1140  });
1141  }
1142 
1143  ax.FillBoundary(geom.periodicity());
1144  ay.FillBoundary(geom.periodicity());
1145  az.FillBoundary(geom.periodicity());
1146 }

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

1050 {
1051  const auto *dx = geom.CellSize();
1052  Real dzInv = one/dx[2];
1053 
1054  // Domain valid box (z_nd is nodal)
1055  const Box& domain = geom.Domain();
1056  int domlo_z = domain.smallEnd(2);
1057 
1058  // Number of ghost cells
1059  int ngrow= detJ_cc.nGrow();
1060 
1061 #ifdef _OPENMP
1062 #pragma omp parallel if (Gpu::notInLaunchRegion())
1063 #endif
1064  for ( MFIter mfi(detJ_cc, TilingIfNotGPU()); mfi.isValid(); ++mfi )
1065  {
1066  Box gbx = mfi.growntilebox(ngrow);
1067  if (gbx.smallEnd(2) < domlo_z) {
1068  gbx.setSmall(2,domlo_z);
1069  }
1070  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
1071  Array4<Real > detJ = detJ_cc.array(mfi);
1072  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
1073  detJ(i, j, k) = Real(.25) * dzInv * (
1074  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)
1075  -z_nd(i,j,k ) - z_nd(i+1,j,k ) - z_nd(i,j+1,k ) - z_nd(i+1,j+1,k ) );
1076  });
1077  }
1078  detJ_cc.FillBoundary(geom.periodicity());
1079 }

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

277 {
278  const Box& domain = geom.Domain();
279 
280  int domlo_z = domain.smallEnd(2);
281  int domhi_z = domain.bigEnd(2) + 1;
282 
283  // Just in case ...
284  z_phys_nd.setDomainBndry(bogus_large_value,0,1,geom);
285 
286  // ****************************************************************************
287 
288  if (lev == 0) {
289  BoxArray ba(z_phys_nd.boxArray());
290  bool all_boxes_touch_bottom = true;
291  for (int i = 0; i < ba.size(); i++) {
292  if (ba[i].smallEnd(2) != domlo_z) {
293  all_boxes_touch_bottom = false;
294  }
295  }
296 
297  if (all_boxes_touch_bottom) {
298  init_which_terrain_grid(lev, geom, z_phys_nd, z_levels_h, fine_terrain, z_phys_interp);
299  } else {
300 
301  BoxArray ba_new(domain);
302  ChopGrids2D(ba_new, domain, ParallelDescriptor::NProcs());
303 
304  DistributionMapping dm_new(ba_new);
305  ba_new.surroundingNodes();
306 
307  MultiFab z_phys_nd_new(ba_new, dm_new, 1, z_phys_nd.nGrowVect());
308 
309  z_phys_nd_new.ParallelCopy(z_phys_nd,0,0,1,z_phys_nd.nGrowVect(),z_phys_nd.nGrowVect());
310 
311  init_which_terrain_grid(lev, geom, z_phys_nd_new, z_levels_h, fine_terrain, z_phys_interp);
312 
313  z_phys_nd.ParallelCopy(z_phys_nd_new,0,0,1,z_phys_nd.nGrowVect(),z_phys_nd.nGrowVect());
314  }
315  } else { // lev > 0
316  //
317  // BTF maps each column of a box between the box's lowest node and the domain top.
318  // A box that sits on another box of this level must continue the columns of the box
319  // below it: its own lowest node holds only the mesh interpolated from the coarse
320  // level, so starting from there would make the mesh above it follow the coarse
321  // terrain and depend on where the grids are split in z. As on level 0 we therefore
322  // build the mesh on boxes that hold whole columns and copy it back. The bottom box
323  // of a column, such as a refined patch aloft, still starts from its own lowest node.
324  //
325  ParmParse pp("erf");
326  int terrain_smoothing = 0;
327  pp.query("terrain_smoothing", terrain_smoothing);
328 
329  const ColumnGrids* cg = (terrain_smoothing == 0)
330  ? &column_grids(lev, geom, amrex::convert(z_phys_nd.boxArray(), IntVect::TheCellVector()))
331  : nullptr;
332 
333  if (cg == nullptr || !cg->stacked) {
334  init_which_terrain_grid(lev, geom, z_phys_nd, z_levels_h, fine_terrain, z_phys_interp);
335  } else {
336  const IntVect& ng = z_phys_nd.nGrowVect();
337  MultiFab z_phys_nd_col(cg->ba_col_nd, cg->dm_col, 1, ng);
338 
339  z_phys_nd_col.ParallelCopy(z_phys_nd,0,0,1,ng,ng);
340 
341  init_which_terrain_grid(lev, geom, z_phys_nd_col, z_levels_h, fine_terrain, z_phys_interp);
342 
343  // Copy the ghost nodes too, then give every node the value of a box that holds it
344  // as valid, rather than one a neighbouring box computed for its ghost node
345  z_phys_nd.ParallelCopy(z_phys_nd_col,0,0,1,ng,ng);
346  z_phys_nd.ParallelCopy(z_phys_nd_col,0,0,1,IntVect(0),ng);
347  }
348  }
349 
350  //
351  // Fill ghost layers and corners (including periodic) -- no matter what level
352  //
353  z_phys_nd.FillBoundary(geom.periodicity());
354 
355  if (phys_bc_type[Orientation(0,Orientation::low )] == ERF_BC::symmetry ||
356  phys_bc_type[Orientation(0,Orientation::high)] == ERF_BC::symmetry ||
357  phys_bc_type[Orientation(1,Orientation::low )] == ERF_BC::symmetry ||
358  phys_bc_type[Orientation(1,Orientation::high)] == ERF_BC::symmetry) {
359 
360  const auto& dom_lo = lbound(convert(domain,IntVect(1,1,1)));
361  const auto& dom_hi = ubound(convert(domain,IntVect(1,1,1)));
362 
363  //
364  // Mirror the whole ghost region, not just its first layer: the base state carries
365  // ComputeGhostCells+1 layers and the state stencils reach several cells past the
366  // boundary, so mirroring only the first would leave the rest of the ghost region
367  // holding whatever the terrain construction extrapolated into it.
368  //
369  const IntVect& ngv = z_phys_nd.nGrowVect();
370 
371  for (MFIter mfi(z_phys_nd,true); mfi.isValid(); ++mfi) {
372  //
373  // Whether this tile reaches a domain boundary is a question about its valid
374  // nodes, so ask it of the tilebox. The grown tilebox begins a ghost width short
375  // of the boundary and so never equals it -- asking it of the grown box is what
376  // kept this mirroring from ever running. The grown box is still what we sweep,
377  // so that the ghost rows in the other two directions are covered as well.
378  //
379  const Box& vbx = mfi.tilebox();
380  const Box& bx = mfi.growntilebox();
381  const Array4< Real> z_nd_arr = z_phys_nd.array(mfi);
382  if (phys_bc_type[Orientation(0,Orientation::low)] == ERF_BC::symmetry && vbx.smallEnd(0) == dom_lo.x) {
383  const int ng = ngv[0];
384  ParallelFor(makeSlab(bx,0,1), [=] AMREX_GPU_DEVICE (int , int j, int k)
385  {
386  for (int n = 1; n <= ng; ++n) {
387  z_nd_arr(dom_lo.x-n,j,k) = z_nd_arr(dom_lo.x+n,j,k);
388  }
389  });
390  }
391  if (phys_bc_type[Orientation(0,Orientation::high)] == ERF_BC::symmetry && vbx.bigEnd(0) == dom_hi.x) {
392  const int ng = ngv[0];
393  ParallelFor(makeSlab(bx,0,1), [=] AMREX_GPU_DEVICE (int , int j, int k)
394  {
395  for (int n = 1; n <= ng; ++n) {
396  z_nd_arr(dom_hi.x+n,j,k) = z_nd_arr(dom_hi.x-n,j,k);
397  }
398  });
399  }
400  if (phys_bc_type[Orientation(1,Orientation::low)] == ERF_BC::symmetry && vbx.smallEnd(1) == dom_lo.y) {
401  const int ng = ngv[1];
402  ParallelFor(makeSlab(bx,1,1), [=] AMREX_GPU_DEVICE (int i, int , int k)
403  {
404  for (int n = 1; n <= ng; ++n) {
405  z_nd_arr(i,dom_lo.y-n,k) = z_nd_arr(i,dom_lo.y+n,k);
406  }
407  });
408  }
409  if (phys_bc_type[Orientation(1,Orientation::high)] == ERF_BC::symmetry && vbx.bigEnd(1) == dom_hi.y) {
410  const int ng = ngv[1];
411  ParallelFor(makeSlab(bx,1,1), [=] AMREX_GPU_DEVICE (int i, int , int k)
412  {
413  for (int n = 1; n <= ng; ++n) {
414  z_nd_arr(i,dom_hi.y+n,k) = z_nd_arr(i,dom_hi.y-n,k);
415  }
416  });
417  }
418  }
419  }
420 
421  //********************************************************************************
422  // Populate domain boundary cells in z-direction
423  //********************************************************************************
424  int ngrow = z_phys_nd.nGrow();
425 
426  for ( MFIter mfi(z_phys_nd, TilingIfNotGPU()); mfi.isValid(); ++mfi )
427  {
428  // Only set values above top of domain if this box reaches that far
429  Box nd_bx = mfi.tilebox();
430 
431  // Note that domhi_z is already nodal in the z-direction
432  if (nd_bx.bigEnd(2) >= domhi_z) {
433  // Grown box with no z range
434  Box bx_zhi = mfi.growntilebox(ngrow);
435  bx_zhi.setSmall(2,domhi_z+1);
436  Array4<Real> const& z_arr = z_phys_nd.array(mfi);
437 
438  // Extrapolate top layer
439  ParallelFor(bx_zhi, [=] AMREX_GPU_DEVICE (int i, int j, int k) {
440  z_arr(i,j,k) = z_arr(i,j,domhi_z)
441  + (k-domhi_z) * (z_arr(i,j,domhi_z) - z_arr(i,j,domhi_z-1));
442  });
443  }
444  }
445 } // 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:619
@ ng
Definition: ERF_Morrison.H:50
real(c_double), private cg
Definition: ERF_module_mp_morr_two_moment.F90:203

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

1155 {
1156 #ifdef _OPENMP
1157 #pragma omp parallel if (Gpu::notInLaunchRegion())
1158 #endif
1159  for ( MFIter mfi(z_phys_cc, TilingIfNotGPU()); mfi.isValid(); ++mfi )
1160  {
1161  Box gbx = mfi.growntilebox();
1162  Array4<Real const> z_nd = z_phys_nd.const_array(mfi);
1163  Array4<Real > z_cc = z_phys_cc.array(mfi);
1164  ParallelFor(gbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
1165  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 )
1166  +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) );
1167  });
1168  }
1169  z_phys_cc.FillBoundary(geom.periodicity());
1170 }

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:

◆ validate_flat_terrain()

void validate_flat_terrain ( int  lev,
const MultiFab &  z_phys_nd,
const Vector< Real > &  z_levels_h 
)
21 {
22  // The comparison is against heights that have been round-tripped through z_phys_nd, so the
23  // threshold has to track the working precision: a hard-wired 1e-10 is far below single-
24  // precision round-off at domain-top magnitudes and would abort on a genuinely flat mesh.
25  // Keep the original (double-precision) threshold, floored at a few hundred ulps.
26  const Real scale = std::max(Real(1.0), std::abs(z_levels_h.back()));
27  const Real rel_tol = std::max(Real(1.0e-10), Real(100.0) * std::numeric_limits<Real>::epsilon());
28  const Real tolerance = rel_tol * scale;
29 
30  MultiFab expected(z_phys_nd.boxArray(), z_phys_nd.DistributionMap(), 1, 0);
31  Gpu::DeviceVector<Real> z_levels_d(z_levels_h.size());
32  Gpu::copy(Gpu::hostToDevice, z_levels_h.begin(), z_levels_h.end(), z_levels_d.begin());
33  const Real* z_levels = z_levels_d.data();
34 
35  for (MFIter mfi(expected); mfi.isValid(); ++mfi) {
36  const Box& bx = mfi.validbox();
37  auto const& expected_arr = expected.array(mfi);
38  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) {
39  expected_arr(i,j,k) = z_levels[k];
40  });
41  }
42 
43  MultiFab error(expected.boxArray(), expected.DistributionMap(), 1, 0);
44  MultiFab::Copy(error, expected, 0, 0, 1, 0);
45  MultiFab::Subtract(error, z_phys_nd, 0, 0, 1, 0);
46  const Real max_error = error.norminf(0, 0, false);
47 
48  if (max_error > tolerance) {
49  Abort("erf.flat_terrain = true, but z_phys_nd is not horizontally "
50  "flat or does not match the configured vertical spacing at "
51  "level " + std::to_string(lev) + ".");
52  }
53 }

Referenced by ERF::init_zphys().

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.

452 {
453  ParmParse pp("erf");
454  std::string amr_terrain_refinement = "interpolate";
455  pp.query("amr_terrain_refinement", amr_terrain_refinement);
456 
457  if (amr_terrain_refinement == "interpolate") {
459  } else if (amr_terrain_refinement == "transform") {
460  return FineTerrain::Transform;
461  }
462 
463  Abort("erf.amr_terrain_refinement = " + amr_terrain_refinement +
464  " is not recognized; it must be \"interpolate\" or \"transform\"");
466 }
@ 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: