PISM, A Parallel Ice Sheet Model 2.3.3-e0e7dfb06 committed by Constantine Khrulev on 2026-10-20
Loading...
Searching...
No Matches
input_helpers.cc
Go to the documentation of this file.
1/* Copyright (C) 2025, 2026 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 <cmath>
21
22#include "io_helpers.hh"
23#include "pism/util/Grid.hh"
24#include "pism/util/io/LocalInterpCtx.hh"
25#include "pism/util/io/IO_Flags.hh"
26#include "pism/util/error_handling.hh"
27#include "pism/util/VariableMetadata.hh"
28#include "pism/util/Profiling.hh"
29#include "pism/util/Context.hh"
30#include "pism/util/pism_utilities.hh"
31#include "pism/util/Logger.hh"
32
33namespace pism {
34namespace io {
35
36/*!
37 * Check if units are set in an input file and warn if they are not.
38 */
39static std::string check_units(const VariableMetadata &variable, const std::string &input_units,
40 const Logger &log) {
41 std::string internal_units = variable["units"];
42 if (input_units.empty() and not internal_units.empty()) {
43 log.message(2,
44 "PISM WARNING: Variable '%s' ('%s') does not have the units attribute.\n"
45 " Assuming that it is in '%s'.\n",
46 variable.get_name().c_str(), variable.get_string("long_name").c_str(),
47 internal_units.c_str());
48 return internal_units;
49 }
50
51 return input_units;
52}
53
54//! \brief Bi-(or tri-)linear interpolation.
55/*!
56 * This is the interpolation code itself.
57 *
58 * Note that its inputs are (essentially)
59 * - the definition of the input grid
60 * - the definition of the output grid
61 * - input array (lic->buffer)
62 * - output array (double *output_array)
63 *
64 * The `output_array` is expected to be big enough to contain
65 * `grid.xm()*`grid.ym()*length(zlevels_out)` numbers.
66 *
67 * We should be able to switch to using an external interpolation library
68 * fairly easily...
69 */
70static void interpolate(const grid::DistributedGridInfo &grid, const LocalInterpCtx &lic, double const *input_array,
71 double *output_array) {
72 // We'll work with the raw storage here so that the array we are filling is
73 // indexed the same way as the buffer we are pulling from (input_array)
74
75 unsigned int nlevels = lic.z->n_output();
76
77 int x_count = lic.count[X_AXIS], z_count = lic.count[Z_AXIS];
78 auto input = [input_array, x_count, z_count](int X, int Y, int Z) {
79 // the map from logical to linear indices for the input array
80 int index = (Y * x_count + X) * z_count + Z;
81 return input_array[index];
82 };
83
84 for (auto p : GridPoints(grid)) {
85 const int i_global = p.i(), j_global = p.j();
86
87 const int i = i_global - grid.xs, j = j_global - grid.ys;
88
89 // Indices of neighboring points.
90 const int X_m = lic.x->left(i), X_p = lic.x->right(i);
91 const int Y_m = lic.y->left(j), Y_p = lic.y->right(j);
92
93 for (unsigned int k = 0; k < nlevels; k++) {
94
95 double a_mm = 0.0, a_mp = 0.0, a_pm = 0.0, a_pp = 0.0;
96
97 if (nlevels > 1) {
98 const int Z_m = lic.z->left(k), Z_p = lic.z->right(k);
99
100 const double alpha_z = lic.z->alpha(k);
101
102 // We pretend that there are always 8 neighbors (4 in the map plane,
103 // 2 vertical levels). And compute the indices into the input_array for
104 // those neighbors.
105 const double
106 mmm = input(X_m, Y_m, Z_m),
107 mmp = input(X_m, Y_m, Z_p),
108 pmm = input(X_p, Y_m, Z_m),
109 pmp = input(X_p, Y_m, Z_p),
110 mpm = input(X_m, Y_p, Z_m),
111 mpp = input(X_m, Y_p, Z_p),
112 ppm = input(X_p, Y_p, Z_m),
113 ppp = input(X_p, Y_p, Z_p);
114
115 // linear interpolation in the z-direction
116 a_mm = mmm * (1.0 - alpha_z) + mmp * alpha_z;
117 a_mp = pmm * (1.0 - alpha_z) + pmp * alpha_z;
118 a_pm = mpm * (1.0 - alpha_z) + mpp * alpha_z;
119 a_pp = ppm * (1.0 - alpha_z) + ppp * alpha_z;
120 } else {
121 // no interpolation in Z in the 2-D case
122 a_mm = input(X_m, Y_m, 0);
123 a_mp = input(X_p, Y_m, 0);
124 a_pm = input(X_m, Y_p, 0);
125 a_pp = input(X_p, Y_p, 0);
126 }
127
128 // interpolation coefficient in the x direction
129 const double x_alpha = lic.x->alpha(i);
130 // interpolation coefficient in the y direction
131 const double y_alpha = lic.y->alpha(j);
132
133 // interpolate in x direction
134 const double a_m = a_mm * (1.0 - x_alpha) + a_mp * x_alpha;
135 const double a_p = a_pm * (1.0 - x_alpha) + a_pp * x_alpha;
136
137 auto index = (j * grid.xm + i) * nlevels + k;
138
139 // index into the new array and interpolate in y direction
140 output_array[index] = a_m * (1.0 - y_alpha) + a_p * y_alpha;
141 // done with the point at (x,y,z)
142 } // end of the loop over vertical levels
143 }
144}
145
146/**
147 * Check if the storage order of a variable in the current file
148 * matches the memory storage order used by PISM.
149 *
150 * @param[in] file input file
151 * @param var_name name of the variable to check
152 * @returns false if storage orders match, true otherwise
153 */
154static bool transpose(std::vector<AxisType> dimension_types) {
155
156 std::vector<AxisType> storage, memory = { Y_AXIS, X_AXIS };
157
158 bool first = true;
159 for (auto dimtype : dimension_types) {
160
161 if (first && dimtype == T_AXIS) {
162 // ignore the time dimension, but only if it is the first
163 // dimension in the list
164 first = false;
165 continue;
166 }
167
168 if (dimtype == X_AXIS || dimtype == Y_AXIS) {
169 storage.push_back(dimtype);
170 } else if (dimtype == Z_AXIS) {
171 memory.push_back(dimtype); // now memory = {Y_AXIS, X_AXIS, Z_AXIS}
172 // assume that this variable has only one Z_AXIS in the file
173 storage.push_back(dimtype);
174 } else {
175 // an UNKNOWN_AXIS or T_AXIS at index != 0 was found, use transposed I/O
176 return true;
177 }
178 }
179
180 // we support 2D and 3D in-memory arrays, but not 4D
181 assert(memory.size() <= 3);
182
183 return storage != memory;
184}
185
186static std::vector<AxisType> dimension_types(const File &file, const std::string &var_name,
187 std::shared_ptr<units::System> unit_system) {
188 std::vector<AxisType> result;
189 for (const auto &dimension : file.dimensions(var_name)) {
190 result.push_back(file.dimension_type(dimension, unit_system));
191 }
192 return result;
193}
194
195/*!
196 * Transpose data in `input`, putting results in `output`.
197 *
198 * We assume that both `input` and `output` hold prod(`count`) elements.
199 *
200 * The `output` array uses the Y,X,Z order (columns in Z are contiguous).
201 *
202 * The `input` array uses the ordering corresponding to `input_axes` (ordering present in
203 * an input file).
204 *
205 * The array `count` provides the size of a block in `input`, listing axes in the order of
206 * values of AxisType (T,X,Y,Z).
207 */
208static void transpose(const double *input, const std::vector<AxisType> &input_axes,
209 const std::array<int, 4> &count, double *output) {
210
211 // make a copy of `input_axes`:
212 auto axes = input_axes;
213
214 // Override the last axis if it is "unknown". This is needed to be able to read
215 // variables such as isochrone deposition times.
216 if (axes.back() == UNKNOWN_AXIS) {
217 axes.back() = Z_AXIS;
218 }
219
220 // validate `axes`:
221 int N = (int)axes.size();
222 for (int k = 0; k < N; ++k) {
223 if (axes[k] < 0 or axes[k] > 3) {
224 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "invalid `axes[%d]` = %d", k, axes[k]);
225 }
226 }
227
228 // delta[X_AXIS] is the change in the linear index corresponding to incrementing x in
229 // the `input` ordering. delta[Y_AXIS], delta[Z_AXIS] and delta[T_AXIS] correspond to
230 // changes in y, z, t.
231 std::vector<unsigned> delta = {1, 1, 1, 1}; // 4 to store steps for T,Y,X,Z axes
232 {
233 // compute changes in the linear index corresponding to incrementing one of the
234 // "spatial" indexes, in the order used in `input`:
235 std::vector<unsigned> tmp(N, 1);
236 for (int k = 0; k < N; ++k) {
237 for (int n = k + 1; n < N; ++n) {
238 tmp[k] *= count[axes[n]];
239 }
240 }
241 // re-arrange so that they are stored in the `T,X,Y,Z` order:
242 for (int k = 0; k < N; ++k) {
243 delta[axes[k]] = tmp[k];
244 }
245 }
246
247 // change in the linear index corresponding to incrementing x (`output` ordering):
248 unsigned delta_x = count[Z_AXIS];
249 // change in the linear index corresponding to incrementing y (`output` ordering):
250 unsigned delta_y = count[X_AXIS] * count[Z_AXIS];
251
252 // traverse in the memory storage order:
253 for (int y = 0; y < count[Y_AXIS]; ++y) {
254 for (int x = 0; x < count[X_AXIS]; ++x) {
255 for (int z = 0; z < count[Z_AXIS]; ++z) {
256 auto OUT = x * delta_x + y * delta_y + z * 1;
257 auto IN = x * delta[X_AXIS] + y * delta[Y_AXIS] + z * delta[Z_AXIS];
258
259 output[OUT] = input[IN];
260 }
261 }
262 }
263}
264
265/*!
266 * Check if some values in `buffer` match the _FillValue attribute and stop with an error
267 * message if such values are found.
268 */
269static void check_for_missing_values(const File &file, const std::string &variable_name,
270 double tolerance, const double *buffer, size_t buffer_length) {
271 auto attribute = file.read_double_attribute(variable_name, "_FillValue");
272 if (attribute.size() != 1) {
273 return;
274 }
275
276 // Check that all value in the local portion are finite, stop with an error message if
277 // not:
278 {
279 int failed = 0;
280 for (size_t k = 0; k < buffer_length; ++k) {
281 if (not std::isfinite(buffer[k])) {
282 failed = 1;
283 break;
284 }
285 }
286 int failed_global = 0;
287 MPI_Allreduce(&failed, &failed_global, 1, MPI_INT, MPI_MAX, file.com());
288 if (failed_global > 0) {
291 "Variable '%s' in '%s' contains values that are not finite (NaN or infinity)",
292 variable_name.c_str(), file.name().c_str());
293 }
294 }
295
296 // Check that values in the local portion don't match _FillValue:
297 {
298 int failed = 0;
299 double fill_value = attribute[0];
300 bool fill_value_is_finite = std::isfinite(fill_value);
301 for (size_t k = 0; k < buffer_length; ++k) {
302 if (fill_value_is_finite and fabs(buffer[k] - fill_value) < tolerance) {
303 failed = 1;
304 break;
305 }
306 }
307 int failed_global = 0;
308 MPI_Allreduce(&failed, &failed_global, 1, MPI_INT, MPI_MAX, file.com());
309 if (failed_global > 0) {
312 "Variable '%s' in '%s' contains values matching the _FillValue attribute",
313 variable_name.c_str(), file.name().c_str());
314 }
315 }
316}
317
319 std::vector<unsigned int> start;
320 std::vector<unsigned int> count;
321};
322
323
324/*!
325 * Assemble start and count arrays for use with I/O function calls.
326 *
327 * This function re-arranges provided `start` and `count` (listed in the order T,X,Y,Z) to
328 * get start and count in the order matching the one in `dim_types`.
329 */
330static StartCountInfo compute_start_and_count(std::vector<AxisType> &dim_types,
331 const std::array<int, 4> &start,
332 const std::array<int, 4> &count) {
333 auto ndims = dim_types.size();
334
335 // Resize output vectors:
336 StartCountInfo result;
337 result.start.resize(ndims);
338 result.count.resize(ndims);
339
340 // Assemble start and count:
341 for (unsigned int j = 0; j < ndims; j++) {
342 switch (dim_types[j]) {
343 case T_AXIS:
344 result.start[j] = start[T_AXIS];
345 result.count[j] = count[T_AXIS];
346 break;
347 case Y_AXIS:
348 result.start[j] = start[Y_AXIS];
349 result.count[j] = count[Y_AXIS];
350 break;
351 case X_AXIS:
352 result.start[j] = start[X_AXIS];
353 result.count[j] = count[X_AXIS];
354 break;
355 case EXP_ID_AXIS:
356 result.start[j] = 0;
357 result.count[j] = 1;
358 break;
359 default:
360 // Note: the "default" case is used to handle "3D" variables where the third axis is
361 // not a "Z" axis, i.e. dim_types[j] == UNKNOWN_AXIS. We use this to write
362 // deposition times in the isochrone tracking model, latitude and longitude bounds,
363 // etc. In all these cases the data for the "unknown" axis is input in the slot for
364 // the "Z" axis. (At least this matches the in-memory storage order.)
365 case Z_AXIS:
366 result.start[j] = start[Z_AXIS];
367 result.count[j] = count[Z_AXIS];
368 break;
369 }
370 }
371
372 return result;
373}
374
375//! \brief Read an array distributed according to the grid.
376static void read_distributed_array(const File &file, const std::string &variable_name,
377 std::shared_ptr<units::System> unit_system,
378 const std::array<int,4> &start,
379 const std::array<int,4> &count,
380 double *output) {
381 try {
382 auto dim_types = dimension_types(file, variable_name, unit_system);
383
384 auto sc = compute_start_and_count(dim_types, start, count);
385
386 auto size = count[X_AXIS] * count[Y_AXIS] * count[Z_AXIS];
387
388 if (transpose(dim_types)) {
389 std::vector<double> tmp(size);
390 file.read_variable(variable_name, sc.start, sc.count, tmp.data());
391 transpose(tmp.data(), dim_types, count, output);
392 } else {
393 file.read_variable(variable_name, sc.start, sc.count, output);
394 }
395
396 // Stop with an error message if some values match the _FillValue attribute:
397 check_for_missing_values(file, variable_name, 1e-12, output, size);
398
399 } catch (RuntimeError &e) {
400 e.add_context("reading variable '%s' from '%s'", variable_name.c_str(), file.name().c_str());
401 throw;
402 }
403}
404
405//! \brief Regrid from a NetCDF file into a distributed array `output`.
406/*!
407 - if `flag` is `CRITICAL` or `CRITICAL_FILL_MISSING`, stops if the
408 variable was not found in the input file
409 - if `flag` is one of `CRITICAL_FILL_MISSING` and
410 `OPTIONAL_FILL_MISSING`, replace _FillValue with `default_value`.
411 - sets `v` to `default_value` if `flag` is `OPTIONAL` and the
412 variable was not found in the input file
413 - uses the last record in the file
414*/
416 const Grid &target_grid,
417 const LocalInterpCtx &interp_context, const File &file,
418 const Logger &log,
419 double *output) {
420
421 auto var = file.find_variable(variable.get_name(), variable["standard_name"]);
422
423 const auto &profiling = target_grid.ctx()->profiling();
424
425 profiling.begin("io.regridding.read");
426 std::vector<double> buffer(interp_context.buffer_size());
427 read_distributed_array(file, var.name, variable.unit_system(), interp_context.start,
428 interp_context.count, buffer.data());
429 profiling.end("io.regridding.read");
430
431 // interpolate
432 profiling.begin("io.regridding.interpolate");
433 interpolate(target_grid.info(), interp_context, buffer.data(), output);
434 profiling.end("io.regridding.interpolate");
435
436 // Get the units string from the file and convert the units:
437 {
438 std::string input_units = file.read_text_attribute(var.name, "units");
439 std::string internal_units = variable["units"];
440
441 input_units = check_units(variable, input_units, log);
442
443 const size_t data_size = target_grid.xm() * target_grid.ym() * interp_context.z->n_output();
444
445 // Convert data:
446 units::Converter(variable.unit_system(), input_units, internal_units)
447 .convert_doubles(output, data_size);
448 }
449}
450
451std::vector<double> read_1d_variable(const File &file, const std::string &variable_name,
452 const std::string &units,
453 std::shared_ptr<units::System> unit_system) {
454
455
456 try {
457 if (not file.variable_exists(variable_name)) {
458 throw RuntimeError(PISM_ERROR_LOCATION, "variable " + variable_name + " is missing");
459 }
460
461 auto dims = file.dimensions(variable_name);
462 if (dims.size() != 1) {
464 "variable '%s' in '%s' should to have 1 dimension (got %d)",
465 variable_name.c_str(), file.name().c_str(), (int)dims.size());
466 }
467
468 const auto &dimension_name = dims[0];
469
470 unsigned int length = file.dimension_length(dimension_name);
471 if (length == 0) {
472 throw RuntimeError(PISM_ERROR_LOCATION, "dimension " + dimension_name + " has length zero");
473 }
474
475 units::Unit internal_units(unit_system, units), input_units(unit_system, "1");
476
477 auto input_units_string = file.read_text_attribute(variable_name, "units");
478
479 if (not input_units_string.empty()) {
480 input_units = units::Unit(unit_system, input_units_string);
481 } else {
484 "variable '%s' does not have the units attribute", variable_name.c_str());
485 }
486
487 std::vector<double> result(length); // memory allocation happens here
488
489 file.read_variable(variable_name, { 0 }, { length }, result.data());
490
491 units::Converter(input_units, internal_units).convert_doubles(result.data(), result.size());
492
493 return result;
494 } catch (RuntimeError &e) {
495 e.add_context("reading 1D variable '%s' from '%s'", variable_name.c_str(),
496 file.name().c_str());
497 throw;
498 }
499}
500
501std::vector<double> read_timeseries_variable(const File &file, const std::string &variable_name,
502 const std::string &units,
503 std::shared_ptr<units::System> unit_system,
504 size_t start, size_t count) {
505
506 auto input_units_string = file.read_text_attribute(variable_name, "units");
507
508 if (input_units_string.empty()) {
510 "variable '%s' does not have the units attribute",
511 variable_name.c_str());
512 }
513
514 std::vector<double> result(count); // memory allocation happens here
515 // read from a file
516 {
517 std::vector<unsigned int> start_{}, count_{};
518
519 for (const auto &dim : file.dimensions(variable_name)) {
520 if (file.dimension_type(dim, unit_system) == T_AXIS) {
521 start_.push_back(start);
522 count_.push_back(count);
523 } else {
524 start_.push_back(0);
525 count_.push_back(1);
526 }
527 }
528 file.read_variable(variable_name, start_, count_, result.data());
529 }
530
531 // convert units
532 {
533 auto input_units = units::Unit(unit_system, input_units_string);
534
535 units::Unit internal_units(unit_system, units);
536 units::Converter(input_units, internal_units).convert_doubles(result.data(), result.size());
537 }
538
539 return result;
540}
541
542
543std::vector<double> read_bounds(const File &file, const std::string &bounds_variable_name,
544 const std::string &internal_units,
545 std::shared_ptr<units::System> unit_system) {
546
547 try {
548 units::Unit internal(unit_system, internal_units);
549
550 // Find the variable:
551 if (not file.variable_exists(bounds_variable_name)) {
552 throw RuntimeError(PISM_ERROR_LOCATION, "variable " + bounds_variable_name + " is missing");
553 }
554
555 auto dims = file.dimensions(bounds_variable_name);
556
557 if (dims.size() != 2) {
558 throw RuntimeError(PISM_ERROR_LOCATION, "variable " + bounds_variable_name + " has to have two dimensions");
559 }
560
561 const auto &dimension_name = dims[0];
562 const auto &bounds_dimension_name = dims[1];
563
564 // Check that we have 2 vertices (interval end-points) per record.
565 size_t length = file.dimension_length(bounds_dimension_name);
566 if (length != 2) {
568 "time-bounds variable " + bounds_variable_name + " has to have exactly 2 bounds per time record");
569 }
570
571 // Get the number of time records.
572 length = file.dimension_length(dimension_name);
573 if (length <= 0) {
574 throw RuntimeError(PISM_ERROR_LOCATION, "dimension " + dimension_name + " has length zero");
575 }
576
577 // Find the corresponding coordinate variable. (We get units from the 'time'
578 // variable, because according to CF-1.5 section 7.1 a "boundary variable"
579 // may not have metadata set.)
580 if (not file.variable_exists(dimension_name)) {
582 "coordinate variable " + dimension_name + " is missing");
583 }
584
585 units::Unit input_units(unit_system, "1");
586
587 std::string input_units_string = file.read_text_attribute(dimension_name, "units");
588 if (input_units_string.empty()) {
590 "variable '%s' does not have the units attribute",
591 dimension_name.c_str());
592 }
593
594 input_units = units::Unit(unit_system, input_units_string);
595
596
597 std::vector<double> result(length * 2); // memory allocation happens here
598
599 file.read_variable(bounds_variable_name, {0, 0}, {(unsigned)length, 2}, result.data());
600
601 units::Converter(input_units, internal).convert_doubles(result.data(), result.size());
602
603 return result;
604 } catch (RuntimeError &e) {
605 e.add_context("reading bounds variable '%s' from '%s'", bounds_variable_name.c_str(),
606 file.name().c_str());
607 throw;
608 }
609}
610
611/*!
612 * Reads and validates times and time bounds.
613 *
614 * This requires careful handling of unit conversion because UDUNITS always uses the
615 * Gregorian calendar when computing offsets between dates of the form "X since Y-M-D".
616 */
617TimeInfo read_time_info(const units::Unit &time_units, const std::string &calendar, const File &file,
618 const std::string &time_name) {
619 TimeInfo result;
620 auto unit_system = time_units.system();
621
622 auto input_calendar = file.read_text_attribute(time_name, "calendar");
623
624 // make sure the input file uses a matching calendar
625 if (calendar != input_calendar) {
628 "cannot read time info from a file: calendar in '%s' (%s) does not match PISM's calendar (%s)",
629 file.name().c_str(), input_calendar.c_str(), calendar.c_str());
630 }
631
632 // time offset
633 std::string input_reference_date;
634 double time_offset = 0;
635 {
636 auto input_time_units = file.read_text_attribute(time_name, "units");
637
638 size_t position = input_time_units.find("since");
639
640 if (position != std::string::npos) {
641 input_reference_date = string_strip(input_time_units.substr(position + strlen("since")));
642 }
643
644 time_offset = time_units.time(units::DateTime(input_reference_date, calendar));
645 }
646
647 // read time
648 {
649 auto units = "seconds since " + input_reference_date;
650 // read time, converting to seconds since the input reference date:
651 result.times =
652 io::read_1d_variable(file, time_name, units, unit_system);
653
654 if (not is_increasing(result.times)) {
655 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "times have to be strictly increasing");
656 }
657
658 // apply time_offset
659 size_t N = result.times.size();
660 for (size_t k = 0; k < N; ++k) {
661 result.times[k] += time_offset;
662 }
663 }
664
665 // Read time bounds
666 {
667 auto N = result.times.size();
668
669 std::string time_bounds_name = file.read_text_attribute(time_name, "bounds");
670 auto units = "seconds since " + input_reference_date;
671
672 if (time_bounds_name.empty()) {
673 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "please provide time bounds for '%s'",
674 time_name.c_str());
675 }
676
677 result.bounds = io::read_bounds(file, time_bounds_name, units, unit_system);
678
679 if (2 * N != result.bounds.size()) {
681 "each time record has to have 2 bounds");
682 }
683
684 // apply time_offset
685 for (size_t k = 0; k < N; ++k) {
686 result.bounds[2 * k + 0] += time_offset;
687 result.bounds[2 * k + 1] += time_offset;
688 }
689
690 // validate bounds
691 for (size_t k = 0; k < N; ++k) {
692 if (not (result.times[k] >= result.bounds[2 * k + 0] and
693 result.times[k] <= result.bounds[2 * k + 1])) {
695 "each time has to be contained in its time interval");
696 }
697 }
698 } // end of the block reading time bounds
699
700 return result;
701}
702
703//! Read a variable from a file into an array `output`.
704/*! This also converts data from input units to internal units if needed.
705 */
706void read_spatial_variable(const VariableMetadata &variable, const Grid &grid,
707 const File &file, unsigned int time, double *output) {
708
709 const auto &log = *grid.ctx()->log();
710
711 // Find the variable:
712 auto var = file.find_variable(variable.get_name(), variable["standard_name"]);
713
714 if (not var.exists) {
716 PISM_ERROR_LOCATION, "Can't find '%s' (%s) in '%s'.", variable.get_name().c_str(),
717 variable.get_string("standard_name").c_str(), file.name().c_str());
718 }
719
720 // Sanity check: the variable in an input file should have the expected
721 // number of spatial dimensions.
722 {
723 // Set of spatial dimensions for this field:
724 std::set<int> axes{ X_AXIS, Y_AXIS };
725
726 for (const auto &dim : variable.dimensions()) {
727 if (axis_type_from_string(dim["axis"]) == Z_AXIS) {
728 axes.insert(Z_AXIS);
729 break;
730 }
731 }
732
733 int input_spatial_dim_count = 0; // number of spatial dimensions (input file)
734 size_t matching_dim_count = 0; // number of matching dimensions
735
736 auto input_dims = file.dimensions(var.name);
737 for (const auto &d : input_dims) {
738 auto dim_type = file.dimension_type(d, variable.unit_system());
739
740 if (dim_type == X_AXIS or dim_type == Y_AXIS or dim_type == Z_AXIS) {
741 ++input_spatial_dim_count;
742 }
743
744 if (axes.find(dim_type) != axes.end()) {
745 ++matching_dim_count;
746 }
747 }
748
749 if (axes.size() != matching_dim_count) {
750
751 // Print the error message and stop:
754 "found the %dD variable %s (%s) in '%s' while trying to read\n"
755 "'%s' ('%s'), which is %d-dimensional.",
756 input_spatial_dim_count, var.name.c_str(), join(input_dims, ",").c_str(),
757 file.name().c_str(), variable.get_name().c_str(),
758 variable.get_string("long_name").c_str(), static_cast<int>(axes.size()));
759 }
760 }
761
762 // make sure we have at least one level
763 size_t nlevels = std::max(variable.levels().size(), (size_t)1);
764
765 read_distributed_array(file, var.name, variable.unit_system(),
766 {(int)time, grid.xs(), grid.ys(), 0},
767 {1, grid.xm(), grid.ym(), (int)nlevels},
768 output);
769
770 auto input_units = file.read_text_attribute(var.name, "units");
771 const std::string &internal_units = variable["units"];
772
773 input_units = check_units(variable, input_units, log);
774
775 // Convert data:
776 size_t size = grid.xm() * grid.ym() * nlevels;
777
778 units::Converter(variable.unit_system(), input_units, internal_units)
779 .convert_doubles(output, size);
780}
781
782/*!
783 * Check the overlap of the input grid and the internal grid.
784 *
785 * Set `allow_extrapolation` to `true` to "extend" the vertical grid during "bootstrapping".
786 */
788 const grid::DistributedGridInfo &internal,
789 const std::vector<double> &z_internal) {
790
791 // Grid spacing (assume that the grid is equally-spaced) and the
792 // extent of the domain. To compute the extent of the domain, assume
793 // that the grid is cell-centered, so edge of the domain is half of
794 // the grid spacing away from grid points at the edge.
795 //
796 // Note that x_min is not the same as internal.x(0).
797 const double x_min = internal.x0 - internal.Lx, x_max = internal.x0 + internal.Lx,
798 y_min = internal.y0 - internal.Ly, y_max = internal.y0 + internal.Ly,
799 input_x_min = input.x0 - input.Lx, input_x_max = input.x0 + input.Lx,
800 input_y_min = input.y0 - input.Ly, input_y_max = input.y0 + input.Ly;
801
802 // tolerance (one micron)
803 double eps = 1e-6;
804
805 // horizontal grid extent
806 if (not(x_min >= input_x_min - eps and x_max <= input_x_max + eps and
807 y_min >= input_y_min - eps and y_max <= input_y_max + eps)) {
810 "PISM's computational domain is not a subset of the domain in '%s'\n"
811 "PISM grid: x: [%3.3f, %3.3f] y: [%3.3f, %3.3f] meters\n"
812 "input file grid: x: [%3.3f, %3.3f] y: [%3.3f, %3.3f] meters",
813 input.filename.c_str(), x_min, x_max, y_min, y_max, input_x_min, input_x_max, input_y_min,
814 input_y_max);
815 }
816
817 if (z_internal.empty()) {
819 "Internal vertical grid has 0 levels. This should never happen.");
820 }
821
822 if (z_internal.size() == 1 and input.z.size() > 1) {
823 // internal field is 2D or 3D with one level, input variable is 3D with more than one
824 // vertical level
826 "trying to read in a 2D field but the input file %s contains\n"
827 "a 3D field with %d levels",
828 input.filename.c_str(), static_cast<int>(input.z.size()));
829 }
830
831 if (z_internal.size() > 1 and input.z.size() <= 1) {
832 // internal field is 3D with more than one vertical level, input variable is 2D or 3D
833 // with 1 level
835 "trying to read in a 3D field but the input file %s contains\n"
836 "a 2D field",
837 input.filename.c_str());
838 }
839
840 if (z_internal.size() > 1 and (not input.z.empty())) {
841 // both internal field and input variable are 3D: check vertical grid extent
842 // Note: in PISM 2D fields have one vertical level (z = 0).
843 const double input_z_min = vector_min(input.z), input_z_max = vector_max(input.z),
844 z_min = z_internal.front(), z_max = z_internal.back();
845
846 if (not(z_min >= input_z_min - eps and z_max <= input_z_max + eps)) {
849 "PISM's computational domain is not a subset of the domain in '%s'\n"
850 "PISM grid: z: [%3.3f, %3.3f] meters\n"
851 "input file grid: z: [%3.3f, %3.3f] meters",
852 input.filename.c_str(), z_min, z_max, input_z_min, input_z_max);
853 }
854 }
855}
856
857/*! @brief Check that x, y, and z coordinates of the input grid are strictly increasing. */
859 const grid::DistributedGridInfo &internal_grid,
860 const std::vector<double> &internal_z_levels, bool allow_extrapolation) {
861 if (not is_increasing(input_grid.x)) {
863 "input x coordinate has to be strictly increasing");
864 }
865
866 if (not is_increasing(input_grid.y)) {
868 "input y coordinate has to be strictly increasing");
869 }
870
871 if (not is_increasing(input_grid.z)) {
873 "input vertical grid has to be strictly increasing");
874 }
875
876 if (not allow_extrapolation) {
877 check_grid_overlap(input_grid, internal_grid, internal_z_levels);
878 }
879}
880
881/*!
882 * Return the name of the time dimension corresponding to a NetCDF variable.
883 *
884 * Returns an empty string if this variable is time-independent.
885 */
886std::string time_dimension(units::System::Ptr unit_system,
887 const File &file,
888 const std::string &variable_name) {
889
890 auto dims = file.dimensions(variable_name);
891
892 for (const auto &d : dims) {
893 if (file.dimension_type(d, unit_system) == T_AXIS) {
894 return d;
895 }
896 }
897
898 return "";
899}
900
902 const std::string &variable_name,
903 std::shared_ptr<units::System> unit_system) {
904
905 VariableMetadata variable(variable_name, unit_system);
906
907 try {
908
909 if (not file.variable_exists(variable_name)) {
910 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "variable '%s' is missing", variable_name.c_str());
911 }
912
913 unsigned int nattrs = file.nattributes(variable_name);
914
915 for (unsigned int j = 0; j < nattrs; ++j) {
916 std::string attribute_name = file.attribute_name(variable_name, j);
917 io::Type nctype = file.attribute_type(variable_name, attribute_name);
918
919 if (nctype == PISM_CHAR) {
920 variable[attribute_name] = file.read_text_attribute(variable_name,
921 attribute_name);
922 } else {
923 variable[attribute_name] = file.read_double_attribute(variable_name,
924 attribute_name);
925 }
926 } // end of for (int j = 0; j < nattrs; ++j)
927 } catch (RuntimeError &e) {
928 e.add_context("reading attributes of variable '%s' from '%s'",
929 variable_name.c_str(), file.name().c_str());
930 throw;
931 }
932 return variable;
933}
934
935} // namespace io
936} // namespace pism
void read_variable(const std::string &variable_name, const std::vector< unsigned int > &start, const std::vector< unsigned int > &count, double *ip) const
Definition File.cc:704
std::string attribute_name(const std::string &var_name, unsigned int n) const
Definition File.cc:680
AxisType dimension_type(const std::string &name, units::System::Ptr unit_system) const
Get the "type" of a dimension.
Definition File.cc:460
VariableLookupData find_variable(const std::string &short_name, const std::string &std_name) const
Find a variable using its standard name and/or short name.
Definition File.cc:328
MPI_Comm com() const
Definition File.cc:185
bool variable_exists(const std::string &short_name) const
Checks if a variable exists.
Definition File.cc:378
io::Type attribute_type(const std::string &var_name, const std::string &att_name) const
Definition File.cc:692
std::string name() const
Definition File.cc:274
std::vector< double > read_double_attribute(const std::string &var_name, const std::string &att_name) const
Get a double attribute.
Definition File.cc:622
unsigned int nattributes(const std::string &var_name) const
Definition File.cc:668
unsigned int dimension_length(const std::string &name) const
Get the length of a dimension.
Definition File.cc:420
std::vector< std::string > dimensions(const std::string &variable_name) const
Definition File.cc:390
std::string read_text_attribute(const std::string &var_name, const std::string &att_name) const
Get a text attribute.
Definition File.cc:650
High-level PISM I/O class.
Definition File.hh:57
const grid::DistributedGridInfo & info() const
Definition Grid.cc:511
std::shared_ptr< const Context > ctx() const
Return execution context this grid corresponds to.
Definition Grid.cc:507
int xm() const
Width of this processor's sub-domain.
Definition Grid.cc:569
int ym() const
Width of this processor's sub-domain.
Definition Grid.cc:574
Describes the PISM grid and the distribution of data across processors.
Definition Grid.hh:285
std::array< int, 4 > count
std::shared_ptr< Interpolation1D > z
std::array< int, 4 > start
std::shared_ptr< Interpolation1D > x
std::shared_ptr< Interpolation1D > y
void message(int threshold, const char format[],...) const __attribute__((format(printf
Print a message to the log.
Definition Logger.cc:50
A basic logging class.
Definition Logger.hh:40
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
const std::vector< double > & levels() const
std::string get_string(const std::string &name) const
Get a string attribute.
std::shared_ptr< units::System > unit_system() const
std::string get_name() const
std::vector< DimensionMetadata > dimensions() const
double Lx
domain half-width in the X direction
Definition GridInfo.hh:46
std::vector< double > y
y coordinates
Definition GridInfo.hh:53
double y0
y-coordinate of the domain center
Definition GridInfo.hh:44
double x0
x-coordinate of the domain center
Definition GridInfo.hh:42
std::vector< double > x
x coordinates
Definition GridInfo.hh:51
double Ly
domain half-width in the Y direction
Definition GridInfo.hh:48
std::string filename
Definition Grid.hh:70
std::vector< double > z
z coordinates: input grids may be 3-dimensional
Definition Grid.hh:82
void convert_doubles(double *data, size_t length) const
Definition Units.cc:262
std::shared_ptr< System > Ptr
Definition Units.hh:47
double time(const DateTime &d) const
Definition Units.cc:180
System::Ptr system() const
Definition Units.cc:160
#define PISM_ERROR_LOCATION
#define n
Definition exactTestM.c:37
std::vector< double > read_timeseries_variable(const File &file, const std::string &variable_name, const std::string &units, std::shared_ptr< units::System > unit_system, size_t start, size_t count)
void read_spatial_variable(const VariableMetadata &variable, const Grid &grid, const File &file, unsigned int time, double *output)
Read a variable from a file into an array output.
static StartCountInfo compute_start_and_count(std::vector< AxisType > &dim_types, const std::array< int, 4 > &start, const std::array< int, 4 > &count)
VariableMetadata read_attributes(const File &file, const std::string &variable_name, std::shared_ptr< units::System > unit_system)
void check_input_grid(const grid::InputGridInfo &input_grid, const grid::DistributedGridInfo &internal_grid, const std::vector< double > &internal_z_levels, bool allow_extrapolation)
Check that x, y, and z coordinates of the input grid are strictly increasing.
std::vector< double > read_1d_variable(const File &file, const std::string &variable_name, const std::string &units, std::shared_ptr< units::System > unit_system)
@ PISM_CHAR
Definition IO_Flags.hh:49
static void read_distributed_array(const File &file, const std::string &variable_name, std::shared_ptr< units::System > unit_system, const std::array< int, 4 > &start, const std::array< int, 4 > &count, double *output)
Read an array distributed according to the grid.
static std::string check_units(const VariableMetadata &variable, const std::string &input_units, const Logger &log)
static void check_grid_overlap(const grid::InputGridInfo &input, const grid::DistributedGridInfo &internal, const std::vector< double > &z_internal)
static void interpolate(const grid::DistributedGridInfo &grid, const LocalInterpCtx &lic, double const *input_array, double *output_array)
Bi-(or tri-)linear interpolation.
static void check_for_missing_values(const File &file, const std::string &variable_name, double tolerance, const double *buffer, size_t buffer_length)
void regrid_spatial_variable(const VariableMetadata &variable, const Grid &target_grid, const LocalInterpCtx &interp_context, const File &file, const Logger &log, double *output)
Regrid from a NetCDF file into a distributed array output.
std::vector< double > read_bounds(const File &file, const std::string &bounds_variable_name, const std::string &internal_units, std::shared_ptr< units::System > unit_system)
std::string time_dimension(units::System::Ptr unit_system, const File &file, const std::string &variable_name)
TimeInfo read_time_info(const units::Unit &time_units, const std::string &calendar, const File &file, const std::string &time_name)
static bool transpose(std::vector< AxisType > dimension_types)
static std::vector< AxisType > dimension_types(const File &file, const std::string &var_name, std::shared_ptr< units::System > unit_system)
bool is_increasing(const std::vector< double > &a)
Checks if a vector of doubles is strictly increasing.
@ UNKNOWN_AXIS
Definition IO_Flags.hh:34
@ T_AXIS
Definition IO_Flags.hh:34
@ X_AXIS
Definition IO_Flags.hh:34
@ EXP_ID_AXIS
Definition IO_Flags.hh:34
@ Z_AXIS
Definition IO_Flags.hh:34
@ Y_AXIS
Definition IO_Flags.hh:34
static const double k
Definition exactTestP.cc:42
double vector_min(const std::vector< double > &input)
std::string string_strip(const std::string &input)
static std::string calendar(const File *input_file, const Config &config, const Logger &log)
Definition Time.cc:158
std::string join(const std::vector< std::string > &strings, const std::string &separator)
Concatenate strings, inserting separator between elements.
AxisType axis_type_from_string(const std::string &input)
Definition File.cc:436
double vector_max(const std::vector< double > &input)
std::vector< unsigned int > start
std::vector< unsigned int > count
std::vector< double > times
Definition io_helpers.hh:91
std::vector< double > bounds
Definition io_helpers.hh:91
int count
Definition test_cube.c:16