Loading [MathJax]/extensions/tex2jax.js
PISM, A Parallel Ice Sheet Model 2.2.2-d6b3a29ca committed by Constantine Khrulev on 2025-03-28
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
ScalarForcing.cc
Go to the documentation of this file.
1/* Copyright (C) 2018, 2019, 2020, 2021, 2023, 2024 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/ScalarForcing.hh"
21
22#include <algorithm> // std::min_element(), std::max_element()
23#include <cassert> // assert()
24#include <cmath> // std::floor()
25
26#include <gsl/gsl_spline.h>
27
28#include "pism/util/ConfigInterface.hh"
29#include "pism/util/Context.hh"
30#include "pism/util/Time.hh"
31#include "pism/util/error_handling.hh"
32#include "pism/util/Logger.hh"
33#include "pism/util/io/File.hh"
34#include "pism/util/io/io_helpers.hh"
35#include "pism/util/VariableMetadata.hh"
36#include "pism/util/io/IO_Flags.hh"
37
38namespace pism {
39
40//! \brief Report the range of a time-series stored in `data`.
41static void report_range(const std::vector<double> &data,
42 const VariableMetadata &metadata,
43 const Logger &log) {
44 // min_element and max_element return iterators; "*" is used to get
45 // the value corresponding to this iterator
46 double min = *std::min_element(data.begin(), data.end());
47 double max = *std::max_element(data.begin(), data.end());
48
49 metadata.report_range(log, min, max);
50}
51
53 // period, in seconds (zero if not periodic)
54 double period;
55
56 // start of the period, in seconds (not used if not periodic)
58
59 // Times associated with corresponding values (used for linear interpolation)
60 std::vector<double> times;
61
62 // Forcing values
63 std::vector<double> values;
64
65 gsl_interp_accel* acc;
66 gsl_spline* spline;
67};
68
70 const std::string &filename,
71 const std::string &variable_name,
72 const std::string &units,
73 const std::string &output_units,
74 const std::string &long_name,
75 bool periodic) {
76 try {
77 auto unit_system = ctx.unit_system();
78
79 double forcing_t0{};
80 double forcing_t1{};
81
82 // Read data from a NetCDF file
83 {
84 ctx.log()->message(2,
85 " reading %s (%s) from file '%s'...\n",
86 long_name.c_str(), variable_name.c_str(), filename.c_str());
87
88 File file(ctx.com(), filename, io::PISM_NETCDF3, io::PISM_READONLY);
89
90 // Read forcing data. The read_1d_variable() call will ensure that variable_name is a
91 // scalar variable.
92 auto data = io::read_1d_variable(file, variable_name, units, unit_system);
93
94 // The following line relies on the fact that this is a scalar variable.
95 std::string time_name = file.dimensions(variable_name)[0];
96
97 std::vector<double> times{};
98 std::vector<double> bounds{};
99 io::read_time_info(unit_system, file, time_name, ctx.time()->units_string(), times, bounds);
100 size_t N = times.size();
101
102 // Compute values used to extend data read from file
103 //
104 // Initialize using values corresponding to constant extrapolation:
105 double v0 = data.front();
106 double v1 = data.back();
107 if (periodic) {
108 double b0 = bounds.front();
109 double b1 = bounds.back();
110
111 // compute the value at the beginning and end of the period:
112 {
113 double dt1 = times.front() - b0;
114 double dt2 = b1 - times.back();
115
116 // interpolation factor
117 double alpha = dt1 + dt2 > 0 ? dt2 / (dt1 + dt2) : 0.0;
118
119 v0 = (1.0 - alpha) * data.back() + alpha * data.front();
120 v1 = v0;
121 }
122
123 {
124 m_impl->period = b1 - b0;
126
127 assert(m_impl->period > 0.0);
128 }
129 }
130
131 // Note: this should take care of file with one record as well.
132 m_impl->times.clear();
133 m_impl->values.clear();
134 {
135 if (bounds.front() < times.front()) {
136 m_impl->times.emplace_back(bounds.front());
137 m_impl->values.emplace_back(v0);
138 }
139 for (size_t k = 0; k < N; ++k) {
140 m_impl->times.emplace_back(times[k]);
141 m_impl->values.emplace_back(data[k]);
142 }
143 if (bounds.back() > times.back()) {
144 m_impl->times.emplace_back(bounds.back());
145 m_impl->values.emplace_back(v1);
146 }
147 }
148
149 // Save the time interval covered by this forcing.
150 forcing_t0 = bounds.front();
151 forcing_t1 = bounds.back();
152 } // end of the block initializing data
153
154 // validate resulting times
155 if (not is_increasing(m_impl->times)) {
157 "m_impl->times have to be strictly increasing (this is a bug)");
158 }
159
160 {
161 VariableMetadata variable(variable_name, unit_system);
162
163 variable["units"] = units;
164 variable["output_units"] = output_units;
165 variable["long_name"] = long_name;
166
167 report_range(m_impl->values, variable, *ctx.log());
168 }
169
170 // Set up interpolation.
171 {
172 m_impl->acc = gsl_interp_accel_alloc();
173 m_impl->spline = gsl_spline_alloc(gsl_interp_linear, m_impl->times.size());
174 gsl_spline_init(m_impl->spline, m_impl->times.data(), m_impl->values.data(), m_impl->times.size());
175 }
176
177 bool extrapolate = ctx.config()->get_flag("input.forcing.time_extrapolation");
178
179 if (not (extrapolate or periodic)) {
180 check_forcing_duration(*ctx.time(), forcing_t0, forcing_t1);
181 }
182
183 } catch (RuntimeError &e) {
184 e.add_context("reading '%s' (%s) from '%s'",
185 long_name.c_str(), variable_name.c_str(), filename.c_str());
186 throw;
187 }
188}
189
190ScalarForcing::ScalarForcing(const Context &ctx, const std::string &prefix,
191 const std::string &variable_name, const std::string &units,
192 const std::string &output_units, const std::string &long_name)
193 : m_impl(new Impl) {
194
195 m_impl->acc = nullptr;
196 m_impl->spline = nullptr;
197 m_impl->period = 0.0;
198 m_impl->period_start = 0.0;
199
200 auto config = ctx.config();
201
202 auto filename = config->get_string(prefix + ".file");
203 bool periodic = config->get_flag(prefix + ".periodic");
204
205 if (filename.empty()) {
207 "%s.file is required", prefix.c_str());
208 }
209
210 initialize(ctx, filename, variable_name, units, output_units,
211 long_name, periodic);
212}
213
215 const std::string &filename,
216 const std::string &variable_name,
217 const std::string &units,
218 const std::string &output_units,
219 const std::string &long_name,
220 bool periodic)
221 : m_impl(new Impl) {
222
223 m_impl->acc = nullptr;
224 m_impl->spline = nullptr;
225 m_impl->period = 0.0;
226 m_impl->period_start = 0.0;
227
228 initialize(ctx, filename, variable_name, units, output_units,
229 long_name, periodic);
230}
231
233 if (m_impl->spline != nullptr) {
234 gsl_spline_free(m_impl->spline);
235 }
236 if (m_impl->acc != nullptr) {
237 gsl_interp_accel_free(m_impl->acc);
238 }
239
240 delete m_impl;
241}
242
243double ScalarForcing::value(double t) const {
244 double T = t;
245
246 if (m_impl->period > 0.0) {
247 // compute time since the period start
248 T -= m_impl->period_start;
249
250 // remove an integer number of periods
251 T -= std::floor(T / m_impl->period) * m_impl->period;
252
253 // add the period start back
254 T += m_impl->period_start;
255 }
256
257 if (T > m_impl->times.back()) {
258 return m_impl->values.back();
259 }
260
261 if (T < m_impl->times.front()) {
262 return m_impl->values.front();
263 }
264
265 return gsl_spline_eval(m_impl->spline, T, m_impl->acc);
266}
267
268// Integrate from a to b, interpreting data as *not* periodic
269double ScalarForcing::integral(double a, double b) const {
270 assert(b >= a);
271
272 double dt = b - a;
273
274 double t0 = m_impl->times.front();
275 double t1 = m_impl->times.back();
276 double v0 = m_impl->values[0];
277 double v1 = m_impl->values.back();
278
279 // both points are to the left of [t0, t1]
280 if (b <= t0) {
281 return v0 * (b - a);
282 }
283
284 // both points are to the right of [t0, t1]
285 if (a >= t1) {
286 return v1 * (b - a);
287 }
288
289 // a is to the left of [t0, t1]
290 if (a < t0) {
291 return v0 * (t0 - a) + integral(t0, b);
292 }
293
294 // b is to the right of [t0, t1]
295 if (b > t1) {
296 return integral(a, t1) + v1 * (b - t1);
297 }
298
299 // both points are inside [t0, t1]
300 size_t ai = gsl_interp_bsearch(m_impl->times.data(), a, 0, m_impl->times.size() - 1);
301 size_t bi = gsl_interp_bsearch(m_impl->times.data(), b, 0, m_impl->times.size() - 1);
302
303 // gsl_interp_bsearch() above returns the index i of the array ‘m_impl->times’ such that
304 // ‘m_impl->times[i] <= t < m_impl->times[i+1]’. The index is searched for in the
305 // range [0, m_impl->times.size() - 1] (inclusive).
306
307 double v_a = gsl_spline_eval(m_impl->spline, a, m_impl->acc);
308 double v_b = gsl_spline_eval(m_impl->spline, b, m_impl->acc);
309
310 if (ai == bi) {
311 return 0.5 * (v_a + v_b) * dt;
312 }
313
314 double result = 0.0;
315 // integrate from a to the data point just to its right
316 result += 0.5 * (v_a + m_impl->values[ai + 1]) * (m_impl->times[ai + 1] - a);
317
318 // integrate over (possibly zero) intervals between a and b
319 for (size_t k = ai + 1; k < bi; ++k) {
320 result += 0.5 * (m_impl->values[k] + m_impl->values[k + 1]) * (m_impl->times[k + 1] - m_impl->times[k]);
321 }
322
323 result += 0.5 * (m_impl->values[bi] + v_b) * (b - m_impl->times[bi]);
324
325 return result;
326}
327
328double ScalarForcing::average(double t, double dt) const {
329 if (dt == 0.0) {
330 return value(t);
331 }
332
333 if (dt < 0.0) {
334 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "negative interval length");
335 }
336
337 if (m_impl->period <= 0.0) {
338 // regular case
339 return integral(t, t + dt) / dt;
340 }
341
342 // periodic case
343 {
344 double a = t;
345 double b = t + dt;
346 double t0 = m_impl->period_start;
347 double P = m_impl->period;
348
349 double N = std::floor((a - t0) / P);
350 double M = std::floor((b - t0) / P);
351 double delta = a - t0 - P * N;
352 double gamma = b - t0 - P * M;
353
354 double N_periods = M - (N + 1);
355
356 if (N_periods >= 0.0) {
357 return (integral(t0 + delta, t0 + P) +
358 N_periods * integral(t0, t0 + P) +
359 integral(t0, t0 + gamma)) / dt;
360 }
361
362 return integral(t0 + delta, t0 + gamma) / dt;
363 }
364}
365
366
367} // end of namespace pism
MPI_Comm com() const
Definition Context.cc:65
std::shared_ptr< const Config > config() const
Definition Context.cc:89
std::shared_ptr< const Logger > log() const
Definition Context.cc:113
std::shared_ptr< units::System > unit_system() const
Definition Context.cc:81
std::shared_ptr< const Time > time() const
Definition Context.cc:101
std::vector< std::string > dimensions(const std::string &variable_name) const
Definition File.cc:390
High-level PISM I/O class.
Definition File.hh:55
A basic logging class.
Definition Logger.hh:40
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
double average(double t, double dt) const
double value(double t) const
void initialize(const Context &ctx, const std::string &filename, const std::string &variable_name, const std::string &units, const std::string &output_units, const std::string &long_name, bool periodic)
ScalarForcing(const Context &ctx, const std::string &option_prefix, const std::string &variable_name, const std::string &units, const std::string &output_units, const std::string &long_name)
double integral(double a, double b) const
void report_range(const Logger &log, double min, double max) const
Report the range of a global Vec v.
#define PISM_ERROR_LOCATION
static const double b0
Definition exactTestL.cc:41
@ PISM_NETCDF3
Definition IO_Flags.hh:57
@ PISM_READONLY
open an existing file for reading only
Definition IO_Flags.hh:68
void read_time_info(std::shared_ptr< units::System > unit_system, const File &file, const std::string &time_name, const std::string &time_units, std::vector< double > &times, std::vector< double > &bounds)
std::vector< double > read_1d_variable(const File &file, const std::string &variable_name, const std::string &units, std::shared_ptr< units::System > unit_system)
bool is_increasing(const std::vector< double > &a)
Checks if a vector of doubles is strictly increasing.
static const double v0
Definition exactTestP.cc:50
static const double k
Definition exactTestP.cc:42
static void report_range(const std::vector< double > &data, const VariableMetadata &metadata, const Logger &log)
Report the range of a time-series stored in data.
void check_forcing_duration(const Time &time, double forcing_start, double forcing_end)
Definition Time.cc:923
std::vector< double > times
std::vector< double > values
gsl_interp_accel * acc