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