PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
output_spatial.cc
Go to the documentation of this file.
1/* Copyright (C) 2017, 2018, 2019, 2020, 2021, 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#include <memory>
20
21#include "pism/icemodel/IceModel.hh"
22
23#include "pism/util/pism_utilities.hh"
24#include "pism/util/Profiling.hh"
25#include "pism/util/io/io_helpers.hh"
26
27namespace pism {
28
29//! Computes the maximum time-step we can take and still hit all `-spatial_times`.
31
32 if (m_spatial_filename.empty() or
33 (not m_config->get_flag("time_stepping.hit_spatial_times"))) {
34 return MaxTimestep("reporting (-spatial_times)");
35 }
36
37 double eps = m_config->get_number("time_stepping.resolution");
38
40 "reporting (-spatial_times)");
41}
42
43static std::set<std::string> process_variable_list_shortcuts(const Config &config,
44 const std::set<std::string> &input) {
45 std::set<std::string> result = input;
46
47 // process shortcuts
48 if (result.find("amount_fluxes") != result.end()) {
49 result.erase("amount_fluxes");
50 result.insert({ "tendency_of_ice_amount", "tendency_of_ice_amount_due_to_basal_mass_flux",
51 "tendency_of_ice_amount_due_to_conservation_error",
52 "tendency_of_ice_amount_due_to_discharge", "tendency_of_ice_amount_due_to_flow",
53 "tendency_of_ice_amount_due_to_surface_mass_flux" });
54 }
55
56 if (result.find("mass_fluxes") != result.end()) {
57 result.erase("mass_fluxes");
58 result.insert({ "tendency_of_ice_mass", "tendency_of_ice_mass_due_to_basal_mass_flux",
59 "tendency_of_ice_mass_due_to_conservation_error",
60 "tendency_of_ice_mass_due_to_discharge", "tendency_of_ice_mass_due_to_flow",
61 "tendency_of_ice_mass_due_to_surface_mass_flux" });
62 }
63
64 if (result.find("pdd_fluxes") != result.end()) {
65 result.erase("pdd_fluxes");
66 result.insert({ "surface_accumulation_flux", "surface_runoff_flux", "surface_melt_flux" });
67 }
68
69 if (result.find("pdd_rates") != result.end()) {
70 result.erase("pdd_rates");
71 result.insert({ "surface_accumulation_rate", "surface_runoff_rate", "surface_melt_rate" });
72 }
73
74 if (result.find("hydrology_fluxes") != result.end()) {
75 result.erase("hydrology_fluxes");
76 result.insert({ "tendency_of_subglacial_water_mass",
77 "tendency_of_subglacial_water_mass_due_to_input",
78 "tendency_of_subglacial_water_mass_due_to_flow",
79 "tendency_of_subglacial_water_mass_due_to_conservation_error",
80 "tendency_of_subglacial_water_mass_at_grounded_margins",
81 "tendency_of_subglacial_water_mass_at_grounding_line",
82 "tendency_of_subglacial_water_mass_at_domain_boundary" });
83 }
84
85 if (result.find("ismip6") != result.end()) {
86
87 const char *flag_name = "output.ISMIP6";
88
89 if (not config.get_flag(flag_name)) {
90 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "Please set %s to save ISMIP6 diagnostics "
91 "(-spatial_vars ismip6).", flag_name);
92 }
93
94 result.erase("ismip6");
95 for (const auto& v : set_split(config.get_string("output.ISMIP6_spatial_variables"), ',')) {
96 result.insert(v);
97 }
98 }
99
100 return result;
101}
102
103//! Initialize the code saving spatially-variable diagnostic quantities.
105
106 m_last_spatial_time = 0; // will be set in write_extras()
108
109 m_spatial_filename = m_config->get_string("output.spatial.file");
110 std::string times = m_config->get_string("output.spatial.times");
111 bool split = m_config->get_flag("output.spatial.split");
112 bool append = m_config->get_flag("output.spatial.append");
113
114 bool file_set = not m_spatial_filename.empty();
115 bool times_set = not times.empty();
116
117 if (file_set ^ times_set) {
119 "you need to set both output.spatial.file and output.spatial.times"
120 " to save spatial time-series.");
121 }
122
123 if (not file_set and not times_set) {
124 m_spatial_filename.clear();
125 return;
126 }
127
128 try {
129 m_spatial_times = m_time->parse_times(times);
130 } catch (RuntimeError &e) {
131 e.add_context("parsing the output.spatial.times argument %s", times.c_str());
132 throw;
133 }
134
135 if (m_spatial_times.empty()) {
136 throw RuntimeError(PISM_ERROR_LOCATION, "output.spatial.times cannot be empty");
137 }
138
139 if (append and split) {
141 "both output.spatial.split and output.spatial.append are set.");
142 }
143
144 {
145 if (split) {
146 m_log->message(2, "saving spatial time-series to '%s+date.nc'; ", m_spatial_filename.c_str());
147 } else {
148 if (not ends_with(m_spatial_filename, ".nc")) {
149 m_log->message(
150 2, "PISM WARNING: spatial time-series file name '%s' does not have the '.nc' suffix!\n",
151 m_spatial_filename.c_str());
152 }
153 m_log->message(2, "saving spatial time-series to '%s'%s\n", m_spatial_filename.c_str(),
154 m_spatial_writer->is_async() ? " using asynchronous output" : "");
155 }
156
157 m_log->message(2, " times requested: %s\n", times.c_str());
158
159 if (m_spatial_times.size() > 500) {
160 m_log->message(
161 2, "PISM WARNING: more than 500 times requested. This might fill your hard-drive!\n");
162 }
163 }
164
165 // initialize m_spatial_vars and m_spatial_file_contents
166 {
167 auto vars = m_config->get_string("output.spatial.vars");
168 if (not vars.empty()) {
170 } else {
171 m_spatial_vars = {};
172 }
173 m_log->message(2, " variables requested: %s\n",
174 vars.empty() ? "model state variables" : vars.c_str());
175
176 if (m_spatial_vars.empty()) {
178 } else {
180 }
182 }
183
184 m_spatial_file = nullptr;
185 if (not split) {
186 m_spatial_file = std::make_shared<OutputFile>(m_spatial_writer, m_spatial_filename);
187
188 if (append) {
189 // assume that the file is ready to write to, i.e. time and time_bounds are already
190 // defined
191 m_spatial_file->append();
192
193 if (m_spatial_file->time_dimension_length() > 0) {
194 double time_max = m_spatial_file->last_time_value();
195
196 while (m_next_spatial_index + 1 < m_spatial_times.size() &&
197 m_spatial_times[m_next_spatial_index + 1] < time_max) {
199 }
200
201 if (m_next_spatial_index > 0) {
202 m_log->message(2, "skipping times before the last record in %s (at %s)\n",
203 m_spatial_filename.c_str(), m_time->date(time_max).c_str());
204 }
205
206 // discard requested times before the beginning of the run
207 std::vector<double> tmp(m_spatial_times.size() - m_next_spatial_index);
208 for (unsigned int k = 0; k < tmp.size(); ++k) {
210 }
211
212 m_spatial_times = tmp;
214 }
215 } else {
216 // prepare the output file
217 bool with_time_bounds = true;
218 define_time(*m_spatial_file, with_time_bounds);
220 }
221 }
222
223 if (pism::netcdf_version() > 0 and pism::netcdf_version() < 473) {
224 if (m_spatial_times.size() > 5000 and
225 m_config->get_string("output.format") == "netcdf4_parallel") {
226 throw RuntimeError(
228 "more than 5000 times requested."
229 "Please use -spatial_split to avoid a crash caused by a bug in NetCDF versions older than 4.7.3.\n"
230 "Alternatively\n"
231 "- split this simulation into several runs and then concatenate results\n"
232 "- select a different output.format value\n"
233 "- upgrade NetCDF to 4.7.3");
234 }
235 }
236
237}
238
239//! Write spatially-variable diagnostic quantities.
241 double saving_after = -1.0e30; // initialize to avoid compiler warning; this
242 // value is never used, because saving_after
243 // is only used if save_now == true, and in
244 // this case saving_after is guaranteed to be
245 // initialized. See the code below.
246 unsigned int current_index;
247 // determine if the user set the -save_at and -save_to options
248 if (m_spatial_filename.empty()) {
249 return;
250 }
251
252 const double time_resolution = m_config->get_number("time_stepping.resolution");
253 double current_time = m_time->current();
254
255 // do we need to save *now*?
257 (current_time >= m_spatial_times[m_next_spatial_index] or
258 fabs(current_time - m_spatial_times[m_next_spatial_index]) < time_resolution)) {
259 // the condition above is "true" if we passed a requested time or got to
260 // within time_resolution seconds from it
261
262 current_index = m_next_spatial_index;
263
264 // update m_next_spatial_index
265 while (m_next_spatial_index < m_spatial_times.size() and
266 (m_spatial_times[m_next_spatial_index] <= current_time or
267 fabs(current_time - m_spatial_times[m_next_spatial_index]) < time_resolution)) {
269 }
270
271 saving_after = m_spatial_times[current_index];
272 } else {
273 return;
274 }
275
276 if (current_index == 0) {
277 // The first time defines the left end-point of the first reporting interval; we don't write a
278 // report at this time.
279
280 // Re-initialize last_extra (the correct value is not known at the time init_extras() is
281 // called).
282 m_last_spatial_time = current_time;
283
284 // ISMIP6 runs need to save diagnostics at the beginning of the run
285 if (not m_config->get_flag("output.ISMIP6")) {
286 return;
287 }
288 }
289
290 if (saving_after < m_time->start()) {
291 // Suppose a user tells PISM to write data at times 0:1000:10000. Suppose
292 // also that PISM writes a backup file at year 2500 and gets stopped.
293 //
294 // When restarted, PISM will decide that it's time to write data for time
295 // 2000, but
296 // * that record was written already and
297 // * PISM will end up writing at year 2500, producing a file containing one
298 // more record than necessary.
299 //
300 // This check makes sure that this never happens.
301 return;
302 }
303
304 bool split = m_config->get_flag("output.spatial.split");
305
306 const Profiling &profiling = m_ctx->profiling();
307 profiling.begin("io.spatial_file");
308 {
309 if (m_spatial_file == nullptr) {
310
311 std::string filename = m_spatial_filename;
312 if (split) {
313 // each time-series record is written to a separate file
314 auto date_without_spaces = replace_character(m_time->date(m_time->current()), ' ', '_');
315 filename = pism::printf("%s_%s.nc", m_spatial_filename.c_str(), date_without_spaces.c_str());
316 }
317
318 m_spatial_file.reset(new OutputFile(m_spatial_writer, filename));
319
320 if (m_config->get_flag("output.spatial.append")) {
321 m_spatial_file->append();
322 } else {
323 // prepare the output file
324 bool with_time_bounds = true;
325 define_time(*m_spatial_file, with_time_bounds);
327 }
328 }
329
330 m_log->message(3, "saving spatial time-series to %s at %s\n", m_spatial_file->name().c_str(),
331 m_time->date(m_time->current()).c_str());
332
333 {
334 io::write_config(*m_config, "pism_config", *m_spatial_file);
335
336 // use the mid-point of the current reporting interval
337 double time = 0.5 * (m_last_spatial_time + current_time);
338 m_spatial_file->append_time(time);
339
340 if (m_spatial_vars.empty()) {
342 } else {
344 }
345
346 // write time bounds
347 {
348 // Get the length of the time dimension *after* it is appended to.
349 auto time_length = m_spatial_file->time_dimension_length();
350 auto time_start = time_length > 0 ? (time_length - 1) : 0;
351
352 auto bounds_name = m_time->variable_name() + "_bounds";
353
354 m_spatial_file->write_array(bounds_name, { time_start, 0 }, { 1, 2 },
355 { m_last_spatial_time, current_time });
356 }
357
359 }
360
361 // FIXME: evaluate if we need to "sync" extra files. We should probably do this every
362 // Nth (e.g. 10th) time we write to an extra file. This would accomplish most of what
363 // "sync()" is for, but at a lower cost.
364 //
365 if (not m_spatial_writer->is_async()) {
366 // make sure all changes are written
367 m_spatial_file->sync();
368 }
369 }
370 profiling.end("io.spatial_file");
371
373
374 if (split) {
375 // each record is saved to a new file, so we can close this one
376 m_spatial_file->close();
377 m_spatial_file = nullptr;
378 }
379
380 m_last_spatial_time = current_time;
381
382 // reset accumulators in diagnostics that compute time averaged quantities
383 for (auto &d : m_available_spatial_diagnostics) {
384 d.second->reset();
385 }
386}
387
388} // end of namespace pism
std::string get_string(const std::string &name, UseFlag flag=REMEMBER_THIS_USE) const
Definition Config.cc:322
bool get_flag(const std::string &name, UseFlag flag=REMEMBER_THIS_USE) const
Definition Config.cc:351
A class for storing and accessing PISM configuration flags and parameters.
Definition Config.hh:56
void define_variables(const OutputFile &file, const std::set< VariableMetadata > &variables) const
Definition output.cc:206
std::set< std::string > m_spatial_vars
Definition IceModel.hh:494
std::shared_ptr< Config > m_config
Configuration flags and parameters.
Definition IceModel.hh:278
MaxTimestep spatial_diagnostics_max_timestep(double t)
Computes the maximum time-step we can take and still hit all -spatial_times.
void define_time(const OutputFile &file, bool with_bounds=false) const
Definition output.cc:73
void write_run_stats(const OutputFile &file) const
Definition output.cc:182
std::shared_ptr< Context > m_ctx
Execution context.
Definition IceModel.hh:280
std::shared_ptr< Logger > m_log
Logger.
Definition IceModel.hh:284
std::set< VariableMetadata > m_spatial_file_contents
set of variables that will be written to extra files
Definition IceModel.hh:497
virtual std::set< VariableMetadata > diagnostic_variables(const std::set< std::string > &variable_names) const
unsigned int m_next_spatial_index
Definition IceModel.hh:492
virtual std::set< VariableMetadata > state_variables() const
Definition output.cc:217
std::shared_ptr< OutputWriter > m_spatial_writer
Definition IceModel.hh:290
std::shared_ptr< Time > m_time
Time manager.
Definition IceModel.hh:286
void write_spatial_diagnostics()
Write spatially-variable diagnostic quantities.
std::vector< double > m_spatial_times
Definition IceModel.hh:491
std::map< std::string, Diagnostic::Ptr > m_available_spatial_diagnostics
Available spatially-variable diagnostics.
Definition IceModel.hh:461
std::set< VariableMetadata > common_metadata() const
Definition output.cc:81
std::shared_ptr< OutputFile > m_spatial_file
Definition IceModel.hh:499
void write_diagnostics(const OutputFile &file, const std::set< std::string > &variable_names) const
Writes variables listed in variable_names to file.
std::string m_spatial_filename
Definition IceModel.hh:490
void init_spatial_diagnostics()
Initialize the code saving spatially-variable diagnostic quantities.
void scalar_diagnostics_flush_buffers()
Flush scalar time-series.
virtual void write_state(const OutputFile &file) const
Definition output.cc:240
double m_last_spatial_time
Definition IceModel.hh:493
Combines the max. time step with the flag indicating if a restriction is active. Makes is possible to...
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
#define PISM_ERROR_LOCATION
void write_config(const Config &config, const std::string &variable_name, const OutputFile &file)
MaxTimestep reporting_max_timestep(const std::vector< double > &times, double t, double eps, const std::string &description)
Definition output.cc:43
static std::set< std::string > process_variable_list_shortcuts(const Config &config, const std::set< std::string > &input)
bool ends_with(const std::string &str, const std::string &suffix)
Returns true if str ends with suffix and false otherwise.
std::string printf(const char *format,...)
int netcdf_version()
return NetCDF version as an integer
static const double k
Definition exactTestP.cc:42
std::set< std::string > set_split(const std::string &input, char separator)
Transform a separator-separated list (a string) into a set of strings.
T combine(const T &a, const T &b)
std::string replace_character(const std::string &input, char from, char to)
std::vector< std::string > split(const std::string &input, char separator)
Transform a separator-separated list (a string) into a vector of strings.