PISM, A Parallel Ice Sheet Model 2.3.3-e0e7dfb06 committed by Constantine Khrulev on 2026-10-20
Loading...
Searching...
No Matches
Units.cc
Go to the documentation of this file.
1/* Copyright (C) 2013, 2014, 2015, 2016, 2017, 2018, 2020, 2023, 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 2 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 <limits>
20
21#include "pism/util/Units.hh"
22
23#include <memory>
24#include <string>
25#include <udunits2.h>
26
27#include "pism/external/calcalcs/utCalendar2_cal.h"
28
29#include "pism/external/calcalcs/calcalcs.h"
30
31#include "pism/util/error_handling.hh"
32#include "pism/util/pism_utilities.hh"
33
34namespace pism {
35
36namespace units {
37
39 Impl(const std::string &path) {
40 ut_system *tmp;
41
42 ut_set_error_message_handler(ut_ignore);
43
44 if (not path.empty()) {
45 tmp = ut_read_xml(path.c_str());
46 } else {
47 tmp = ut_read_xml(NULL);
48 }
49
50 if (tmp == NULL) {
51 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "ut_read_xml(%s) failed", path.c_str());
52 }
53 ut_set_error_message_handler(ut_write_to_stderr);
54
55 system = tmp;
56 }
58 ut_free_system(system);
59 }
60 ut_system *system;
61};
62
63/** Initialize the unit system by reading from an XML unit
64 * definition file.
65 */
66System::System(const std::string &path) {
67 m_impl.reset(new Impl(path));
68}
69
70//! \brief Convert a quantity from unit1 to unit2.
71/*!
72 * Example: convert(1, "m year-1", "m second-1").
73 *
74 * Please avoid using in computationally-intensive code.
75 */
76double convert(System::Ptr system, double input,
77 const std::string &spec1, const std::string &spec2) {
78 Converter c(Unit(system, spec1), Unit(system, spec2));
79
80 return c(input);
81}
82
83struct Unit::Impl {
84 Impl(System::Ptr sys, const std::string &spec)
85 : system(sys), unit_string(spec) {
86 unit = ut_parse(sys->m_impl->system, spec.c_str(), UT_ASCII);
87
88 if (unit == NULL) {
90 "unit specification '%s' is unknown or invalid",
91 spec.c_str());
92 }
93 }
94 Impl(const Unit::Impl &other) {
95
96 unit = ut_clone(other.unit);
97 if (unit == NULL) {
98 throw RuntimeError(PISM_ERROR_LOCATION, "ut_clone failed");
99 }
100
101 system = other.system;
102 unit_string = other.unit_string;
103 }
105 reset();
106 }
107 void reset() {
108 ut_free(unit);
109 unit_string = "";
110 }
111
113 std::string unit_string;
114 ut_unit *unit;
115};
116
117Unit::Unit(System::Ptr system, const std::string &spec) {
118 m_impl.reset(new Impl(system, spec));
119}
120
121Unit::Unit(const Unit &other) {
122 m_impl.reset(new Impl(*other.m_impl));
123}
124
125Unit& Unit::operator=(const Unit& other) {
126 if (this == &other) {
127 return *this;
128 }
129
130 reset();
131
132 m_impl->system = other.m_impl->system;
133 m_impl->unit_string = other.m_impl->unit_string;
134
135 m_impl->unit = ut_clone(other.m_impl->unit);
136 if (m_impl->unit == NULL) {
137 throw RuntimeError(PISM_ERROR_LOCATION, "ut_clone failed");
138 }
139
140 return *this;
141}
142
143bool Unit::is_convertible(const Unit &other) const {
144 return ut_are_convertible(m_impl->unit, other.m_impl->unit) != 0;
145}
146
147std::string Unit::string() const {
148 return m_impl->unit_string;
149}
150
151Unit::operator std::string() const {
152 return m_impl->unit_string;
153}
154
155
157 m_impl->reset();
158}
159
161 return m_impl->system;
162}
163
164DateTime Unit::date(double T, const std::string &calendar) const {
165
166 int year, month, day, hour, minute;
167 double second;
168 int errcode = utCalendar2_cal(T, m_impl->unit,
169 &year, &month, &day,
170 &hour, &minute, &second,
171 calendar.c_str());
172 if (errcode != 0) {
174 "cannot convert time %f to a date in calendar %s",
175 T, calendar.c_str());
176 }
177 return {year, month, day, hour, minute, second, calendar};
178}
179
180double Unit::time(const DateTime &d) const {
181 double result;
182
183 int errcode = utInvCalendar2_cal(d.year, d.month, d.day,
184 d.hour, d.minute, d.second,
185 m_impl->unit,
186 &result,
187 d.calendar.c_str());
188 if (errcode != 0) {
190 "cannot convert date and time %d-%d-%d %d:%d:%f to time in calendar %s",
191 d.year, d.month, d.day,
192 d.hour, d.minute, d.second,
193 d.calendar.c_str());
194 }
195 return result;
196}
197
200 converter = cv_get_trivial();
201 }
202 Impl(System::Ptr sys, const std::string &spec1, const std::string &spec2) {
203
204 Unit u1(sys, spec1), u2(sys, spec2);
205
206 if (not u1.is_convertible(u2)) {
208 "cannot convert '%s' to '%s'",
209 spec1.c_str(), spec2.c_str());
210 }
211
212 converter = ut_get_converter(u1.m_impl->unit, u2.m_impl->unit);
213 if (converter == nullptr) {
215 "cannot create a converter from %s to %s",
216 spec1.c_str(), spec2.c_str());
217 }
218
219 }
220 Impl(const Unit &u1, const Unit &u2) {
221 if (not u1.is_convertible(u2)) {
222 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "cannot convert '%s' to '%s'",
223 u1.string().c_str(), u2.string().c_str());
224 }
225
226 converter = ut_get_converter(u1.m_impl->unit, u2.m_impl->unit);
227 if (converter == nullptr) {
229 "failed to create a converter from '%s' to '%s'",
230 u1.string().c_str(), u2.string().c_str());
231 }
232
233 }
235 cv_free(converter);
236 converter = NULL;
237 }
238 cv_converter *converter;
239};
240
242 m_impl.reset(new Impl());
243}
244
246 const std::string &spec1, const std::string &spec2) {
247 m_impl.reset(new Impl(sys, spec1, spec2));
248}
249
250Converter::Converter(const Unit &u1, const Unit &u2) {
251 m_impl.reset(new Impl(u1, u2));
252}
253
254bool are_convertible(const Unit &u1, const Unit &u2) {
255 return u1.is_convertible(u2);
256}
257
258double Converter::operator()(double input) const {
259 return cv_convert_double(m_impl->converter, input);
260}
261
262void Converter::convert_doubles(double *data, size_t length) const {
263 cv_convert_doubles(m_impl->converter, data, length, data);
264}
265
266DateTime::DateTime(int year_, int month_, int day_, int hour_, int minute_, double second_,
267 const std::string &calendar_) {
268 year = year_;
269 month = month_;
270 day = day_;
271 hour = hour_;
272 minute = minute_;
273 second = second_;
274 calendar = calendar_;
275}
276
277DateTime::DateTime(const std::string &input, const std::string &input_calendar) {
278
279 auto spec = string_strip(input);
280
281 // separate spec into date and time
282 std::string date, time;
283 {
284 auto parts = split(spec, ' ');
285
286 if (parts.empty()) {
289 "expected a date-time spec consisting of date and time separated by spaces; got '%s'",
290 spec.c_str());
291 }
292
293 date = parts[0];
294 if (parts.size() == 1) {
295 time = "0:0:0";
296 } else if (parts.size() == 2) {
297 time = parts[1];
298 } else {
301 "expected a date-time spec consisting of date and time separated by spaces; got '%s'",
302 spec.c_str());
303 }
304 }
305
306 std::vector<int> date_parts = { 0, 0, 0 };
307 std::vector<int> time_parts = { 0, 0, 0 };
308 try {
309 // date
310 {
311 // We need to remember if the year was negative in the input string: split() will ignore
312 // empty tokens separated by "-", so "-1000-1-1" will produce ["1000", "1", "1"].
313 bool year_is_negative = (date[0] == '-');
314
315 if (year_is_negative and not set_member(input_calendar, { "365_day", "360_day", "noleap" })) {
318 "negative dates such as '%s' are not allowed with the '%s' calendar.\n"
319 "Please submit a bug report if this is a problem.",
320 date.c_str(), input_calendar.c_str());
321 }
322
323 auto parts = split(date, '-');
324 if (parts.size() != 3) {
327 "date string has to contain 3 parts separated by '-' (Y-M-D); got '%s'", spec.c_str());
328 }
329
330 for (int k = 0; k < 3; ++k) {
331 auto n = parse_integer(parts[k]);
332
333 if (n > std::numeric_limits<int>::max() or n < std::numeric_limits<int>::min()) {
334 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "%ld in '%s' does not fit in an 'int'",
335 n, spec.c_str());
336 }
337
338 date_parts[k] = static_cast<int>(n);
339 }
340
341 date_parts[0] *= year_is_negative ? -1 : 1;
342 }
343
344 // time
345 {
346 auto parts = split(time, ':');
347 if (parts.size() < 2 or parts.size() > 3) {
350 "time string has to contain 2 or 3 parts separated by ':' (H:M:S); got '%s'",
351 time.c_str());
352 }
353
354 for (int k = 0; k < (int)parts.size(); ++k) {
355 auto n = parse_integer(parts[k]);
356
357 if (n > std::numeric_limits<int>::max() or n < std::numeric_limits<int>::min()) {
358 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "%ld in '%s' does not fit in an 'int'",
359 n, spec.c_str());
360 }
361
362 time_parts[k] = static_cast<int>(n);
363 }
364 }
365 } catch (RuntimeError &e) {
366 e.add_context("parsing a date-time specification %s", spec.c_str());
367 throw;
368 }
369
370 {
371 year = date_parts[0];
372 month = date_parts[1];
373 day = date_parts[2];
374 hour = time_parts[0];
375 minute = time_parts[1];
376 second = time_parts[2];
377 calendar = input_calendar;
378 }
379
380 // Validate the calendar string and the date in this calendar:
381 {
383 if (cal == NULL) {
384 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "calendar string '%s' is invalid",
385 input_calendar.c_str());
386 }
387
388 int jday_dummy = 0;
389 int errcode = ccs_date2jday(cal, year, month, day, &jday_dummy);
390 if (errcode != 0) {
392 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "date %s is invalid in the %s calendar",
393 spec.c_str(), calendar.c_str());
394 }
396 }
397
398 // Validate hour
399 if (hour < 0 or hour > 23) {
400 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "invalid hour in the date-time spec '%s'",
401 spec.c_str());
402 }
403 // Validate minute
404 if (minute < 0 or minute > 59) {
405 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "invalid minute in the date-time spec '%s'",
406 spec.c_str());
407 }
408 // Validate second
409 if (second < 0.0 or second >= 60.0) {
410 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "invalid second in the date-time spec '%s'",
411 spec.c_str());
412 }
413}
414
415
416} // end of namespace units
417
418} // end of namespace pism
void ccs_free_calendar(calcalcs_cal *cc)
Definition calcalcs.c:1338
int ccs_date2jday(calcalcs_cal *calendar, int year, int month, int day, int *jday)
Definition calcalcs.c:442
calcalcs_cal * ccs_init_calendar(const char *calname)
Definition calcalcs.c:104
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
std::shared_ptr< Impl > m_impl
Definition Units.hh:125
double operator()(double input) const
Definition Units.cc:258
void convert_doubles(double *data, size_t length) const
Definition Units.cc:262
System(const std::string &path="")
Definition Units.cc:66
std::shared_ptr< Impl > m_impl
Definition Units.hh:52
std::shared_ptr< System > Ptr
Definition Units.hh:47
double time(const DateTime &d) const
Definition Units.cc:180
DateTime date(double T, const std::string &calendar) const
Definition Units.cc:164
Unit & operator=(const Unit &other)
Definition Units.cc:125
System::Ptr system() const
Definition Units.cc:160
std::string string() const
Definition Units.cc:147
Unit(System::Ptr system, const std::string &spec)
Definition Units.cc:117
std::shared_ptr< Impl > m_impl
Definition Units.hh:93
bool is_convertible(const Unit &other) const
Definition Units.cc:143
#define PISM_ERROR_LOCATION
#define n
Definition exactTestM.c:37
bool are_convertible(const Unit &u1, const Unit &u2)
Definition Units.cc:254
double convert(System::Ptr system, double input, const std::string &spec1, const std::string &spec2)
Convert a quantity from unit1 to unit2.
Definition Units.cc:76
static const double k
Definition exactTestP.cc:42
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
long int parse_integer(const std::string &input)
bool set_member(const std::string &string, const std::set< std::string > &set)
std::vector< std::string > split(const std::string &input, char separator)
Transform a separator-separated list (a string) into a vector of strings.
cv_converter * converter
Definition Units.cc:238
Impl(const Unit &u1, const Unit &u2)
Definition Units.cc:220
Impl(System::Ptr sys, const std::string &spec1, const std::string &spec2)
Definition Units.cc:202
std::string calendar
Definition Units.hh:69
DateTime(int year_, int month_, int day_, int hour_, int minute_, double second_, const std::string &calendar_)
Definition Units.cc:266
Impl(const std::string &path)
Definition Units.cc:39
Impl(System::Ptr sys, const std::string &spec)
Definition Units.cc:84
Impl(const Unit::Impl &other)
Definition Units.cc:94
std::string unit_string
Definition Units.cc:113
System::Ptr system
Definition Units.cc:112
int utCalendar2_cal(double val, ut_unit *dataunits, int *year, int *month, int *day, int *hour, int *minute, double *second, const char *calendar_name)
int utInvCalendar2_cal(int year, int month, int day, int hour, int minute, double second, ut_unit *user_unit, double *value, const char *calendar_name)