ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
erf_grid_utils Namespace Reference

Classes

struct  UniformGridMetadata
 
struct  InterpolationStencil
 

Functions

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real comparison_tolerance (amrex::Real lhs, amrex::Real rhs) noexcept
 
bool nearly_equal (amrex::Real lhs, amrex::Real rhs)
 
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)
 
std::string validate_matching_grid (const UniformGridMetadata &reference, const UniformGridMetadata &candidate, const std::string &reference_description, const std::string &candidate_description)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE InterpolationStencil uniform_interpolation_stencil (amrex::Real coordinate, amrex::Real origin, amrex::Real spacing, int point_count) noexcept
 

Function Documentation

◆ comparison_tolerance()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real erf_grid_utils::comparison_tolerance ( amrex::Real  lhs,
amrex::Real  rhs 
)
noexcept
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 }
amrex::Real Real
Definition: ERF_ShocInterface.H:19
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12

Referenced by nearly_equal(), uniform_interpolation_stencil(), and validate_uniform_axis().

Here is the caller graph for this function:

◆ nearly_equal()

bool erf_grid_utils::nearly_equal ( amrex::Real  lhs,
amrex::Real  rhs 
)
inline
51 {
52  return std::abs(lhs - rhs) <= comparison_tolerance(lhs, rhs);
53 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real comparison_tolerance(amrex::Real lhs, amrex::Real rhs) noexcept
Definition: ERF_GridUtils.H:29

Referenced by validate_matching_grid().

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

◆ uniform_interpolation_stencil()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE InterpolationStencil erf_grid_utils::uniform_interpolation_stencil ( amrex::Real  coordinate,
amrex::Real  origin,
amrex::Real  spacing,
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 }

Referenced by ForestDrag::define_drag_field().

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

◆ validate_matching_grid()

std::string erf_grid_utils::validate_matching_grid ( const UniformGridMetadata reference,
const UniformGridMetadata candidate,
const std::string &  reference_description,
const std::string &  candidate_description 
)
inline
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 }
std::string name
Definition: ERF_Plotfile2DCatalog.cpp:101
bool nearly_equal(amrex::Real lhs, amrex::Real rhs)
Definition: ERF_GridUtils.H:50

Referenced by ForestDrag::ForestDrag().

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

◆ validate_uniform_axis()

std::string erf_grid_utils::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 
)
inline
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 }
Here is the call graph for this function: