PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
pism_utilities.cc
Go to the documentation of this file.
1/* Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026 PISM Authors
2 *
3 * This file is part of PISM.
4 *
5 * PISM is free software; you can redistribute it and/or modify it under the
6 * terms of the GNU General Public License as published by the Free Software
7 * Foundation; either version 3 of the License, or (at your option) any later
8 * version.
9 *
10 * PISM is distributed in the hope that it will be useful, but WITHOUT ANY
11 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 * details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with PISM; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include "pism/util/pism_utilities.hh"
21
22#include <cstdarg> // va_list, va_start(), va_end()
23#include <sstream> // istringstream, ostringstream
24#include <cstdio> // vsnprintf
25#include <cassert> // assert
26
27#include <mpi.h> // MPI_Get_library_version
28#include <fftw3.h> // fftw_version
29#include <gsl/gsl_version.h> // GSL_VERSION
30
31#include "pism/pism_config.hh" // Pism_USE_XXX, version info
32
33// The following is a stupid kludge necessary to make NetCDF 4.x work in
34// serial mode in an MPI program:
35#ifndef MPI_INCLUDED
36#define MPI_INCLUDED 1
37#endif
38#include <netcdf.h> // nc_inq_libvers
39#ifdef NC_HAVE_META_H
40#include <netcdf_meta.h> // NC_VERSION_MAJOR, etc
41#endif
42
43#if (Pism_USE_PROJ==1)
44#include "pism/util/Proj.hh" // pj_release
45#endif
46
47#include <petsctime.h> // PetscTime
48
49#include <cstdlib> // strtol(), strtod()
50
51#include "pism/util/error_handling.hh"
52
53namespace pism {
54
55std::string string_strip(const std::string &input) {
56 if (input.empty()) {
57 return "";
58 }
59
60 std::string tmp = input;
61
62 // strip leading spaces
63 tmp.erase(0, tmp.find_first_not_of(" \t"));
64
65 // strip trailing spaces
66 tmp.erase(tmp.find_last_not_of(" \t") + 1);
67
68 return tmp;
69}
70
71//! Returns true if `str` ends with `suffix` and false otherwise.
72bool ends_with(const std::string &str, const std::string &suffix) {
73 if (suffix.size() > str.size()) {
74 return false;
75 }
76
77 return (str.rfind(suffix) + suffix.size() == str.size());
78}
79
80template <class T>
81std::string join_impl(const T& input, const std::string& separator) {
82 if (input.empty()) {
83 return "";
84 }
85
86 auto j = input.begin();
87 std::string result = *j;
88 ++j;
89 while (j != input.end()) {
90 result += separator + *j;
91 ++j;
92 }
93 return result;
94}
95
96//! Concatenate `strings`, inserting `separator` between elements.
97std::string join(const std::vector<std::string> &strings, const std::string &separator) {
98 return join_impl(strings, separator);
99}
100
101std::string set_join(const std::set<std::string> &input, const std::string& separator) {
102 return join_impl(input, separator);
103}
104
105/*!
106 * Replace all occurrences of the character `from` with `to` and return the resulting string.
107 *
108 * We could use std::regex_replace(), but this will do for now.
109 */
110std::string replace_character(const std::string &input, char from, char to) {
111 std::string result;
112 for (const char &c : input) {
113 result += c == from ? to : c;
114 }
115 return result;
116}
117
118
119//! Transform a `separator`-separated list (a string) into a vector of strings.
120std::vector<std::string> split(const std::string &input, char separator) {
121 std::istringstream input_list(input);
122 std::string token;
123 std::vector<std::string> result;
124
125 while (getline(input_list, token, separator)) {
126 if (not token.empty()) {
127 result.emplace_back(token);
128 }
129 }
130 return result;
131}
132
133//! Transform a `separator`-separated list (a string) into a set of strings.
134std::set<std::string> set_split(const std::string &input, char separator) {
135 std::set<std::string> result;
136 for (const auto &token : split(input, separator)) {
137 result.insert(token);
138 }
139 return result;
140}
141
142//! Checks if a vector of doubles is strictly increasing.
143bool is_increasing(const std::vector<double> &a) {
144 int len = (int)a.size();
145 for (int k = 0; k < len-1; k++) {
146 if (a[k] >= a[k+1]) {
147 return false;
148 }
149 }
150 return true;
151}
152
153template<typename T, typename A>
154bool member(const T& x, const A& iterable) {
155 return std::any_of(iterable.begin(), iterable.end(),
156 [&x](const std::string &y) { return x == y; });
157}
158
159bool set_member(const std::string &string, const std::set<std::string> &set) {
160 return member(string, set);
161}
162
163bool vector_member(const std::string &string, const std::vector<std::string> &vector) {
164 return member(string, vector);
165}
166
167void GlobalReduce(MPI_Comm comm, double *local, double *result, int count, MPI_Op op) {
168 assert(local != result);
169 int err = MPI_Allreduce(local, result, count, MPI_DOUBLE, op, comm);
170 PISM_C_CHK(err, 0, "MPI_Allreduce");
171}
172
173void GlobalReduce(MPI_Comm comm, int *local, int *result, int count, MPI_Op op) {
174 assert(local != result);
175 int err = MPI_Allreduce(local, result, count, MPI_INT, op, comm);
176 PISM_C_CHK(err, 0, "MPI_Allreduce");
177}
178
179void GlobalMin(MPI_Comm comm, double *local, double *result, int count) {
180 GlobalReduce(comm, local, result, count, MPI_MIN);
181}
182
183void GlobalMax(MPI_Comm comm, double *local, double *result, int count) {
184 GlobalReduce(comm, local, result, count, MPI_MAX);
185}
186
187void GlobalMax(MPI_Comm comm, int *local, int *result, int count) {
188 GlobalReduce(comm, local, result, count, MPI_MAX);
189}
190
191void GlobalSum(MPI_Comm comm, double *local, double *result, int count) {
192 GlobalReduce(comm, local, result, count, MPI_SUM);
193}
194
195void GlobalSum(MPI_Comm comm, int *local, int *result, int count) {
196 GlobalReduce(comm, local, result, count, MPI_SUM);
197}
198
199unsigned int GlobalSum(MPI_Comm comm, unsigned int input) {
200 unsigned int result;
201 int err = MPI_Allreduce(&input, &result, 1, MPI_UNSIGNED, MPI_SUM, comm);
202 PISM_C_CHK(err, 0, "MPI_Allreduce");
203 return result;
204}
205
206int GlobalSum(MPI_Comm comm, int input) {
207 int result;
208 int err = MPI_Allreduce(&input, &result, 1, MPI_INT, MPI_SUM, comm);
209 PISM_C_CHK(err, 0, "MPI_Allreduce");
210 return result;
211}
212
213double GlobalMin(MPI_Comm comm, double local) {
214 double result;
215 GlobalMin(comm, &local, &result, 1);
216 return result;
217}
218
219double GlobalMax(MPI_Comm comm, double local) {
220 double result;
221 GlobalMax(comm, &local, &result, 1);
222 return result;
223}
224
225int GlobalMax(MPI_Comm comm, int local) {
226 int result;
227 GlobalMax(comm, &local, &result, 1);
228 return result;
229}
230
231double GlobalSum(MPI_Comm comm, double local) {
232 double result;
233 GlobalSum(comm, &local, &result, 1);
234 return result;
235}
236
237static const int TEMPORARY_STRING_LENGTH = 32768;
238
239std::string version() {
240 char buffer[TEMPORARY_STRING_LENGTH];
241 std::string result;
242
243 result += pism::printf("PISM (%s)\n", pism::revision);
244 result += pism::printf("CMake %s.\n", pism::cmake_version);
245
246 PetscGetVersion(buffer, TEMPORARY_STRING_LENGTH);
247 result += buffer;
248 result += "\n";
249 result += pism::printf("PETSc configure: %s\n", pism::petsc_configure_flags);
250
251 // OpenMPI added MPI_Get_library_version in version 1.7 (relatively recently).
252#ifdef OPEN_MPI
253 result += pism::printf("OpenMPI %d.%d.%d\n",
254 OMPI_MAJOR_VERSION,
255 OMPI_MINOR_VERSION,
256 OMPI_RELEASE_VERSION);
257#else
258 // Assume that other MPI libraries implement this part of the MPI-3 standard...
259 int string_length = TEMPORARY_STRING_LENGTH;
260 MPI_Get_library_version(buffer, &string_length);
261 result += buffer;
262#endif
263
264 result += pism::printf("NetCDF %s.\n", nc_inq_libvers());
265 result += pism::printf("FFTW %s.\n", fftw_version);
266 result += pism::printf("GSL %s.\n", GSL_VERSION);
267
268#if (Pism_USE_PROJ==1)
269 result += pism::printf("PROJ %s.\n", pj_release);
270#endif
271
272#if (Pism_BUILD_PYTHON_BINDINGS==1)
273 result += pism::printf("SWIG %s.\n", pism::swig_version);
274 result += pism::printf("petsc4py %s.\n", pism::petsc4py_version);
275#endif
276
277 return result;
278}
279
281#ifdef NC_HAVE_META_H
282 return 100 * NC_VERSION_MAJOR + 10 * NC_VERSION_MINOR + NC_VERSION_PATCH;
283#else
284 return 0; // unknown
285#endif
286}
287
288
289//! Return time since the beginning of the run, in hours.
290double wall_clock_hours(MPI_Comm com, double start_time) {
291 const double seconds_per_hour = 3600.0;
292
293 return (get_time(com) - start_time) / seconds_per_hour;
294}
295
296//! Creates a time-stamp used for the history NetCDF attribute.
297std::string timestamp(MPI_Comm com) {
298 int rank = 0;
299 MPI_Comm_rank(com, &rank);
300
301 char date_str[50];
302 if (rank == 0) {
303 time_t now;
304 tm tm_now;
305 now = time(NULL);
306 localtime_r(&now, &tm_now);
307 // Format specifiers for strftime():
308 // %F = ISO date format, %T = Full 24 hour time, %Z = Time Zone name
309 strftime(date_str, sizeof(date_str), "%F %T %Z", &tm_now);
310 }
311
312 MPI_Bcast(date_str, 50, MPI_CHAR, 0, com);
313
314 return std::string(date_str);
315}
316
317//! Creates a string with the user name, hostname and the time-stamp (for history strings).
318std::string username_prefix(MPI_Comm com) {
319 PetscErrorCode ierr;
320
321 char username[50];
322 ierr = PetscGetUserName(username, sizeof(username));
323 PISM_CHK(ierr, "PetscGetUserName");
324 if (ierr != 0) {
325 username[0] = '\0';
326 }
327 char hostname[100];
328 ierr = PetscGetHostName(hostname, sizeof(hostname));
329 PISM_CHK(ierr, "PetscGetHostName");
330 if (ierr != 0) {
331 hostname[0] = '\0';
332 }
333
334 auto time = timestamp(com);
335 std::string result = pism::printf("%s@%s %s: ", username, hostname, time.c_str());
336
337 unsigned int length = result.size();
338 MPI_Bcast(&length, 1, MPI_UNSIGNED, 0, com);
339
340 result.resize(length);
341 MPI_Bcast(&result[0], length, MPI_CHAR, 0, com);
342
343 return result;
344}
345
346//! \brief Uses argc and argv to create the string with current PISM
347//! command-line arguments.
348std::string args_string() {
349 int argc;
350 char **argv;
351 PetscErrorCode ierr = PetscGetArgs(&argc, &argv);
352 PISM_CHK(ierr, "PetscGetArgs");
353
354 std::string cmdstr;
355 std::string argument;
356 for (int j = 0; j < argc; j++) {
357 argument = argv[j];
358
359 // enclose arguments containing spaces with double quotes:
360 if (argument.find(' ') != std::string::npos) {
361 argument = "\"" + argument + "\"";
362 }
363
364 cmdstr += std::string(" ") + argument;
365 }
366 cmdstr += "\n";
367
368 return cmdstr;
369}
370
371//! \brief Adds a suffix to a filename.
372/*!
373 * Returns filename + separator + suffix + .nc if the original filename had the
374 * .nc suffix, otherwise filename + separator. If the old filename had the form
375 * "name + separator + more stuff + .nc", then removes the string after the
376 * separator.
377 */
378std::string filename_add_suffix(const std::string &filename, const std::string &separator,
379 const std::string &suffix) {
380 std::string basename = filename;
381 std::string result;
382
383 // find where the separator begins:
384 std::string::size_type j = basename.rfind(separator);
385 if (j == std::string::npos) {
386 j = basename.rfind(".nc");
387 }
388
389 // if the separator was not found, find the .nc suffix:
390 if (j == std::string::npos) {
391 j = basename.size();
392 }
393
394 // cut off everything starting from the separator (or the .nc suffix):
395 basename.resize(static_cast<int>(j));
396
397 result = basename + separator + suffix;
398
399 if (ends_with(filename, ".nc")) {
400 result += ".nc";
401 }
402
403 return result;
404}
405
406double get_time(MPI_Comm comm) {
407 int rank = 0;
408 double result = 0.0;
409
410 MPI_Comm_rank(comm, &rank);
411
412 ParallelSection rank0(comm);
413 try {
414 if (rank == 0) {
415 PetscLogDouble tmp;
416 PetscErrorCode ierr = PetscTime(&tmp); PISM_CHK(ierr, "PetscTime");
417 result = tmp;
418 }
419 } catch (...) {
420 rank0.failed();
421 }
422 rank0.check();
423
424 MPI_Bcast(&result, 1, MPI_DOUBLE, 0, comm);
425
426 return result;
427}
428
429std::string printf(const char* format, ...) {
430 std::string result(1024, '\0'); // allocate and zero-terminate
431 va_list ap;
432 va_start(ap, format);
433
434 // first try
435 va_list ap1; va_copy(ap1, ap);
436 int n = std::vsnprintf(&result[0], result.size(), format, ap1);
437 va_end(ap1);
438 if (n < 0) { va_end(ap); return {}; }
439
440 if (static_cast<size_t>(n) >= result.size()) {
441 // need a bigger buffer: resize (not reserve), and use a *fresh* va_list
442 result.resize(static_cast<size_t>(n) + 1); // +1 for '\0' space
443 va_list ap2; va_copy(ap2, ap);
444 std::vsnprintf(&result[0], result.size(), format, ap2);
445 va_end(ap2);
446 }
447
448 va_end(ap);
449 result.resize(static_cast<size_t>(n)); // drop the '\0'
450 return result;
451}
452
453/*!
454 * Validate a format string. In this application a format string should contain `%` exactly
455 * once, followed by `s` (i.e. `%s`).
456 *
457 * Throws RuntimeError if the provided string is invalid.
458 */
459void validate_format_string(const std::string &format) {
460 if (format.find("%s") == std::string::npos) {
461 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "format string %s does not contain %%s",
462 format.c_str());
463 }
464
465 if (format.find('%') != format.rfind('%')) {
466 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "format string %s contains more than one %%",
467 format.c_str());
468 }
469}
470
471double vector_min(const std::vector<double> &input) {
472 double my_min = input[0];
473 for (auto x : input) {
474 my_min = std::min(x, my_min);
475 }
476 return my_min;
477}
478
479double vector_max(const std::vector<double> &input) {
480 double my_max = input[0];
481 for (auto x : input) {
482 my_max = std::max(x, my_max);
483 }
484 return my_max;
485}
486
487
488/*!
489 * Fletcher's checksum
490 *
491 * See https://en.wikipedia.org/wiki/Fletcher%27s_checksum#Optimizations
492 */
493uint64_t fletcher64(const uint32_t *data, size_t length) {
494 // Accumulating a sum of block_size unsigned 32-bit integers in an unsigned 64-bit
495 // integer will not lead to an overflow.
496 //
497 // This constant is found by solving n * (n + 1) / 2 * (2^32 - 1) < (2^64 - 1).
498 static const size_t block_size = 92681;
499
500 uint64_t c0 = 0;
501 uint64_t c1 = 0;
502 while (length != 0) {
503 size_t block = std::min(block_size, length);
504
505 for (size_t i = 0; i < block; ++i) {
506 c0 = c0 + *data++;
507 c1 = c1 + c0;
508 }
509
510 c0 = c0 % UINT32_MAX;
511 c1 = c1 % UINT32_MAX;
512
513 length = length > block_size ? length - block_size : 0;
514 }
515 return (c1 << 32 | c0);
516}
517
518/*!
519 * Call fletcher64() to compute the checksum and print it.
520 */
521void print_checksum(MPI_Comm com,
522 const std::vector<double> &data,
523 const char *label) {
524 int rank = 0;
525 MPI_Comm_rank(com, &rank);
526
527 uint64_t sum = fletcher64((uint32_t*)data.data(), data.size() * 2);
528
529 PetscPrintf(PETSC_COMM_SELF, "[%d] %s: %016llx\n",
530 rank, label, (unsigned long long int)sum);
531}
532
533void print_vector(MPI_Comm com,
534 const std::vector<double> &data,
535 const char *label) {
536 int rank = 0;
537 MPI_Comm_rank(com, &rank);
538
539 std::vector<std::string> tmp;
540 tmp.reserve(data.size());
541 for (const auto &f : data) {
542 tmp.emplace_back(pism::printf("%f", f));
543 }
544
545 auto str = join(tmp, ",");
546
547 PetscPrintf(PETSC_COMM_SELF, "[%d] %s: %s\n",
548 rank, label, str.c_str());
549}
550
551void print_vector(MPI_Comm com,
552 const std::vector<int> &data,
553 const char *label) {
554 int rank = 0;
555 MPI_Comm_rank(com, &rank);
556
557 std::vector<std::string> tmp;
558 tmp.reserve(data.size());
559 for (const auto &f : data) {
560 tmp.emplace_back(pism::printf("%d", f));
561 }
562
563 auto str = join(tmp, ",");
564
565 PetscPrintf(PETSC_COMM_SELF, "[%d] %s: %s\n",
566 rank, label, str.c_str());
567}
568
569/*!
570 * Compute water column pressure vertically-averaged over the height of an ice cliff at a
571 * margin.
572 */
573double average_water_column_pressure(double ice_thickness, double bed,
574 double floatation_level, double rho_ice,
575 double rho_water, double g) {
576
577 double ice_bottom = std::max(bed, floatation_level - rho_ice / rho_water * ice_thickness);
578 double water_column_height = std::max(floatation_level - ice_bottom, 0.0);
579
580 if (ice_thickness > 0.0) {
581 return 0.5 * rho_water * g * pow(water_column_height, 2.0) / ice_thickness;
582 }
583 return 0.0;
584}
585
586double parse_number(const std::string &input) {
587 char *endptr = NULL;
588 double result = strtod(input.c_str(), &endptr);
589 if (*endptr != '\0') {
591 "Can't parse %s (expected a floating point number)",
592 input.c_str());
593 }
594 return result;
595}
596
597std::vector<double> parse_number_list(const std::string &input) {
598 std::vector<double> result;
599
600 for (const auto &p : split(input, ',')) {
601 result.push_back(parse_number(p));
602 }
603 return result;
604}
605
606long int parse_integer(const std::string &input) {
607 char *endptr = NULL;
608 long int result = strtol(input.c_str(), &endptr, 10);
609 if (*endptr != '\0') {
611 "Can't parse %s (expected an integer)",
612 input.c_str());
613 }
614 return result;
615}
616
617std::vector<long> parse_integer_list(const std::string &input) {
618 std::vector<long> result;
619
620 for (const auto &p : split(input, ',')) {
621 result.push_back(parse_integer(p));
622 }
623 return result;
624}
625
626} // end of namespace pism
void failed()
Indicates a failure of a parallel section.
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
#define PISM_CHK(errcode, name)
#define PISM_ERROR_LOCATION
#define PISM_C_CHK(errcode, success, name)
const double rho_ice
Definition exactTestK.c:31
#define n
Definition exactTestM.c:37
double get_time(MPI_Comm comm)
bool is_increasing(const std::vector< double > &a)
Checks if a vector of doubles is strictly increasing.
double parse_number(const std::string &input)
bool member(const T &x, const A &iterable)
double average_water_column_pressure(double ice_thickness, double bed, double floatation_level, double rho_ice, double rho_water, double g)
double wall_clock_hours(MPI_Comm com, double start_time)
Return time since the beginning of the run, in hours.
std::vector< long > parse_integer_list(const std::string &input)
void print_checksum(MPI_Comm com, const std::vector< double > &data, const char *label)
static const double g
Definition exactTestP.cc:36
bool ends_with(const std::string &str, const std::string &suffix)
Returns true if str ends with suffix and false otherwise.
std::vector< double > parse_number_list(const std::string &input)
void GlobalMax(MPI_Comm comm, double *local, double *result, int count)
bool vector_member(const std::string &string, const std::vector< std::string > &vector)
std::string set_join(const std::set< std::string > &input, const std::string &separator)
std::string printf(const char *format,...)
int netcdf_version()
return NetCDF version as an integer
void print_vector(MPI_Comm com, const std::vector< double > &data, const char *label)
uint64_t fletcher64(const uint32_t *data, size_t length)
static const double k
Definition exactTestP.cc:42
void validate_format_string(const std::string &format)
std::set< std::string > set_split(const std::string &input, char separator)
Transform a separator-separated list (a string) into a set of strings.
double vector_min(const std::vector< double > &input)
std::string string_strip(const std::string &input)
std::string filename_add_suffix(const std::string &filename, const std::string &separator, const std::string &suffix)
Adds a suffix to a filename.
std::string version()
std::string timestamp(MPI_Comm com)
Creates a time-stamp used for the history NetCDF attribute.
static const double c1
Definition exactTestP.cc:44
void GlobalMin(MPI_Comm comm, double *local, double *result, int count)
static double start_time(const Config &config, const File *file, const std::string &reference_date, const std::string &calendar, const units::Unit &time_units)
Definition Time.cc:349
std::string args_string()
Uses argc and argv to create the string with current PISM command-line arguments.
long int parse_integer(const std::string &input)
static const int TEMPORARY_STRING_LENGTH
std::string join(const std::vector< std::string > &strings, const std::string &separator)
Concatenate strings, inserting separator between elements.
void GlobalSum(MPI_Comm comm, double *local, double *result, int count)
std::string username_prefix(MPI_Comm com)
Creates a string with the user name, hostname and the time-stamp (for history strings).
bool set_member(const std::string &string, const std::set< std::string > &set)
std::string join_impl(const T &input, const std::string &separator)
void GlobalReduce(MPI_Comm comm, double *local, double *result, int count, MPI_Op op)
std::string replace_character(const std::string &input, char from, char to)
double vector_max(const std::vector< double > &input)
std::vector< std::string > split(const std::string &input, char separator)
Transform a separator-separated list (a string) into a vector of strings.
int count
Definition test_cube.c:16