ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_ProbCommon.H
Go to the documentation of this file.
1 #ifndef ERF_PROBCOMMON_H_
2 #define ERF_PROBCOMMON_H_
3 
4 #include <algorithm>
5 #include <cmath>
6 
7 #include <AMReX_ParmParse.H>
8 #include <AMReX_Geometry.H>
9 #include <AMReX_FArrayBox.H>
10 #include <AMReX_MultiFab.H>
11 #include <AMReX_Reduce.H>
12 
13 #include "ERF_DataStruct.H"
14 #include "ERF_NumericalConstants.H"
15 #include "ERF_EOS.H"
16 #include "ERF_HSEUtils.H"
17 #include "ERF_GridUtils.H"
18 #include "ERF_TileNoZ.H"
19 #include "ERF_ColumnBands.H"
20 #include "ERF_TerrainFileChecks.H"
21 
22 #ifdef ERF_USE_NETCDF
23 #include <ERF_NCInterface.H>
24 #endif
25 
29 };
30 
31 void
32 init_my_custom_terrain (const amrex::Geometry& geom,
33  amrex::FArrayBox& terrain_fab,
34  const double& time);
35 
36 /**
37  * Class to hold problem-specific routines
38 */
40 {
41 public:
42 
43  /**
44  * Virtual destructor to avoid data leakage with derived class
45  */
46  virtual ~ProblemBase () = default;
47 
48  /**
49  * Function to initialize the hydrostatic reference density
50  *
51  * @param[out] rho_hse hydrostatic reference density
52  * @param[in] z_phys_nd height coordinate at nodes
53  * @param[in] z_phys_cc height coordinate at cell centers
54  * @param[in] geom container for geometric information
55  */
56  virtual void
57  erf_init_dens_hse_dry (amrex::MultiFab& /*rho_hse*/,
58  std::unique_ptr<amrex::MultiFab>& /*z_phys_nd*/,
59  std::unique_ptr<amrex::MultiFab>& /*z_phys_cc*/,
60  amrex::Geometry const& /*geom*/,
61  const amrex::Vector<amrex::Real>& /*stretched_dz_h*/,
62  bool /*is_constant_dz*/, bool /*is_stretched_dz*/)
63  {
64  amrex::Print() << "Hydrostatically balanced density was NOT set"
65  << " -- an appropriate init_type should probably have been specified"
66  << " (e.g., input_sounding, WRFInput, or Metgrid)"
67  << std::endl;
68  amrex::Error("Should never call this version of erf_init_dens_hse_dry for "+name()+" problem");
69  }
70 
71  virtual void
72  erf_init_const_dens_hse (amrex::MultiFab& /*rho_hse*/)
73  {
74  amrex::Error("Should never call this version of erf_init_const_dens_hse for "+name()+" problem");
75  }
76 
77  virtual void
78  erf_init_const_dens_and_th_hse (amrex::MultiFab& /*rho_hse*/, amrex::MultiFab& /* p_hse*/,
79  amrex::MultiFab& /* pi_hse*/, amrex::MultiFab& /*th_hse*/,
80  amrex::MultiFab& /* qv_hse*/, amrex::Real /*l_rdOcp*/)
81  {
82  amrex::Error("Should never call this version of erf_init_const_dens_and_th_hse for "+name()+" problem");
83  }
84 
85  virtual void
86  erf_init_const_dens_and_linear_th_hse (amrex::MultiFab& /*rho_hse*/, amrex::MultiFab& /* p_hse*/,
87  amrex::MultiFab& /* pi_hse*/, amrex::MultiFab& /*th_hse*/,
88  amrex::MultiFab& /* qv_hse*/, amrex::Real /*l_rdOcp*/,
89  std::unique_ptr<amrex::MultiFab>& /*z_phys_cc*/)
90  {
91  amrex::Error("Should never call this version of erf_init_const_dens_and_linear_th_hse for "+name()+" problem");
92  }
93 
94  virtual void
95  erf_init_dens_hse_moist (amrex::MultiFab& /*rho_hse*/,
96  std::unique_ptr<amrex::MultiFab>& /*z_phys_nd*/,
97  amrex::Geometry const& /*geom*/,
98  const amrex::Vector<amrex::Real>& /*zlevels_stag*/)
99  {
100 
101  }
102 
103  /**
104  * Function to perform custom initialization of a test problem
105  *
106  * @param[in] bx cell-centered box on which to initialize scalars
107  * @param[out] state cell-centered variables to be filled in this routine
108  * @param[out] r_hse hydrostatic reference density
109  * @param[out] p_hse hydrostatic reference pressure
110  * @param[in] z_nd height coordinate at nodes
111  * @param[in] z_cc height coordinate at cell centers
112  * @param[in] z_sfc_nd height of the ground at the nodes of the bottom face of each
113  * column, at k = 0, over the planar footprint of bx
114  * @param[in] mf_m map factor on cell centers
115  * @param[in] mf_u map factor on x-faces
116  * @param[in] mf_v map factor on y-faces
117  * @param[in] sc SolverChoice structure that carries parameters
118  * @param[in] lev AMR level
119  */
120  virtual void
121  init_custom_pert (const amrex::Box& /*bx*/,
122  amrex::Array4<amrex::Real const> const& /*state*/,
123  amrex::Array4<amrex::Real > const& /*state_pert*/,
124  amrex::Array4<amrex::Real > const& /*r_hse*/,
125  amrex::Array4<amrex::Real > const& /*p_hse*/,
126  amrex::Array4<amrex::Real const> const& /*z_nd*/,
127  amrex::Array4<amrex::Real const> const& /*z_cc*/,
128  amrex::Array4<amrex::Real const> const& /*z_sfc_nd*/,
129  amrex::GeometryData const& /*geomdata*/,
130  amrex::Array4<amrex::Real const> const& /*mf_m*/,
131  const SolverChoice& /*sc*/, const int /*lev*/)
132  {
133  amrex::Print() << "No perturbation to background fields supplied for "
134  << name() << " problem" << std::endl;
135  }
136 
137  /**
138  * Function to perform custom initialization of the velocities
139  *
140  * @param[in] xbx box on which to initialize x_vel_pert
141  * @param[in] ybx box on which to initialize y_vel_pert
142  * @param[in] zbx box on which to initialize z_vel_pert
143  * @param[out] x_vel_pert x-component of velocity perturbation to be filled in this routine
144  * @param[out] y_vel_pert y-component of velocity perturbation to be filled in this routine
145  * @param[out] z_vel_pert z-component of velocity perturbation to be filled in this routine
146  * @param[in] z_nd height coordinate at nodes
147  * @param[in] z_sfc_nd height of the ground at the nodes of the bottom face of each
148  * column, at k = 0 (see init_custom_pert)
149  * @param[in] mf_m map factor on cell centers
150  * @param[in] mf_u map factor on x-faces
151  * @param[in] mf_v map factor on y-faces
152  * @param[in] sc SolverChoice structure that carries parameters
153  * @param[in] lev AMR level
154  */
155  virtual void
156  init_custom_pert_vels (const amrex::Box& /*xbx*/,
157  const amrex::Box& /*ybx*/,
158  const amrex::Box& /*zbx*/,
159  amrex::Array4<amrex::Real > const& /*x_vel_pert*/,
160  amrex::Array4<amrex::Real > const& /*y_vel_pert*/,
161  amrex::Array4<amrex::Real > const& /*z_vel_pert*/,
162  amrex::Array4<amrex::Real const> const& /*z_nd*/,
163  amrex::Array4<amrex::Real const> const& /*z_sfc_nd*/,
164  amrex::GeometryData const& /*geomdata*/,
165  amrex::Array4<amrex::Real const> const& /*mf_u*/,
166  amrex::Array4<amrex::Real const> const& /*mf_v*/,
167  const SolverChoice& /*sc*/, const int /*lev*/)
168  {
169  amrex::Print() << "No perturbation velocities supplied for " << name() << " problem" << std::endl;
170  }
171 
172  /**
173  * Function to update user-specified temperature source terms that can
174  * vary with time and height.
175  *
176  * @param[in] time current time
177  * @param[out] rhotheta_source forcing profile
178  * @param[in] geom container for geometric information
179  * @param[in] z_phys_cc height coordinate at cell centers
180  * @param[in] zlevels_stag nominal heights of the z faces of this level
181  */
182  virtual void
183  update_rhotheta_sources (const double& /*time*/,
184  amrex::MultiFab* src,
185  const amrex::Geometry& /*geom*/,
186  std::unique_ptr<amrex::MultiFab>& /*z_phys_cc*/,
187  const amrex::Vector<amrex::Real>& /*zlevels_stag*/)
188  {
189  if (src->empty()) return;
190 
191  amrex::Warning("Temperature forcing not defined for "+name()+" problem");
192  for ( amrex::MFIter mfi(*src, amrex::TilingIfNotGPU()); mfi.isValid(); ++mfi )
193  {
194  const auto &box = mfi.tilebox();
195  const amrex::Array4<amrex::Real>& src_arr = src->array(mfi);
196  // src is a spatial function if erf.spatial_rhotheta_forcing = true
197  // otherwise, qsrc_arr is defined only in over Z (box x and y dimensions are 1)
198  ParallelFor(box, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
199  // const amrex::Real z_cc = prob_lo[2] + (k+myhalf)* dx[2];
200  // set RHS term of RhoTheta equation based on time, z_cc here
201  src_arr(i, j, k) = zero;
202  });
203  }
204  }
205 
206  /**
207  * Function to update user-specified moisture source terms that can
208  * vary with time and height.
209  *
210  * @param[in] time current time
211  * @param[out] rhoqt_source moisture forcing profile
212  * @param[in] geom container for geometric information
213  * @param[in] z_phys_cc height coordinate at cell centers
214  * @param[in] zlevels_stag nominal heights of the z faces of this level
215  */
216  virtual void
217  update_rhoqt_sources (const double& /*time*/,
218  amrex::MultiFab* qsrc,
219  const amrex::Geometry& /*geom*/,
220  std::unique_ptr<amrex::MultiFab>& /*z_phys_cc*/,
221  const amrex::Vector<amrex::Real>& /*zlevels_stag*/)
222  {
223  if (qsrc->empty()) return;
224 
225  amrex::Warning("Moisture forcing not defined for "+name()+" problem");
226  for ( amrex::MFIter mfi(*qsrc, amrex::TilingIfNotGPU()); mfi.isValid(); ++mfi )
227  {
228  const auto &box = mfi.tilebox();
229  const amrex::Array4<amrex::Real>& qsrc_arr = qsrc->array(mfi);
230  // src is a spatial function if erf.spatial_moisture_forcing = true
231  // otherwise, qsrc_arr is defined only in over Z (box x and y dimensions are 1)
232  ParallelFor(box, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
233  // const amrex::Real z_cc = prob_lo[2] + (k+myhalf)* dx[2];
234  // set RHS term of RhoQ1 equation based on time, z_cc here
235  qsrc_arr(i, j, k) = zero;
236  });
237  }
238  }
239 
240  /**
241  * Function to update the vertical velocity profile, used to add subsidence
242  * source terms for x-mom, y-mom, rho*theta, rho*Q1, and rho*Q2.
243  *
244  * TODO: Currently, this is only called by InitData, so there is no time
245  * dependence.
246  *
247  * @param[in] time current time
248  * @param[out] wbar w vel forcing profile
249  * @param[in] geom container for geometric information
250  * @param[in] z_phys_cc height coordinate at cell centers
251  * @param[in] zlevels_stag nominal heights of the z faces of this level
252  */
253  virtual void
254  update_w_subsidence (const double& /*time*/,
255  amrex::Vector<amrex::Real>& wbar,
256  amrex::Gpu::DeviceVector<amrex::Real>& d_wbar,
257  const amrex::MultiFab& /*state*/,
258  const amrex::Geometry& geom,
259  std::unique_ptr<amrex::MultiFab>& /*z_phys_cc*/,
260  const amrex::Vector<amrex::Real>& /*zlevels_stag*/)
261  {
262  if (wbar.empty()) return;
263 
264  amrex::Warning("Moisture forcing not defined for "+name()+" problem");
265 
266  const int khi = geom.Domain().bigEnd()[2];
267  // const amrex::Real* prob_lo = geom.ProbLo();
268  // const auto dx = geom.CellSize();
269  for (int k = 0; k <= khi; k++)
270  {
271  // const amrex::Real z_cc = prob_lo[2] + (k+myhalf)* dx[2];
272  // set vertical velocity profile based on time, z_cc here
273  wbar[k] = zero;
274  }
275 
276  // Copy from host version to device version
277  amrex::Gpu::copy(amrex::Gpu::hostToDevice, wbar.begin(), wbar.end(), d_wbar.begin());
278  }
279 
280  /**
281  * Function to update user-specified geostrophic wind profile.
282  *
283  * @param[in] time current time
284  * @param[out] u_geos geostrophic wind profile
285  * @param[out] v_geos geostrophic wind profile
286  * @param[in] geom container for geometric information
287  * @param[in] z_phys_cc height coordinate at cell centers
288  * @param[in] zlevels_stag nominal heights of the z faces of this level
289  */
290  virtual void
291  update_geostrophic_profile (const double& /*time*/,
292  amrex::Vector<amrex::Real>& u_geos,
293  amrex::Gpu::DeviceVector<amrex::Real>& d_u_geos,
294  amrex::Vector<amrex::Real>& v_geos,
295  amrex::Gpu::DeviceVector<amrex::Real>& d_v_geos,
296  const amrex::Geometry& geom,
297  std::unique_ptr<amrex::MultiFab>& /*z_phys_cc*/,
298  const amrex::Vector<amrex::Real>& /*zlevels_stag*/)
299  {
300  if (u_geos.empty()) return;
301 
302  amrex::Warning("Geostrophic wind profile not defined for "+name()+" problem");
303 
304  const int khi = geom.Domain().bigEnd()[2];
305  // const amrex::Real* prob_lo = geom.ProbLo();
306  // const auto dx = geom.CellSize();
307  for (int k = 0; k <= khi; k++)
308  {
309  // const amrex::Real z_cc = prob_lo[2] + (k+myhalf)* dx[2];
310  // set RHS term of RhoTheta equation based on time, z_cc here
311  u_geos[k] = zero;
312  v_geos[k] = zero;
313  }
314 
315  // Copy from host version to device version
316  amrex::Gpu::copy(amrex::Gpu::hostToDevice, u_geos.begin(), u_geos.end(), d_u_geos.begin());
317  amrex::Gpu::copy(amrex::Gpu::hostToDevice, v_geos.begin(), v_geos.end(), d_v_geos.begin());
318  }
319 
320  /**
321  * Function to perform custom initialization of buildings
322  *
323  * @param[in] geom container for geometric information
324  * @param[out] z_phys_nd height coordinate at nodes
325  * @param[in] time current time
326  */
327  void
328  init_buildings_surface (const amrex::Geometry& geom,
329  amrex::FArrayBox& buildings_fab,
330  const double& time)
331  {
332  // Check if a valid text file exists for the buildings
333  std::string fname;
334  amrex::ParmParse pp("erf");
335  auto valid_fname = pp.queryAdd("buildings_file_name",fname);
336 
337  if (valid_fname) {
338  // Buildings may occupy only a local patch of the physical domain.
339  read_custom_terrain(fname,false,geom,buildings_fab,time,
340  /*require_domain_coverage=*/false,
341  /*zero_outside_table=*/true);
342  } else {
343  init_my_custom_terrain(geom, buildings_fab, time);
344  }
345 
346  make_terrain_surface_periodic(geom, buildings_fab);
347  }
348 
349 
350  /**
351  * Function to perform custom initialization of terrain
352  *
353  * @param[in] geom container for geometric information
354  * @param[out] z_phys_nd height coordinate at nodes
355  * @param[in] time current time
356  */
357  void
358  init_terrain_surface (const amrex::Geometry& geom,
359  amrex::FArrayBox& terrain_fab,
360  const double& time)
361  {
362  amrex::ParmParse pp("erf");
363 
364  // NetCDF terrain file takes priority
365  std::string fname_nc;
366  auto valid_fname_nc = pp.query("terrain_file_name_nc", fname_nc);
367 
368  std::string fname, fname_usgs;
369  auto valid_fname = pp.query("terrain_file_name", fname);
370  auto valid_fname_USGS = pp.query("terrain_file_name_USGS", fname_usgs);
371 
372  if (valid_fname_nc) {
373 #ifdef ERF_USE_NETCDF
374  read_terrain_netcdf(fname_nc, geom, terrain_fab, time);
375 #else
376  amrex::Abort("ERF must be compiled with NetCDF support to use terrain_file_name_nc");
377 #endif
378  } else if (valid_fname) {
379  read_custom_terrain(fname, false, geom, terrain_fab, time);
380 
381  } else if (valid_fname_USGS) {
382  read_custom_terrain(fname_usgs, true, geom, terrain_fab, time);
383 
384  } else {
385  init_my_custom_terrain (geom, terrain_fab, time);
386  }
387 
388  make_terrain_surface_periodic(geom, terrain_fab);
389  }
390 
391  /**
392  * Give every node in a periodic direction the height of its periodic image inside the
393  * domain. The nodes on the high side of a periodic domain are the same nodes as those
394  * on the low side, but none of the sources above knows that: the file readers clip a
395  * node outside the table to its edge (and look every node up 1e-3 past where it is),
396  * and an analytic surface is periodic only to round-off. Left as they are the two
397  * copies of a node differ, and so do the metrics of the faces on either side of it --
398  * which means the two grids that share a face update it differently, the mass flux
399  * through it is no longer the same on both sides, and which copy a fill keeps depends
400  * on how the grids are distributed. The low side is the one we keep.
401  *
402  * terrain_fab must hold every node of the domain in a periodic direction, since that is
403  * where the images are read from. A surface that is far from periodic is still made
404  * periodic here, but we say so, since the cells next to the high side then see a step.
405  */
406  static void
407  make_terrain_surface_periodic (const amrex::Geometry& geom,
408  amrex::FArrayBox& terrain_fab)
409  {
410  const bool per_x = geom.isPeriodic(0);
411  const bool per_y = geom.isPeriodic(1);
412  if (!per_x && !per_y) { return; }
413 
414  const amrex::Box& domain = geom.Domain();
415  const int ilo = domain.smallEnd(0); const int nx = domain.length(0);
416  const int jlo = domain.smallEnd(1); const int ny = domain.length(1);
417 
418  const amrex::Box bx = terrain_fab.box();
419  const auto blo = amrex::lbound(bx);
420  const auto bhi = amrex::ubound(bx);
421  amrex::Array4<amrex::Real> const& z_arr = terrain_fab.array();
422 
424  (!per_x || (blo.x <= ilo && bhi.x >= ilo+nx-1)) &&
425  (!per_y || (blo.y <= jlo && bhi.y >= jlo+ny-1)),
426  "make_terrain_surface_periodic: the terrain FArrayBox must hold every node of the domain in a periodic direction");
427 
428  // How far the surface is from periodic: the largest difference between a node on the
429  // high side of the domain and its image on the low side (ghost nodes are left out:
430  // they hold whatever the source clipped them to)
431  amrex::Box hi_bx = bx & amrex::surroundingNodes(domain);
432  hi_bx.setSmall(2, bx.smallEnd(2)); hi_bx.setBig(2, bx.smallEnd(2));
433  if (!hi_bx.isEmpty()) {
434  amrex::ReduceOps<amrex::ReduceOpMax> reduce_op;
435  amrex::ReduceData<amrex::Real> reduce_data(reduce_op);
436  reduce_op.eval(hi_bx, reduce_data,
437  [=] AMREX_GPU_DEVICE (int i, int j, int k) -> amrex::GpuTuple<amrex::Real>
438  {
439  int ii = (per_x) ? ilo + ((i-ilo) % nx + nx) % nx : i;
440  int jj = (per_y) ? jlo + ((j-jlo) % ny + ny) % ny : j;
441  return { (ii != i || jj != j) ? amrex::Math::abs(z_arr(i,j,k) - z_arr(ii,jj,k))
442  : amrex::Real(0.0) };
443  });
444  // Every rank fills its own copy of the whole surface, so there is nothing to reduce
445  // across ranks. Warn once: with a moving mesh we are called several times a step.
446  const amrex::Real max_diff = amrex::get<0>(reduce_data.value());
447  static bool warned = false;
448  if (!warned && max_diff > amrex::Real(0.1) * geom.CellSize(2)) {
449  warned = true;
450  amrex::Print() << "WARNING: the terrain surface is not periodic in a periodic direction: "
451  << "a node on the high side of the domain differs from its image on the low "
452  << "side by up to " << max_diff << " m. The low side is used for both, so the "
453  << "cells next to the high side see a step in the surface.\n";
454  }
455  }
456 
457  // We only write nodes outside [lo, lo+n) in a periodic direction and only read
458  // nodes inside it, so no node is both read and written here
459  amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
460  {
461  int ii = (per_x) ? ilo + ((i-ilo) % nx + nx) % nx : i;
462  int jj = (per_y) ? jlo + ((j-jlo) % ny + ny) % ny : j;
463  if ((ii != i || jj != j) &&
464  ii >= blo.x && ii <= bhi.x && jj >= blo.y && jj <= bhi.y) {
465  z_arr(i,j,k) = z_arr(ii,jj,k);
466  }
467  });
468  }
469 
470  /**
471  * Function to report whether the user has actually specified a terrain surface,
472  * i.e. whether init_terrain_surface will do anything other than return flat terrain.
473  *
474  * This mirrors the source precedence in init_terrain_surface, and is deliberately
475  * kept next to it so that the two cannot drift apart. A problem that overrides
476  * the terrain unconditionally, rather than keying off prob.custom_terrain_type,
477  * should override this to return true.
478  */
479  virtual bool
481  {
482  amrex::ParmParse pp("erf");
483  std::string fname;
484  if (pp.query("terrain_file_name_nc", fname) ||
485  pp.query("terrain_file_name", fname) ||
486  pp.query("terrain_file_name_USGS", fname)) {
487  return true;
488  }
489 
490  std::string custom_terrain_type = "None";
491  amrex::ParmParse pp_prob("prob");
492  pp_prob.query("custom_terrain_type", custom_terrain_type);
493  return (custom_terrain_type != "None");
494  }
495 
496 #ifdef ERF_USE_NETCDF
497  void
498  read_terrain_netcdf (const std::string& fname,
499  const amrex::Geometry& geom,
500  amrex::FArrayBox& terrain_fab,
501  const amrex::Real& /*time*/)
502  {
503  amrex::Print() << "Reading terrain from NetCDF file: " << fname << std::endl;
504 
505  amrex::Vector<amrex::Real> m_xterrain, m_yterrain, m_zterrain;
506  int nx = 0, ny = 0;
507  // Broadcast this as a trivially broadcastable integer before the
508  // interpolation kernel is launched.
509  int mass_point_terrain = 0;
510 
511  if (amrex::ParallelDescriptor::IOProcessor()) {
512  auto ncf = ncutils::NCFile::open(fname, NC_NOWRITE);
513 
514  // Try common terrain variable names
515  std::string height_varname = "height";
516  if (!ncf.has_var("height")) {
517  if (ncf.has_var("z")) height_varname = "z";
518  else if (ncf.has_var("terrain")) height_varname = "terrain";
519  else if (ncf.has_var("HGT_M")) height_varname = "HGT_M";
520  else if (ncf.has_var("AGL")) height_varname = "AGL";
521  else
522  amrex::Abort("No terrain height variable found in NetCDF file. "
523  "Expected: 'height', 'z', 'terrain', 'HGT_M', or 'AGL'");
524  }
525  amrex::Print() << " Using terrain variable: " << height_varname << std::endl;
526 
527  auto var = ncf.var(height_varname);
528  auto dims = var.shape();
529  const auto dim_names = var.dimnames();
530  const auto is_y_dimension = [] (const std::string& name) {
531  return name == "y" || name == "south_north";
532  };
533  const auto is_x_dimension = [] (const std::string& name) {
534  return name == "x" || name == "west_east";
535  };
536  const auto is_time_dimension = [] (const std::string& name) {
537  return name == "time" || name == "Time";
538  };
539 
540  bool has_time_dimension = false;
541  if (dims.size() == 2) {
542  if (dim_names.size() != 2 || !is_y_dimension(dim_names[0]) ||
543  !is_x_dimension(dim_names[1])) {
544  amrex::Abort("NetCDF terrain field '" + height_varname + "' in '" +
545  fname + "' must use dimensions (y,x) or "
546  "(south_north,west_east)");
547  }
548  ny = dims[0];
549  nx = dims[1];
550  } else if (dims.size() == 3) {
551  if (dim_names.size() != 3 || !is_time_dimension(dim_names[0]) ||
552  !is_y_dimension(dim_names[1]) || !is_x_dimension(dim_names[2])) {
553  amrex::Abort("NetCDF terrain field '" + height_varname + "' in '" +
554  fname + "' must use dimensions (time,y,x) with time leading; "
555  "accepted layouts are (y,x) and (time,y,x)");
556  }
557  if (dims[0] < 1) {
558  amrex::Abort("NetCDF terrain field '" + height_varname + "' in '" +
559  fname + "' has no time records");
560  }
561  has_time_dimension = true;
562  ny = dims[1];
563  nx = dims[2];
564  } else {
565  amrex::Abort("NetCDF terrain field '" + height_varname + "' in '" + fname +
566  "' has unsupported rank " + std::to_string(dims.size()) +
567  "; accepted layouts are (y,x) and (time,y,x)");
568  }
569 
570  if (nx < 2 || ny < 2) {
571  amrex::Abort("NetCDF terrain field '" + height_varname + "' in '" +
572  fname + "' requires at least two x and y coordinates "
573  "for bilinear interpolation");
574  }
575 
576  const bool has_x = ncf.has_var("x");
577  const bool has_y = ncf.has_var("y");
578  if (has_x != has_y) {
579  amrex::Abort("NetCDF terrain file '" + fname +
580  "' must provide both one-dimensional 'x' and 'y' coordinates");
581  }
582 
583  const std::string terrain_description =
584  "NetCDF terrain field '" + height_varname + "' in '" + fname + "'";
585  m_zterrain.resize(nx * ny);
586  if (!has_x) {
587  if (height_varname != "HGT_M" || !has_time_dimension ||
588  dim_names != std::vector<std::string>{"Time", "south_north", "west_east"}) {
589  amrex::Abort("NetCDF terrain field '" + height_varname + "' in '" + fname +
590  "' requires one-dimensional 'x' and 'y' coordinates; a genuine "
591  "WPS file must provide HGT_M(Time,south_north,west_east)");
592  }
593 
594  const auto require_real_attribute = [&] (const std::string& name) {
595  if (!ncf.has_attr(name)) {
596  amrex::Abort("WPS terrain file '" + fname +
597  "' is missing required global attribute '" + name + "'");
598  }
599  std::vector<double> values;
600  ncf.get_attr(name, values);
601  if (values.size() != 1 || !std::isfinite(values[0])) {
602  amrex::Abort("WPS terrain file '" + fname +
603  "' must have one finite global attribute '" + name + "'");
604  }
605  return values[0];
606  };
607  const auto require_int_attribute = [&] (const std::string& name) {
608  if (!ncf.has_attr(name)) {
609  amrex::Abort("WPS terrain file '" + fname +
610  "' is missing required global attribute '" + name + "'");
611  }
612  std::vector<int> values;
613  ncf.get_attr(name, values);
614  if (values.size() != 1) {
615  amrex::Abort("WPS terrain file '" + fname +
616  "' must have one global attribute '" + name + "'");
617  }
618  return values[0];
619  };
620 
621  const int map_proj = require_int_attribute("MAP_PROJ");
622  // Projection 6 (latitude/longitude) is deliberately excluded.
623  // WPS writes DX/DY in degrees for that projection, whereas
624  // everything below consumes them as metres -- both the
625  // geometry-consistency check and the m_xterrain/m_yterrain
626  // axes built from ProbLo + (i+0.5)*DX. Accepting it would
627  // either abort with a misleading "grid does not match"
628  // message, or, if prob_hi were specified in degrees to get
629  // past that check, silently place metre-valued terrain
630  // heights on a degree-spaced horizontal grid and corrupt the
631  // terrain-fitted metrics with no warning.
632  if (map_proj != 1 && map_proj != 2 && map_proj != 3) {
633  std::string reason =
634  (map_proj == 6)
635  ? "; latitude/longitude WPS grids are not supported because their "
636  "DX/DY are in degrees rather than metres"
637  : "; supported projections are 1 (Lambert), 2 (polar), "
638  "and 3 (Mercator)";
639  amrex::Abort("WPS terrain file '" + fname + "' uses unsupported MAP_PROJ=" +
640  std::to_string(map_proj) + reason);
641  }
642  const double cen_lat = require_real_attribute("CEN_LAT");
643  const double cen_lon = require_real_attribute("CEN_LON");
644  const double stand_lon = require_real_attribute("STAND_LON");
645  const double wps_dx = require_real_attribute("DX");
646  const double wps_dy = require_real_attribute("DY");
647  if (wps_dx <= 0.0 || wps_dy <= 0.0 || std::abs(cen_lat) > 90.0 ||
648  std::abs(cen_lon) > 360.0 || std::abs(stand_lon) > 360.0) {
649  amrex::Abort("WPS terrain file '" + fname +
650  "' has invalid projection or grid metadata");
651  }
652  // TRUELAT1 is required by all three remaining projections.
653  const double truelat1 = require_real_attribute("TRUELAT1");
654  if (std::abs(truelat1) > 90.0) {
655  amrex::Abort("WPS terrain file '" + fname +
656  "' has invalid TRUELAT1");
657  }
658  if (map_proj == 1) {
659  const double truelat2 = require_real_attribute("TRUELAT2");
660  if (std::abs(truelat2) > 90.0) {
661  amrex::Abort("WPS terrain file '" + fname +
662  "' has invalid TRUELAT2");
663  }
664  }
665 
666  const int wps_nx = require_int_attribute("WEST-EAST_GRID_DIMENSION");
667  const int wps_ny = require_int_attribute("SOUTH-NORTH_GRID_DIMENSION");
668  if (wps_nx != nx + 1 || wps_ny != ny + 1) {
669  amrex::Abort("WPS terrain file '" + fname +
670  "' has grid dimensions inconsistent with HGT_M: expected " +
671  std::to_string(nx + 1) + " x " + std::to_string(ny + 1));
672  }
673 
674  const auto validate_wps_coordinate = [&] (const std::string& name) {
675  if (!ncf.has_var(name)) {
676  amrex::Abort("WPS terrain file '" + fname +
677  "' is missing required coordinate variable '" + name + "'");
678  }
679  const auto coordinate = ncf.var(name);
680  if (coordinate.shape() != dims ||
681  coordinate.dimnames() != dim_names) {
682  amrex::Abort("WPS terrain coordinate '" + name + "' in '" + fname +
683  "' must have dimensions (Time,south_north,west_east) "
684  "matching HGT_M");
685  }
686  amrex::Vector<amrex::Real> values(nx * ny);
687  const std::vector<size_t> start{0, 0, 0};
688  const std::vector<size_t> count{
689  1, static_cast<size_t>(ny), static_cast<size_t>(nx)};
690  coordinate.get(values.data(), start, count);
691  if (!std::all_of(values.begin(), values.end(),
692  [] (amrex::Real value) { return std::isfinite(value); })) {
693  amrex::Abort("WPS terrain coordinate '" + name + "' in '" + fname +
694  "' contains nonfinite values");
695  }
696  };
697  validate_wps_coordinate("XLAT_M");
698  validate_wps_coordinate("XLONG_M");
699 
700  const auto dx = geom.CellSizeArray();
701  const auto close_enough = [] (amrex::Real lhs, double rhs) {
702  const amrex::Real scale = std::max(amrex::Real(1.0),
703  std::abs(static_cast<amrex::Real>(rhs)));
704  return std::abs(lhs - static_cast<amrex::Real>(rhs)) <=
705  amrex::Real(1.0e-6) * scale;
706  };
707  if (geom.Domain().length(0) != nx || geom.Domain().length(1) != ny ||
708  !close_enough(dx[0], wps_dx) || !close_enough(dx[1], wps_dy) ||
709  !close_enough(geom.ProbHi(0) - geom.ProbLo(0), wps_dx * nx) ||
710  !close_enough(geom.ProbHi(1) - geom.ProbLo(1), wps_dy * ny)) {
711  amrex::Abort("WPS terrain grid does not match the ERF Geometry; "
712  "HGT_M dimensions, DX/DY, and the ERF domain extent must agree");
713  }
714 
715  // WPS HGT_M/XLAT_M/XLONG_M are mass-point fields. Their
716  // logical grid is anchored at the ERF lower corner, with the
717  // first mass point half a cell from that corner. The
718  // explicit clamp below is the documented one-sided boundary
719  // conversion from mass points to ERF terrain nodes.
720  m_xterrain.resize(nx);
721  m_yterrain.resize(ny);
722  for (int i = 0; i < nx; ++i) {
723  m_xterrain[i] = geom.ProbLo(0) +
724  (static_cast<amrex::Real>(i) + amrex::Real(0.5)) *
725  static_cast<amrex::Real>(wps_dx);
726  }
727  for (int j = 0; j < ny; ++j) {
728  m_yterrain[j] = geom.ProbLo(1) +
729  (static_cast<amrex::Real>(j) + amrex::Real(0.5)) *
730  static_cast<amrex::Real>(wps_dy);
731  }
732  const std::vector<size_t> start{0, 0, 0};
733  const std::vector<size_t> count{
734  1, static_cast<size_t>(ny), static_cast<size_t>(nx)};
735  var.get(m_zterrain.data(), start, count);
736  mass_point_terrain = 1;
737  } else {
738  const auto x_shape = ncf.var("x").shape();
739  const auto y_shape = ncf.var("y").shape();
740  if (x_shape.size() != 1 || x_shape[0] != static_cast<std::size_t>(nx)) {
741  amrex::Abort("NetCDF terrain x coordinate in '" + fname +
742  "' must be one-dimensional with length " + std::to_string(nx));
743  }
744  if (y_shape.size() != 1 || y_shape[0] != static_cast<std::size_t>(ny)) {
745  amrex::Abort("NetCDF terrain y coordinate in '" + fname +
746  "' must be one-dimensional with length " + std::to_string(ny));
747  }
748 
749  m_xterrain.resize(nx);
750  m_yterrain.resize(ny);
751  ncf.var("x").get(m_xterrain.data());
752  ncf.var("y").get(m_yterrain.data());
753 
754  amrex::Real x_origin, x_spacing, y_origin, y_spacing;
755  std::string coordinate_error = erf_grid_utils::validate_uniform_axis(
756  m_xterrain, nx, "x", terrain_description, x_origin, x_spacing);
757  if (coordinate_error.empty()) {
758  coordinate_error = erf_grid_utils::validate_uniform_axis(
759  m_yterrain, ny, "y", terrain_description, y_origin, y_spacing);
760  }
761  if (!coordinate_error.empty()) {
762  amrex::Abort(coordinate_error);
763  }
764 
765  if (has_time_dimension) {
766  const std::vector<size_t> start{0, 0, 0};
767  const std::vector<size_t> count{
768  1, static_cast<size_t>(ny), static_cast<size_t>(nx)};
769  var.get(m_zterrain.data(), start, count);
770  } else {
771  var.get(m_zterrain.data());
772  }
773  }
774  if (!std::all_of(m_zterrain.begin(), m_zterrain.end(),
775  [] (amrex::Real value) { return std::isfinite(value); })) {
776  amrex::Abort(terrain_description + " contains nonfinite height values");
777  }
778  ncf.close();
779 
780  amrex::Print() << " Grid size: " << nx << " x " << ny << "\n"
781  << " X range: [" << m_xterrain[0] << ", " << m_xterrain[nx-1] << "]\n"
782  << " Y range: [" << m_yterrain[0] << ", " << m_yterrain[ny-1] << "]\n"
783  << " Height range: ["
784  << *std::min_element(m_zterrain.begin(), m_zterrain.end()) << ", "
785  << *std::max_element(m_zterrain.begin(), m_zterrain.end()) << "]\n";
786  }
787 
788  // Broadcast to all MPI ranks
789  amrex::ParallelDescriptor::Bcast(&nx, 1, amrex::ParallelDescriptor::IOProcessorNumber());
790  amrex::ParallelDescriptor::Bcast(&ny, 1, amrex::ParallelDescriptor::IOProcessorNumber());
791  amrex::ParallelDescriptor::Bcast(&mass_point_terrain, 1,
792  amrex::ParallelDescriptor::IOProcessorNumber());
793 
794  if (!amrex::ParallelDescriptor::IOProcessor()) {
795  m_xterrain.resize(nx);
796  m_yterrain.resize(ny);
797  m_zterrain.resize(nx * ny);
798  }
799 
800  amrex::ParallelDescriptor::Bcast(m_xterrain.data(), nx, amrex::ParallelDescriptor::IOProcessorNumber());
801  amrex::ParallelDescriptor::Bcast(m_yterrain.data(), ny, amrex::ParallelDescriptor::IOProcessorNumber());
802  amrex::ParallelDescriptor::Bcast(m_zterrain.data(), nx * ny, amrex::ParallelDescriptor::IOProcessorNumber());
803 
804  // Copy to GPU
805  amrex::Gpu::DeviceVector<amrex::Real> d_xterrain(nx), d_yterrain(ny), d_zterrain(nx * ny);
806  amrex::Gpu::copy(amrex::Gpu::hostToDevice, m_xterrain.begin(), m_xterrain.end(), d_xterrain.begin());
807  amrex::Gpu::copy(amrex::Gpu::hostToDevice, m_yterrain.begin(), m_yterrain.end(), d_yterrain.begin());
808  amrex::Gpu::copy(amrex::Gpu::hostToDevice, m_zterrain.begin(), m_zterrain.end(), d_zterrain.begin());
809 
810  amrex::Real* d_xt = d_xterrain.data();
811  amrex::Real* d_yt = d_yterrain.data();
812  amrex::Real* d_zt = d_zterrain.data();
813 
814  auto dx_arr = geom.CellSizeArray();
815  auto prob_lo = geom.ProbLoArray();
816 
817  int ilo = geom.Domain().smallEnd(0);
818  int jlo = geom.Domain().smallEnd(1);
819  int klo = geom.Domain().smallEnd(2);
820  int ihi = geom.Domain().bigEnd(0) + 1;
821  int jhi = geom.Domain().bigEnd(1) + 1;
822 
823  amrex::Box zbx = terrain_fab.box();
824  amrex::Array4<amrex::Real> const& z_arr = terrain_fab.array();
825  const amrex::Real terrain_dx = m_xterrain[1] - m_xterrain[0];
826  const amrex::Real terrain_dy = m_yterrain[1] - m_yterrain[0];
827 
828  if (!mass_point_terrain && !zbx.isEmpty()) {
829  amrex::Box target_box = zbx & amrex::convert(geom.Domain(), zbx.ixType());
830  if (!target_box.isEmpty()) {
831  target_box.setSmall(2, zbx.smallEnd(2));
832  target_box.setBig(2, zbx.smallEnd(2));
833  // The node on the high side of a periodic direction takes the value of its
834  // image on the low side (make_terrain_surface_periodic), so don't count it
835  for (int d = 0; d < 2; ++d) {
836  if (geom.isPeriodic(d)) {
837  target_box.setBig(d, amrex::min(target_box.bigEnd(d), geom.Domain().bigEnd(d)));
838  }
839  }
840  amrex::ReduceOps<amrex::ReduceOpSum, amrex::ReduceOpSum> reduce_op;
841  amrex::ReduceData<amrex::Long, amrex::Long> reduce_data(reduce_op);
842  reduce_op.eval(target_box, reduce_data,
843  [=] AMREX_GPU_DEVICE (int i, int j, int /*k*/) -> amrex::GpuTuple<amrex::Long, amrex::Long> {
844  const amrex::Real x = prob_lo[0] + static_cast<amrex::Real>(i) * dx_arr[0];
845  const amrex::Real y = prob_lo[1] + static_cast<amrex::Real>(j) * dx_arr[1];
846  const auto x_stencil = erf_grid_utils::uniform_interpolation_stencil(
847  x, d_xt[0], terrain_dx, nx);
848  const auto y_stencil = erf_grid_utils::uniform_interpolation_stencil(
849  y, d_yt[0], terrain_dy, ny);
850  const amrex::Long outside =
851  (x_stencil.inside && y_stencil.inside) ? amrex::Long(0) : amrex::Long(1);
852  return {amrex::Long(1), outside};
853  });
854  // No parallel reduction here: init_terrain_surface is called
855  // on every rank with its own private full-domain FArrayBox,
856  // not a distributed MultiFab, so every rank has already
857  // counted the whole domain. Summing across ranks would
858  // simply multiply both counts by the rank count.
859  const auto local = reduce_data.value();
860  const amrex::Long total_targets = amrex::get<0>(local);
861  const amrex::Long outside_targets = amrex::get<1>(local);
862  if (amrex::ParallelDescriptor::IOProcessor() && outside_targets > 0) {
863  const amrex::Real source_xhi = m_xterrain[nx-1];
864  const amrex::Real source_yhi = m_yterrain[ny-1];
865  const amrex::Real target_xlo = geom.ProbLo(0);
866  const amrex::Real target_ylo = geom.ProbLo(1);
867  const amrex::Real target_xhi = geom.ProbHi(0);
868  const amrex::Real target_yhi = geom.ProbHi(1);
869  const amrex::Real percentage = total_targets > 0
870  ? amrex::Real(100.0) * static_cast<amrex::Real>(outside_targets) /
871  static_cast<amrex::Real>(total_targets) : amrex::Real(0.0);
872  amrex::Print() << "WARNING: Terrain source grid does not cover ERF terrain nodes: outside "
873  << outside_targets << " of " << total_targets << " ("
874  << percentage << "%). Source extent x=[" << m_xterrain[0]
875  << ", " << source_xhi << "] y=[" << m_yterrain[0] << ", "
876  << source_yhi << "]; target extent x=[" << target_xlo << ", "
877  << target_xhi << "] y=[" << target_ylo << ", " << target_yhi
878  << "]. Outside nodes are set to zero.\n";
879  }
880  }
881  }
882 
883  amrex::ParallelFor(zbx, [=] AMREX_GPU_DEVICE (int i, int j, int /*k*/) noexcept
884  {
885  int ii = amrex::min(amrex::max(i, ilo), ihi);
886  int jj = amrex::min(amrex::max(j, jlo), jhi);
887 
888  amrex::Real x = prob_lo[0] + static_cast<amrex::Real>(ii) * dx_arr[0];
889  amrex::Real y = prob_lo[1] + static_cast<amrex::Real>(jj) * dx_arr[1];
890 
891  const amrex::Real dx_t = d_xt[1] - d_xt[0];
892  const amrex::Real dy_t = d_yt[1] - d_yt[0];
893  if (mass_point_terrain) {
894  // A WPS mass grid does not contain the ERF terrain nodes at
895  // the four outer edges. Hold the nearest mass value there;
896  // this is intentional, finite one-sided staggering rather
897  // than accidental extrapolation.
898  x = amrex::min(amrex::max(x, d_xt[0]), d_xt[nx-1]);
899  y = amrex::min(amrex::max(y, d_yt[0]), d_yt[ny-1]);
900  }
901  const auto x_stencil = erf_grid_utils::uniform_interpolation_stencil(
902  x, d_xt[0], dx_t, nx);
903  const auto y_stencil = erf_grid_utils::uniform_interpolation_stencil(
904  y, d_yt[0], dy_t, ny);
905 
906  if (x_stencil.inside && y_stencil.inside) {
907  const int i_t = x_stencil.lower;
908  const int j_t = y_stencil.lower;
909  const amrex::Real wx = x_stencil.weight;
910  const amrex::Real wy = y_stencil.weight;
911 
912  // Data is stored [y, x] in NetCDF
913  amrex::Real z00 = d_zt[ j_t * nx + i_t ];
914  amrex::Real z10 = d_zt[ j_t * nx + (i_t + 1)];
915  amrex::Real z01 = d_zt[(j_t + 1) * nx + i_t ];
916  amrex::Real z11 = d_zt[(j_t + 1) * nx + (i_t + 1)];
917 
918  z_arr(i, j, klo) = (amrex::Real(1.0) - wx) * (amrex::Real(1.0) - wy) * z00 +
919  wx * (amrex::Real(1.0) - wy) * z10 +
920  (amrex::Real(1.0) - wx) * wy * z01 +
921  wx * wy * z11;
922  } else {
923  z_arr(i, j, klo) = amrex::Real(0.0);
924  }
925  });
926 
927  // The interpolation kernel dereferences the device vectors above;
928  // synchronize before their scope ends and their storage is released.
929  amrex::Gpu::synchronize();
930  }
931 #endif
932 
933  void
934  read_custom_terrain (const std::string& fname,
935  const bool is_usgs,
936  const amrex::Geometry& geom,
937  amrex::FArrayBox& terrain_fab,
938  const double& /*time*/,
939  bool require_domain_coverage = true,
940  bool zero_outside_table = false)
941  {
942  // Read, checked and broadcast once per file; other levels, regrids, the EB build and
943  // the station slabs reuse the table
944  const auto& table = erf_terrain_file::ReadHeightTable(fname, is_usgs,
945  geom.ProbLoArray(), geom.ProbHiArray(),
946  require_domain_coverage);
947  const int nx = table.nx;
948  const int ny = table.ny;
949  const amrex::Vector<amrex::Real>& m_xterrain = table.x;
950  const amrex::Vector<amrex::Real>& m_yterrain = table.y;
951  const amrex::Vector<amrex::Real>& m_zterrain = table.z;
952 
953  const int nz = nx * ny;
954  const int nx_vals = is_usgs ? (nx * ny) : nx;
955 
956  // Copy data to the GPU
957  amrex::Gpu::DeviceVector<amrex::Real> d_xterrain(nx_vals),d_yterrain(ny),d_zterrain(nz);
958  amrex::Gpu::copy(amrex::Gpu::hostToDevice, m_xterrain.begin(), m_xterrain.end(), d_xterrain.begin());
959  amrex::Gpu::copy(amrex::Gpu::hostToDevice, m_yterrain.begin(), m_yterrain.end(), d_yterrain.begin());
960  amrex::Gpu::copy(amrex::Gpu::hostToDevice, m_zterrain.begin(), m_zterrain.end(), d_zterrain.begin());
961 
962  amrex::Real* d_xt = d_xterrain.data();
963  amrex::Real* d_yt = d_yterrain.data();
964  amrex::Real* d_zt = d_zterrain.data();
965 
966  auto dx = geom.CellSizeArray();
967  auto ProbLoArr = geom.ProbLoArray();
968 
969  int ilo = geom.Domain().smallEnd(0);
970  int jlo = geom.Domain().smallEnd(1);
971  int klo = geom.Domain().smallEnd(2);
972  int ihi = geom.Domain().bigEnd(0) + 1;
973  int jhi = geom.Domain().bigEnd(1) + 1;
974 
975  amrex::Box zbx = terrain_fab.box();
976  amrex::Array4<amrex::Real> const& z_arr = terrain_fab.array();
977 
978  amrex::ParallelFor(zbx, [=] AMREX_GPU_DEVICE (int i, int j, int /*k*/)
979  {
980  // Clip indices for ghost cells
981  int ii = amrex::min(amrex::max(i,ilo),ihi);
982  int jj = amrex::min(amrex::max(j,jlo),jhi);
983 
984  // Location of nodes
985  amrex::Real x_node = ProbLoArr[0] + ii * dx[0];
986  amrex::Real y_node = ProbLoArr[1] + jj * dx[1];
987  amrex::Real x = x_node + amrex::Real(1e-3);
988  amrex::Real y = y_node + amrex::Real(1e-3);
989 
990  // A local building table describes no buildings beyond its footprint.
991  // Use the unshifted node position so a node on the last table point
992  // remains inside, despite the interpolation offset above.
993  if (zero_outside_table) {
994  const bool out_x = nx > 1 && (x_node < d_xt[0] || x_node > d_xt[nx-1]);
995  const bool out_y = ny > 1 && (y_node < d_yt[0] || y_node > d_yt[ny-1]);
996  if (out_x || out_y) {
997  z_arr(i,j,klo) = amrex::Real(0.0);
998  return;
999  }
1000  }
1001 
1002  int ind11, ind12, ind21, ind22;
1003  amrex::Real x1, x2, y1, y2;
1004 
1005  int iindex_terrain=-1;
1006  int jindex_terrain=-1;
1007  // *******************************************************************
1008  // NOTE: usgs-format is contiguous in x
1009  // *******************************************************************
1010  if (is_usgs) {
1011 
1012  // NOTE: the searches below must distinguish "the node is below the first
1013  // table value" (bracket index -1) from "no table value exceeds the
1014  // node" (search never triggered), so we can not use the initial
1015  // value of -1 as the not-found flag
1016  bool jfound = false;
1017  for (int it = 0; it < ny && !jfound; it++) {
1018  if (d_yt[it] > y) {
1019  jindex_terrain = it-1; jfound = true;
1020  }
1021  }
1022  if (!jfound) {
1023  jindex_terrain = ny-1;
1024  }
1025  // Keep the 2x2 stencil inside the table
1026  jindex_terrain = amrex::min(amrex::max(jindex_terrain,0), ny-2);
1027 
1028  int gstart = (jindex_terrain )*nx;
1029  int gend = (jindex_terrain+1)*nx-1;
1030  bool ifound = false;
1031  for (int it = gstart; it <= gend && !ifound; it++) {
1032  if (d_xt[it] > x) {
1033  iindex_terrain = it-gstart-1; ifound = true;
1034  }
1035  }
1036  if (!ifound) {
1037  iindex_terrain = nx-1;
1038  }
1039  // Keep the 2x2 stencil inside the table
1040  iindex_terrain = amrex::min(amrex::max(iindex_terrain,0), nx-2);
1041 
1042  // Define the four values to interpolate between
1043  ind11 = jindex_terrain*nx + iindex_terrain; // (x1,y1)
1044  ind12 = ind11+nx; // (x1,y2)
1045  ind21 = ind11+1; // (x2,y1)
1046  ind22 = ind12+1; // (x2,y2)
1047 
1048  x1 = d_xt[ind11];
1049  x2 = d_xt[ind21];
1050  y1 = d_yt[jindex_terrain];
1051  y2 = d_yt[jindex_terrain+1];
1052 
1053  amrex::Real denom = (x2-x1)*(y2-y1);
1054 
1055  if (denom == amrex::Real(0.0)) {
1056  // Degenerate cell (repeated x or y in the table) -- use the corner value
1057  z_arr(i,j,klo) = d_zt[ind11];
1058  } else {
1059  amrex::Real w_11 = (x2-x)*(y2-y)/denom; // (x1,y1)
1060  amrex::Real w_12 = (x2-x)*(y-y1)/denom; // (x1,y2)
1061  amrex::Real w_21 = (x-x1)*(y2-y)/denom; // (x2,y1)
1062  amrex::Real w_22 = (x-x1)*(y-y1)/denom; // (x2,y2)
1063 
1064  // Do bilinear interpolation
1065  z_arr(i,j,klo) = w_11*d_zt[ind11] + w_12*d_zt[ind12] + w_21*d_zt[ind21] + w_22*d_zt[ind22];
1066  }
1067 
1068  } else {
1069 
1070  bool jfound = false;
1071  for (int it = 0; it < ny && !jfound; it++) {
1072  if (d_yt[it] > y) {
1073  jindex_terrain = it-1; jfound = true;
1074  }
1075  }
1076  if (!jfound) {
1077  // No table value exceeds y -- fall back to the last row
1078  jindex_terrain = ny-1;
1079  }
1080  jindex_terrain = amrex::max(jindex_terrain,0);
1081 
1082  bool ifound = false;
1083  for (int it = 0; it < nx && !ifound; it++) {
1084  if (d_xt[it] > x) {
1085  iindex_terrain = it-1; ifound = true;
1086  }
1087  }
1088  if (!ifound) {
1089  // No table value exceeds x -- fall back to the last column
1090  iindex_terrain = nx-1;
1091  }
1092  iindex_terrain = amrex::max(iindex_terrain,0);
1093 
1094  // NOTE: these must be clamped since the fallbacks above can leave
1095  // iindex_terrain = nx-1 (jindex_terrain = ny-1)
1096  int ip1 = amrex::min(iindex_terrain+1,nx-1);
1097  int jp1 = amrex::min(jindex_terrain+1,ny-1);
1098 
1099  // Define the four values to interpolate between
1100  x1 = d_xt[iindex_terrain];
1101  x2 = d_xt[ip1];
1102  y1 = d_yt[jindex_terrain];
1103  y2 = d_yt[jp1];
1104 
1105 #if 1
1106  // *******************************************************************
1107  // NOTE: this format is contiguous in y to match the AMR-Wind read
1108  // *******************************************************************
1109  ind11 = iindex_terrain * ny + jindex_terrain; // (x1,y1)
1110  ind21 = ip1 * ny + jindex_terrain; // (x2,y1)
1111 
1112  ind12 = iindex_terrain * ny + jp1; // (x1,y2)
1113  ind22 = ip1 * ny + jp1; // (x2,y2)
1114 #else
1115  // *******************************************************************
1116  // NOTE: this format is contiguous in x as an alternative
1117  // *******************************************************************
1118 
1119  ind11 = jindex_terrain * nx + iindex_terrain; // (x1,y1)
1120  ind12 = jp1 * nx + iindex_terrain; // (x1,y2)
1121 
1122  ind21 = jindex_terrain * nx + ip1; // (x2,y1)
1123  ind22 = jp1 * nx + ip1; // (x2,y2)
1124 #endif
1125 
1126  // Only interpolate in a direction if we have a non-degenerate interval there
1127  bool interp_x = (ip1 != iindex_terrain) && (x2 != x1);
1128  bool interp_y = (jp1 != jindex_terrain) && (y2 != y1);
1129 
1130  if (!interp_x && !interp_y)
1131  {
1132  z_arr(i,j,klo) = d_zt[ind11];
1133  }
1134  else if (interp_x && !interp_y)
1135  {
1136  amrex::Real w_11 = (x2-x); // (x1,y1)
1137  amrex::Real w_21 = (x-x1); // (x2,y1)
1138  amrex::Real denom = (x2-x1);
1139  z_arr(i,j,klo) = (w_11*d_zt[ind11] + w_21*d_zt[ind21])/denom;
1140  }
1141  else if (!interp_x && interp_y)
1142  {
1143  amrex::Real w_11 = (y2-y); // (x1,y1)
1144  amrex::Real w_12 = (y-y1); // (x1,y2)
1145  amrex::Real denom = (y2-y1);
1146  z_arr(i,j,klo) = (w_11*d_zt[ind11] + w_12*d_zt[ind12])/denom;
1147  }
1148  else
1149  {
1150  amrex::Real w_11 = (x2-x)*(y2-y); // (x1,y1)
1151  amrex::Real w_21 = (x-x1)*(y2-y); // (x2,y1)
1152  amrex::Real w_12 = (x2-x)*(y-y1); // (x1,y2)
1153  amrex::Real w_22 = (x-x1)*(y-y1); // (x2,y2)
1154  amrex::Real denom = (x2-x1)*(y2-y1);
1155  z_arr(i,j,klo) = (w_11*d_zt[ind11] + w_12*d_zt[ind12] + w_21*d_zt[ind21] + w_22*d_zt[ind22]) / denom;
1156  }
1157  } // usgs?
1158  });
1159 
1160  // The kernel reads the device vectors above: finish it before they are freed
1161  amrex::Gpu::streamSynchronize();
1162  }
1163 
1164  /**
1165  * Function to perform custom initialization of terrain
1166  *
1167  * Note: Terrain functionality can also be used to provide grid stretching.
1168  *
1169  * @param[in] geom container for geometric information
1170  * @param[out] terrain_mf height coordinate at nodes
1171  * @param[in] time current time
1172  */
1173  virtual void
1174  init_custom_terrain (const amrex::Geometry& geom,
1175  amrex::FArrayBox& terrain_fab,
1176  const double& time)
1177  {
1178  std::string custom_terrain_type = "None";
1179  amrex::ParmParse pp_prob("prob"); pp_prob.queryAdd("custom_terrain_type",custom_terrain_type);
1180 
1181  if (custom_terrain_type != "None")
1182  {
1183  amrex::Print() << "Calling custom terrain initialization" << std::endl;
1184  init_my_custom_terrain(geom,terrain_fab,time);
1185 
1186  } else {
1187  amrex::Print() << "Initializing flat terrain" << std::endl;
1188  // A flat surface at the bottom of the domain
1189  terrain_fab.template setVal<amrex::RunOn::Device>(geom.ProbLo(2));
1190 
1191  if (SolverChoice::mesh_type == MeshType::VariableDz) {
1192  SolverChoice::set_mesh_type(MeshType::StretchedDz);
1193  amrex::Print() << "Resetting mesh type to StretchedDz" << std::endl;
1194  }
1195  }
1196  }
1197 
1198 #ifdef ERF_USE_TERRAIN_VELOCITY
1199  virtual amrex::Real compute_terrain_velocity (const double /*time*/)
1200  {
1201  amrex::Error("Should never call compute_terrain_velocity for "+name()+" problem");
1202  }
1203 #endif
1204 
1205  /**
1206  * Function to define the quantities needed to impose Rayleigh damping
1207  *
1208  * @param[out] rayleigh_ptrs = {strength of Rayleigh damping, reference values for xvel/yvel/zvel/theta used to define Rayleigh damping}
1209  * @param[in] geom container for geometric information
1210  * @param[in] z_phys_cc height coordinate at cell centers
1211  */
1212  virtual void
1213  erf_init_rayleigh (amrex::Vector<amrex::Vector<amrex::Real> >& /*rayleigh_ptrs*/,
1214  amrex::Geometry const& /*geom*/,
1215  std::unique_ptr<amrex::MultiFab>& /*z_phys_nd*/,
1216  amrex::Real /*zdamp*/)
1217  {
1218  // Default which does no harm
1219  }
1220 
1221 protected:
1222  // Struct to store problem parameters
1224 
1225  /**
1226  * Function to update default base parameters, currently only used for
1227  * init_type == InitType::Uniform
1228  */
1231  base_parms.T_0 = T_0;
1232  }
1233 
1234  std::string name () {
1235  std::string prob_name;
1236  amrex::ParmParse pp("erf");
1237  pp.get("prob_name",prob_name); return prob_name;
1238  }
1239 
1240 public:
1241  /**
1242  * Store the staggered vertical levels of every AMR level. On a mesh other than
1243  * ConstantDz, geometry.prob_lo[2], prob_hi[2] and CellSize(2) only bound and step the
1244  * computational coordinate; the physical heights are these levels (and z_phys_cc/nd).
1245  */
1246  void set_zlevels_stag (const amrex::Vector<amrex::Vector<amrex::Real>>& zlevels_stag) {
1247  m_zlevels_stag = zlevels_stag;
1248  }
1249 
1250 protected:
1251  //! True when heights must come from the vertical levels or z_phys, not from the geometry
1252  static bool use_physical_z () { return SolverChoice::mesh_type != MeshType::ConstantDz; }
1253 
1254  //! Physical bottom of the domain at AMR level lev
1255  [[nodiscard]] amrex::Real phys_zlo (const amrex::GeometryData& geomdata, int lev) const {
1256  return use_physical_z() ? m_zlevels_stag[lev].front() : geomdata.ProbLo(2);
1257  }
1258 
1259  //! Physical top of the domain at AMR level lev
1260  [[nodiscard]] amrex::Real phys_zhi (const amrex::GeometryData& geomdata, int lev) const {
1261  return use_physical_z() ? m_zlevels_stag[lev].back() : geomdata.ProbHi(2);
1262  }
1263 
1264  //! Staggered vertical levels of AMR level lev (set by set_zlevels_stag)
1265  amrex::Vector<amrex::Vector<amrex::Real>> m_zlevels_stag;
1266 };
1267 
1268 
1269 /**
1270  * Function to init the physical bounds of the domain
1271  * and instantiate a Problem derived from ProblemBase
1272 */
1273 extern std::unique_ptr<ProblemBase> amrex_probinit (const amrex_real* problo,
1274  const amrex_real* probhi) AMREX_ATTRIBUTE_WEAK;
1275 #endif
@ wbar
Definition: ERF_DataStruct.H:207
auto probhi
Definition: ERF_InitCustomPertVels_ABL.H:37
auto problo
Definition: ERF_InitCustomPertVels_ABL.H:36
const int nx
Definition: ERF_InitCustomPertVels_CloudChamber.H:14
const int ny
Definition: ERF_InitCustomPertVels_CloudChamber.H:15
Real rho_0
Definition: ERF_InitCustomPert_ABL.H:18
ParmParse pp_prob("prob")
Real T_0
Definition: ERF_InitCustomPert_ABL.H:19
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")
ParmParse pp("prob")
const int khi
Definition: ERF_InitCustomPert_Bubble.H:25
const GpuArray< Real, AMREX_SPACEDIM > prob_lo
Definition: ERF_InitCustomPert_CloudChamber.H:33
const int klo
Definition: ERF_InitCustomPert_KE.H:9
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);})
Dimensionless numeric literals and pure mathematical constants.
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:37
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:36
void init_my_custom_terrain(const amrex::Geometry &geom, amrex::FArrayBox &terrain_fab, const double &time)
std::unique_ptr< ProblemBase > amrex_probinit(const amrex_real *problo, const amrex_real *probhi) AMREX_ATTRIBUTE_WEAK
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Definition: ERF_ProbCommon.H:40
static void make_terrain_surface_periodic(const amrex::Geometry &geom, amrex::FArrayBox &terrain_fab)
Definition: ERF_ProbCommon.H:407
void init_terrain_surface(const amrex::Geometry &geom, amrex::FArrayBox &terrain_fab, const double &time)
Definition: ERF_ProbCommon.H:358
void read_custom_terrain(const std::string &fname, const bool is_usgs, const amrex::Geometry &geom, amrex::FArrayBox &terrain_fab, const double &, bool require_domain_coverage=true, bool zero_outside_table=false)
Definition: ERF_ProbCommon.H:934
virtual void update_geostrophic_profile(const double &, amrex::Vector< amrex::Real > &u_geos, amrex::Gpu::DeviceVector< amrex::Real > &d_u_geos, amrex::Vector< amrex::Real > &v_geos, amrex::Gpu::DeviceVector< amrex::Real > &d_v_geos, const amrex::Geometry &geom, std::unique_ptr< amrex::MultiFab > &, const amrex::Vector< amrex::Real > &)
Definition: ERF_ProbCommon.H:291
virtual void update_rhotheta_sources(const double &, amrex::MultiFab *src, const amrex::Geometry &, std::unique_ptr< amrex::MultiFab > &, const amrex::Vector< amrex::Real > &)
Definition: ERF_ProbCommon.H:183
amrex::Real phys_zlo(const amrex::GeometryData &geomdata, int lev) const
Physical bottom of the domain at AMR level lev.
Definition: ERF_ProbCommon.H:1255
void set_zlevels_stag(const amrex::Vector< amrex::Vector< amrex::Real >> &zlevels_stag)
Definition: ERF_ProbCommon.H:1246
virtual void erf_init_dens_hse_moist(amrex::MultiFab &, std::unique_ptr< amrex::MultiFab > &, amrex::Geometry const &, const amrex::Vector< amrex::Real > &)
Definition: ERF_ProbCommon.H:95
virtual void erf_init_const_dens_and_linear_th_hse(amrex::MultiFab &, amrex::MultiFab &, amrex::MultiFab &, amrex::MultiFab &, amrex::MultiFab &, amrex::Real, std::unique_ptr< amrex::MultiFab > &)
Definition: ERF_ProbCommon.H:86
virtual bool terrain_is_specified()
Definition: ERF_ProbCommon.H:480
static bool use_physical_z()
True when heights must come from the vertical levels or z_phys, not from the geometry.
Definition: ERF_ProbCommon.H:1252
virtual void erf_init_const_dens_hse(amrex::MultiFab &)
Definition: ERF_ProbCommon.H:72
virtual void init_custom_terrain(const amrex::Geometry &geom, amrex::FArrayBox &terrain_fab, const double &time)
Definition: ERF_ProbCommon.H:1174
virtual void erf_init_dens_hse_dry(amrex::MultiFab &, std::unique_ptr< amrex::MultiFab > &, std::unique_ptr< amrex::MultiFab > &, amrex::Geometry const &, const amrex::Vector< amrex::Real > &, bool, bool)
Definition: ERF_ProbCommon.H:57
virtual void update_w_subsidence(const double &, amrex::Vector< amrex::Real > &wbar, amrex::Gpu::DeviceVector< amrex::Real > &d_wbar, const amrex::MultiFab &, const amrex::Geometry &geom, std::unique_ptr< amrex::MultiFab > &, const amrex::Vector< amrex::Real > &)
Definition: ERF_ProbCommon.H:254
amrex::Vector< amrex::Vector< amrex::Real > > m_zlevels_stag
Staggered vertical levels of AMR level lev (set by set_zlevels_stag)
Definition: ERF_ProbCommon.H:1265
void init_buildings_surface(const amrex::Geometry &geom, amrex::FArrayBox &buildings_fab, const double &time)
Definition: ERF_ProbCommon.H:328
virtual void init_custom_pert(const amrex::Box &, amrex::Array4< amrex::Real const > const &, amrex::Array4< amrex::Real > const &, amrex::Array4< amrex::Real > const &, amrex::Array4< amrex::Real > const &, amrex::Array4< amrex::Real const > const &, amrex::Array4< amrex::Real const > const &, amrex::Array4< amrex::Real const > const &, amrex::GeometryData const &, amrex::Array4< amrex::Real const > const &, const SolverChoice &, const int)
Definition: ERF_ProbCommon.H:121
virtual void erf_init_rayleigh(amrex::Vector< amrex::Vector< amrex::Real > > &, amrex::Geometry const &, std::unique_ptr< amrex::MultiFab > &, amrex::Real)
Definition: ERF_ProbCommon.H:1213
void init_base_parms(amrex::Real rho_0, amrex::Real T_0)
Definition: ERF_ProbCommon.H:1229
virtual ~ProblemBase()=default
amrex::Real phys_zhi(const amrex::GeometryData &geomdata, int lev) const
Physical top of the domain at AMR level lev.
Definition: ERF_ProbCommon.H:1260
std::string name()
Definition: ERF_ProbCommon.H:1234
virtual void erf_init_const_dens_and_th_hse(amrex::MultiFab &, amrex::MultiFab &, amrex::MultiFab &, amrex::MultiFab &, amrex::MultiFab &, amrex::Real)
Definition: ERF_ProbCommon.H:78
virtual void init_custom_pert_vels(const amrex::Box &, const amrex::Box &, const amrex::Box &, amrex::Array4< amrex::Real > const &, amrex::Array4< amrex::Real > const &, amrex::Array4< amrex::Real > const &, amrex::Array4< amrex::Real const > const &, amrex::Array4< amrex::Real const > const &, amrex::GeometryData const &, amrex::Array4< amrex::Real const > const &, amrex::Array4< amrex::Real const > const &, const SolverChoice &, const int)
Definition: ERF_ProbCommon.H:156
ProbParmDefaults base_parms
Definition: ERF_ProbCommon.H:1223
virtual void update_rhoqt_sources(const double &, amrex::MultiFab *qsrc, const amrex::Geometry &, std::unique_ptr< amrex::MultiFab > &, const amrex::Vector< amrex::Real > &)
Definition: ERF_ProbCommon.H:217
std::string validate_uniform_axis(const amrex::Vector< amrex::Real > &coordinates, int expected_size, const std::string &axis_name, const std::string &field_description, amrex::Real &origin, amrex::Real &spacing)
Definition: ERF_GridUtils.H:57
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE InterpolationStencil uniform_interpolation_stencil(amrex::Real coordinate, amrex::Real origin, amrex::Real spacing, int point_count) noexcept
Definition: ERF_GridUtils.H:305
const HeightTable & ReadHeightTable(const std::string &fname, bool is_usgs, const GpuArray< Real, AMREX_SPACEDIM > &prob_lo, const GpuArray< Real, AMREX_SPACEDIM > &prob_hi, bool require_domain_coverage)
Definition: ERF_TerrainFileChecks.cpp:331
Definition: ERF_ProbCommon.H:26
amrex::Real T_0
Definition: ERF_ProbCommon.H:28
amrex::Real rho_0
Definition: ERF_ProbCommon.H:27
Definition: ERF_DataStruct.H:715
static MeshType mesh_type
Vertical mesh representation.
Definition: ERF_DataStruct.H:2604
static void set_mesh_type(MeshType new_mesh_type)
Override the globally selected mesh type.
Definition: ERF_DataStruct.H:2611