Loading [MathJax]/extensions/tex2jax.js
PISM, A Parallel Ice Sheet Model 2.2.2-d6b3a29ca committed by Constantine Khrulev on 2025-03-28
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
InputInterpolationYAC.cc
Go to the documentation of this file.
1/* Copyright (C) 2024, 2025 PISM Authors
2 *
3 * This file is part of PISM.
4 *
5 * PISM is free software; you can redistribute it and/or modify it under the
6 * terms of the GNU General Public License as published by the Free Software
7 * Foundation; either version 3 of the License, or (at your option) any later
8 * version.
9 *
10 * PISM is distributed in the hope that it will be useful, but WITHOUT ANY
11 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 * details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with PISM; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <cstddef>
21#include <memory>
22#include <vector>
23#include <cmath>
24
25#include "InputInterpolation.hh"
26#include "Interpolation1D.hh"
27#include "pism/util/VariableMetadata.hh"
28#include "pism/util/projection.hh"
29#include "pism/util/Grid.hh"
30#include "pism/util/error_handling.hh"
31#include "pism/util/Context.hh"
32#include "pism/util/Logger.hh"
33#include "pism/util/petscwrappers/Vec.hh"
34#include "pism/util/array/Scalar.hh"
35#include "pism/util/InputInterpolationYAC.hh"
36#include "pism/util/pism_utilities.hh" // GlobalMin()
37
38#if (Pism_USE_PROJ == 0)
39#error "This code requires PROJ"
40#endif
41
42#include <proj.h>
43
44#include "pism/util/Proj.hh"
45
46#if (Pism_USE_YAC_INTERPOLATION == 0)
47#error "This code requires YAC"
48#endif
49
50extern "C" {
51#include "yac.h"
52}
53
54namespace pism {
55
56/*!
57 * Utility class converting `x,y` coordinates in a projection to a `lon,lat` pair.
58 *
59 * Requires the `PROJ` library.
60 */
62public:
63 LonLatCalculator(const std::string &proj_string)
64 : m_coordinate_mapping(proj_string, "EPSG:4326") {
65 }
66
67 std::array<double, 2> lonlat(double x, double y) {
68 PJ_COORD in, out;
69
70 in.xy = { x, y };
71 out = proj_trans(m_coordinate_mapping, PJ_FWD, in);
72
73 return { out.lp.phi, out.lp.lam };
74 }
75
76private:
78};
79
80/*!
81 * Grid definition using coordinates in radians.
82 */
83struct LonLatGrid {
84 std::vector<double> lon;
85 std::vector<double> lat;
86
87 /*!
88 *
89 * Converts a Cartesian grid in a `projection` that uses coordinates
90 * `x` and `y` in meters into the form that can be used to define a
91 * curvilinear grid in YAC.
92 *
93 * The `projection` string has to use the format compatible with PROJ.
94 */
95 LonLatGrid(const std::vector<double> &x, const std::vector<double> &y,
96 const std::string &projection) {
97
98 size_t nrow = y.size();
99 size_t ncol = x.size();
100 size_t N = nrow * ncol;
101
102 lon.resize(N);
103 lat.resize(N);
104
105 // convert from (row, col) to the linear index in "cell" arrays
106 auto C = [ncol](size_t row, size_t col) { return row * ncol + col; };
107
108 // convert from degrees to radians
109 auto deg2rad = [](double degree) { return degree * M_PI / 180; };
110
111 pism::LonLatCalculator mapping(projection);
112
113 for (size_t row = 0; row < nrow; ++row) {
114 for (size_t col = 0; col < ncol; ++col) {
115 auto coords = mapping.lonlat(x[col], y[row]);
116
117 lon[C(row, col)] = deg2rad(coords[0]);
118 lat[C(row, col)] = deg2rad(coords[1]);
119 }
120 }
121 }
122};
123
124
125/*!
126 * Define the PISM grid. Each PE defines its own subdomain.
127 *
128 * Returns the point ID that can be used to define a "field".
129 */
130int InputInterpolationYAC::define_grid(const std::vector<double> &x_cell,
131 const std::vector<double> &y_cell,
132 const std::string &grid_name,
133 const std::string &projection) {
134
135 if (projection.empty()) {
137 PISM_ERROR_LOCATION, "grid '%s' has no projection information", grid_name.c_str());
138 }
139
140 // Shift x and y by half a grid spacing and add one more row and column to get
141 // coordinates of corners of cells in the local sub-domain:
142 std::vector<double> x_node(x_cell.size() + 1), y_node(y_cell.size() + 1);
143 {
144 // note: dx and dy may be negative here
145 double dx = x_cell[1] - x_cell[0];
146 double dy = y_cell[1] - y_cell[0];
147
148 for (size_t k = 0; k < x_cell.size(); ++k) {
149 x_node[k] = x_cell[k] - 0.5 * dx;
150 }
151 x_node.back() = x_cell.back() + 0.5 * dx;
152
153 for (size_t k = 0; k < y_cell.size(); ++k) {
154 y_node[k] = y_cell[k] - 0.5 * dy;
155 }
156 y_node.back() = y_cell.back() + 0.5 * dy;
157 }
158
159 // Compute lon,lat coordinates of cell centers:
160 LonLatGrid cells(x_cell, y_cell, projection);
161 // Compute lon,lat coordinates of cell corners:
162 LonLatGrid nodes(x_node, y_node, projection);
163
164 int point_id = 0;
165 {
166 int cyclic[] = { 0, 0 };
167
168 int grid_id = 0;
169
170 int n_nodes[2] = { (int)x_node.size(), (int)y_node.size() };
171 yac_cdef_grid_curve2d(grid_name.c_str(), n_nodes, cyclic, nodes.lon.data(), nodes.lat.data(),
172 &grid_id);
173
174 int n_cells[2] = { (int)x_cell.size(), (int)y_cell.size() };
175 yac_cdef_points_curve2d(grid_id, n_cells, YAC_LOCATION_CELL, cells.lon.data(), cells.lat.data(),
176 &point_id);
177 }
178 return point_id;
179}
180
181/*!
182 * Return the YAC field_id corresponding to a given PISM grid and its
183 * projection info.
184 *
185 * @param[in] component_id YAC component ID
186 * @param[in] pism_grid PISM's grid
187 * @param[in] name string describing this grid and field
188 */
189int InputInterpolationYAC::define_field(int component_id, const std::vector<double> &x,
190 const std::vector<double> &y,
191 const std::string &proj_string, const std::string &name) {
192
193 int point_id = define_grid(x, y, name, proj_string);
194
195 const char *time_step_length = "1";
196 const int point_set_size = 1;
197 const int collection_size = 1;
198
199 int field_id = 0;
200 yac_cdef_field(name.c_str(), component_id, &point_id, point_set_size, collection_size,
201 time_step_length, YAC_TIME_UNIT_SECOND, &field_id);
202 return field_id;
203}
204
205static void pism_yac_error_handler(MPI_Comm /* unused */, const char *msg, const char *source,
206 int line) {
207 throw pism::RuntimeError::formatted(pism::ErrorLocation(source, line), "YAC error: %s", msg);
208}
209
210/*!
211 * Extract the "local" (corresponding to the current sub-domain) grid subset.
212 */
213static std::vector<double> grid_subset(int xs, int xm, const std::vector<double> &coords) {
214 std::vector<double> result(xm);
215 for (int k = 0; k < xm; ++k) {
216 result[k] = coords[xs + k];
217 }
218
219 return result;
220}
221
222static double dx_estimate(Proj &mapping, double x1, double x2, double y) {
223 PJ_COORD p1, p2;
224 p1.lp = {proj_torad(x1), proj_torad(y)};
225 p2.lp = {proj_torad(x2), proj_torad(y)};
226
227 return proj_lp_dist(mapping, p1, p2);
228}
229
230static double dx_min(const std::string &proj_string,
231 const std::vector<double> &x,
232 const std::vector<double> &y) {
233 size_t Nx = x.size();
234 size_t Ny = y.size();
235
236 Proj mapping(proj_string, "EPSG:4326");
237
238 double dx = dx_estimate(mapping, x[0], x[1], y[0]);
239 for (size_t j = 0; j < Ny; ++j) { // y
240 for (size_t i = 1; i < Nx; ++i) { // x; note: starts from 1
241 dx = std::min(dx, dx_estimate(mapping, x[i-1], x[i], y[j]));
242 }
243 }
244 return dx;
245}
246
247static double dy_estimate(Proj &mapping, double x, double y1, double y2) {
248 PJ_COORD p1, p2;
249 p1.lp = {proj_torad(x), proj_torad(y1)};
250 p2.lp = {proj_torad(x), proj_torad(y2)};
251
252 return proj_lp_dist(mapping, p1, p2);
253}
254
255static double dy_min(const std::string &proj_string,
256 const std::vector<double> &x,
257 const std::vector<double> &y) {
258 size_t Nx = x.size();
259 size_t Ny = y.size();
260
261 Proj mapping(proj_string, "EPSG:4326");
262
263 double dy = dy_estimate(mapping, x[0], x[1], y[0]);
264 for (size_t i = 0; i < Nx; ++i) { // x
265 for (size_t j = 1; j < Ny; ++j) { // y; note: starts from 1
266 dy = std::min(dy, dy_estimate(mapping, x[i], y[j-1], y[j]));
267 }
268 }
269 return dy;
270}
271
273 const pism::File &input_file,
274 const std::string &variable_name,
276 : m_instance_id(0), m_source_field_id(0), m_target_field_id(0) {
277 auto ctx = target_grid.ctx();
278
279 if (target_grid.get_mapping_info().proj_string.empty()) {
280 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "internal grid projection is not known");
281 }
282
283 yac_set_abort_handler((yac_abort_func)pism_yac_error_handler);
284
285 try {
286 auto log = ctx->log();
287
288 auto source_grid_name = grid_name(input_file, variable_name, ctx->unit_system(),
289 type == PIECEWISE_CONSTANT);
290
291 log->message(
292 2, "* Initializing 2D interpolation on the sphere from '%s' to the internal grid...\n",
293 source_grid_name.c_str());
294
295 grid::InputGridInfo source_grid_info(input_file, variable_name, ctx->unit_system(),
297
298 auto source_grid_mapping = MappingInfo::FromFile(input_file, variable_name, ctx->unit_system());
299
300 std::string grid_mapping_name = source_grid_mapping.cf_mapping["grid_mapping_name"];
301
302 log->message(2, "Input grid:\n");
303 if (not grid_mapping_name.empty()) {
304 log->message(2, " Grid mapping: %s\n", grid_mapping_name.c_str());
305 }
306 if (not source_grid_mapping.proj_string.empty()) {
307 log->message(2, " PROJ string: '%s'\n", source_grid_mapping.proj_string.c_str());
308 }
309 source_grid_info.report(*log, 2, ctx->unit_system());
310
311 grid::Parameters P(*ctx->config(), source_grid_info.x.size(), source_grid_info.y.size(),
312 source_grid_info.Lx, source_grid_info.Ly);
313
314 P.x0 = source_grid_info.x0;
315 P.y0 = source_grid_info.y0;
317 P.variable_name = variable_name;
318 P.vertical_grid_from_options(*ctx->config());
319 P.ownership_ranges_from_options(*ctx->config(), ctx->size());
320
321 auto source_grid = std::make_shared<Grid>(ctx, P);
322
323 source_grid->set_mapping_info(source_grid_mapping);
324
325 if (source_grid->get_mapping_info().proj_string.empty()) {
327 "unsupported or missing projection info for the grid '%s'",
328 source_grid_name.c_str());
329 }
330
331 m_buffer = std::make_shared<pism::array::Scalar>(source_grid, variable_name);
332
333 std::string target_grid_name = "internal for " + source_grid_name;
334 {
335 // Initialize YAC:
336 {
337 yac_cinit_instance(&m_instance_id);
338 yac_cdef_calendar(YAC_YEAR_OF_365_DAYS);
339 // Note: zero-padding of months and days *is* required.
340 yac_cdef_datetime_instance(m_instance_id, "-1-01-01", "+1-01-01");
341 }
342
343 // Define components: this has to be done using *one* call
344 // (cannot call yac_cdef_comp?_instance() more than once)
345 const int n_comps = 2;
346 const char *comp_names[n_comps] = { "source_component", "target_component" };
347 int comp_ids[n_comps] = { 0, 0 };
348 yac_cdef_comps_instance(m_instance_id, comp_names, n_comps, comp_ids);
349
350 double source_grid_spacing = 0.0;
351 log->message(2, "Defining the source grid (%s)...\n", source_grid_name.c_str());
352 {
353 auto x = grid_subset(source_grid->xs(), source_grid->xm(), source_grid_info.x);
354 auto y = grid_subset(source_grid->ys(), source_grid->ym(), source_grid_info.y);
355
357 comp_ids[0], x, y, source_grid->get_mapping_info().proj_string, source_grid_name);
358
359 double dx = 0.0;
360 double dy = 0.0;
361 if (source_grid_info.longitude_latitude) {
362 dx = dx_min(source_grid_mapping.proj_string, x, y);
363 dy = dy_min(source_grid_mapping.proj_string, x, y);
364 } else {
365 dx = std::abs(x[1] - x[0]);
366 dy = std::abs(y[1] - y[0]);
367 }
368 source_grid_spacing = GlobalMin(ctx->com(), std::min(dx, dy));
369 log->message(2, " Source grid spacing: ~%3.3f m\n", source_grid_spacing);
370 }
371
372 double target_grid_spacing = 0.0;
373 log->message(2, "Defining the target grid (%s)...\n", target_grid_name.c_str());
374 {
375 auto x = grid_subset(target_grid.xs(), target_grid.xm(), target_grid.x());
376 auto y = grid_subset(target_grid.ys(), target_grid.ym(), target_grid.y());
377
379 comp_ids[1], x, y, target_grid.get_mapping_info().proj_string, target_grid_name);
380
381 target_grid_spacing = GlobalMin(ctx->com(), std::min(target_grid.dx(), target_grid.dy()));
382 log->message(2, " Target grid spacing: %3.3f m\n", target_grid_spacing);
383 }
384
385 // Define the interpolation stack:
386 {
387 std::string method;
388 int interp_stack_id = 0;
389 yac_cget_interp_stack_config(&interp_stack_id);
390
391 if (type == PIECEWISE_CONSTANT) {
392 method = "nearest neighbor";
393
394 // use nearest neighbor interpolation to interpolate integer fields:
395 {
396 // nearest neighbor
397 int n_neighbors = 1; // only one neighbor
398 double scaling = 1.0;
399 double max_search_distance = 0.0; // unlimited
400 yac_cadd_interp_stack_config_nnn(interp_stack_id, YAC_NNN_DIST, n_neighbors,
401 max_search_distance, scaling);
402 }
403 } else {
404 int partial_coverage = 0;
405 if (source_grid_spacing < target_grid_spacing) {
406 method = "1st order conservative";
407
408 int order = 1;
409 int enforce_conservation = 1;
410
411 yac_cadd_interp_stack_config_conservative(interp_stack_id, order, enforce_conservation,
412 partial_coverage, YAC_CONSERV_DESTAREA);
413 } else {
414 method = "weighted average of source cell nodes";
415
416 // use average over source grid nodes containing a target point as a backup:
417 yac_cadd_interp_stack_config_average(interp_stack_id, YAC_AVG_BARY, partial_coverage);
418 }
419
420 {
421 // nearest neighbor as a fallback
422 int n_neighbors = 1;
423 double scaling = 1.0;
424 double max_search_distance = 0.0; // unlimited
425 yac_cadd_interp_stack_config_nnn(interp_stack_id, YAC_NNN_DIST, n_neighbors,
426 max_search_distance, scaling);
427 }
428 }
429
430 log->message(2, "Interpolation method: %s\n", method.c_str());
431
432 // Define the coupling between fields:
433 const int src_lag = 0;
434 const int tgt_lag = 0;
435 yac_cdef_couple_instance(m_instance_id,
436 "source_component", // source component name
437 source_grid_name.c_str(), // source grid name
438 source_grid_name.c_str(), // source field name
439 "target_component", // target component name
440 target_grid_name.c_str(), // target grid name
441 target_grid_name.c_str(), // target field name
442 "1", // time step length in units below
443 YAC_TIME_UNIT_SECOND, // time step length units
444 YAC_REDUCTION_TIME_NONE, // reduction in time (for
445 // asynchronous coupling)
446 interp_stack_id, src_lag, tgt_lag);
447
448 // free the interpolation stack config now that we defined the coupling
449 yac_cfree_interp_stack_config(interp_stack_id);
450 }
451
452 double start = MPI_Wtime();
453 yac_cenddef_instance(m_instance_id);
454 double end = MPI_Wtime();
455 log->message(2, "Initialized interpolation from %s in %f seconds.\n",
456 source_grid_name.c_str(), end - start);
457 }
458 } catch (pism::RuntimeError &e) {
459 e.add_context("initializing interpolation from %s to the internal grid",
460 input_file.name().c_str());
461 throw;
462 }
463}
464
468
470 pism::petsc::Vec &target) const {
471
472 pism::petsc::VecArray input_array(source.vec());
473 pism::petsc::VecArray output_array(target);
474
475 double *send_field_ = input_array.get();
476 double **send_field[1] = { &send_field_ };
477
478 double *recv_field[1] = { output_array.get() };
479
480 int ierror = 0;
481 int send_info = 0;
482 int recv_info = 0;
483 int collection_size = 1;
484 double start = MPI_Wtime();
485 yac_cexchange(m_source_field_id, m_target_field_id, collection_size, send_field, recv_field,
486 &send_info, &recv_info, &ierror);
487 double end = MPI_Wtime();
488
489 return end - start;
490}
491
492
494
495 double time_spent =
496 InputInterpolation::regrid(output.metadata(0), file, -1, *output.grid(), output.vec());
497
498 auto log = output.grid()->ctx()->log();
499
500 log->message(2, "Interpolation took %f seconds.\n", time_spent);
501}
502
503
505 const pism::File &file, int record_index,
506 const Grid & /* target_grid (unused) */,
507 petsc::Vec &output) const {
508
509 // set metadata to help the following call find the variable, convert units, etc
510 m_buffer->metadata(0) = metadata;
511 m_buffer->read(file, record_index);
512
513 double time_spent = interpolate(*m_buffer, output);
514
515 return time_spent;
516}
517
518} // namespace pism
std::string name() const
Definition File.cc:274
High-level PISM I/O class.
Definition File.hh:55
const std::vector< double > & x() const
X-coordinates.
Definition Grid.cc:623
const std::vector< double > & y() const
Y-coordinates.
Definition Grid.cc:633
int ys() const
Global starting index of this processor's subset.
Definition Grid.cc:593
double dx() const
Horizontal grid spacing.
Definition Grid.cc:653
std::shared_ptr< const Context > ctx() const
Return execution context this grid corresponds to.
Definition Grid.cc:540
const MappingInfo & get_mapping_info() const
Definition Grid.cc:1577
double dy() const
Horizontal grid spacing.
Definition Grid.cc:658
int xs() const
Global starting index of this processor's subset.
Definition Grid.cc:588
int xm() const
Width of this processor's sub-domain.
Definition Grid.cc:598
int ym() const
Width of this processor's sub-domain.
Definition Grid.cc:603
Describes the PISM grid and the distribution of data across processors.
Definition Grid.hh:290
double regrid_impl(const SpatialVariableMetadata &metadata, const pism::File &file, int record_index, const Grid &target_grid, petsc::Vec &output) const
static int define_field(int component_id, const std::vector< double > &x, const std::vector< double > &y, const std::string &proj_string, const std::string &name)
static int define_grid(const std::vector< double > &x, const std::vector< double > &y, const std::string &grid_name, const std::string &projection)
double interpolate(const array::Scalar &source, petsc::Vec &target) const
InputInterpolationYAC(const Grid &target_grid, const File &input_file, const std::string &variable_name, InterpolationType type)
std::shared_ptr< array::Scalar > m_buffer
void regrid(const File &file, array::Scalar &output) const
double regrid(const SpatialVariableMetadata &metadata, const pism::File &file, int record_index, const Grid &grid, petsc::Vec &output) const
LonLatCalculator(const std::string &proj_string)
std::array< double, 2 > lonlat(double x, double y)
std::string proj_string
a projection definition string in a format recognized by PROJ 6.x+
Definition projection.hh:72
static MappingInfo FromFile(const File &input_file, const std::string &variable_name, units::System::Ptr unit_system)
Get projection info from a file.
A wrapper for PJ that makes sure pj_destroy is called.
Definition Proj.hh:32
void add_context(const std::string &message)
Add a message providing some context. This way we can (sort of) get a stack trace even though C++ exc...
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
Spatial NetCDF variable (corresponding to a 2D or 3D scalar field).
petsc::Vec & vec() const
Definition Array.cc:310
std::shared_ptr< const Grid > grid() const
Definition Array.cc:131
SpatialVariableMetadata & metadata(unsigned int N=0)
Returns a reference to the SpatialVariableMetadata object containing metadata for the compoment N.
Definition Array.cc:476
std::vector< double > y
y coordinates
Definition Grid.hh:93
double Lx
domain half-width
Definition Grid.hh:82
double Ly
domain half-height
Definition Grid.hh:84
double y0
y-coordinate of the domain center
Definition Grid.hh:80
double x0
x-coordinate of the domain center
Definition Grid.hh:78
std::vector< double > x
x coordinates
Definition Grid.hh:91
void report(const Logger &log, int threshold, std::shared_ptr< units::System > s) const
Definition Grid.cc:885
Contains parameters of an input file grid.
Definition Grid.hh:68
std::string variable_name
Name of the variable used to initialize the instance (empty if not used)
Definition Grid.hh:172
void vertical_grid_from_options(const Config &config)
Process -Mz and -Lz; set z;.
Definition Grid.cc:1326
double y0
Domain center in the Y direction.
Definition Grid.hh:155
double x0
Domain center in the X direction.
Definition Grid.hh:153
void ownership_ranges_from_options(const Config &config, unsigned int size)
Re-compute ownership ranges. Uses current values of Mx and My.
Definition Grid.cc:1192
Registration registration
Grid registration.
Definition Grid.hh:161
Grid parameters; used to collect defaults before an Grid is allocated.
Definition Grid.hh:123
double * get()
Definition Vec.cc:54
Wrapper around VecGetArray and VecRestoreArray.
Definition Vec.hh:44
#define PISM_ERROR_LOCATION
@ CELL_CENTER
Definition Grid.hh:56
@ PIECEWISE_CONSTANT
static double dy_estimate(Proj &mapping, double x, double y1, double y2)
static std::vector< double > grid_subset(int xs, int xm, const std::vector< double > &coords)
static const double k
Definition exactTestP.cc:42
static double dy_min(const std::string &proj_string, const std::vector< double > &x, const std::vector< double > &y)
std::string grid_name(const pism::File &file, const std::string &variable_name, pism::units::System::Ptr sys, bool piecewise_constant)
static double dx_estimate(Proj &mapping, double x1, double x2, double y)
void GlobalMin(MPI_Comm comm, double *local, double *result, int count)
static void pism_yac_error_handler(MPI_Comm, const char *msg, const char *source, int line)
static double dx_min(const std::string &proj_string, const std::vector< double > &x, const std::vector< double > &y)
std::vector< double > lat
LonLatGrid(const std::vector< double > &x, const std::vector< double > &y, const std::string &projection)
std::vector< double > lon