PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
OutputWriter.cc
Go to the documentation of this file.
1/* Copyright (C) 2025, 2026 PISM Authors
2 *
3 * This file is part of PISM.
4 *
5 * PISM is free software; you can redistribute it and/or modify it under the
6 * terms of the GNU General Public License as published by the Free Software
7 * Foundation; either version 3 of the License, or (at your option) any later
8 * version.
9 *
10 * PISM is distributed in the hope that it will be useful, but WITHOUT ANY
11 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 * details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with PISM; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <cstddef>
21#include <map>
22#include <memory>
23#include <mpi.h>
24#include <string>
25#include <vector>
26#include <cassert>
27
28#include "pism/util/io/IO_Flags.hh"
29#include "pism/util/Config.hh"
30#include "pism/util/GridInfo.hh"
31#include "pism/util/VariableMetadata.hh"
32#include "pism/util/io/OutputWriter.hh"
33#include "pism/util/error_handling.hh"
34#include "pism/util/pism_utilities.hh"
35
36namespace pism {
37
38/*!
39 * Pre-process variable attributes for writing.
40 */
42
43 // make a copy so we can edit metadata
44 auto variable = metadata;
45
46 auto get = [&variable](const std::string &name) {
47 auto j = variable.strings.find(name);
48 if (j != variable.strings.end()) {
49 return j->second;
50 }
51
52 return std::string{};
53 };
54
55 std::string units = get("units");
56 std::string output_units = get("output_units");
57
58 // output units should never be written to a file
59 variable.strings["output_units"] = "";
60
61 bool use_output_units =
62 (not units.empty() and not output_units.empty() and units != output_units);
63
64 if (use_output_units) {
65 // Replace "units" with "output_units"
66 if (variable.is_set("units")) {
67 variable.strings["units"] = output_units;
68 }
69
70 units::Converter c(variable.unit_system, units, output_units);
71
72 // We need to convert units of valid_min, valid_max and valid_range:
73 {
74 if (variable.is_set("valid_range")) {
75 auto bounds = variable.numbers["valid_range"];
76
77 variable.numbers["valid_range"] = { c(bounds[0]), c(bounds[1]) };
78 } else {
79 if (variable.is_set("valid_min")) {
80 auto min = variable.numbers["valid_min"][0];
81 variable.numbers["valid_min"] = { c(min) };
82 }
83 if (variable.is_set("valid_max")) {
84 auto max = variable.numbers["valid_max"][0];
85 variable.numbers["valid_max"] = { c(max) };
86 }
87 }
88
89 if (variable.is_set("_FillValue")) {
90 auto fill = variable.numbers["_FillValue"][0];
91 variable.numbers["_FillValue"] = { c(fill) };
92 }
93 }
94 }
95 return variable;
96}
97
99 Impl(MPI_Comm comm_, const Config &config)
100 : comm(comm_) {
101 time_name = config.get_string("time.dimension_name");
102 experiment_id = config.get_string("output.experiment_id");
103 experiment_id_name = config.get_string("output.experiment_id_dimension");
104 experiment_id_max_length = (int)config.get_number("output.experiment_id_max_length");
105 relaxed_mode = false;
106 is_async = false;
107
108 // subtract one to account for the trailing null character:
109 if ((int)experiment_id.size() > experiment_id_max_length - 1) {
111 "the length of '%s' (%d) exceeds '%s' - 1. Please increase '%s'.",
112 "output.experiment_id", (int)experiment_id.size(),
113 "output.experiment_id_max_length",
114 "output.experiment_id_max_length");
115 }
116
117 if (not experiment_id.empty()) {
118 auto format = config.get_string("output.format");
119 if (format == "netcdf3" or format == "pnetcdf") {
122 "cannot save experiment ID \"%s\" to NetCDF-3 output files ('output.format' == \"%s\")",
123 experiment_id.c_str(), format.c_str());
124 }
125 }
126 }
127
128 std::string time_name;
129 MPI_Comm comm;
130
131 std::map<std::tuple<std::string, std::string>, bool> written_time_independent;
132 std::map<std::tuple<std::string, std::string>, bool> written_time_dependent;
133 std::map<std::string, VariableMetadata> variables;
135 std::string experiment_id;
137
138 // if true, call add_variable() in define_variable(), allowing a user to define array
139 // variables that were not declared using the call to initialize()
141
142 // set to true if this writer is asynchronous, false otherwise
144};
145
146bool &OutputWriter::already_written(const std::string &file_name,
147 const std::string &variable_name,
148 bool time_dependent) {
149 if (time_dependent) {
150 return m_impl->written_time_dependent[{ file_name, variable_name }];
151 }
152
153 return m_impl->written_time_independent[{ file_name, variable_name }];
154}
155
156OutputWriter::OutputWriter(MPI_Comm comm, const Config &config)
157 : m_impl(new Impl(comm, config)) {
158}
159
163
164void OutputWriter::initialize(const std::set<VariableMetadata> &array_variables,
165 bool relaxed_mode) {
166 m_impl->relaxed_mode = relaxed_mode;
167
168 for (const auto &variable : array_variables) {
169 if (variable.grid_info() != nullptr) {
170 add_variable(variable);
171 }
172 }
173
174 initialize_impl(array_variables);
175}
176
177MPI_Comm OutputWriter::comm() const {
178 return m_impl->comm;
179}
180
182 return m_impl->is_async;
183}
184
186 m_impl->is_async = flag;
187}
188
189const std::string &OutputWriter::time_name() const {
190 return m_impl->time_name;
191}
192
193bool OutputWriter::variable_info_is_available(const std::string &variable_name) const {
194 return m_impl->variables.find(variable_name) != m_impl->variables.end();
195}
196
197const VariableMetadata &OutputWriter::variable_info(const std::string &variable_name) const {
198 auto i = m_impl->variables.find(variable_name);
199 if (i != m_impl->variables.end()) {
200 return i->second;
201 }
203 "variable '%s' was not added using add_variable()",
204 variable_name.c_str());
205}
206
207void OutputWriter::define_dimension(const std::string &file_name, const std::string &dimension_name,
208 unsigned int length) {
209 define_dimension_impl(file_name, dimension_name, length);
210}
211
212void OutputWriter::define_variable(const std::string &file_name, const std::string &variable_name,
213 const std::vector<std::string> &dims, io::Type type,
214 const VariableAttributes &attributes) {
215 define_variable_impl(file_name, variable_name, dims, type, format_attributes(attributes));
216}
217
218std::vector<std::string> OutputWriter::define_dimensions(const std::string &file_name,
219 const VariableMetadata &variable) {
220
221 // define dimensions and corresponding coordinate variables
222 for (const auto &dimension : variable.dimensions()) {
223 define_dimension(file_name, dimension.get_name(), dimension.length());
224 if (dimension.coordinate_variable()) {
225 define_variable(file_name, dimension.get_name(), dimension.dimension_names(),
226 dimension.get_output_type(), dimension.attributes());
227 }
228 }
229
230 // define the experiment ID dimension and its variable
231 if (not m_impl->experiment_id.empty()) {
232 const auto &exp_id = m_impl->experiment_id_name;
233
234 VariableMetadata id(exp_id, { { exp_id, 1 }, { "nc", m_impl->experiment_id_max_length } },
235 variable.unit_system());
236
237 // NOTE (also FIXME because this is not great): this long name is significant: we use it
238 // to recognize the experiment ID dimension in File::dimension_type(). This is needed to
239 // compute start and count arrays correctly when re-starting from a file containing this
240 // dimension.
241 id.long_name("experiment ID");
242
243 define_dimension(file_name, exp_id, 1);
245 define_variable(file_name, exp_id, { exp_id, "nc" }, io::PISM_CHAR, id.attributes());
246 }
247
248 // build the list of dimension names, adding the name of the time dimension (for
249 // time-dependent variables) and the experiment ID dimension (if requested)
250 auto dimensions = variable.dimension_names();
251 {
252 if (variable.get_time_dependent()) {
253 dimensions.insert(dimensions.begin(), time_name());
254 }
255
256 // don't add the "experiment ID" dimension to the time dimension and time bounds:
257 bool time_or_bounds =
258 pism::set_member(variable.get_name(), { time_name(), time_name() + "_bounds" });
259
260 if (not time_or_bounds and not m_impl->experiment_id.empty()) {
261 dimensions.insert(dimensions.begin(), m_impl->experiment_id_name);
262 }
263 }
264 return dimensions;
265}
266
267void OutputWriter::define_variable(const std::string &file_name, const VariableMetadata &variable) {
268
269 if (m_impl->relaxed_mode and variable.grid_info() != nullptr) {
270 // add 2d and 3d variables in "relaxed" mode
271 add_variable(variable);
272 }
273
274 auto dimensions = define_dimensions(file_name, variable);
275
276 // define the variable
277 define_variable(file_name, variable.get_name(), dimensions, variable.get_output_type(),
278 variable.attributes());
279}
280
282 const auto &name = metadata.get_name();
283
284 if (m_impl->variables.find(name) == m_impl->variables.end()) {
285 m_impl->variables.insert({ name, metadata });
286 }
287}
288
290 const std::string &file_name, const std::map<std::string, std::string> &strings,
291 const std::map<std::string, std::vector<double> > &numbers) {
292 set_global_attributes_impl(file_name, strings, numbers);
293}
294
295void OutputWriter::append_time(const std::string &file_name, double time_seconds) {
296 append_time_impl(file_name, time_seconds);
297 // mark all time-dependent variables as "not written yet"
299}
300
301void OutputWriter::append_history(const std::string &file_name, const std::string &text) {
302 append_history_impl(file_name, text);
303}
304
305void OutputWriter::write_array(const std::string &file_name, const std::string &variable_name,
306 const std::vector<unsigned int> &start,
307 const std::vector<unsigned int> &count,
308 const std::vector<double> &input) {
309 write_array_impl(file_name, variable_name, start, count, input.data());
310}
311
312void OutputWriter::write_text(const std::string &file_name, const std::string &variable_name,
313 const std::vector<unsigned int> &start,
314 const std::vector<unsigned int> &count, const std::string &input) {
315 write_text_impl(file_name, variable_name, start, count, input);
316}
317
318void OutputWriter::write_dimensions(const std::string &file_name,
319 const VariableMetadata &variable) {
320
321 for (const auto &dim : variable.dimensions()) {
322 auto axis_type = axis_type_from_string(dim["axis"]);
323
324 const std::vector<double> *coordinates = nullptr;
325 switch (axis_type) {
326 case X_AXIS: {
327 const auto *grid = variable.grid_info();
328 if (grid != nullptr) {
329 coordinates = &grid->x;
330 }
331 break;
332 }
333 case Y_AXIS: {
334 const auto *grid = variable.grid_info();
335 if (grid != nullptr) {
336 coordinates = &grid->y;
337 }
338 break;
339 }
340 default:
341 coordinates = &variable.levels();
342 }
343
344 if (coordinates == nullptr or coordinates->empty()) {
345 // nothing to write for this dimension
346 continue;
347 }
348
349 auto dimension_name = dim.get_name();
350 if (not already_written(file_name, dimension_name, false)) {
351 write_array(file_name, dimension_name, { 0 }, { (unsigned int)coordinates->size() },
352 *coordinates);
353 already_written(file_name, dimension_name, false) = true;
354 }
355 }
356
357 // write experiment ID
358 if (not experiment_id().empty()) {
359 write_experiment_id(file_name);
360 }
361}
362
363void OutputWriter::write_distributed_array(const std::string &file_name,
364 const std::string &variable_name, const double *input) {
365 try {
366 const auto &variable = variable_info(variable_name);
367
368 if (variable.grid_info() == nullptr) {
371 "write_distributed_array() called for a variable (%s) that has no grid info",
372 variable_name.c_str());
373 }
374
375 bool time_dependent = variable.get_time_dependent();
376
377 // Avoid writing time-independent variables more than once (saves time when writing to
378 // spatial_files) and also avoid writing time-dependent variables more than once per
379 // time record
380 if (already_written(file_name, variable_name, time_dependent)) {
381 return;
382 }
383
384 write_dimensions(file_name, variable);
385
386 write_distributed_array_impl(file_name, variable_name, input);
387
388 already_written(file_name, variable_name, time_dependent) = true;
389 } catch (RuntimeError &e) {
390 e.add_context("writing gridded variable '%s' to '%s'", variable_name.c_str(),
391 file_name.c_str());
392 throw;
393 }
394}
395
396
397void OutputWriter::write_timeseries(const std::string &file_name,
398 const std::string &variable_name,
399 const std::vector<unsigned int> &start,
400 const std::vector<unsigned int> &count,
401 const std::vector<double> &input) {
402 auto S = start;
403 auto C = count;
404
405 if (not experiment_id().empty()) {
406 write_experiment_id(file_name);
407 S.insert(S.cbegin(), 0);
408 C.insert(C.cbegin(), 1);
409 }
410
411 write_array(file_name, variable_name, S, C, input);
412}
413
414void OutputWriter::append(const std::string &file_name) {
415 append_impl(file_name);
416}
417
418void OutputWriter::sync(const std::string &file_name) {
419 sync_impl(file_name);
420}
421
422void OutputWriter::close(const std::string &file_name) {
423 close_impl(file_name);
424}
425
426unsigned int OutputWriter::time_dimension_length(const std::string &file_name) {
427 return time_dimension_length_impl(file_name);
428}
429
430double OutputWriter::last_time_value(const std::string &file_name) {
431 return last_time_value_impl(file_name);
432}
433
434void OutputWriter::write_experiment_id(const std::string &file_name) {
435 const auto &variable_name = m_impl->experiment_id_name;
436 if (already_written(file_name, variable_name, false)) {
437 return;
438 }
439
440 const auto &exp_id = experiment_id();
441 write_text(file_name, variable_name, { 0, 0 }, { 1, (unsigned)exp_id.size() + 1 }, exp_id);
442
443 // mark experiment ID as "already written"
444 already_written(file_name, variable_name, false) = true;
445}
446
447const std::string &OutputWriter::experiment_id() const {
448 return m_impl->experiment_id;
449}
450
451
452} // namespace pism
double get_number(const std::string &name, UseFlag flag=REMEMBER_THIS_USE) const
Definition Config.cc:188
std::string get_string(const std::string &name, UseFlag flag=REMEMBER_THIS_USE) const
Definition Config.cc:322
A class for storing and accessing PISM configuration flags and parameters.
Definition Config.hh:56
OutputWriter(MPI_Comm comm, const Config &config)
void write_text(const std::string &file_name, const std::string &variable_name, const std::vector< unsigned int > &start, const std::vector< unsigned int > &count, const std::string &input)
unsigned int time_dimension_length(const std::string &file_name)
void close(const std::string &file_name)
bool is_async() const
void append(const std::string &file_name)
void add_variable(const VariableMetadata &metadata)
void set_is_async(bool flag)
virtual void write_array_impl(const std::string &file_name, const std::string &variable_name, const std::vector< unsigned int > &start, const std::vector< unsigned int > &count, const double *data)=0
virtual double last_time_value_impl(const std::string &file_name)=0
const std::string & time_name() const
virtual void set_global_attributes_impl(const std::string &file_name, const std::map< std::string, std::string > &strings, const std::map< std::string, std::vector< double > > &numbers)=0
void write_dimensions(const std::string &file_name, const VariableMetadata &variable)
bool & already_written(const std::string &file_name, const std::string &variable_name, bool time_dependent)
void define_variable(const std::string &file_name, const VariableMetadata &variable)
virtual void define_variable_impl(const std::string &file_name, const std::string &variable_name, const std::vector< std::string > &dims, io::Type type, const VariableAttributes &attributes)=0
virtual void define_dimension_impl(const std::string &file_name, const std::string &name, unsigned int length)=0
void set_global_attributes(const std::string &file_name, const std::map< std::string, std::string > &strings, const std::map< std::string, std::vector< double > > &numbers)
MPI_Comm comm() const
double last_time_value(const std::string &file_name)
void define_dimension(const std::string &file_name, const std::string &dimension_name, unsigned int length)
virtual void initialize_impl(const std::set< VariableMetadata > &array_variables)=0
void write_timeseries(const std::string &file_name, const std::string &variable_name, const std::vector< unsigned int > &start, const std::vector< unsigned int > &count, const std::vector< double > &input)
void append_time(const std::string &file_name, double time_seconds)
virtual void write_distributed_array_impl(const std::string &file_name, const std::string &variable_name, const double *data)=0
virtual void append_time_impl(const std::string &file_name, double time_seconds)=0
virtual void write_text_impl(const std::string &file_name, const std::string &variable_name, const std::vector< unsigned int > &start, const std::vector< unsigned int > &count, const std::string &input)=0
void initialize(const std::set< VariableMetadata > &array_variables, bool relaxed_mode=false)
virtual unsigned int time_dimension_length_impl(const std::string &file_name)=0
const std::string & experiment_id() const
void sync(const std::string &file_name)
std::vector< std::string > define_dimensions(const std::string &file_name, const VariableMetadata &variable)
virtual void close_impl(const std::string &file_name)=0
void write_experiment_id(const std::string &file_name)
void write_distributed_array(const std::string &file_name, const std::string &variable_name, const double *input)
virtual void sync_impl(const std::string &file_name)=0
const VariableMetadata & variable_info(const std::string &variable_name) const
bool variable_info_is_available(const std::string &variable_name) const
void append_history(const std::string &file_name, const std::string &text)
virtual void append_history_impl(const std::string &file_name, const std::string &text)=0
void write_array(const std::string &file_name, const std::string &variable_name, const std::vector< unsigned int > &start, const std::vector< unsigned int > &count, const std::vector< double > &input)
virtual void append_impl(const std::string &file_name)=0
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::vector< std::string > dimension_names() const
const VariableAttributes & attributes() const
const std::vector< double > & levels() const
const grid::DistributedGridInfo * grid_info() const
std::shared_ptr< units::System > unit_system() const
io::Type get_output_type() const
std::string get_name() const
std::vector< DimensionMetadata > dimensions() const
#define PISM_ERROR_LOCATION
@ PISM_CHAR
Definition IO_Flags.hh:49
@ X_AXIS
Definition IO_Flags.hh:34
@ Y_AXIS
Definition IO_Flags.hh:34
static VariableAttributes format_attributes(const VariableAttributes &metadata)
AxisType axis_type_from_string(const std::string &input)
Definition File.cc:436
bool set_member(const std::string &string, const std::set< std::string > &set)
Impl(MPI_Comm comm_, const Config &config)
std::map< std::tuple< std::string, std::string >, bool > written_time_dependent
std::map< std::tuple< std::string, std::string >, bool > written_time_independent
std::map< std::string, VariableMetadata > variables
static double S(unsigned n)
Definition test_cube.c:58
int count
Definition test_cube.c:16