PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
VariableMetadata.cc
Go to the documentation of this file.
1// Copyright (C) 2009--2026 Constantine Khroulev and Ed Bueler
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#include <set>
20#include <algorithm>
21#include <cmath>
22#include <string>
23#include <utility>
24
25#include "Grid.hh"
26#include "GridInfo.hh"
27#include "pism/util/VariableMetadata.hh"
28
29#include "pism/util/Logger.hh"
30#include "pism/util/error_handling.hh"
31#include "pism/util/io/File.hh"
32#include "pism/util/io/IO_Flags.hh"
33#include "pism_utilities.hh"
34
35namespace pism {
36
37VariableMetadata::VariableMetadata(const std::string &name, units::System::Ptr system,
38 unsigned int ndims)
39 : m_n_spatial_dims(ndims),
40 m_name(name),
41 m_time_dependent(false),
42 m_output_type(io::PISM_DOUBLE) {
43
44 m_attributes.unit_system = std::move(system);
45 clear();
46
47 // long_name is unset
48 // standard_name is unset
49 // coordinates is unset
50
51 // valid_min and valid_max are unset
52}
53
54VariableMetadata::VariableMetadata(const std::string &name,
55 const std::vector<std::tuple<std::string, int> > &dimensions,
56 std::shared_ptr<units::System> system)
57 : VariableMetadata(name, system, 0) {
58
59 for (const auto &dim : dimensions) {
60 m_dimensions.emplace_back(DimensionMetadata{std::get<0>(dim), system, std::get<1>(dim), false});
61 }
62}
63
64VariableMetadata::VariableMetadata(std::shared_ptr<units::System> system, const std::string &name,
65 const Grid &grid, const std::vector<double> &levels)
66 : VariableMetadata(name, system) {
67
69 m_grid_info.reset(new grid::DistributedGridInfo(grid.info()));
70
71 // spatial variables are time-dependent by default
73
74 m_dimensions = {{"y", system, (int)grid.My(), true},
75 {"x", system, (int)grid.Mx(), true}};
76
77 auto &x = dimension("x");
78 x["axis"] = "X";
79 x["long_name"] = "X-coordinate in Cartesian system";
80 x["standard_name"] = "projection_x_coordinate";
81 x["units"] = "m";
82 x["spacing_meters"] = { grid.dx() };
83
84 auto &y = dimension("y");
85 y["axis"] = "Y";
86 y["long_name"] = "Y-coordinate in Cartesian system";
87 y["standard_name"] = "projection_y_coordinate";
88 y["units"] = "m";
89 y["spacing_meters"] = { grid.dy() };
90
91 if (not levels.empty()) {
92 m_dimensions.push_back({ "z", system, (int)levels.size(), true });
93
94 auto &z = dimension("z");
95 z["axis"] = "Z";
96 z["long_name"] = "Z-coordinate in Cartesian system";
97 z["units"] = "m";
98 z["positive"] = "up";
99
100 {
101 auto nlevels = z.length();
102
103 double dz_max = nlevels > 1 ? levels[1] - levels[0] : 0.0;
104 double dz_min = levels.back() - levels.front();
105
106 for (int k = 0; k < nlevels - 1; ++k) {
107 double dz = levels[k + 1] - levels[k];
108 dz_max = std::max(dz_max, dz);
109 dz_min = std::min(dz_min, dz);
110 }
111
112 z["spacing_min_meters"] = { dz_min };
113 z["spacing_max_meters"] = { dz_max };
114 }
115 }
116
118}
119
120
121/** Get the number of spatial dimensions.
122 */
124 return m_n_spatial_dims;
125}
126
127std::vector<DimensionMetadata> VariableMetadata::dimensions() const {
128 return dimensions_impl();
129}
130
131std::vector<std::string> VariableMetadata::dimension_names() const {
132 std::vector<std::string> result;
133 for (const auto &dim : dimensions()) {
134 result.emplace_back(dim.get_name());
135 }
136 return result;
137}
138
139std::vector<DimensionMetadata> VariableMetadata::dimensions_impl() const {
140 return m_dimensions;
141}
142
143const DimensionMetadata& VariableMetadata::dimension(const std::string &name) const {
144 return const_cast<VariableMetadata*>(this)->dimension(name);
145}
146
148 for (auto &dim : m_dimensions) {
149 if (dim.get_name() == name) {
150 return dim;
151 }
152 }
153 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "variable '%s' does not have the '%s' dimension",
154 get_name().c_str(), name.c_str());
155}
156
158 if (m_grid_info != nullptr) {
159 return m_grid_info.get();
160 }
161 return nullptr;
162}
163
164const std::vector<double> &VariableMetadata::levels() const {
165 return m_levels;
166}
167
168/** A "time independent" variable will be saved to a NetCDF
169 variable which does not depend on the "time" dimension.
170 */
172 m_time_dependent = flag;
173
174 return *this;
175}
176
178 m_output_type = type;
179
180 return *this;
181}
182
186
190
194
195//! @brief Check if the range `[min, max]` is a subset of `[valid_min, valid_max]`.
196/*! Throws an exception if this check failed.
197 */
198void VariableMetadata::check_range(const std::string &filename, double min, double max) const {
199
200 auto units_string = get_string("units");
201 auto name_string = get_name();
202 const char *units = units_string.c_str(), *name = name_string.c_str(), *file = filename.c_str();
203 double eps = 1e-12;
204
205 if (has_attribute("valid_min") and has_attribute("valid_max")) {
206 double valid_min = get_number("valid_min");
207 double valid_max = get_number("valid_max");
208 if ((min < valid_min - eps) or (max > valid_max + eps)) {
211 "some values of '%s' in '%s' are outside the valid range [%e, %e] (%s).\n"
212 "computed min = %e %s, computed max = %e %s",
213 name, file, valid_min, valid_max, units, min, units, max, units);
214 }
215 } else if (has_attribute("valid_min")) {
216 double valid_min = get_number("valid_min");
217 if (min < valid_min - eps) {
220 "some values of '%s' in '%s' are less than the valid minimum (%e %s).\n"
221 "computed min = %e %s, computed max = %e %s",
222 name, file, valid_min, units, min, units, max, units);
223 }
224 } else if (has_attribute("valid_max")) {
225 double valid_max = get_number("valid_max");
226 if (max > valid_max + eps) {
229 "some values of '%s' in '%s' are greater than the valid maximum (%e %s).\n"
230 "computed min = %e %s, computed max = %e %s",
231 name, file, valid_max, units, min, units, max, units);
232 }
233 }
234}
235
236DimensionMetadata::DimensionMetadata(const std::string &name, std::shared_ptr<units::System> system,
237 int length, bool coordinate_variable)
238 : VariableMetadata(name, system), m_length(length), m_coordinate_variable(coordinate_variable) {
239 // empty
240}
241
243 return m_length;
244}
245
249
250std::vector<DimensionMetadata> DimensionMetadata::dimensions_impl() const {
251 return { *this };
252}
253
254//! Report the range of a \b global Vec `v`.
255void VariableMetadata::report_range(const Logger &log, double min, double max) const {
256
257 // units::Converter constructor will make sure that units are compatible.
258 units::Converter c(unit_system(), get_string("units"), get_string("output_units"));
259 min = c(min);
260 max = c(max);
261
262 std::string name = get_name();
263 std::string spacer(name.size(), ' ');
264 std::string info = get_string("long_name");
265
266 std::string units = get_string("output_units");
267 std::string range;
268 if (min == max) {
269 range = pism::printf("constant %9.3f %s", min, units.c_str());
270 } else {
271 range = pism::printf("min = %9.3f, max = %9.3f %s", min, max, units.c_str());
272 }
273
274 log.message(2,
275 " %s / %s\n"
276 " %s \\ %s\n",
277 name.c_str(), info.c_str(), spacer.c_str(), range.c_str());
278}
279
280bool VariableAttributes::is_set(const std::string &name) const {
281 auto j = strings.find(name);
282 if (j != strings.end()) {
283 if (name != "units" and (j->second).empty()) {
284 return false;
285 }
286
287 return true;
288 }
289
290 return (numbers.find(name) != numbers.end());
291}
292
293
294//! Checks if an attribute is present. Ignores empty strings, except
295//! for the "units" attribute.
296bool VariableMetadata::has_attribute(const std::string &name) const {
297 return m_attributes.is_set(name);
298}
299
301 return not(this->all_strings().empty() and this->all_doubles().empty());
302}
303
305 m_name = name;
306
307 return *this;
308}
309
310//! Set a scalar attribute to a single (scalar) value.
311VariableMetadata &VariableMetadata::set_number(const std::string &name, double value) {
312 return set_numbers(name, {value});
313}
314
315//! Set a scalar attribute to a single (scalar) value.
316VariableMetadata &VariableMetadata::set_numbers(const std::string &name, const std::vector<double> &values) {
317 m_attributes.numbers[name] = values;
318
319 return *this;
320}
321
323 m_attributes.numbers.clear();
324 m_attributes.strings.clear();
325
326 return *this;
327}
328
329std::string VariableMetadata::get_name() const {
330 return m_name;
331}
332
333//! Get a single-valued scalar attribute.
334double VariableMetadata::get_number(const std::string &name) const {
335 auto j = m_attributes.numbers.find(name);
336 if (j != m_attributes.numbers.end()) {
337 return (j->second)[0];
338 }
339
341 "variable \"%s\" does not have a double attribute \"%s\"",
342 get_name().c_str(), name.c_str());
343}
344
345//! Get an array-of-doubles attribute.
346std::vector<double> VariableMetadata::get_numbers(const std::string &name) const {
347 auto j = m_attributes.numbers.find(name);
348 if (j != m_attributes.numbers.end()) {
349 return j->second;
350 }
351
352 return {};
353}
354
355const std::map<std::string, std::string> &VariableMetadata::all_strings() const {
356 return m_attributes.strings;
357}
358
359const std::map<std::string, std::vector<double> > &VariableMetadata::all_doubles() const {
360 return m_attributes.numbers;
361}
362
366
367/*!
368 * Set units that may not be supported by UDUNITS.
369 *
370 * For example: "Pa s^(1/3)" (ice hardness units with Glen exponent n=3).
371 */
373 m_attributes.strings["units"] = value;
374 m_attributes.strings["output_units"] = value;
375
376 return *this;
377}
378
379//! Set a string attribute.
380VariableMetadata &VariableMetadata::set_string(const std::string &name, const std::string &value) {
381
382 if (name == "units") {
383 // create a dummy object to validate the units string
384 units::Unit tmp(unit_system(), value);
385
386 m_attributes.strings[name] = value;
387 m_attributes.strings["output_units"] = value;
388 } else if (name == "output_units") {
389 m_attributes.strings[name] = value;
390
391 if (not value.empty()) {
392 units::Unit internal(unit_system(), get_string("units"));
393 units::Unit output(unit_system(), value);
394
395 if (not units::are_convertible(internal, output)) {
397 "units \"%s\" and \"%s\" are not compatible",
398 get_string("units").c_str(), value.c_str());
399 }
400 }
401 } else if (name == "short_name") {
402 set_name(name);
403 } else {
404 m_attributes.strings[name] = value;
405 }
406
407 return *this;
408}
409
410//! Get a string attribute.
411/*!
412 * Returns an empty string if an attribute is not set.
413 */
414std::string VariableMetadata::get_string(const std::string &name) const {
415 if (name == "short_name") {
416 return get_name();
417 }
418
419 auto j = m_attributes.strings.find(name);
420 if (j != m_attributes.strings.end()) {
421 return j->second;
422 }
423
424 return {};
425}
426
427void VariableMetadata::report_to_stdout(const Logger &log, int verbosity_threshold) const {
428
429 const auto &strings = this->all_strings();
430 const auto &doubles = this->all_doubles();
431
432 // Find the maximum name length so that we can pad output below:
433 size_t max_name_length = 0;
434 for (const auto &s : strings) {
435 max_name_length = std::max(max_name_length, s.first.size());
436 }
437 for (const auto &d : doubles) {
438 max_name_length = std::max(max_name_length, d.first.size());
439 }
440
441 // Print text attributes:
442 for (const auto &s : strings) {
443 std::string name = s.first;
444 std::string value = s.second;
445 std::string padding(max_name_length - name.size(), ' ');
446
447 if (value.empty()) {
448 continue;
449 }
450
451 log.message(verbosity_threshold, " %s%s = \"%s\"\n",
452 name.c_str(), padding.c_str(), value.c_str());
453 }
454
455 // Print double attributes:
456 for (const auto &d : doubles) {
457 std::string name = d.first;
458 std::vector<double> values = d.second;
459 std::string padding(max_name_length - name.size(), ' ');
460
461 if (values.empty()) {
462 continue;
463 }
464
465 const double large = 1.0e7;
466 const double small = 1.0e-4;
467 if ((std::fabs(values[0]) >= large) || (std::fabs(values[0]) <= small)) {
468 // use scientific notation if a number is big or small
469 log.message(verbosity_threshold, " %s%s = %12.3e\n",
470 name.c_str(), padding.c_str(), values[0]);
471 } else {
472 log.message(verbosity_threshold, " %s%s = %12.5f\n",
473 name.c_str(), padding.c_str(), values[0]);
474 }
475
476 }
477}
478
479ConstAttribute::ConstAttribute(const VariableMetadata *var, const std::string &name)
480 : m_name(name), m_var(const_cast<VariableMetadata*>(var)) {
481}
482
484 : m_name(std::move(a.m_name)), m_var(a.m_var) {
485 a.m_name.clear();
486 a.m_var = nullptr;
487}
488
489ConstAttribute::operator std::string() const {
490 return m_var->get_string(m_name);
491}
492
493ConstAttribute::operator double() const {
494 auto values = m_var->get_numbers(m_name);
495 if (values.size() == 1) {
496 return values[0];
497 }
499 "%s:%s has more than one value",
500 m_var->get_name().c_str(), m_name.c_str());
501}
502
503ConstAttribute::operator std::vector<double> () const {
504 return m_var->get_numbers(m_name);
505}
506
507void Attribute::operator=(const std::string &value) {
508 m_var->set_string(m_name, value);
509}
510void Attribute::operator=(const std::initializer_list<double> &value) {
511 m_var->set_numbers(m_name, value);
512}
513
514void Attribute::operator=(const std::vector<double> &value) {
515 m_var->set_numbers(m_name, value);
516}
517
518} // end of namespace pism
void operator=(const std::string &value)
ConstAttribute(const ConstAttribute &)=delete
VariableMetadata * m_var
DimensionMetadata(const std::string &name, std::shared_ptr< units::System > system, int length, bool coordinate_variable=false)
std::vector< DimensionMetadata > dimensions_impl() const
Describes the PISM grid and the distribution of data across processors.
Definition Grid.hh:285
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
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
std::shared_ptr< units::System > unit_system
The unit system to use.
bool is_set(const std::string &name) const
std::map< std::string, std::string > strings
string and boolean attributes
std::map< std::string, std::vector< double > > numbers
scalar and array attributes
VariableMetadata(const std::string &name, std::shared_ptr< units::System > system, unsigned int ndims=0)
VariableMetadata & clear()
Clear all attributes.
std::vector< std::string > dimension_names() const
std::vector< double > m_levels
vertical grid levels (or similar)
std::shared_ptr< grid::DistributedGridInfo > m_grid_info
const VariableAttributes & attributes() const
DimensionMetadata & dimension(const std::string &name)
const std::vector< double > & levels() const
void report_to_stdout(const Logger &log, int verbosity_threshold) const
VariableAttributes m_attributes
VariableMetadata & units(const std::string &input)
VariableMetadata & set_name(const std::string &name)
unsigned int n_spatial_dimensions() const
VariableMetadata & set_number(const std::string &name, double value)
Set a scalar attribute to a single (scalar) value.
virtual std::vector< DimensionMetadata > dimensions_impl() const
double get_number(const std::string &name) const
Get a single-valued scalar attribute.
std::vector< DimensionMetadata > m_dimensions
std::string get_string(const std::string &name) const
Get a string attribute.
const grid::DistributedGridInfo * grid_info() const
const std::map< std::string, std::string > & all_strings() const
void report_range(const Logger &log, double min, double max) const
Report the range of a global Vec v.
std::shared_ptr< units::System > unit_system() const
io::Type get_output_type() const
VariableMetadata & set_units_without_validation(const std::string &value)
std::vector< double > get_numbers(const std::string &name) const
Get an array-of-doubles attribute.
VariableMetadata & set_output_type(io::Type type)
VariableMetadata & set_time_dependent(bool flag)
bool has_attribute(const std::string &name) const
VariableMetadata & set_numbers(const std::string &name, const std::vector< double > &values)
Set a scalar attribute to a single (scalar) value.
std::string get_name() const
VariableMetadata & set_string(const std::string &name, const std::string &value)
Set a string attribute.
void check_range(const std::string &filename, double min, double max) const
Check if the range [min, max] is a subset of [valid_min, valid_max].
std::vector< DimensionMetadata > dimensions() const
const std::map< std::string, std::vector< double > > & all_doubles() const
std::shared_ptr< System > Ptr
Definition Units.hh:47
#define PISM_ERROR_LOCATION
@ PISM_DOUBLE
Definition IO_Flags.hh:53
bool are_convertible(const Unit &u1, const Unit &u2)
Definition Units.cc:247
std::string printf(const char *format,...)
static const double k
Definition exactTestP.cc:42