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