ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_GridUtils.H
Go to the documentation of this file.
1 #ifndef ERF_GRID_UTILS_H_
2 #define ERF_GRID_UTILS_H_
3 
4 #include <algorithm>
5 #include <cmath>
6 #include <limits>
7 #include <sstream>
8 #include <string>
9 
10 #include <AMReX_GpuQualifiers.H>
11 #include <AMReX_Math.H>
12 #include <AMReX_REAL.H>
13 #include <AMReX_Vector.H>
14 
15 namespace erf_grid_utils {
16 
18 {
19  int nx{0};
20  int ny{0};
25 };
26 
27 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
30 {
31  amrex::Real scale = amrex::Real(1.0);
32  const amrex::Real lhs_abs = amrex::Math::abs(lhs);
33  const amrex::Real rhs_abs = amrex::Math::abs(rhs);
34  if (lhs_abs > scale) scale = lhs_abs;
35  if (rhs_abs > scale) scale = rhs_abs;
36  // NetCDF coordinate variables are commonly stored as float32 even when
37  // ERF is built with double precision. A few float32 ulps at the
38  // coordinate magnitude are therefore source-file quantization, not grid
39  // nonuniformity. The Real tolerance is retained for native Real data;
40  // the float tolerance is deliberately a small ulp-based allowance rather
41  // than an arbitrary absolute threshold.
42  const amrex::Real real_ulp =
44  const amrex::Real float_ulp =
45  amrex::Real(8.0) * static_cast<amrex::Real>(std::numeric_limits<float>::epsilon()) * scale;
46  return (real_ulp > float_ulp) ? real_ulp : float_ulp;
47 }
48 
49 inline bool
51 {
52  return std::abs(lhs - rhs) <= comparison_tolerance(lhs, rhs);
53 }
54 
55 inline std::string
56 validate_uniform_axis (const amrex::Vector<amrex::Real>& coordinates,
57  int expected_size,
58  const std::string& axis_name,
59  const std::string& field_description,
60  amrex::Real& origin,
61  amrex::Real& spacing)
62 {
63  if (expected_size < 2) {
64  return field_description + " requires at least two " + axis_name +
65  " coordinates for bilinear interpolation";
66  }
67  if (coordinates.size() != static_cast<amrex::Long>(expected_size)) {
68  return field_description + " has " + axis_name + " coordinate length " +
69  std::to_string(coordinates.size()) + ", expected " +
70  std::to_string(expected_size);
71  }
72 
73  origin = coordinates.front();
74  // Derive the spacing from the two endpoints rather than from the first
75  // interval alone. The returned origin/spacing pair is what
76  // uniform_interpolation_stencil uses to reconstruct the axis as
77  // origin + index*spacing, so a first interval that is rounded slightly
78  // short or long would otherwise accumulate that error over the whole axis
79  // -- for a float32 axis with many points the reconstructed far end can
80  // drift by (point_count-1) ulps, which is large enough to shift the
81  // stencil by a cell near the upper edge. Averaging over the full extent
82  // pins both endpoints instead. A non-finite last coordinate propagates
83  // into spacing and is caught by the isfinite check below.
84  spacing = (coordinates[expected_size-1] - coordinates.front()) /
85  static_cast<amrex::Real>(expected_size - 1);
86  if (!std::isfinite(origin) || !std::isfinite(spacing) || spacing <= amrex::Real(0.0)) {
87  return field_description + " must have finite, strictly increasing " +
88  axis_name + " coordinates with nonzero spacing";
89  }
90 
91  for (int index = 1; index < expected_size; ++index) {
92  const amrex::Real coordinate = coordinates[index];
93  if (!std::isfinite(coordinate) || coordinate <= coordinates[index-1]) {
94  return field_description + " must have finite, strictly increasing " +
95  axis_name + " coordinates";
96  }
97  // Compare each interval against the mean spacing instead of
98  // accumulating the first spacing over the whole axis. This avoids
99  // rejecting a valid float32 axis because each stored coordinate was
100  // rounded independently, while still rejecting materially nonuniform
101  // grids.
102  //
103  // The tolerance is the *larger* of the two allowances, not the
104  // smaller: the ulp term covers source-file quantization, which scales
105  // with the coordinate magnitude, while the fraction-of-spacing term
106  // covers grids whose coordinates are small enough that a few ulps are
107  // negligible. Taking the minimum cancels the ulp allowance in exactly
108  // the case it exists for -- a fine grid at a large offset. Concretely,
109  // at a UTM easting near 5e5 the float32 ulp is 0.03125 m, so a 0.4 m
110  // raster stores intervals that deviate from the nominal spacing by up
111  // to 0.025 m: more than 5% of the spacing, yet far below the ulp
112  // allowance. Integer-metre spacings are unaffected either way, since
113  // integers are exactly representable in float32 at UTM magnitudes.
114  const amrex::Real local_spacing = coordinate - coordinates[index-1];
115  const amrex::Real spacing_tolerance = std::max(
116  amrex::Real(0.05) * std::abs(spacing),
117  comparison_tolerance(coordinate, coordinates[index-1]));
118  if (std::abs(local_spacing - spacing) > spacing_tolerance) {
119  return field_description + " has nonuniform " + axis_name +
120  " coordinates, which are not supported";
121  }
122  }
123 
124  return {};
125 }
126 
127 inline std::string
129  const UniformGridMetadata& candidate,
130  const std::string& reference_description,
131  const std::string& candidate_description)
132 {
133  if (candidate.nx != reference.nx || candidate.ny != reference.ny) {
134  return candidate_description + " dimensions (" + std::to_string(candidate.nx) +
135  " x " + std::to_string(candidate.ny) + ") do not match " +
136  reference_description + " dimensions (" + std::to_string(reference.nx) +
137  " x " + std::to_string(reference.ny) + ")";
138  }
139 
140  struct MetadataValue {
141  const char* name;
142  amrex::Real reference_value;
143  amrex::Real candidate_value;
144  };
145  const MetadataValue values[] = {
146  {"x spacing", reference.dx, candidate.dx},
147  {"y spacing", reference.dy, candidate.dy},
148  {"x origin", reference.xmin, candidate.xmin},
149  {"y origin", reference.ymin, candidate.ymin}
150  };
151  for (const auto& value : values) {
152  if (!nearly_equal(value.reference_value, value.candidate_value)) {
153  std::ostringstream message;
154  message << candidate_description << " " << value.name << " ("
155  << value.candidate_value << ") does not match "
156  << reference_description << " (" << value.reference_value << ")";
157  return message.str();
158  }
159  }
160 
161  return {};
162 }
163 
165 {
166  int lower{0};
168  bool inside{false};
169 };
170 
171 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
172 InterpolationStencil
174  amrex::Real origin,
175  amrex::Real spacing,
176  int point_count) noexcept
177 {
178  InterpolationStencil stencil;
179  if (point_count < 2 || spacing <= amrex::Real(0.0)) {
180  return stencil;
181  }
182 
183  const amrex::Real upper = origin + static_cast<amrex::Real>(point_count - 1) * spacing;
184  const amrex::Real lower_tolerance = comparison_tolerance(coordinate, origin);
185  const amrex::Real upper_tolerance = comparison_tolerance(coordinate, upper);
186 
187  if (coordinate < origin - lower_tolerance || coordinate > upper + upper_tolerance) {
188  return stencil;
189  }
190 
191  const amrex::Real bounded_coordinate =
192  (coordinate <= origin + lower_tolerance) ? origin :
193  ((coordinate >= upper - upper_tolerance) ? upper : coordinate);
194  const amrex::Real fractional_index = (bounded_coordinate - origin) / spacing;
195  int lower = static_cast<int>(amrex::Math::floor(fractional_index));
196  lower = (lower < point_count - 1) ? lower : point_count - 2;
197 
198  amrex::Real weight = fractional_index - static_cast<amrex::Real>(lower);
199  weight = (weight < amrex::Real(0.0)) ? amrex::Real(0.0) :
200  ((weight > amrex::Real(1.0)) ? amrex::Real(1.0) : weight);
201 
202  stencil.lower = lower;
203  stencil.weight = weight;
204  stencil.inside = true;
205  return stencil;
206 }
207 
208 } // namespace erf_grid_utils
209 
210 #endif
std::string name
Definition: ERF_Plotfile2DCatalog.cpp:101
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Definition: ERF_GridUtils.H:15
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
bool nearly_equal(amrex::Real lhs, amrex::Real rhs)
Definition: ERF_GridUtils.H:50
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real comparison_tolerance(amrex::Real lhs, amrex::Real rhs) noexcept
Definition: ERF_GridUtils.H:29
std::string validate_matching_grid(const UniformGridMetadata &reference, const UniformGridMetadata &candidate, const std::string &reference_description, const std::string &candidate_description)
Definition: ERF_GridUtils.H:128
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12
Definition: ERF_GridUtils.H:165
int lower
Definition: ERF_GridUtils.H:166
bool inside
Definition: ERF_GridUtils.H:168
amrex::Real weight
Definition: ERF_GridUtils.H:167
Definition: ERF_GridUtils.H:18
amrex::Real dy
Definition: ERF_GridUtils.H:22
int nx
Definition: ERF_GridUtils.H:19
int ny
Definition: ERF_GridUtils.H:20
amrex::Real dx
Definition: ERF_GridUtils.H:21
amrex::Real ymin
Definition: ERF_GridUtils.H:24
amrex::Real xmin
Definition: ERF_GridUtils.H:23