5#include <AMReX_GpuContainers.H>
6#include <AMReX_Utility.H>
7#include <AMReX_ParallelDescriptor.H>
8#include <AMReX_ParallelReduce.H>
15#ifdef REMORA_USE_NETCDF
22void bcast_string (std::string&
s)
24 amrex::BroadcastString(
s, ParallelDescriptor::MyProc(), ParallelDescriptor::IOProcessorNumber(),
25 ParallelDescriptor::Communicator());
29void bcast_vector (Vector<double>& v)
31 amrex::BroadcastArray(v, ParallelDescriptor::MyProc(), ParallelDescriptor::IOProcessorNumber(),
32 ParallelDescriptor::Communicator());
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);
46bool units_say (
const ncutils::NCVar& var,
const std::string& direction)
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);
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;
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;
86find_source_grid (
const std::string& file_name,
const std::string& field_name,
87 const std::vector<long long>& field_shape,
94 ncmpi_begin_indep_data(ncf.
ncid);
95 if (ParallelDescriptor::IOProcessor())
97 const auto var = ncf.
var(field_name);
98 const int rank = var.
ndim();
102 if (g.lon_name.empty() || g.lat_name.empty()) {
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); }
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";
114 std::istringstream tokens(var.
get_attr(
"coordinates"));
115 std::vector<std::string> names;
116 for (std::string t; tokens >> t; ) {
118 dim_name(ncf, ncf.
var(t), 0) != dim_name(ncf, var, 0)) {
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; }
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) {
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";
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; }
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)";
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) })
170 std::vector<double> v;
172 if (!v.empty()) { *has = 1; *val = v[0]; }
179 if (!error.empty()) {
180 Abort(
"Cannot interpolate '" + field_name +
"' in " + file_name +
181 " onto the model grid: " + error +
"." + context);
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);
193 Vector<NDArray<double>> arrays(2);
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]);
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,
215 const SourceGrid g = find_source_grid(file_name, field_name, field_shape, options, context);
218 lon_file.assign(g.lon.begin(), g.lon.end());
219 lat_file.assign(g.lat.begin(), g.lat.end());
229 domain = makeSlab(a_domain, 2, 0);
231 const std::string where =
"'" + field_name +
"' in " + file_name;
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.");
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) +
").");
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.");
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));
276 amrex::Print() <<
"Interpolating " << field_name <<
" from " <<
summary() << std::endl;
282 Gpu::DeviceVector<Real> d_lon(
nlon), d_lat(
nlat);
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());
289 const Real* plon = d_lon.data();
290 const Real* plat = d_lat.data();
294 const Real tol = Real(1.e-9);
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;
300 for (MFIter mfi(
weights); mfi.isValid(); ++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);
307 reduce_op.eval(bx, reduce_data, [=] AMREX_GPU_DEVICE (
int i,
int j,
int k) -> ReduceTuple
310 const Real pt = lat_a(i,j,k);
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;
320 return { st.
inside ? 0 : 1, lt, lt, pt, pt };
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);
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.";
349 Vector<NDArray<Real>> arrays(1);
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");
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]);
368 ParallelDescriptor::Bcast(buf.data(), buf.size(), ParallelDescriptor::IOProcessorNumber());
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());
377 ReduceOps<ReduceOpSum> op;
378 ReduceData<int> data(op);
379 for (MFIter mfi(dst); mfi.isValid(); ++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>
384 return { (d(i,j,k) == d(i,j,k)) ? 0 : 1 };
387 int n_missing = amrex::get<0>(data.value(op));
388 ParallelDescriptor::ReduceIntSum(n_missing);
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.");
401 auto const& src = src_fab.const_array();
404 const Real nan = std::numeric_limits<Real>::quiet_NaN();
406 for (MFIter mfi(dst); mfi.isValid(); ++mfi) {
408 auto const& d = dst.array(mfi);
409 auto const& w =
weights.const_array(mfi);
410 auto const& idx =
indices.const_array(mfi);
412 ParallelFor(bx, [=] AMREX_GPU_DEVICE (
int i,
int j,
int k)
414 if (idx(i,j,k,0) < 0) { d(i,j,k) = nan;
return; }
416 Real sum = Real(0.0), wsum = Real(0.0);
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;
424 if (wsum > Real(0.0)) { d(i,j,k) = sum / wsum;
return; }
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) {
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;
450 if (d2 < best_d2) { best_d2 = d2; best = v; }
460 const std::vector<long long>& field_shape)
const
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; }
474 g.has_fill ==
static_cast<int>(
has_fill) && g.has_missing ==
static_cast<int>(
has_missing) &&
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");
495 ParallelDescriptor::Bcast(&found, 1, ParallelDescriptor::IOProcessorNumber());
502 std::ostringstream os;
506 << (
periodic_lon ?
", periodic in longitude" :
"") <<
")";
513 Box bx = mfi.validbox();
514 bx.grow(IntVect(1,1,0));
515 bx &= amrex::grow(
domain, IntVect(1,1,0));
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
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.
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.