PISM, A Parallel Ice Sheet Model  stable v2.1-1-g6902d5502 committed by Ed Bueler on 2023-12-20 08:38:27 -0800
io_helpers.cc
Go to the documentation of this file.
1 /* Copyright (C) 2015--2023 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 <cassert>
21 #include <cmath> // isfinite
22 #include <cstddef>
23 #include <memory>
24 #include <array>
25 #include <vector>
26 
27 #include "pism/util/ConfigInterface.hh"
28 #include "pism/util/Context.hh"
29 #include "pism/util/Grid.hh"
30 #include "pism/util/Logger.hh"
31 #include "pism/util/Profiling.hh"
32 #include "pism/util/Time.hh"
33 #include "pism/util/VariableMetadata.hh"
34 #include "pism/util/error_handling.hh"
35 #include "pism/util/interpolation.hh"
36 #include "pism/util/io/File.hh"
37 #include "pism/util/io/IO_Flags.hh"
38 #include "pism/util/io/LocalInterpCtx.hh"
39 #include "pism/util/io/io_helpers.hh"
40 #include "pism/util/pism_utilities.hh"
41 #include "pism/util/projection.hh"
42 
43 namespace pism {
44 namespace io {
45 
46 //! \brief Bi-(or tri-)linear interpolation.
47 /*!
48  * This is the interpolation code itself.
49  *
50  * Note that its inputs are (essentially)
51  * - the definition of the input grid
52  * - the definition of the output grid
53  * - input array (lic->buffer)
54  * - output array (double *output_array)
55  *
56  * The `output_array` is expected to be big enough to contain
57  * `grid.xm()*`grid.ym()*length(zlevels_out)` numbers.
58  *
59  * We should be able to switch to using an external interpolation library
60  * fairly easily...
61  */
62 static void regrid(const Grid &grid, const LocalInterpCtx &lic, double const *input_array,
63  double *output_array) {
64  // We'll work with the raw storage here so that the array we are filling is
65  // indexed the same way as the buffer we are pulling from (input_array)
66 
67  const int X = X_AXIS,
68  Z = Z_AXIS; // indices, just for clarity
69 
70  unsigned int nlevels = lic.z->n_output();
71 
72  // array sizes for mapping from logical to "flat" indices
73  int x_count = lic.count[X], z_count = lic.count[Z];
74 
75  for (auto p = grid.points(); p; p.next()) {
76  const int i_global = p.i(), j_global = p.j();
77 
78  const int i = i_global - grid.xs(), j = j_global - grid.ys();
79 
80  // Indices of neighboring points.
81  const int X_m = lic.x->left(i), X_p = lic.x->right(i), Y_m = lic.y->left(j),
82  Y_p = lic.y->right(j);
83 
84  for (unsigned int k = 0; k < nlevels; k++) {
85 
86  double a_mm = 0.0, a_mp = 0.0, a_pm = 0.0, a_pp = 0.0;
87 
88  if (nlevels > 1) {
89  const int Z_m = lic.z->left(k), Z_p = lic.z->right(k);
90 
91  const double alpha_z = lic.z->alpha(k);
92 
93  // We pretend that there are always 8 neighbors (4 in the map plane,
94  // 2 vertical levels). And compute the indices into the input_array for
95  // those neighbors.
96  const int mmm = (Y_m * x_count + X_m) * z_count + Z_m,
97  mmp = (Y_m * x_count + X_m) * z_count + Z_p,
98  mpm = (Y_m * x_count + X_p) * z_count + Z_m,
99  mpp = (Y_m * x_count + X_p) * z_count + Z_p,
100  pmm = (Y_p * x_count + X_m) * z_count + Z_m,
101  pmp = (Y_p * x_count + X_m) * z_count + Z_p,
102  ppm = (Y_p * x_count + X_p) * z_count + Z_m,
103  ppp = (Y_p * x_count + X_p) * z_count + Z_p;
104 
105  // linear interpolation in the z-direction
106  a_mm = input_array[mmm] * (1.0 - alpha_z) + input_array[mmp] * alpha_z;
107  a_mp = input_array[mpm] * (1.0 - alpha_z) + input_array[mpp] * alpha_z;
108  a_pm = input_array[pmm] * (1.0 - alpha_z) + input_array[pmp] * alpha_z;
109  a_pp = input_array[ppm] * (1.0 - alpha_z) + input_array[ppp] * alpha_z;
110  } else {
111  // we don't need to interpolate vertically for the 2-D case
112  a_mm = input_array[Y_m * x_count + X_m];
113  a_mp = input_array[Y_m * x_count + X_p];
114  a_pm = input_array[Y_p * x_count + X_m];
115  a_pp = input_array[Y_p * x_count + X_p];
116  }
117 
118  // interpolation coefficient in the x direction
119  const double x_alpha = lic.x->alpha(i);
120  // interpolation coefficient in the y direction
121  const double y_alpha = lic.y->alpha(j);
122 
123  // interpolate in x direction
124  const double a_m = a_mm * (1.0 - x_alpha) + a_mp * x_alpha;
125  const double a_p = a_pm * (1.0 - x_alpha) + a_pp * x_alpha;
126 
127  int index = (j * grid.xm() + i) * nlevels + k;
128 
129  // index into the new array and interpolate in x direction
130  output_array[index] = a_m * (1.0 - y_alpha) + a_p * y_alpha;
131  // done with the point at (x,y,z)
132  }
133  }
134 }
135 
137  std::vector<unsigned int> start;
138  std::vector<unsigned int> count;
139  std::vector<unsigned int> imap;
140 };
141 
142 static StartCountInfo compute_start_and_count(std::vector<AxisType> &dim_types,
143  std::array<int, 4> start_in,
144  std::array<int, 4> count_in) {
145 
146  auto x_start = start_in[X_AXIS];
147  auto x_count = count_in[X_AXIS];
148  auto y_start = start_in[Y_AXIS];
149  auto y_count = count_in[Y_AXIS];
150  auto z_start = start_in[Z_AXIS];
151  auto z_count = count_in[Z_AXIS];
152 
153  StartCountInfo result;
154 
155  auto ndims = dim_types.size();
156 
157  // Resize output vectors:
158  result.start.resize(ndims);
159  result.count.resize(ndims);
160  result.imap.resize(ndims);
161 
162  // Assemble start, count and imap:
163  for (unsigned int j = 0; j < ndims; j++) {
164  AxisType dimtype = dim_types[j];
165 
166  switch (dimtype) {
167  case T_AXIS:
168  result.start[j] = start_in[T_AXIS];
169  result.count[j] = count_in[T_AXIS];
170  result.imap[j] = x_count * y_count * z_count;
171  break;
172  case Y_AXIS:
173  result.start[j] = y_start;
174  result.count[j] = y_count;
175  result.imap[j] = x_count * z_count;
176  break;
177  case X_AXIS:
178  result.start[j] = x_start;
179  result.count[j] = x_count;
180  result.imap[j] = z_count;
181  break;
182  default:
183  case Z_AXIS:
184  result.start[j] = z_start;
185  result.count[j] = z_count;
186  result.imap[j] = 1;
187  break;
188  }
189  }
190 
191  return result;
192 }
193 
194 //! \brief Define a dimension \b and the associated coordinate variable. Set attributes.
195 void define_dimension(const File &file, unsigned long int length,
196  const VariableMetadata &metadata) {
197  std::string name = metadata.get_name();
198  try {
199  file.define_dimension(name, length);
200 
201  file.define_variable(name, PISM_DOUBLE, { name });
202 
203  write_attributes(file, metadata, PISM_DOUBLE);
204 
205  } catch (RuntimeError &e) {
206  e.add_context("defining dimension '%s' in '%s'", name.c_str(), file.filename().c_str());
207  throw;
208  }
209 }
210 
211 
212 //! Prepare a file for output.
213 void define_time(const File &file, const Context &ctx) {
214  const Time &time = *ctx.time();
215  const Config &config = *ctx.config();
216 
217  define_time(file, config.get_string("time.dimension_name"), time.calendar(), time.units_string(),
218  ctx.unit_system());
219 }
220 
221 /*!
222  * Define a time dimension and the corresponding coordinate variable. Does nothing if the time
223  * variable is already present.
224  */
225 void define_time(const File &file, const std::string &name, const std::string &calendar,
226  const std::string &units, units::System::Ptr unit_system) {
227  try {
228  if (file.find_variable(name)) {
229  return;
230  }
231 
232  // time
233  VariableMetadata time(name, unit_system);
234  time["long_name"] = "time";
235  time["calendar"] = calendar;
236  time["units"] = units;
237  time["axis"] = "T";
238 
239  define_dimension(file, PISM_UNLIMITED, time);
240  } catch (RuntimeError &e) {
241  e.add_context("defining the time dimension in \"" + file.filename() + "\"");
242  throw;
243  }
244 }
245 
246 //! Prepare a file for output.
247 void append_time(const File &file, const Config &config, double time_seconds) {
248  append_time(file, config.get_string("time.dimension_name"), time_seconds);
249 }
250 
251 //! \brief Append to the time dimension.
252 void append_time(const File &file, const std::string &name, double value) {
253  try {
254  unsigned int start = file.dimension_length(name);
255 
256  file.write_variable(name, { start }, { 1 }, &value);
257 
258  // PIO's I/O type PnetCDF requires this
259  file.sync();
260  } catch (RuntimeError &e) {
261  e.add_context("appending to the time dimension in \"" + file.filename() + "\"");
262  throw;
263  }
264 }
265 
266 //! \brief Define dimensions a variable depends on.
267 static void define_dimensions(const SpatialVariableMetadata &var, const Grid &grid,
268  const File &file) {
269 
270  // x
271  std::string x_name = var.x().get_name();
272  if (not file.find_dimension(x_name)) {
273  define_dimension(file, grid.Mx(), var.x());
274  file.write_attribute(x_name, "spacing_meters", PISM_DOUBLE, { grid.x(1) - grid.x(0) });
275  file.write_attribute(x_name, "not_written", PISM_INT, { 1.0 });
276  }
277 
278  // y
279  std::string y_name = var.y().get_name();
280  if (not file.find_dimension(y_name)) {
281  define_dimension(file, grid.My(), var.y());
282  file.write_attribute(y_name, "spacing_meters", PISM_DOUBLE, { grid.y(1) - grid.y(0) });
283  file.write_attribute(y_name, "not_written", PISM_INT, { 1.0 });
284  }
285 
286  // z
287  std::string z_name = var.z().get_name();
288  if (not z_name.empty()) {
289  if (not file.find_dimension(z_name)) {
290  const std::vector<double> &levels = var.levels();
291  // make sure we have at least one level
292  unsigned int nlevels = std::max(levels.size(), (size_t)1);
293  define_dimension(file, nlevels, var.z());
294  file.write_attribute(z_name, "not_written", PISM_INT, { 1.0 });
295 
296  bool spatial_dim = not var.z().get_string("axis").empty();
297 
298  if (nlevels > 1 and spatial_dim) {
299  double dz_max = levels[1] - levels[0];
300  double dz_min = levels.back() - levels.front();
301 
302  for (unsigned int k = 0; k < nlevels - 1; ++k) {
303  double dz = levels[k + 1] - levels[k];
304  dz_max = std::max(dz_max, dz);
305  dz_min = std::min(dz_min, dz);
306  }
307 
308  file.write_attribute(z_name, "spacing_min_meters", PISM_DOUBLE, { dz_min });
309  file.write_attribute(z_name, "spacing_max_meters", PISM_DOUBLE, { dz_max });
310  }
311  }
312  }
313 }
314 
315 static void write_dimension_data(const File &file, const std::string &name,
316  const std::vector<double> &data) {
317  bool written = file.attribute_type(name, "not_written") == PISM_NAT;
318  if (not written) {
319  file.write_variable(name, { 0 }, { (unsigned int)data.size() }, data.data());
320  file.redef();
321  file.remove_attribute(name, "not_written");
322  }
323 }
324 
325 void write_dimensions(const SpatialVariableMetadata &var, const Grid &grid, const File &file) {
326  // x
327  std::string x_name = var.x().get_name();
328  if (file.find_dimension(x_name)) {
329  write_dimension_data(file, x_name, grid.x());
330  }
331 
332  // y
333  std::string y_name = var.y().get_name();
334  if (file.find_dimension(y_name)) {
335  write_dimension_data(file, y_name, grid.y());
336  }
337 
338  // z
339  std::string z_name = var.z().get_name();
340  if (file.find_dimension(z_name)) {
341  write_dimension_data(file, z_name, var.levels());
342  }
343 }
344 
345 /**
346  * Check if the storage order of a variable in the current file
347  * matches the memory storage order used by PISM.
348  *
349  * @param[in] file input file
350  * @param var_name name of the variable to check
351  * @returns false if storage orders match, true otherwise
352  */
353 static bool use_transposed_io(std::vector<AxisType> dimension_types) {
354 
355  std::vector<AxisType> storage, memory = { Y_AXIS, X_AXIS };
356 
357  bool first = true;
358  for (auto dimtype : dimension_types) {
359 
360  if (first && dimtype == T_AXIS) {
361  // ignore the time dimension, but only if it is the first
362  // dimension in the list
363  first = false;
364  continue;
365  }
366 
367  if (dimtype == X_AXIS || dimtype == Y_AXIS) {
368  storage.push_back(dimtype);
369  } else if (dimtype == Z_AXIS) {
370  memory.push_back(dimtype); // now memory = {Y_AXIS, X_AXIS, Z_AXIS}
371  // assume that this variable has only one Z_AXIS in the file
372  storage.push_back(dimtype);
373  } else {
374  // an UNKNOWN_AXIS or T_AXIS at index != 0 was found, use transposed I/O
375  return true;
376  }
377  }
378 
379  // we support 2D and 3D in-memory arrays, but not 4D
380  assert(memory.size() <= 3);
381 
382  return storage != memory;
383 }
384 
385 static std::vector<AxisType> dimension_types(const File &file, const std::string &var_name,
386  std::shared_ptr<units::System> unit_system) {
387  std::vector<AxisType> result;
388  for (const auto &dimension : file.dimensions(var_name)) {
389  result.push_back(file.dimension_type(dimension, unit_system));
390  }
391  return result;
392 }
393 
394 //! \brief Read an array distributed according to the grid.
395 static void read_distributed_array(const File &file, const Grid &grid, const std::string &var_name,
396  unsigned int z_count, unsigned int t_start, double *output) {
397  try {
398  auto dim_types = dimension_types(file, var_name, grid.ctx()->unit_system());
399 
400  auto sc = compute_start_and_count(dim_types,
401  { (int)t_start, grid.xs(), grid.ys(), 0 },
402  { 1, grid.xm(), grid.ym(), (int)z_count });
403 
404  if (use_transposed_io(dim_types)) {
405  file.read_variable_transposed(var_name, sc.start, sc.count, sc.imap, output);
406  } else {
407  file.read_variable(var_name, sc.start, sc.count, output);
408  }
409 
410  } catch (RuntimeError &e) {
411  e.add_context("reading variable '%s' from '%s'", var_name.c_str(), file.filename().c_str());
412  throw;
413  }
414 }
415 
416 /** Regrid `variable_name` from a file, possibly replacing missing values with `default_value`.
417  *
418  * @param file input file
419  * @param variable_name variable to regrid
420  * @param internal_grid computational grid; used to initialize interpolation
421  */
422 static std::vector<double> read_for_interpolation(const File &file,
423  const std::string &variable_name,
424  const Grid &internal_grid,
425  const LocalInterpCtx &lic) {
426 
427  try {
428  auto unit_system = internal_grid.ctx()->unit_system();
429 
430  std::vector<double> buffer(lic.buffer_size());
431 
432  auto dim_types = dimension_types(file, variable_name, internal_grid.ctx()->unit_system());
433 
434  auto sc = compute_start_and_count(dim_types, lic.start, lic.count);
435 
436  if (use_transposed_io(dim_types)) {
437  file.read_variable_transposed(variable_name, sc.start, sc.count, sc.imap, buffer.data());
438  } else {
439  file.read_variable(variable_name, sc.start, sc.count, buffer.data());
440  }
441 
442  // Stop with an error message if some values match the _FillValue attribute:
443  {
444  auto attribute = file.read_double_attribute(variable_name, "_FillValue");
445  if (attribute.size() == 1) {
446  double fill_value = attribute[0], epsilon = 1e-12;
447 
448  for (const auto &value : buffer) {
449  if (fabs(value - fill_value) < epsilon) {
451  PISM_ERROR_LOCATION, "Some values of '%s' in '%s' match the _FillValue attribute.",
452  variable_name.c_str(), file.filename().c_str());
453  }
454  }
455  }
456  }
457 
458  return buffer;
459  } catch (RuntimeError &e) {
460  e.add_context("reading variable '%s' from '%s'", variable_name.c_str(),
461  file.filename().c_str());
462  throw;
463  }
464 }
465 
466 //! Define a NetCDF variable corresponding to a VariableMetadata object.
467 void define_spatial_variable(const SpatialVariableMetadata &metadata, const Grid &grid,
468  const File &file, io::Type default_type) {
469  auto config = grid.ctx()->config();
470 
471  // make a copy of `metadata` so we can override `output_units` if "output.use_MKS" is
472  // set.
473  SpatialVariableMetadata var = metadata;
474  if (config->get_flag("output.use_MKS")) {
475  var.output_units(var["units"]);
476  }
477 
478  std::vector<std::string> dims;
479  std::string name = var.get_name();
480 
481  if (file.find_variable(name)) {
482  return;
483  }
484 
485  define_dimensions(var, grid, file);
486 
487  std::string x = var.x().get_name(), y = var.y().get_name(), z = var.z().get_name();
488 
489  if (not var.get_time_independent()) {
490  dims.push_back(config->get_string("time.dimension_name"));
491  }
492 
493  dims.push_back(y);
494  dims.push_back(x);
495 
496  if (not z.empty()) {
497  dims.push_back(z);
498  }
499 
500  assert(dims.size() > 1);
501 
502  io::Type type = var.get_output_type();
503  if (type == PISM_NAT) {
504  type = default_type;
505  }
506  file.define_variable(name, type, dims);
507 
508  write_attributes(file, var, type);
509 
510  // add the "grid_mapping" attribute if the grid has an associated mapping. Variables lat, lon,
511  // lat_bnds, and lon_bnds should not have the grid_mapping attribute to support CDO (see issue
512  // #384).
513  const VariableMetadata &mapping = grid.get_mapping_info().mapping;
514  if (mapping.has_attributes() and not member(name, { "lat_bnds", "lon_bnds", "lat", "lon" })) {
515  file.write_attribute(var.get_name(), "grid_mapping", mapping.get_name());
516  }
517 
518  if (var.get_time_independent()) {
519  // mark this variable as "not written" so that write_spatial_variable can avoid
520  // writing it more than once.
521  file.write_attribute(var.get_name(), "not_written", PISM_INT, { 1.0 });
522  }
523 }
524 
525 //! Read a variable from a file into an array `output`.
526 /*! This also converts data from input units to internal units if needed.
527  */
528 void read_spatial_variable(const SpatialVariableMetadata &variable, const Grid &grid,
529  const File &file, unsigned int time, double *output) {
530 
531  const Logger &log = *grid.ctx()->log();
532 
533  // Find the variable:
534  auto var = file.find_variable(variable.get_name(), variable["standard_name"]);
535 
536  if (not var.exists) {
538  PISM_ERROR_LOCATION, "Can't find '%s' (%s) in '%s'.", variable.get_name().c_str(),
539  variable.get_string("standard_name").c_str(), file.filename().c_str());
540  }
541 
542  // Sanity check: the variable in an input file should have the expected
543  // number of spatial dimensions.
544  {
545  // Set of spatial dimensions for this field:
546  std::set<int> axes{ X_AXIS, Y_AXIS };
547  if (axis_type_from_string(variable.z()["axis"]) == Z_AXIS) {
548  axes.insert(Z_AXIS);
549  }
550 
551  int input_spatial_dim_count = 0; // number of spatial dimensions (input file)
552  size_t matching_dim_count = 0; // number of matching dimensions
553 
554  auto input_dims = file.dimensions(var.name);
555  for (const auto &d : input_dims) {
556  auto dim_type = file.dimension_type(d, variable.unit_system());
557 
558  if (dim_type != T_AXIS) {
559  ++input_spatial_dim_count;
560  }
561 
562  if (axes.find(dim_type) != axes.end()) {
563  ++matching_dim_count;
564  }
565  }
566 
567  if (axes.size() != matching_dim_count) {
568 
569  // Print the error message and stop:
572  "found the %dD variable %s (%s) in '%s' while trying to read\n"
573  "'%s' ('%s'), which is %d-dimensional.",
574  input_spatial_dim_count, var.name.c_str(), join(input_dims, ",").c_str(),
575  file.filename().c_str(), variable.get_name().c_str(),
576  variable.get_string("long_name").c_str(), static_cast<int>(axes.size()));
577  }
578  }
579 
580  // make sure we have at least one level
581  size_t nlevels = std::max(variable.levels().size(), (size_t)1);
582 
583  read_distributed_array(file, grid, var.name, nlevels, time, output);
584 
585  std::string input_units = file.read_text_attribute(var.name, "units");
586  const std::string &internal_units = variable["units"];
587 
588  if (input_units.empty() and not internal_units.empty()) {
589  const std::string &long_name = variable["long_name"];
590  log.message(2,
591  "PISM WARNING: Variable '%s' ('%s') does not have the units attribute.\n"
592  " Assuming that it is in '%s'.\n",
593  variable.get_name().c_str(), long_name.c_str(), internal_units.c_str());
594  input_units = internal_units;
595  }
596 
597  // Convert data:
598  size_t size = grid.xm() * grid.ym() * nlevels;
599 
600  // stop if some values match the _FillValue attribute
601  {
602  auto att = file.read_double_attribute(var.name, "_FillValue");
603  if (att.size() == 1) {
604  double fill_value = att[0];
605  for (size_t k = 0; k < size; ++k) {
606  if (output[k] == fill_value) {
609  "Some values of the variable '%s' in '%s' match the _FillValue attribute.",
610  var.name.c_str(), file.filename().c_str());
611  }
612  }
613  }
614  }
615 
616  units::Converter(variable.unit_system(), input_units, internal_units)
617  .convert_doubles(output, size);
618 }
619 
620 //! \brief Write a double array to a file.
621 /*!
622  Converts units if internal and "output" units are different.
623  */
624 void write_spatial_variable(const SpatialVariableMetadata &metadata, const Grid &grid,
625  const File &file, const double *input) {
626  auto config = grid.ctx()->config();
627 
628  // make a copy of `metadata` so we can override `output_units` if "output.use_MKS" is
629  // set.
630  SpatialVariableMetadata var = metadata;
631  if (config->get_flag("output.use_MKS")) {
632  var.output_units(var["units"]);
633  }
634 
635  auto name = var.get_name();
636 
637  if (not file.find_variable(name)) {
638  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "Can't find '%s' in '%s'.", name.c_str(),
639  file.filename().c_str());
640  }
641 
642  write_dimensions(var, grid, file);
643 
644  bool time_dependent = not var.get_time_independent();
645 
646  // avoid writing time-independent variables more than once (saves time when writing to
647  // extra_files)
648  if (not time_dependent) {
649  bool written = file.attribute_type(var.get_name(), "not_written") == PISM_NAT;
650  if (written) {
651  return;
652  }
653 
654  file.redef();
655  file.remove_attribute(var.get_name(), "not_written");
656  }
657 
658  // make sure we have at least one level
659  unsigned int nlevels = std::max(var.levels().size(), (size_t)1);
660 
661  std::string units = var["units"], output_units = var["output_units"];
662 
663  if (units != output_units) {
664  size_t data_size = grid.xm() * grid.ym() * nlevels;
665 
666  // create a temporary array, convert to output units, and
667  // save
668  std::vector<double> tmp(data_size);
669  for (size_t k = 0; k < data_size; ++k) {
670  tmp[k] = input[k];
671  }
672 
673  units::Converter(var.unit_system(), units, output_units)
674  .convert_doubles(tmp.data(), tmp.size());
675 
676  file.write_distributed_array(name, grid, nlevels, time_dependent, tmp.data());
677  } else {
678  file.write_distributed_array(name, grid, nlevels, time_dependent, input);
679  }
680 }
681 
682 /*!
683  * Check the overlap of the input grid and the internal grid.
684  *
685  * Set `allow_extrapolation` to `true` to "extend" the vertical grid during "bootstrapping".
686  */
687 static void check_grid_overlap(const grid::InputGridInfo &input, const Grid &internal,
688  const std::vector<double> &z_internal) {
689 
690  // Grid spacing (assume that the grid is equally-spaced) and the
691  // extent of the domain. To compute the extent of the domain, assume
692  // that the grid is cell-centered, so edge of the domain is half of
693  // the grid spacing away from grid points at the edge.
694  //
695  // Note that x_min is not the same as internal.x(0).
696  const double x_min = internal.x0() - internal.Lx(), x_max = internal.x0() + internal.Lx(),
697  y_min = internal.y0() - internal.Ly(), y_max = internal.y0() + internal.Ly(),
698  input_x_min = input.x0 - input.Lx, input_x_max = input.x0 + input.Lx,
699  input_y_min = input.y0 - input.Ly, input_y_max = input.y0 + input.Ly;
700 
701  // tolerance (one micron)
702  double eps = 1e-6;
703 
704  // horizontal grid extent
705  if (not(x_min >= input_x_min - eps and x_max <= input_x_max + eps and
706  y_min >= input_y_min - eps and y_max <= input_y_max + eps)) {
709  "PISM's computational domain is not a subset of the domain in '%s'\n"
710  "PISM grid: x: [%3.3f, %3.3f] y: [%3.3f, %3.3f] meters\n"
711  "input file grid: x: [%3.3f, %3.3f] y: [%3.3f, %3.3f] meters",
712  input.filename.c_str(), x_min, x_max, y_min, y_max, input_x_min, input_x_max, input_y_min,
713  input_y_max);
714  }
715 
716  if (z_internal.empty()) {
718  "Internal vertical grid has 0 levels. This should never happen.");
719  }
720 
721  if (z_internal.size() == 1 and input.z.size() > 1) {
722  // internal field is 2D or 3D with one level, input variable is 3D with more than one
723  // vertical level
725  "trying to read in a 2D field but the input file %s contains\n"
726  "a 3D field with %d levels",
727  input.filename.c_str(), static_cast<int>(input.z.size()));
728  }
729 
730  if (z_internal.size() > 1 and input.z.size() <= 1) {
731  // internal field is 3D with more than one vertical level, input variable is 2D or 3D
732  // with 1 level
734  "trying to read in a 3D field but the input file %s contains\n"
735  "a 2D field",
736  input.filename.c_str());
737  }
738 
739  if (z_internal.size() > 1 and (not input.z.empty())) {
740  // both internal field and input variable are 3D: check vertical grid extent
741  // Note: in PISM 2D fields have one vertical level (z = 0).
742  const double input_z_min = input.z.front(), input_z_max = input.z.back(),
743  z_min = z_internal.front(), z_max = z_internal.back();
744 
745  if (not(z_min >= input.z_min - eps and z_max <= input.z_max + eps)) {
748  "PISM's computational domain is not a subset of the domain in '%s'\n"
749  "PISM grid: z: [%3.3f, %3.3f] meters\n"
750  "input file grid: z: [%3.3f, %3.3f] meters",
751  input.filename.c_str(), z_min, z_max, input_z_min, input_z_max);
752  }
753  }
754 }
755 
756 /*! @brief Check that x, y, and z coordinates of the input grid are strictly increasing. */
757 void check_input_grid(const grid::InputGridInfo &input_grid,
758  const Grid& internal_grid,
759  const std::vector<double> &internal_z_levels) {
760  if (not is_increasing(input_grid.x)) {
762  "input x coordinate has to be strictly increasing");
763  }
764 
765  if (not is_increasing(input_grid.y)) {
767  "input y coordinate has to be strictly increasing");
768  }
769 
770  if (not is_increasing(input_grid.z)) {
772  "input vertical grid has to be strictly increasing");
773  }
774 
775  bool allow_extrapolation = internal_grid.ctx()->config()->get_flag("grid.allow_extrapolation");
776 
777  if (not allow_extrapolation) {
778  check_grid_overlap(input_grid, internal_grid, internal_z_levels);
779  }
780 }
781 
782 //! \brief Regrid from a NetCDF file into a distributed array `output`.
783 /*!
784  - if `flag` is `CRITICAL` or `CRITICAL_FILL_MISSING`, stops if the
785  variable was not found in the input file
786  - if `flag` is one of `CRITICAL_FILL_MISSING` and
787  `OPTIONAL_FILL_MISSING`, replace _FillValue with `default_value`.
788  - sets `v` to `default_value` if `flag` is `OPTIONAL` and the
789  variable was not found in the input file
790  - uses the last record in the file
791 */
792 
794  const Grid &internal_grid,
795  const LocalInterpCtx &lic, const File &file,
796  double *output) {
797 
798  auto var_info = file.find_variable(variable.get_name(), variable["standard_name"]);
799  auto variable_name = var_info.name;
800 
801  const Profiling &profiling = internal_grid.ctx()->profiling();
802 
803  profiling.begin("io.regridding.read");
804  auto buffer = read_for_interpolation(file, variable_name, internal_grid, lic);
805  profiling.end("io.regridding.read");
806 
807  // interpolate
808  profiling.begin("io.regridding.interpolate");
809  regrid(internal_grid, lic, buffer.data(), output);
810  profiling.end("io.regridding.interpolate");
811 
812  // Get the units string from the file and convert the units:
813  {
814  std::string input_units = file.read_text_attribute(variable_name, "units");
815  std::string internal_units = variable["units"];
816 
817  if (input_units.empty() and not internal_units.empty()) {
818  const Logger &log = *internal_grid.ctx()->log();
819  log.message(2,
820  "PISM WARNING: Variable '%s' ('%s') does not have the units attribute.\n"
821  " Assuming that it is in '%s'.\n",
822  variable.get_name().c_str(), variable.get_string("long_name").c_str(),
823  internal_units.c_str());
824  input_units = internal_units;
825  }
826 
827  const size_t data_size = internal_grid.xm() * internal_grid.ym() * lic.z->n_output();
828 
829  // Convert data:
830  units::Converter(variable.unit_system(), input_units, internal_units)
831  .convert_doubles(output, data_size);
832  }
833 
834  read_valid_range(file, variable_name, variable);
835 }
836 
837 
838 //! Define a NetCDF variable corresponding to a time-series.
839 void define_timeseries(const VariableMetadata &var, const std::string &dimension_name,
840  const File &file, io::Type nctype) {
841 
842  std::string name = var.get_name();
843 
844  if (file.find_variable(name)) {
845  return;
846  }
847 
848  if (not file.find_dimension(dimension_name)) {
849  define_dimension(file, PISM_UNLIMITED, VariableMetadata(dimension_name, var.unit_system()));
850  }
851 
852  if (not file.find_variable(name)) {
853  file.define_variable(name, nctype, { dimension_name });
854  }
855 
856  write_attributes(file, var, nctype);
857 }
858 
859 //! Read a time-series variable from a NetCDF file to a vector of doubles.
860 void read_timeseries(const File &file, const VariableMetadata &metadata, const Logger &log,
861  std::vector<double> &data) {
862 
863  std::string name = metadata.get_name();
864 
865  try {
866  // Find the variable:
867  std::string long_name = metadata["long_name"], standard_name = metadata["standard_name"];
868 
869  auto var = file.find_variable(name, standard_name);
870 
871  if (not var.exists) {
872  throw RuntimeError(PISM_ERROR_LOCATION, "variable " + name + " is missing");
873  }
874 
875  auto dims = file.dimensions(var.name);
876  if (dims.size() != 1) {
878  "variable '%s' in '%s' should to have 1 dimension (got %d)",
879  name.c_str(), file.filename().c_str(), (int)dims.size());
880  }
881 
882  auto dimension_name = dims[0];
883 
884  unsigned int length = file.dimension_length(dimension_name);
885  if (length <= 0) {
886  throw RuntimeError(PISM_ERROR_LOCATION, "dimension " + dimension_name + " has length zero");
887  }
888 
889  data.resize(length); // memory allocation happens here
890 
891  file.read_variable(var.name, { 0 }, { length }, data.data());
892 
893  units::System::Ptr system = metadata.unit_system();
894  units::Unit internal_units(system, metadata["units"]), input_units(system, "1");
895 
896  std::string input_units_string = file.read_text_attribute(var.name, "units");
897 
898  bool input_has_units = not input_units_string.empty();
899 
900  if (input_has_units) {
901  input_units = units::Unit(system, input_units_string);
902  }
903 
904  if (metadata.has_attribute("units") && not input_has_units) {
905  std::string units_string = internal_units.format();
906  log.message(2,
907  "PISM WARNING: Variable '%s' ('%s') does not have the units attribute.\n"
908  " Assuming that it is in '%s'.\n",
909  name.c_str(), long_name.c_str(), units_string.c_str());
910  input_units = internal_units;
911  }
912 
913  units::Converter(input_units, internal_units).convert_doubles(data.data(), data.size());
914 
915  } catch (RuntimeError &e) {
916  e.add_context("reading time-series variable '%s' from '%s'", name.c_str(),
917  file.filename().c_str());
918  throw;
919  }
920 }
921 
922 /** @brief Write a time-series `data` to a file.
923  *
924  * Always use output units when saving time-series.
925  */
926 void write_timeseries(const File &file, const VariableMetadata &metadata, size_t t_start,
927  const std::vector<double> &data) {
928 
929  std::string name = metadata.get_name();
930  try {
931  if (not file.find_variable(name)) {
932  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "variable '%s' not found", name.c_str());
933  }
934 
935  // create a copy of "data":
936  std::vector<double> tmp = data;
937 
938  // convert to output units:
939  units::Converter(metadata.unit_system(), metadata["units"], metadata["output_units"])
940  .convert_doubles(tmp.data(), tmp.size());
941 
942  file.write_variable(name, {(unsigned int)t_start}, {(unsigned int)tmp.size()}, tmp.data());
943 
944  } catch (RuntimeError &e) {
945  e.add_context("writing time-series variable '%s' to '%s'", name.c_str(),
946  file.filename().c_str());
947  throw;
948  }
949 }
950 
952  const std::string &dimension_name,
953  const std::string &bounds_name,
954  const File &file, io::Type nctype) {
955  std::string name = var.get_name();
956 
957  if (file.find_variable(name)) {
958  return;
959  }
960 
961  if (not file.find_dimension(dimension_name)) {
962  file.define_dimension(dimension_name, PISM_UNLIMITED);
963  }
964 
965  if (not file.find_dimension(bounds_name)) {
966  file.define_dimension(bounds_name, 2);
967  }
968 
969  file.define_variable(name, nctype, {dimension_name, bounds_name});
970 
971  write_attributes(file, var, nctype);
972 }
973 
974 void read_time_bounds(const File &file,
975  const VariableMetadata &metadata,
976  const Logger &log,
977  std::vector<double> &data) {
978 
979  std::string name = metadata.get_name();
980 
981  try {
982  auto system = metadata.unit_system();
983  units::Unit internal_units(system, metadata["units"]);
984 
985  // Find the variable:
986  if (not file.find_variable(name)) {
987  throw RuntimeError(PISM_ERROR_LOCATION, "variable " + name + " is missing");
988  }
989 
990  std::vector<std::string> dims = file.dimensions(name);
991 
992  if (dims.size() != 2) {
993  throw RuntimeError(PISM_ERROR_LOCATION, "variable " + name + " has to has two dimensions");
994  }
995 
996  std::string
997  &dimension_name = dims[0],
998  &bounds_name = dims[1];
999 
1000  // Check that we have 2 vertices (interval end-points) per time record.
1001  unsigned int length = file.dimension_length(bounds_name);
1002  if (length != 2) {
1004  "time-bounds variable " + name + " has to have exactly 2 bounds per time record");
1005  }
1006 
1007  // Get the number of time records.
1008  length = file.dimension_length(dimension_name);
1009  if (length <= 0) {
1010  throw RuntimeError(PISM_ERROR_LOCATION, "dimension " + dimension_name + " has length zero");
1011  }
1012 
1013  data.resize(2*length); // memory allocation happens here
1014 
1015  file.read_variable(name, {0, 0}, {length, 2}, data.data());
1016 
1017  // Find the corresponding 'time' variable. (We get units from the 'time'
1018  // variable, because according to CF-1.5 section 7.1 a "boundary variable"
1019  // may not have metadata set.)
1020  if (not file.find_variable(dimension_name)) {
1022  "time coordinate variable " + dimension_name + " is missing");
1023  }
1024 
1025  bool input_has_units = false;
1026  units::Unit input_units(internal_units.system(), "1");
1027 
1028  std::string input_units_string = file.read_text_attribute(dimension_name, "units");
1029  if (input_units_string.empty()) {
1030  input_has_units = false;
1031  } else {
1032  input_units = units::Unit(internal_units.system(), input_units_string);
1033  input_has_units = true;
1034  }
1035 
1036  if (metadata.has_attribute("units") && not input_has_units) {
1037  std::string units_string = internal_units.format();
1038  log.message(2,
1039  "PISM WARNING: Variable '%s' does not have the units attribute.\n"
1040  " Assuming that it is in '%s'.\n",
1041  dimension_name.c_str(),
1042  units_string.c_str());
1043  input_units = internal_units;
1044  }
1045 
1046  units::Converter(input_units, internal_units).convert_doubles(data.data(), data.size());
1047 
1048  // FIXME: check that time intervals described by the time bounds
1049  // variable are contiguous (without gaps) and stop if they are not.
1050  } catch (RuntimeError &e) {
1051  e.add_context("reading time bounds variable '%s' from '%s'", name.c_str(),
1052  file.filename().c_str());
1053  throw;
1054  }
1055 }
1056 
1057 void write_time_bounds(const File &file, const VariableMetadata &metadata,
1058  size_t t_start, const std::vector<double> &data) {
1059 
1060  VariableMetadata var = metadata;
1061 
1062  std::string name = var.get_name();
1063  try {
1064  bool variable_exists = file.find_variable(name);
1065  if (not variable_exists) {
1066  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "variable '%s' not found",
1067  name.c_str());
1068  }
1069 
1070  // make a copy of "data"
1071  std::vector<double> tmp = data;
1072 
1073  // convert to output units:
1074  units::Converter(var.unit_system(), var["units"], var["output_units"])
1075  .convert_doubles(tmp.data(), tmp.size());
1076 
1077  file.write_variable(name,
1078  {(unsigned int)t_start, 0},
1079  {(unsigned int)tmp.size() / 2, 2},
1080  tmp.data());
1081 
1082  } catch (RuntimeError &e) {
1083  e.add_context("writing time-bounds variable '%s' to '%s'", name.c_str(),
1084  file.filename().c_str());
1085  throw;
1086  }
1087 }
1088 
1089 /*!
1090  * Reads and validates times and time bounds.
1091  */
1092 void read_time_info(const Logger &log,
1093  std::shared_ptr<units::System> unit_system,
1094  const File &file,
1095  const std::string &time_name,
1096  const std::string &time_units,
1097  std::vector<double> &times,
1098  std::vector<double> &bounds) {
1099 
1100  size_t N = 0;
1101  {
1102  VariableMetadata time_variable(time_name, unit_system);
1103  time_variable["units"] = time_units;
1104 
1105  io::read_timeseries(file, time_variable, log, times);
1106 
1107  if (not is_increasing(times)) {
1109  "times have to be strictly increasing");
1110  }
1111  N = times.size();
1112  }
1113 
1114  // Read time bounds
1115  {
1116  std::string time_bounds_name = file.read_text_attribute(time_name, "bounds");
1117 
1118  if (time_bounds_name.empty()) {
1120  "please provide time bounds for '%s'",
1121  time_name.c_str());
1122  }
1123 
1124  VariableMetadata bounds_variable(time_bounds_name, unit_system);
1125  bounds_variable["units"] = time_units;
1126 
1127  io::read_time_bounds(file, bounds_variable, log, bounds);
1128 
1129  if (2 * N != bounds.size()) {
1131  "each time record has to have 2 bounds");
1132  }
1133 
1134  for (size_t k = 0; k < N; ++k) {
1135  if (not (times[k] >= bounds[2 * k + 0] and
1136  times[k] <= bounds[2 * k + 1])) {
1138  "each time has to be contained in its time interval");
1139  }
1140  }
1141  } // end of the block reading time bounds
1142 }
1143 
1144 bool file_exists(MPI_Comm com, const std::string &filename) {
1145  int file_exists_flag = 0, rank = 0;
1146  MPI_Comm_rank(com, &rank);
1147 
1148  if (rank == 0) {
1149  // Check if the file exists:
1150  if (FILE *f = fopen(filename.c_str(), "r")) {
1151  file_exists_flag = 1;
1152  fclose(f);
1153  } else {
1154  file_exists_flag = 0;
1155  }
1156  }
1157  MPI_Bcast(&file_exists_flag, 1, MPI_INT, 0, com);
1158 
1159  return file_exists_flag == 1;
1160 }
1161 
1162 void read_attributes(const File &file,
1163  const std::string &variable_name,
1164  VariableMetadata &variable) {
1165  try {
1166  if (not file.find_variable(variable_name)) {
1167  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "variable '%s' is missing", variable_name.c_str());
1168  }
1169 
1170  variable.clear();
1171 
1172  unsigned int nattrs = file.nattributes(variable_name);
1173 
1174  for (unsigned int j = 0; j < nattrs; ++j) {
1175  std::string attribute_name = file.attribute_name(variable_name, j);
1176  io::Type nctype = file.attribute_type(variable_name, attribute_name);
1177 
1178  if (nctype == PISM_CHAR) {
1179  variable[attribute_name] = file.read_text_attribute(variable_name,
1180  attribute_name);
1181  } else {
1182  variable[attribute_name] = file.read_double_attribute(variable_name,
1183  attribute_name);
1184  }
1185  } // end of for (int j = 0; j < nattrs; ++j)
1186  } catch (RuntimeError &e) {
1187  e.add_context("reading attributes of variable '%s' from '%s'",
1188  variable_name.c_str(), file.filename().c_str());
1189  throw;
1190  }
1191 }
1192 
1193 //! Write variable attributes to a NetCDF file.
1194 /*!
1195  - If both valid_min and valid_max are set, then valid_range is written
1196  instead of the valid_min, valid_max pair.
1197 
1198  - Skips empty text attributes.
1199 */
1200 void write_attributes(const File &file, const VariableMetadata &variable, io::Type nctype) {
1201  std::string var_name = variable.get_name();
1202 
1203  try {
1204  std::string
1205  units = variable["units"],
1206  output_units = variable["output_units"];
1207 
1208  bool use_output_units = units != output_units;
1209 
1210  // units, valid_min, valid_max and valid_range need special treatment:
1211  if (variable.has_attribute("units")) {
1212  file.write_attribute(var_name, "units", use_output_units ? output_units : units);
1213  }
1214 
1215  std::vector<double> bounds(2);
1216  if (variable.has_attribute("valid_range")) {
1217  bounds = variable.get_numbers("valid_range");
1218  } else {
1219  if (variable.has_attribute("valid_min")) {
1220  bounds[0] = variable.get_number("valid_min");
1221  }
1222  if (variable.has_attribute("valid_max")) {
1223  bounds[1] = variable.get_number("valid_max");
1224  }
1225  }
1226 
1227  double fill_value = 0.0;
1228  if (variable.has_attribute("_FillValue")) {
1229  fill_value = variable.get_number("_FillValue");
1230  }
1231 
1232  // We need to save valid_min, valid_max and valid_range in the units
1233  // matching the ones in the output.
1234  if (use_output_units) {
1235 
1236  units::Converter c(variable.unit_system(), units, output_units);
1237 
1238  bounds[0] = c(bounds[0]);
1239  bounds[1] = c(bounds[1]);
1240  fill_value = c(fill_value);
1241  }
1242 
1243  if (variable.has_attribute("_FillValue")) {
1244  file.write_attribute(var_name, "_FillValue", nctype, {fill_value});
1245  }
1246 
1247  if (variable.has_attribute("valid_range")) {
1248  file.write_attribute(var_name, "valid_range", nctype, bounds);
1249  } else if (variable.has_attribute("valid_min") and
1250  variable.has_attribute("valid_max")) {
1251  file.write_attribute(var_name, "valid_range", nctype, bounds);
1252  } else if (variable.has_attribute("valid_min")) {
1253  file.write_attribute(var_name, "valid_min", nctype, {bounds[0]});
1254  } else if (variable.has_attribute("valid_max")) {
1255  file.write_attribute(var_name, "valid_max", nctype, {bounds[1]});
1256  }
1257 
1258  // Write text attributes:
1259  for (const auto& s : variable.all_strings()) {
1260  std::string
1261  name = s.first,
1262  value = s.second;
1263 
1264  if (name == "units" or
1265  name == "output_units" or
1266  value.empty()) {
1267  continue;
1268  }
1269 
1270  file.write_attribute(var_name, name, value);
1271  }
1272 
1273  // Write double attributes:
1274  for (const auto& d : variable.all_doubles()) {
1275  std::string name = d.first;
1276  std::vector<double> values = d.second;
1277 
1278  if (member(name, {"valid_min", "valid_max", "valid_range", "_FillValue"}) or
1279  values.empty()) {
1280  continue;
1281  }
1282 
1283  file.write_attribute(var_name, name, nctype, values);
1284  }
1285 
1286  } catch (RuntimeError &e) {
1287  e.add_context("writing attributes of variable '%s' to '%s'",
1288  var_name.c_str(), file.filename().c_str());
1289  throw;
1290  }
1291 }
1292 
1293 //! Read the valid range information from a file.
1294 /*! Reads `valid_min`, `valid_max` and `valid_range` attributes; if \c
1295  valid_range is found, sets the pair `valid_min` and `valid_max` instead.
1296 */
1297 void read_valid_range(const File &file, const std::string &name, VariableMetadata &variable) {
1298  try {
1299  // Never reset valid_min/max if they were set internally
1300  if (variable.has_attribute("valid_min") or
1301  variable.has_attribute("valid_max")) {
1302  return;
1303  }
1304 
1305  // Read the units.
1306  std::string file_units = file.read_text_attribute(name, "units");
1307 
1308  if (file_units.empty()) {
1309  // If the variable in the file does not have the units attribute we assume that
1310  // units in the file match internal (PISM) units.
1311  file_units = variable.get_string("units");
1312  }
1313 
1314  units::Converter c(variable.unit_system(), file_units, variable["units"]);
1315 
1316  std::vector<double> bounds = file.read_double_attribute(name, "valid_range");
1317  if (bounds.size() == 2) { // valid_range is present
1318  variable["valid_min"] = {c(bounds[0])};
1319  variable["valid_max"] = {c(bounds[1])};
1320  } else { // valid_range has the wrong length or is missing
1321  bounds = file.read_double_attribute(name, "valid_min");
1322  if (bounds.size() == 1) { // valid_min is present
1323  variable["valid_min"] = {c(bounds[0])};
1324  }
1325 
1326  bounds = file.read_double_attribute(name, "valid_max");
1327  if (bounds.size() == 1) { // valid_max is present
1328  variable["valid_max"] = {c(bounds[0])};
1329  }
1330  }
1331  } catch (RuntimeError &e) {
1332  e.add_context("reading valid range of variable '%s' from '%s'", name.c_str(),
1333  file.filename().c_str());
1334  throw;
1335  }
1336 }
1337 
1338 /*!
1339  * Return the name of the time dimension corresponding to a NetCDF variable.
1340  *
1341  * Returns an empty string if this variable is time-independent.
1342  */
1343 std::string time_dimension(units::System::Ptr unit_system,
1344  const File &file,
1345  const std::string &variable_name) {
1346 
1347  auto dims = file.dimensions(variable_name);
1348 
1349  for (const auto &d : dims) {
1350  if (file.dimension_type(d, unit_system) == T_AXIS) {
1351  return d;
1352  }
1353  }
1354 
1355  return "";
1356 }
1357 
1358 //! \brief Moves the file aside (file.nc -> file.nc~).
1359 /*!
1360  * Note: only one processor does the renaming.
1361  */
1362 void move_if_exists(MPI_Comm com, const std::string &file_to_move, int rank_to_use) {
1363  int stat = 0, rank = 0;
1364  MPI_Comm_rank(com, &rank);
1365  std::string backup_filename = file_to_move + "~";
1366 
1367  if (rank == rank_to_use) {
1368  bool exists = false;
1369 
1370  // Check if the file exists:
1371  if (FILE *f = fopen(file_to_move.c_str(), "r")) {
1372  fclose(f);
1373  exists = true;
1374  } else {
1375  exists = false;
1376  }
1377 
1378  if (exists) {
1379  stat = rename(file_to_move.c_str(), backup_filename.c_str());
1380  }
1381  } // end of "if (rank == rank_to_use)"
1382 
1383  int global_stat = 0;
1384  MPI_Allreduce(&stat, &global_stat, 1, MPI_INT, MPI_SUM, com);
1385 
1386  if (global_stat != 0) {
1388  "PISM ERROR: can't move '%s' to '%s'",
1389  file_to_move.c_str(), backup_filename.c_str());
1390  }
1391 }
1392 
1393 //! \brief Check if a file is present are remove it.
1394 /*!
1395  * Note: only processor 0 does the job.
1396  */
1397 void remove_if_exists(MPI_Comm com, const std::string &file_to_remove, int rank_to_use) {
1398  int stat = 0, rank = 0;
1399  MPI_Comm_rank(com, &rank);
1400 
1401  if (rank == rank_to_use) {
1402  bool exists = false;
1403 
1404  // Check if the file exists:
1405  if (FILE *f = fopen(file_to_remove.c_str(), "r")) {
1406  fclose(f);
1407  exists = true;
1408  } else {
1409  exists = false;
1410  }
1411 
1412  if (exists) {
1413  stat = remove(file_to_remove.c_str());
1414  }
1415  } // end of "if (rank == rank_to_use)"
1416 
1417  int global_stat = 0;
1418  MPI_Allreduce(&stat, &global_stat, 1, MPI_INT, MPI_SUM, com);
1419 
1420  if (global_stat != 0) {
1422  "PISM ERROR: can't remove '%s'", file_to_remove.c_str());
1423  }
1424 }
1425 
1426 } // end of namespace io
1427 } // end of namespace pism
std::string get_string(const std::string &name, UseFlag flag=REMEMBER_THIS_USE) const
A class for storing and accessing PISM configuration flags and parameters.
std::shared_ptr< const Config > config() const
Definition: Context.cc:105
std::shared_ptr< units::System > unit_system() const
Definition: Context.cc:97
std::shared_ptr< const Time > time() const
Definition: Context.cc:117
bool find_dimension(const std::string &name) const
Checks if a dimension exists.
Definition: File.cc:439
void write_distributed_array(const std::string &variable_name, const Grid &grid, unsigned int z_count, bool time_dependent, const double *input) const
Definition: File.cc:773
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:747
void read_variable_transposed(const std::string &variable_name, const std::vector< unsigned int > &start, const std::vector< unsigned int > &count, const std::vector< unsigned int > &imap, double *ip) const
Definition: File.cc:791
void define_dimension(const std::string &name, size_t length) const
Definition: File.cc:563
std::string attribute_name(const std::string &var_name, unsigned int n) const
Definition: File.cc:723
AxisType dimension_type(const std::string &name, units::System::Ptr unit_system) const
Get the "type" of a dimension.
Definition: File.cc:493
void redef() const
Definition: File.cc:288
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:361
std::string filename() const
Definition: File.cc:307
void write_variable(const std::string &variable_name, const std::vector< unsigned int > &start, const std::vector< unsigned int > &count, const double *op) const
Definition: File.cc:760
void sync() const
Definition: File.cc:279
void define_variable(const std::string &name, io::Type nctype, const std::vector< std::string > &dims) const
Define a variable.
Definition: File.cc:573
io::Type attribute_type(const std::string &var_name, const std::string &att_name) const
Definition: File.cc:735
void remove_attribute(const std::string &variable_name, const std::string &att_name) const
Definition: File.cc:261
void write_attribute(const std::string &var_name, const std::string &att_name, io::Type nctype, const std::vector< double > &values) const
Write a multiple-valued double attribute.
Definition: File.cc:638
std::vector< double > read_double_attribute(const std::string &var_name, const std::string &att_name) const
Get a double attribute.
Definition: File.cc:665
unsigned int nattributes(const std::string &var_name) const
Definition: File.cc:711
unsigned int dimension_length(const std::string &name) const
Get the length of a dimension.
Definition: File.cc:454
std::vector< std::string > dimensions(const std::string &variable_name) const
Definition: File.cc:425
std::string read_text_attribute(const std::string &var_name, const std::string &att_name) const
Get a text attribute.
Definition: File.cc:693
High-level PISM I/O class.
Definition: File.hh:56
const std::vector< double > & x() const
X-coordinates.
Definition: Grid.cc:766
const std::vector< double > & y() const
Y-coordinates.
Definition: Grid.cc:776
int ys() const
Global starting index of this processor's subset.
Definition: Grid.cc:736
PointsWithGhosts points(unsigned int stencil_width=0) const
Definition: Grid.hh:362
unsigned int My() const
Total grid size in the Y direction.
Definition: Grid.cc:756
std::shared_ptr< const Context > ctx() const
Return execution context this grid corresponds to.
Definition: Grid.cc:683
unsigned int Mx() const
Total grid size in the X direction.
Definition: Grid.cc:751
const MappingInfo & get_mapping_info() const
Definition: Grid.cc:1346
int xs() const
Global starting index of this processor's subset.
Definition: Grid.cc:731
int xm() const
Width of this processor's sub-domain.
Definition: Grid.cc:741
int ym() const
Width of this processor's sub-domain.
Definition: Grid.cc:746
Describes the PISM grid and the distribution of data across processors.
Definition: Grid.hh:282
std::array< int, 4 > count
std::array< int, 4 > start
std::shared_ptr< Interpolation > y
std::shared_ptr< Interpolation > z
std::shared_ptr< Interpolation > x
void message(int threshold, const char format[],...) const __attribute__((format(printf
Print a message to the log.
Definition: Logger.cc:49
A basic logging class.
Definition: Logger.hh:40
VariableMetadata mapping
Definition: projection.hh:46
void begin(const char *name) const
Definition: Profiling.cc:75
void end(const char *name) const
Definition: Profiling.cc:91
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
Spatial NetCDF variable (corresponding to a 2D or 3D scalar field).
std::string units_string() const
Internal time units as a string.
Definition: Time.cc:477
std::string calendar() const
Returns the calendar string.
Definition: Time.cc:486
Time management class.
Definition: Time.hh:55
VariableMetadata & clear()
Clear all attributes.
VariableMetadata & output_units(const std::string &input)
double get_number(const std::string &name) const
Get a single-valued scalar attribute.
std::string get_string(const std::string &name) const
Get a string attribute.
const std::map< std::string, std::string > & all_strings() const
units::System::Ptr unit_system() const
io::Type get_output_type() const
bool get_time_independent() const
std::vector< double > get_numbers(const std::string &name) const
Get an array-of-doubles attribute.
bool has_attribute(const std::string &name) const
std::string get_name() const
const std::map< std::string, std::vector< double > > & all_doubles() const
std::vector< double > y
y coordinates
Definition: Grid.hh:88
double Lx
domain half-width
Definition: Grid.hh:77
double z_min
minimal value of the z dimension
Definition: Grid.hh:81
std::string filename
Definition: Grid.hh:92
double Ly
domain half-height
Definition: Grid.hh:79
double y0
y-coordinate of the domain center
Definition: Grid.hh:75
double x0
x-coordinate of the domain center
Definition: Grid.hh:73
std::vector< double > z
z coordinates
Definition: Grid.hh:90
double z_max
maximal value of the z dimension
Definition: Grid.hh:83
std::vector< double > x
x coordinates
Definition: Grid.hh:86
Contains parameters of an input file grid.
Definition: Grid.hh:65
void convert_doubles(double *data, size_t length) const
Definition: Units.cc:250
std::shared_ptr< System > Ptr
Definition: Units.hh:47
std::string format() const
Definition: Units.cc:141
System::Ptr system() const
Definition: Units.cc:149
#define PISM_ERROR_LOCATION
double max(const array::Scalar &input)
Finds maximum over all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:165
double min(const array::Scalar &input)
Finds minimum over all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:193
static StartCountInfo compute_start_and_count(std::vector< AxisType > &dim_types, std::array< int, 4 > start_in, std::array< int, 4 > count_in)
Definition: io_helpers.cc:142
void append_time(const File &file, const Config &config, double time_seconds)
Prepare a file for output.
Definition: io_helpers.cc:247
void regrid_spatial_variable(SpatialVariableMetadata &variable, const Grid &internal_grid, const LocalInterpCtx &lic, const File &file, double *output)
Regrid from a NetCDF file into a distributed array output.
Definition: io_helpers.cc:793
void read_spatial_variable(const SpatialVariableMetadata &variable, const Grid &grid, const File &file, unsigned int time, double *output)
Read a variable from a file into an array output.
Definition: io_helpers.cc:528
static void define_dimensions(const SpatialVariableMetadata &var, const Grid &grid, const File &file)
Define dimensions a variable depends on.
Definition: io_helpers.cc:267
void read_timeseries(const File &file, const VariableMetadata &metadata, const Logger &log, std::vector< double > &data)
Read a time-series variable from a NetCDF file to a vector of doubles.
Definition: io_helpers.cc:860
void define_dimension(const File &file, unsigned long int length, const VariableMetadata &metadata)
Define a dimension and the associated coordinate variable. Set attributes.
Definition: io_helpers.cc:195
@ PISM_UNLIMITED
Definition: IO_Flags.hh:83
void write_spatial_variable(const SpatialVariableMetadata &metadata, const Grid &grid, const File &file, const double *input)
Write a double array to a file.
Definition: io_helpers.cc:624
void write_attributes(const File &file, const VariableMetadata &variable, io::Type nctype)
Write variable attributes to a NetCDF file.
Definition: io_helpers.cc:1200
@ PISM_DOUBLE
Definition: IO_Flags.hh:52
@ PISM_NAT
Definition: IO_Flags.hh:46
@ PISM_INT
Definition: IO_Flags.hh:50
@ PISM_CHAR
Definition: IO_Flags.hh:48
void define_time_bounds(const VariableMetadata &var, const std::string &dimension_name, const std::string &bounds_name, const File &file, io::Type nctype)
Definition: io_helpers.cc:951
static void read_distributed_array(const File &file, const Grid &grid, const std::string &var_name, unsigned int z_count, unsigned int t_start, double *output)
Read an array distributed according to the grid.
Definition: io_helpers.cc:395
void check_input_grid(const grid::InputGridInfo &input_grid, const Grid &internal_grid, const std::vector< double > &internal_z_levels)
Check that x, y, and z coordinates of the input grid are strictly increasing.
Definition: io_helpers.cc:757
void write_time_bounds(const File &file, const VariableMetadata &metadata, size_t t_start, const std::vector< double > &data)
Definition: io_helpers.cc:1057
void read_time_info(const Logger &log, std::shared_ptr< units::System > unit_system, const File &file, const std::string &time_name, const std::string &time_units, std::vector< double > &times, std::vector< double > &bounds)
Definition: io_helpers.cc:1092
void define_spatial_variable(const SpatialVariableMetadata &metadata, const Grid &grid, const File &file, io::Type default_type)
Define a NetCDF variable corresponding to a VariableMetadata object.
Definition: io_helpers.cc:467
void read_time_bounds(const File &file, const VariableMetadata &metadata, const Logger &log, std::vector< double > &data)
Definition: io_helpers.cc:974
static void check_grid_overlap(const grid::InputGridInfo &input, const Grid &internal, const std::vector< double > &z_internal)
Definition: io_helpers.cc:687
static std::vector< AxisType > dimension_types(const File &file, const std::string &var_name, std::shared_ptr< units::System > unit_system)
Definition: io_helpers.cc:385
static std::vector< double > read_for_interpolation(const File &file, const std::string &variable_name, const Grid &internal_grid, const LocalInterpCtx &lic)
Definition: io_helpers.cc:422
void write_dimensions(const SpatialVariableMetadata &var, const Grid &grid, const File &file)
Definition: io_helpers.cc:325
static void regrid(const Grid &grid, const LocalInterpCtx &lic, double const *input_array, double *output_array)
Bi-(or tri-)linear interpolation.
Definition: io_helpers.cc:62
void move_if_exists(MPI_Comm com, const std::string &file_to_move, int rank_to_use)
Moves the file aside (file.nc -> file.nc~).
Definition: io_helpers.cc:1362
void read_attributes(const File &file, const std::string &variable_name, VariableMetadata &variable)
Definition: io_helpers.cc:1162
void write_timeseries(const File &file, const VariableMetadata &metadata, size_t t_start, const std::vector< double > &data)
Write a time-series data to a file.
Definition: io_helpers.cc:926
bool file_exists(MPI_Comm com, const std::string &filename)
Definition: io_helpers.cc:1144
void remove_if_exists(MPI_Comm com, const std::string &file_to_remove, int rank_to_use)
Check if a file is present are remove it.
Definition: io_helpers.cc:1397
void read_valid_range(const File &file, const std::string &name, VariableMetadata &variable)
Read the valid range information from a file.
Definition: io_helpers.cc:1297
std::string time_dimension(units::System::Ptr unit_system, const File &file, const std::string &variable_name)
Definition: io_helpers.cc:1343
static void write_dimension_data(const File &file, const std::string &name, const std::vector< double > &data)
Definition: io_helpers.cc:315
void define_time(const File &file, const Context &ctx)
Prepare a file for output.
Definition: io_helpers.cc:213
static bool use_transposed_io(std::vector< AxisType > dimension_types)
Definition: io_helpers.cc:353
void define_timeseries(const VariableMetadata &var, const std::string &dimension_name, const File &file, io::Type nctype)
Define a NetCDF variable corresponding to a time-series.
Definition: io_helpers.cc:839
bool is_increasing(const std::vector< double > &a)
Checks if a vector of doubles is strictly increasing.
AxisType
Definition: IO_Flags.hh:33
@ T_AXIS
Definition: IO_Flags.hh:33
@ X_AXIS
Definition: IO_Flags.hh:33
@ Z_AXIS
Definition: IO_Flags.hh:33
@ Y_AXIS
Definition: IO_Flags.hh:33
static const double k
Definition: exactTestP.cc:42
static std::string calendar(const File *input_file, const Config &config, const Logger &log)
Definition: Time.cc:146
bool member(const std::string &string, const std::set< std::string > &set)
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:469
std::string name
Definition: File.hh:48
std::vector< unsigned int > start
Definition: io_helpers.cc:137
std::vector< unsigned int > imap
Definition: io_helpers.cc:139
std::vector< unsigned int > count
Definition: io_helpers.cc:138