PISM, A Parallel Ice Sheet Model 2.3.3-e0e7dfb06 committed by Constantine Khrulev on 2026-10-20
Loading...
Searching...
No Matches
ScalarForcing.cc
Go to the documentation of this file.
1/* Copyright (C) 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
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/Config.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 auto time = ctx.time();
98 auto info = io::read_time_info(time->units(), time->calendar(), file, time_name);
99 const auto &times = info.times;
100 const auto &bounds = info.bounds;
101
102 size_t N = times.size();
103
104 // Compute values used to extend data read from file
105 //
106 // Initialize using values corresponding to constant extrapolation:
107 double v0 = data.front();
108 double v1 = data.back();
109 if (periodic) {
110 double b0 = bounds.front();
111 double b1 = bounds.back();
112
113 // compute the value at the beginning and end of the period:
114 {
115 double dt1 = times.front() - b0;
116 double dt2 = b1 - times.back();
117
118 // interpolation factor
119 double alpha = dt1 + dt2 > 0 ? dt2 / (dt1 + dt2) : 0.0;
120
121 v0 = (1.0 - alpha) * data.back() + alpha * data.front();
122 v1 = v0;
123 }
124
125 {
126 m_impl->period = b1 - b0;
128
129 assert(m_impl->period > 0.0);
130 }
131 }
132
133 // Note: this should take care of file with one record as well.
134 m_impl->times.clear();
135 m_impl->values.clear();
136 {
137 if (bounds.front() < times.front()) {
138 m_impl->times.emplace_back(bounds.front());
139 m_impl->values.emplace_back(v0);
140 }
141 for (size_t k = 0; k < N; ++k) {
142 m_impl->times.emplace_back(times[k]);
143 m_impl->values.emplace_back(data[k]);
144 }
145 if (bounds.back() > times.back()) {
146 m_impl->times.emplace_back(bounds.back());
147 m_impl->values.emplace_back(v1);
148 }
149 }
150
151 // Save the time interval covered by this forcing.
152 forcing_t0 = bounds.front();
153 forcing_t1 = bounds.back();
154 } // end of the block initializing data
155
156 // validate resulting times
157 if (not is_increasing(m_impl->times)) {
159 "m_impl->times have to be strictly increasing (this is a bug)");
160 }
161
162 {
163 VariableMetadata variable(variable_name, unit_system);
164
165 variable["units"] = units;
166 variable["output_units"] = output_units;
167 variable["long_name"] = long_name;
168
169 report_range(m_impl->values, variable, *ctx.log());
170 }
171
172 // Set up interpolation.
173 {
174 m_impl->acc = gsl_interp_accel_alloc();
175 m_impl->spline = gsl_spline_alloc(gsl_interp_linear, m_impl->times.size());
176 gsl_spline_init(m_impl->spline, m_impl->times.data(), m_impl->values.data(), m_impl->times.size());
177 }
178
179 bool extrapolate = ctx.config()->get_flag("input.forcing.time_extrapolation");
180
181 if (not (extrapolate or periodic)) {
182 check_forcing_duration(*ctx.time(), forcing_t0, forcing_t1);
183 }
184
185 } catch (RuntimeError &e) {
186 e.add_context("reading '%s' (%s) from '%s'",
187 long_name.c_str(), variable_name.c_str(), filename.c_str());
188 throw;
189 }
190}
191
192ScalarForcing::ScalarForcing(const Context &ctx, const std::string &prefix,
193 const std::string &variable_name, const std::string &units,
194 const std::string &output_units, const std::string &long_name)
195 : m_impl(new Impl) {
196
197 m_impl->acc = nullptr;
198 m_impl->spline = nullptr;
199 m_impl->period = 0.0;
200 m_impl->period_start = 0.0;
201
202 auto config = ctx.config();
203
204 auto filename = config->get_string(prefix + ".file");
205 bool periodic = config->get_flag(prefix + ".periodic");
206
207 if (filename.empty()) {
209 "%s.file is required", prefix.c_str());
210 }
211
212 initialize(ctx, filename, variable_name, units, output_units,
213 long_name, periodic);
214}
215
217 const std::string &filename,
218 const std::string &variable_name,
219 const std::string &units,
220 const std::string &output_units,
221 const std::string &long_name,
222 bool periodic)
223 : m_impl(new Impl) {
224
225 m_impl->acc = nullptr;
226 m_impl->spline = nullptr;
227 m_impl->period = 0.0;
228 m_impl->period_start = 0.0;
229
230 initialize(ctx, filename, variable_name, units, output_units,
231 long_name, periodic);
232}
233
235 if (m_impl->spline != nullptr) {
236 gsl_spline_free(m_impl->spline);
237 }
238 if (m_impl->acc != nullptr) {
239 gsl_interp_accel_free(m_impl->acc);
240 }
241
242 delete m_impl;
243}
244
245double ScalarForcing::value(double t) const {
246 double T = t;
247
248 if (m_impl->period > 0.0) {
249 // compute time since the period start
250 T -= m_impl->period_start;
251
252 // remove an integer number of periods
253 T -= std::floor(T / m_impl->period) * m_impl->period;
254
255 // add the period start back
256 T += m_impl->period_start;
257 }
258
259 if (T > m_impl->times.back()) {
260 return m_impl->values.back();
261 }
262
263 if (T < m_impl->times.front()) {
264 return m_impl->values.front();
265 }
266
267 return gsl_spline_eval(m_impl->spline, T, m_impl->acc);
268}
269
270// Integrate from a to b, interpreting data as *not* periodic
271double ScalarForcing::integral(double a, double b) const {
272 assert(b >= a);
273
274 double dt = b - a;
275
276 double t0 = m_impl->times.front();
277 double t1 = m_impl->times.back();
278 double v0 = m_impl->values[0];
279 double v1 = m_impl->values.back();
280
281 // both points are to the left of [t0, t1]
282 if (b <= t0) {
283 return v0 * (b - a);
284 }
285
286 // both points are to the right of [t0, t1]
287 if (a >= t1) {
288 return v1 * (b - a);
289 }
290
291 // a is to the left of [t0, t1]
292 if (a < t0) {
293 return v0 * (t0 - a) + integral(t0, b);
294 }
295
296 // b is to the right of [t0, t1]
297 if (b > t1) {
298 return integral(a, t1) + v1 * (b - t1);
299 }
300
301 // both points are inside [t0, t1]
302 size_t ai = gsl_interp_bsearch(m_impl->times.data(), a, 0, m_impl->times.size() - 1);
303 size_t bi = gsl_interp_bsearch(m_impl->times.data(), b, 0, m_impl->times.size() - 1);
304
305 // gsl_interp_bsearch() above returns the index i of the array ‘m_impl->times’ such that
306 // ‘m_impl->times[i] <= t < m_impl->times[i+1]’. The index is searched for in the
307 // range [0, m_impl->times.size() - 1] (inclusive).
308
309 double v_a = gsl_spline_eval(m_impl->spline, a, m_impl->acc);
310 double v_b = gsl_spline_eval(m_impl->spline, b, m_impl->acc);
311
312 if (ai == bi) {
313 return 0.5 * (v_a + v_b) * dt;
314 }
315
316 double result = 0.0;
317 // integrate from a to the data point just to its right
318 result += 0.5 * (v_a + m_impl->values[ai + 1]) * (m_impl->times[ai + 1] - a);
319
320 // integrate over (possibly zero) intervals between a and b
321 for (size_t k = ai + 1; k < bi; ++k) {
322 result += 0.5 * (m_impl->values[k] + m_impl->values[k + 1]) * (m_impl->times[k + 1] - m_impl->times[k]);
323 }
324
325 result += 0.5 * (m_impl->values[bi] + v_b) * (b - m_impl->times[bi]);
326
327 return result;
328}
329
330double ScalarForcing::average(double t, double dt) const {
331 if (dt == 0.0) {
332 return value(t);
333 }
334
335 if (dt < 0.0) {
336 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "negative interval length");
337 }
338
339 if (m_impl->period <= 0.0) {
340 // regular case
341 return integral(t, t + dt) / dt;
342 }
343
344 // periodic case
345 {
346 double a = t;
347 double b = t + dt;
348 double t0 = m_impl->period_start;
349 double P = m_impl->period;
350
351 double N = std::floor((a - t0) / P);
352 double M = std::floor((b - t0) / P);
353 double delta = a - t0 - P * N;
354 double gamma = b - t0 - P * M;
355
356 double N_periods = M - (N + 1);
357
358 if (N_periods >= 0.0) {
359 return (integral(t0 + delta, t0 + P) +
360 N_periods * integral(t0, t0 + P) +
361 integral(t0, t0 + gamma)) / dt;
362 }
363
364 return integral(t0 + delta, t0 + gamma) / dt;
365 }
366}
367
368
369} // 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:57
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
#define PISM_ERROR_LOCATION
static const double b0
Definition exactTestL.cc:41
@ PISM_NETCDF3
Definition IO_Flags.hh:58
@ PISM_READONLY
open an existing file for reading only
Definition IO_Flags.hh:69
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)
TimeInfo read_time_info(const units::Unit &time_units, const std::string &calendar, const File &file, const std::string &time_name)
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:903
std::vector< double > times
std::vector< double > values
gsl_interp_accel * acc