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