REMORA
Regional Modeling of Oceans Refined Adaptively
Loading...
Searching...
No Matches
REMORA_LatLonRegrid.cpp
Go to the documentation of this file.
3#include "REMORA_NCFile.H"
4
5#include <AMReX_GpuContainers.H>
6#include <AMReX_Utility.H>
7#include <AMReX_ParallelDescriptor.H>
8#include <AMReX_ParallelReduce.H>
9
10#include <algorithm>
11#include <cmath>
12#include <limits>
13#include <sstream>
14
15#ifdef REMORA_USE_NETCDF
16
17using namespace amrex;
18
19namespace {
20
21/** Broadcast a string from the IO rank */
22void bcast_string (std::string& s)
23{
24 amrex::BroadcastString(s, ParallelDescriptor::MyProc(), ParallelDescriptor::IOProcessorNumber(),
25 ParallelDescriptor::Communicator());
26}
27
28/** Broadcast a vector of doubles from the IO rank */
29void bcast_vector (Vector<double>& v)
30{
31 amrex::BroadcastArray(v, ParallelDescriptor::MyProc(), ParallelDescriptor::IOProcessorNumber(),
32 ParallelDescriptor::Communicator());
33}
34
35/** Name of dimension idim of a variable */
36std::string dim_name (const ncutils::NCFile& ncf, const ncutils::NCVar& var, int idim)
37{
38 std::vector<int> dimids(var.ndim());
39 ncmpi_inq_vardimid(ncf.ncid, var.varid, dimids.data());
40 char name[NC_MAX_NAME + 1];
41 ncmpi_inq_dimname(ncf.ncid, dimids[idim], name);
42 return std::string(name);
43}
44
45/** Whether a units attribute names degrees east (or north) */
46bool units_say (const ncutils::NCVar& var, const std::string& direction)
47{
48 if (!var.has_attr("units")) { return false; }
49 std::string u = var.get_attr("units");
50 std::transform(u.begin(), u.end(), u.begin(), [] (unsigned char c) { return std::tolower(c); });
51 return u.find("degree") != std::string::npos &&
52 (u.find(direction) != std::string::npos ||
53 u.find(direction == "east" ? "_e" : "_n") != std::string::npos);
54}
55
56/** The source grid of a variable: the names and values of its coordinate axes, its
57 * missing-data markers and its packing */
58struct SourceGrid
59{
60 std::string lon_name, lat_name;
61 Vector<double> lon, lat;
62 int has_fill = 0, has_missing = 0;
63 double fill_value = 0.0, missing_value = 0.0;
64 double scale_factor = 1.0, add_offset = 0.0;
65};
66
67/** Whether dimension idim of a variable has a CF coordinate variable: a 1D variable of the
68 * dimension's own name */
69bool has_dimension_coordinate (const ncutils::NCFile& ncf, const ncutils::NCVar& var, int idim)
70{
71 const std::string d = dim_name(ncf, var, idim);
72 return ncf.has_var(d) && ncf.var(d).ndim() == 1 && dim_name(ncf, ncf.var(d), 0) == d;
73}
74
75/**
76 * Find and read the coordinate axes of a (time, lat, lon) variable. The names come from
77 * the inputs when given; otherwise from the variable's CF coordinates attribute, each name
78 * identified by its units (degrees east or north) or, failing that, by its leading
79 * "lon"/"lat" or, failing that, by order as ROMS's get_varcoords takes them; otherwise from
80 * the CF coordinate variables of its last two dimensions (longitude(longitude), as ERA5 and
81 * most reanalyses write them). Each axis must be 1D and dimensioned like the variable's
82 * matching dimension. Collective; aborts on every rank with the reason, followed by
83 * `context`, when the axes cannot be found.
84 */
85SourceGrid
86find_source_grid (const std::string& file_name, const std::string& field_name,
87 const std::vector<long long>& field_shape,
88 const LatLonRegridOptions& options, const std::string& context)
89{
90 SourceGrid g;
91 std::string error;
92
93 auto ncf = ncutils::NCFile::open(file_name, NC_NOCLOBBER);
94 ncmpi_begin_indep_data(ncf.ncid);
95 if (ParallelDescriptor::IOProcessor())
96 {
97 const auto var = ncf.var(field_name);
98 const int rank = var.ndim();
99
100 g.lon_name = options.lon_name;
101 g.lat_name = options.lat_name;
102 if (g.lon_name.empty() || g.lat_name.empty()) {
103 if (!var.has_attr("coordinates")) {
104 if (has_dimension_coordinate(ncf, var, rank-1) &&
105 has_dimension_coordinate(ncf, var, rank-2)) {
106 if (g.lon_name.empty()) { g.lon_name = dim_name(ncf, var, rank-1); }
107 if (g.lat_name.empty()) { g.lat_name = dim_name(ncf, var, rank-2); }
108 } else {
109 error = "it has no coordinates attribute naming its longitude and latitude, "
110 "its last two dimensions have no coordinate variables, and "
111 "remora.frc_lon_varname and remora.frc_lat_varname are not both set";
112 }
113 } else {
114 std::istringstream tokens(var.get_attr("coordinates"));
115 std::vector<std::string> names;
116 for (std::string t; tokens >> t; ) {
117 if (ncf.has_var(t) && ncf.var(t).ndim() >= 1 &&
118 dim_name(ncf, ncf.var(t), 0) != dim_name(ncf, var, 0)) {
119 names.push_back(t); // skip the time coordinate
120 }
121 }
122 std::string by_units_lon, by_units_lat, by_name_lon, by_name_lat;
123 for (const auto& t : names) {
124 if (units_say(ncf.var(t), "east")) { by_units_lon = t; }
125 if (units_say(ncf.var(t), "north")) { by_units_lat = t; }
126 if (t.rfind("lon", 0) == 0) { by_name_lon = t; }
127 if (t.rfind("lat", 0) == 0) { by_name_lat = t; }
128 }
129 std::string lon = !by_units_lon.empty() ? by_units_lon : by_name_lon;
130 std::string lat = !by_units_lat.empty() ? by_units_lat : by_name_lat;
131 if ((lon.empty() || lat.empty()) && names.size() == 2) {
132 lon = names[0];
133 lat = names[1];
134 }
135 if (g.lon_name.empty()) { g.lon_name = lon; }
136 if (g.lat_name.empty()) { g.lat_name = lat; }
137 if (g.lon_name.empty() || g.lat_name.empty()) {
138 error = "its coordinates attribute '" + var.get_attr("coordinates") +
139 "' does not name a longitude and a latitude variable; set "
140 "remora.frc_lon_varname and remora.frc_lat_varname";
141 }
142 }
143 }
144
145 // Each axis 1D, along the variable's last (lon) or next-to-last (lat) dimension
146 const std::pair<std::string, int> axes[2] = {{g.lon_name, rank-1}, {g.lat_name, rank-2}};
147 for (const auto& [name, idim] : axes) {
148 if (!error.empty()) { break; }
149 if (!ncf.has_var(name)) {
150 error = "its coordinate variable '" + name + "' is not in the file";
151 } else if (ncf.var(name).ndim() != 1) {
152 error = "its coordinate variable '" + name + "' is " +
153 std::to_string(ncf.var(name).ndim()) + "D; only 1D (rectilinear) "
154 "longitude and latitude axes are supported";
155 } else if (dim_name(ncf, ncf.var(name), 0) != dim_name(ncf, var, idim) ||
156 ncf.var(name).shape()[0] != field_shape[idim]) {
157 error = "its coordinate variable '" + name + "' does not run along its " +
158 std::string(idim == rank-1 ? "last" : "next-to-last") + " dimension '" +
159 dim_name(ncf, var, idim) + "'; the variable must be (time, lat, lon)";
160 }
161 }
162
163 int has_scale = 0, has_offset = 0;
164 for (const auto& [attr, has, val] : { std::make_tuple("_FillValue", &g.has_fill, &g.fill_value),
165 std::make_tuple("missing_value", &g.has_missing, &g.missing_value),
166 std::make_tuple("scale_factor", &has_scale, &g.scale_factor),
167 std::make_tuple("add_offset", &has_offset, &g.add_offset) })
168 {
169 if (var.has_attr(attr)) {
170 std::vector<double> v;
171 var.get_attr(attr, v);
172 if (!v.empty()) { *has = 1; *val = v[0]; }
173 }
174 }
175 }
176 ncf.close();
177
178 bcast_string(error);
179 if (!error.empty()) {
180 Abort("Cannot interpolate '" + field_name + "' in " + file_name +
181 " onto the model grid: " + error + "." + context);
182 }
183 bcast_string(g.lon_name);
184 bcast_string(g.lat_name);
185 const int ioproc = ParallelDescriptor::IOProcessorNumber();
186 ParallelDescriptor::Bcast(&g.has_fill, 1, ioproc);
187 ParallelDescriptor::Bcast(&g.has_missing, 1, ioproc);
188 ParallelDescriptor::Bcast(&g.fill_value, 1, ioproc);
189 ParallelDescriptor::Bcast(&g.missing_value, 1, ioproc);
190 ParallelDescriptor::Bcast(&g.scale_factor, 1, ioproc);
191 ParallelDescriptor::Bcast(&g.add_offset, 1, ioproc);
192
193 Vector<NDArray<double>> arrays(2);
194 ReadNetCDFFile(file_name, {g.lon_name, g.lat_name}, arrays);
195 if (ParallelDescriptor::IOProcessor()) {
196 g.lon.assign(arrays[0].get_data(), arrays[0].get_data() + arrays[0].get_vshape()[0]);
197 g.lat.assign(arrays[1].get_data(), arrays[1].get_data() + arrays[1].get_vshape()[0]);
198 }
199 bcast_vector(g.lon);
200 bcast_vector(g.lat);
201 return g;
202}
203
204} // namespace
205
207
208void
209LatLonRegrid::define (const std::string& file_name, const std::string& field_name,
210 const std::vector<long long>& field_shape,
211 const MultiFab& target_lon, const MultiFab& target_lat,
212 const MultiFab& like, const Box& a_domain,
213 const LatLonRegridOptions& options, const std::string& context)
214{
215 const SourceGrid g = find_source_grid(file_name, field_name, field_shape, options, context);
216 lon_name = g.lon_name;
217 lat_name = g.lat_name;
218 lon_file.assign(g.lon.begin(), g.lon.end());
219 lat_file.assign(g.lat.begin(), g.lat.end());
220 nlon = static_cast<int>(lon_file.size());
221 nlat = static_cast<int>(lat_file.size());
222 has_fill = g.has_fill;
223 has_missing = g.has_missing;
224 fill_value = g.fill_value;
225 missing_value = g.missing_value;
226 scale_factor = g.scale_factor;
227 add_offset = g.add_offset;
229 domain = makeSlab(a_domain, 2, 0);
230
231 const std::string where = "'" + field_name + "' in " + file_name;
232 if (nlon < 2 || nlat < 2) {
233 Abort("Cannot interpolate " + where + ": its source grid is " + std::to_string(nlon) +
234 " x " + std::to_string(nlat) + "; it needs at least two points along each axis.");
235 }
236
237 // Longitude made continuous, so an axis crossing the branch cut (170 .. 180, -180 .. -170)
238 // is monotone; then both axes put in increasing order.
242 for (auto [axis, flip, name] : { std::make_tuple(&lon_inc, &flip_lon, lon_name),
243 std::make_tuple(&lat_inc, &flip_lat, lat_name) })
244 {
245 *flip = axis->back() < axis->front();
246 if (*flip) { std::reverse(axis->begin(), axis->end()); }
247 for (std::size_t m = 1; m < axis->size(); ++m) {
248 if (!((*axis)[m] > (*axis)[m-1])) {
249 Abort("Cannot interpolate " + where + ": its axis '" + name +
250 "' is not strictly monotone (at index " + std::to_string(m) + ").");
251 }
252 }
253 }
254
255 // A longitude axis whose spacing closes the circle covers the globe
256 const double span = latlon_regrid::longitude_span(lon_inc);
258 if (span > 360.0 + 1.e-9) {
259 Abort("Cannot interpolate " + where + ": its longitude axis spans " + std::to_string(span) +
260 " degrees, more than once around the globe.");
261 }
262
263 weights.define(like.boxArray(), like.DistributionMap(), stencil_size, IntVect(1,1,0));
264 indices.define(like.boxArray(), like.DistributionMap(), 3*stencil_size, IntVect(1,1,0));
265 weights.setVal(0.0);
266 indices.setVal(-1);
267
268 // Target coordinates on the forcing layout, with the ring
269 MultiFab lon(like.boxArray(), like.DistributionMap(), 1, IntVect(1,1,0));
270 MultiFab lat(like.boxArray(), like.DistributionMap(), 1, IntVect(1,1,0));
271 lon.ParallelCopy(target_lon, 0, 0, 1, IntVect(1,1,0), IntVect(1,1,0));
272 lat.ParallelCopy(target_lat, 0, 0, 1, IntVect(1,1,0), IntVect(1,1,0));
273
274 build_weights(lon, lat);
275
276 amrex::Print() << "Interpolating " << field_name << " from " << summary() << std::endl;
277}
278
279void
280LatLonRegrid::build_weights (const MultiFab& lon, const MultiFab& lat)
281{
282 Gpu::DeviceVector<Real> d_lon(nlon), d_lat(nlat);
283 {
284 std::vector<Real> h_lon(lon_inc.begin(), lon_inc.end());
285 std::vector<Real> h_lat(lat_inc.begin(), lat_inc.end());
286 Gpu::copy(Gpu::hostToDevice, h_lon.begin(), h_lon.end(), d_lon.begin());
287 Gpu::copy(Gpu::hostToDevice, h_lat.begin(), h_lat.end(), d_lat.begin());
288 }
289 const Real* plon = d_lon.data();
290 const Real* plat = d_lat.data();
291 const int Nx = nlon, Ny = nlat;
292 const bool periodic = periodic_lon, fx = flip_lon, fy = flip_lat;
293 const Real lon_ref = static_cast<Real>(latlon_regrid::longitude_reference(lon_inc, periodic));
294 const Real tol = Real(1.e-9);
295
296 ReduceOps<ReduceOpSum, ReduceOpMin, ReduceOpMax, ReduceOpMin, ReduceOpMax> reduce_op;
297 ReduceData<int, Real, Real, Real, Real> reduce_data(reduce_op);
298 using ReduceTuple = typename decltype(reduce_data)::Type;
299
300 for (MFIter mfi(weights); mfi.isValid(); ++mfi) {
301 const Box bx = target_box(mfi);
302 auto const& lon_a = lon.const_array(mfi);
303 auto const& lat_a = lat.const_array(mfi);
304 auto const& w = weights.array(mfi);
305 auto const& idx = indices.array(mfi);
306
307 reduce_op.eval(bx, reduce_data, [=] AMREX_GPU_DEVICE (int i, int j, int k) -> ReduceTuple
308 {
309 const Real lt = latlon_regrid::wrap_longitude(lon_a(i,j,k), lon_ref);
310 const Real pt = lat_a(i,j,k);
311 const latlon_regrid::Stencil st =
312 latlon_regrid::bilinear_stencil(plon, Nx, plat, Ny, periodic, fx, fy, lt, pt, tol);
313 for (int s = 0; s < 4; ++s) {
314 w(i,j,k,s) = st.inside ? st.w[s] : Real(0.0);
315 idx(i,j,k,3*s) = st.inside ? st.i[s] : -1;
316 idx(i,j,k,3*s+1) = st.inside ? st.j[s] : -1;
317 idx(i,j,k,3*s+2) = 0;
318 }
319
320 return { st.inside ? 0 : 1, lt, lt, pt, pt };
321 });
322 }
323
324 ReduceTuple r = reduce_data.value(reduce_op);
325 int n_out = amrex::get<0>(r);
326 Real lon_min = amrex::get<1>(r), lon_max = amrex::get<2>(r);
327 Real lat_min = amrex::get<3>(r), lat_max = amrex::get<4>(r);
328 ParallelDescriptor::ReduceIntSum(n_out);
329 ParallelDescriptor::ReduceRealMin(lon_min);
330 ParallelDescriptor::ReduceRealMax(lon_max);
331 ParallelDescriptor::ReduceRealMin(lat_min);
332 ParallelDescriptor::ReduceRealMax(lat_max);
333
334 if (n_out > 0) {
335 std::ostringstream os;
336 os << "The forcing grid does not contain the model grid: " << n_out << " rho points of"
337 << " level 0 and its boundary ring lie outside it. Forcing grid: " << summary()
338 << ". Model grid (in the forcing grid's longitude convention): lon " << lon_min
339 << " to " << lon_max << ", lat " << lat_min << " to " << lat_max
340 << ". Supply forcing that covers the domain with its boundary ring.";
341 Abort(os.str());
342 }
343}
344
345void
346LatLonRegrid::apply (const std::string& file_name, const std::string& field_name, int itime,
347 MultiFab& dst) const
348{
349 Vector<NDArray<Real>> arrays(1);
350 ReadNetCDFFile(file_name, {field_name}, arrays, true, itime);
351
352 std::vector<Real> buf(static_cast<std::size_t>(nlon) * nlat);
353 if (ParallelDescriptor::IOProcessor()) {
354 const auto shape = arrays[0].get_vshape();
355 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(shape.size() == 3 && shape[1] == nlat && shape[2] == nlon,
356 "Lat-lon forcing record of '" + field_name + "' has an unexpected shape");
357 // Missing-data markers are compared exactly and in the stored (packed) values, as CF
358 // defines them, then the data unpacked. A marker becomes NaN, which the interpolation
359 // sets aside.
360 const double nan = std::numeric_limits<double>::quiet_NaN();
361 const Real* raw = arrays[0].get_data();
362 for (std::size_t c = 0; c < buf.size(); ++c) {
363 const double v = static_cast<double>(raw[c]);
364 const bool marker = (has_fill && v == fill_value) || (has_missing && v == missing_value);
365 buf[c] = static_cast<Real>(marker ? nan : v * scale_factor + add_offset);
366 }
367 }
368 ParallelDescriptor::Bcast(buf.data(), buf.size(), ParallelDescriptor::IOProcessorNumber());
369
370 // (lat, lon) row-major in the file is (i = lon, j = lat) in a FAB
371 FArrayBox src(Box(IntVect(0,0,0), IntVect(nlon-1, nlat-1, 0)), 1);
372 Gpu::copy(Gpu::hostToDevice, buf.begin(), buf.end(), src.dataPtr());
373
374 interpolate(src, dst);
375
376 // A target point with no valid source value within reach is left NaN
377 ReduceOps<ReduceOpSum> op;
378 ReduceData<int> data(op);
379 for (MFIter mfi(dst); mfi.isValid(); ++mfi) {
380 const Box bx = target_box(mfi);
381 auto const& d = dst.const_array(mfi);
382 op.eval(bx, data, [=] AMREX_GPU_DEVICE (int i, int j, int k) -> GpuTuple<int>
383 {
384 return { (d(i,j,k) == d(i,j,k)) ? 0 : 1 };
385 });
386 }
387 int n_missing = amrex::get<0>(data.value(op));
388 ParallelDescriptor::ReduceIntSum(n_missing);
389 if (n_missing > 0) {
390 Abort("Interpolating '" + field_name + "' record " + std::to_string(itime) + " of " +
391 file_name + " onto the model grid: " + std::to_string(n_missing) +
392 " rho points have no valid source value within " + std::to_string(fill_max_rings) +
393 " cells (the source is _FillValue or missing_value there). Raise "
394 "remora.frc_fill_max_rings or fill the forcing over the ocean.");
395 }
396}
397
398void
399LatLonRegrid::interpolate (const FArrayBox& src_fab, MultiFab& dst) const
400{
401 auto const& src = src_fab.const_array();
402 const int Nx = nlon, Ny = nlat, R = fill_max_rings;
403 const bool periodic = periodic_lon, fx = flip_lon, fy = flip_lat;
404 const Real nan = std::numeric_limits<Real>::quiet_NaN();
405
406 for (MFIter mfi(dst); mfi.isValid(); ++mfi) {
407 const Box bx = target_box(mfi);
408 auto const& d = dst.array(mfi);
409 auto const& w = weights.const_array(mfi);
410 auto const& idx = indices.const_array(mfi);
411
412 ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
413 {
414 if (idx(i,j,k,0) < 0) { d(i,j,k) = nan; return; }
415
416 Real sum = Real(0.0), wsum = Real(0.0);
417 for (int s = 0; s < stencil_size; ++s) {
418 const Real v = src(idx(i,j,k,3*s), idx(i,j,k,3*s+1), 0);
419 if (w(i,j,k,s) > Real(0.0) && is_valid(v)) {
420 sum += w(i,j,k,s) * v;
421 wsum += w(i,j,k,s);
422 }
423 }
424 if (wsum > Real(0.0)) { d(i,j,k) = sum / wsum; return; }
425
426 // Every corner with weight is missing: take the nearest valid source value. The
427 // target's fractional source index follows from the stencil: corner 0 is its
428 // lower-left cell, and the fractions are p = w1 + w2 and q = w2 + w3, counted
429 // backward along an axis the file stores decreasing.
430 const Real p = w(i,j,k,1) + w(i,j,k,2);
431 const Real q = w(i,j,k,2) + w(i,j,k,3);
432 const Real xf = Real(idx(i,j,k,0)) + (fx ? -p : p);
433 const Real yf = Real(idx(i,j,k,1)) + (fy ? -q : q);
434 const int ci = static_cast<int>(std::round(xf));
435 const int cj = static_cast<int>(std::round(yf));
436 Real best = nan, best_d2 = std::numeric_limits<Real>::max();
437 for (int dj = -R; dj <= R; ++dj) {
438 const int n = cj + dj;
439 if (n < 0 || n >= Ny) { continue; }
440 for (int di = -R; di <= R; ++di) {
441 int m = ci + di;
442 if (periodic) { m = ((m % Nx) + Nx) % Nx; }
443 else if (m < 0 || m >= Nx) { continue; }
444 const Real v = src(m, n, 0);
445 if (!is_valid(v)) { continue; }
446 Real dx = Real(m) - xf;
447 if (periodic) { dx -= Real(Nx) * std::round(dx / Real(Nx)); }
448 const Real dy = Real(n) - yf;
449 const Real d2 = dx*dx + dy*dy;
450 if (d2 < best_d2) { best_d2 = d2; best = v; }
451 }
452 }
453 d(i,j,k) = best;
454 });
455 }
456}
457
458bool
459LatLonRegrid::same_source_grid (const std::string& file_name, const std::string& field_name,
460 const std::vector<long long>& field_shape) const
461{
463 opts.lon_name = lon_name;
464 opts.lat_name = lat_name;
465 const SourceGrid g = find_source_grid(file_name, field_name, field_shape, opts, "");
466 auto same = [] (const std::vector<double>& a, const std::vector<double>& b) {
467 if (a.size() != b.size()) { return false; }
468 for (std::size_t m = 0; m < a.size(); ++m) {
469 if (std::abs(a[m] - b[m]) > 1.e-9 * std::max(1.0, std::abs(a[m]))) { return false; }
470 }
471 return true;
472 };
473 return same(g.lon, lon_file) && same(g.lat, lat_file) &&
474 g.has_fill == static_cast<int>(has_fill) && g.has_missing == static_cast<int>(has_missing) &&
475 (!has_fill || g.fill_value == fill_value) && (!has_missing || g.missing_value == missing_value) &&
476 g.scale_factor == scale_factor && g.add_offset == add_offset;
477}
478
479bool
480LatLonRegrid::has_latlon_axes (const std::string& file_name, const std::string& field_name)
481{
482 int found = 0;
483 auto ncf = ncutils::NCFile::open(file_name, NC_NOCLOBBER);
484 ncmpi_begin_indep_data(ncf.ncid);
485 if (ParallelDescriptor::IOProcessor() && ncf.has_var(field_name)) {
486 const auto var = ncf.var(field_name);
487 const int rank = var.ndim();
488 if (rank >= 2 && has_dimension_coordinate(ncf, var, rank-1) &&
489 has_dimension_coordinate(ncf, var, rank-2)) {
490 found = units_say(ncf.var(dim_name(ncf, var, rank-1)), "east") &&
491 units_say(ncf.var(dim_name(ncf, var, rank-2)), "north");
492 }
493 }
494 ncf.close();
495 ParallelDescriptor::Bcast(&found, 1, ParallelDescriptor::IOProcessorNumber());
496 return found != 0;
497}
498
499std::string
501{
502 std::ostringstream os;
503 os << "a " << nlon << " x " << nlat << " lat-lon grid (" << lon_name << " "
504 << lon_file.front() << " to " << lon_file.back() << ", " << lat_name << " "
505 << lat_file.front() << " to " << lat_file.back()
506 << (periodic_lon ? ", periodic in longitude" : "") << ")";
507 return os.str();
508}
509
510Box
511LatLonRegrid::target_box (const MFIter& mfi) const
512{
513 Box bx = mfi.validbox();
514 bx.grow(IntVect(1,1,0));
515 bx &= amrex::grow(domain, IntVect(1,1,0));
516 return bx;
517}
518
519#endif // REMORA_USE_NETCDF
void ReadNetCDFFile(const std::string &fname, amrex::Vector< std::string > names, amrex::Vector< NDArray< DType > > &arrays, bool one_time=false, int fill_time=0)
Read in data from netcdf file and save to data arrays.
void define(const std::string &file_name, const std::string &field_name, const std::vector< long long > &field_shape, const amrex::MultiFab &target_lon, const amrex::MultiFab &target_lat, const amrex::MultiFab &like, const amrex::Box &domain, const LatLonRegridOptions &options, const std::string &context)
Find the source grid of a variable and build the weights. Collective.
void interpolate(const amrex::FArrayBox &src, amrex::MultiFab &dst) const
std::vector< double > lon_inc
void build_weights(const amrex::MultiFab &lon, const amrex::MultiFab &lat)
std::vector< double > lat_inc
amrex::MultiFab weights
std::vector< double > lon_file
void apply(const std::string &file_name, const std::string &field_name, int itime, amrex::MultiFab &dst) const
Read record itime of the variable and interpolate it into dst, over the domain and its one-cell ring....
static constexpr int stencil_size
Number of stencil points per target cell.
amrex::iMultiFab indices
static bool has_latlon_axes(const std::string &file_name, const std::string &field_name)
Whether a variable's last two dimensions are CF longitude and latitude coordinates (1D variables of t...
bool same_source_grid(const std::string &file_name, const std::string &field_name, const std::vector< long long > &field_shape) const
Whether another file's variable lies on the same source grid. Collective.
std::string summary() const
One line describing the source grid, for the log.
amrex::Box target_box(const amrex::MFIter &mfi) const
The cells filled: the valid box of mfi grown by the ring, inside the ring.
std::vector< double > lat_file
bool has_var(const std::string &) const
Check if a variable exists by name.
static NCFile open(const std::string &name, const int cmode=NC_NOWRITE, MPI_Comm comm=MPI_COMM_WORLD, MPI_Info info=MPI_INFO_NULL)
Open an existing file.
void close()
Close file object.
NCVar var(const std::string &) const
Get the variable instance by name.
bool is_periodic(const std::vector< double > &lon_inc)
double longitude_span(const std::vector< double > &lon_inc)
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE Stencil bilinear_stencil(const amrex::Real *lon, int nlon, const amrex::Real *lat, int nlat, bool periodic, bool flip_lon, bool flip_lat, amrex::Real lt, amrex::Real pt, amrex::Real tol)
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool is_valid(amrex::Real v)
double longitude_reference(const std::vector< double > &lon_inc, bool periodic)
void unwrap_longitudes(std::vector< double > &lon)
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real wrap_longitude(amrex::Real lon, amrex::Real ref)
How a NetCDF forcing field on a lat-lon grid is interpolated onto the model grid.
Representation of a NetCDF variable.
bool has_attr(const std::string &name) const
Whether a variable has an attribute with name.
const int varid
Variable ID used with NetCDF API.
std::string get_attr(const std::string &name) const
Read attribute from file.
std::vector< MPI_Offset > shape() const
Shape of the array (size in each array dimension)
int ndim() const
Number of array dimensions for this variable.