PISM, A Parallel Ice Sheet Model  stable v2.1-1-g6902d5502 committed by Ed Bueler on 2023-12-20 08:38:27 -0800
Isochrones.cc
Go to the documentation of this file.
1 /* Copyright (C) 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 <algorithm>
21 #include <cassert> // assert
22 #include <cstddef> // size_t
23 #include <gsl/gsl_interp.h> // gsl_interp_bsearch()
24 #include <memory> // std::shared_ptr
25 #include <string>
26 #include <vector>
27 
28 #include "pism/age/Isochrones.hh"
29 #include "pism/util/Context.hh"
30 #include "pism/util/MaxTimestep.hh"
31 #include "pism/util/Time.hh"
32 #include "pism/stressbalance/StressBalance.hh"
33 #include "pism/util/array/Array3D.hh"
34 #include "pism/util/array/Scalar.hh"
35 #include "pism/util/interpolation.hh"
36 #include "pism/util/petscwrappers/Vec.hh"
37 
38 namespace pism {
39 
40 namespace details {
41 static const char *layer_thickness_variable_name = "isochronal_layer_thickness";
42 static const char *deposition_time_variable_name = "deposition_time";
43 static const char *isochrone_depth_variable_name = "isochrone_depth";
44 
45 static const char *times_parameter = "isochrones.deposition_times";
46 static const char *N_max_parameter = "isochrones.max_n_layers";
47 static const char *N_boot_parameter = "isochrones.bootstrapping.n_layers";
48 
49 
50 //! Checks if a vector of doubles is not decreasing.
51 static bool non_decreasing(const std::vector<double> &a) {
52  size_t N = a.size();
53 
54  if (N < 2) {
55  return true;
56  }
57 
58  for (size_t k = 0; k < N - 1; k++) {
59  if (a[k] > a[k + 1]) {
60  return false;
61  }
62  }
63 
64  return true;
65 }
66 
67 /*!
68  * Allocate storage for layer thicknesses given a list of deposition times `times`.
69  *
70  * Also sets metadata.
71  */
72 static std::shared_ptr<array::Array3D> allocate_layer_thickness(std::shared_ptr<const Grid> grid,
73  const std::vector<double> &times) {
74  using namespace details;
75 
76  auto N_max = (int)grid->ctx()->config()->get_number(N_max_parameter);
77  if ((int)times.size() > N_max) {
79  "the total number of isochronal layers (%d) exceeds '%s' = %d",
80  (int)times.size(), N_max_parameter, (int)N_max);
81  }
82 
83  const auto &time = grid->ctx()->time();
84 
85  auto result = std::make_shared<array::Array3D>(grid, layer_thickness_variable_name,
86  array::WITHOUT_GHOSTS, times);
87 
88  result->metadata().long_name("thicknesses of isochronal layers").units("m");
89 
90  auto z_description =
91  pism::printf("times for isochrones in '%s'; earliest deposition times for layers in '%s'",
93  auto &z = result->metadata(0).z();
94  z.clear()
96  .long_name(z_description)
97  .units(time->units_string());
98  z["calendar"] = time->calendar();
99 
100  return result;
101 }
102 
103 /*!
104  * Allocate layer thicknesses and copy relevant info from `input`.
105  *
106  * @param[in] input input layer thicknesses and deposition times, read from an input file
107  * @param[in] T_start start time of the current run
108  * @param[in] requested_times requested deposition times
109  */
110 static std::shared_ptr<array::Array3D>
111 allocate_layer_thickness(const array::Array3D &input, double T_start,
112  const std::vector<double> &requested_times) {
113 
114  auto grid = input.grid();
115 
116  const auto &input_times = input.levels();
117 
118  // the sequence of deposition times has to be monotonically non-decreasing
119  // (bootstrapping may create several layers with identical deposition times)
120  if (!details::non_decreasing(input_times)) {
122  "deposition times in '%s' have to be non-decreasing",
123  input.get_name().c_str());
124  }
125 
126  std::vector<double> deposition_times;
127  for (auto t : input_times) {
128  if (t <= T_start) {
129  deposition_times.push_back(t);
130  }
131  }
132  auto N_layers_to_copy = deposition_times.size();
133 
134  double last_kept_time = deposition_times.back();
135  for (auto t : requested_times) {
136  if (t > last_kept_time) {
137  deposition_times.push_back(t);
138  }
139  }
140 
141  auto result = allocate_layer_thickness(grid, deposition_times);
142 
143  array::AccessScope scope{ &input, result.get() };
144 
145  for (Points p(*grid); p; p.next()) {
146  const int i = p.i(), j = p.j();
147 
148  const auto *in = input.get_column(i, j);
149  auto *out = result->get_column(i, j);
150 
151  for (size_t k = 0; k < N_layers_to_copy; ++k) {
152  out[k] = in[k];
153  }
154  }
155 
156  return result;
157 }
158 
159 /*!
160  * Discard requested deposition times before the beginning and after the end of the run.
161  */
162 static std::vector<double> prune_deposition_times(const Time &time,
163  const std::vector<double> &times) {
164  double T_start = time.start(), T_end = time.end();
165 
166  std::vector<double> result;
167  for (auto t : times) {
168  if (t >= T_start and t <= T_end) {
169  result.push_back(t);
170  }
171  }
172 
173 
174  return result;
175 }
176 
177 /*!
178  * Read deposition times from an `input_file`.
179  */
180 static std::vector<double> deposition_times(const File &input_file) {
181  using namespace details;
182 
183  auto n_deposition_times = input_file.dimension_length(deposition_time_variable_name);
184 
185  std::vector<double> result(n_deposition_times);
186  input_file.read_variable(deposition_time_variable_name, { 0 }, { n_deposition_times },
187  result.data());
188 
189  return result;
190 }
191 
192 /*!
193  * Process configuration parameters and return requested deposition times, excluding times
194  * outside the current modeled time interval.
195  */
196 std::vector<double> deposition_times(const Config &config, const Time &time) {
197  using namespace details;
198  try {
199  auto N_max = (int)config.get_number(N_max_parameter);
200 
201  if (N_max < 0) {
202  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "%s have to be non-negative (got %d)",
203  N_max_parameter, N_max);
204  }
205 
206  auto requested_times = config.get_string(times_parameter);
207 
208  auto deposition_times = prune_deposition_times(time, time.parse_times(requested_times));
209 
210  auto N_deposition_times = deposition_times.size();
211  if (N_deposition_times == 0) {
213  "'%s' = '%s' has no times within the modeled time span [%s, %s]",
214  times_parameter, requested_times.c_str(),
215  time.date(time.start()).c_str(), time.date(time.end()).c_str());
216  }
217 
218  if ((int)N_deposition_times > N_max) {
221  "the number of times (%d) in '%s' exceeds the allowed maximum ('%s' = %d)",
222  (int)N_deposition_times, times_parameter, N_max_parameter, N_max);
223  }
224 
225  return deposition_times;
226  } catch (RuntimeError &e) {
227  e.add_context("processing parameter '%s'", times_parameter);
228  throw;
229  }
230 }
231 
232 /*!
233  * Read layer deposition times and layer thicknesses from `input_file`.
234  *
235  * Uses bilinear interpolation in the X and Y directions.
236  */
237 static std::shared_ptr<array::Array3D> regrid_layer_thickness(std::shared_ptr<const Grid> grid,
238  const File &input_file, int record) {
239 
240  auto result = details::allocate_layer_thickness(grid, deposition_times(input_file));
241 
242  auto N = (int)result->levels().size();
243 
244  auto metadata = result->metadata(0);
245 
246  auto variable_info = input_file.find_variable(metadata.get_name(), metadata["standard_name"]);
247 
248  grid::InputGridInfo input_grid(input_file, variable_info.name, metadata.unit_system(),
249  grid->registration());
250 
251  // Set up 2D interpolation:
252  LocalInterpCtx lic(input_grid, *grid, LINEAR);
253  lic.start[T_AXIS] = record;
254  lic.count[T_AXIS] = 1;
255  lic.start[Z_AXIS] = 0;
256  lic.count[Z_AXIS] = N;
257  // Create an "identity map" version of the interpolation in the "vertical" direction. We
258  // can't use deposition times themselves since they may not be unique (bootstrapping
259  // layers have the same deposition time).
260  {
261  std::vector<double> Z(N);
262  for (int k = 0; k < N; ++k) {
263  Z[k] = k;
264  }
265 
266  // note matching input and output grids below:
267  lic.z = std::make_shared<Interpolation>(NEAREST, Z, Z);
268  }
269 
270  petsc::VecArray tmp(result->vec());
271  io::regrid_spatial_variable(metadata, *grid, lic, input_file, tmp.get());
272 
273  return result;
274 }
275 
276 /*!
277  * Read layer thickness from an `input_file` (without interpolation).
278  */
279 static std::shared_ptr<array::Array3D> read_layer_thickness(std::shared_ptr<const Grid> grid,
280  const File &input_file, int record) {
281  auto result = allocate_layer_thickness(grid, deposition_times(input_file));
282 
283  result->read(input_file, record);
284 
285  return result;
286 }
287 
288 /*!
289  * Compute the number of "active" layers in `deposition_times`.
290  *
291  * A layer is "active" if the model reached its deposition time.
292  */
293 static size_t n_active_layers(std::vector<double> deposition_times, double current_time) {
294  size_t result = 0;
295  for (auto t : deposition_times) {
296  if (t <= current_time) {
297  result += 1;
298  }
299  }
300 
301  return result;
302 }
303 
304 /*!
305  * Returns `true` if the user asked to regrid layer thicknesses, otherwise `false`.
306  */
307 static bool regridp(const Config &config) {
308 
309  auto regrid_file = config.get_string("input.regrid.file");
310 
311  if (regrid_file.empty()) {
312  return false;
313  }
314 
315  auto regrid_vars = set_split(config.get_string("input.regrid.vars"), ',');
316 
317  return member(details::layer_thickness_variable_name, regrid_vars);
318 }
319 
320 /*!
321  * Re-scale layer thicknesses so that the sum of layer thicknesses is equal to the
322  * ice_thickness.
323  *
324  * @param[in] ice_thickness ice thickness, meters
325  * @param[in,out] layer_thickness isochronal layer thickness
326  */
327 static void renormalize(const array::Scalar &ice_thickness, array::Array3D &layer_thickness) {
328  auto grid = layer_thickness.grid();
329 
330  auto N = layer_thickness.levels().size();
331 
332  array::AccessScope scope{ &ice_thickness, &layer_thickness };
333 
334  for (auto p = grid->points(); p; p.next()) {
335  const int i = p.i(), j = p.j();
336 
337  double *H = layer_thickness.get_column(i, j);
338 
339  double H_total = 0.0;
340  for (int k = 0; k < (int)N; ++k) {
341  H_total += H[k];
342  }
343 
344  // re-scale so that the sum of layer thicknesses is equal to the ice_thickness
345  if (H_total > 0.0) {
346  double S = ice_thickness(i, j) / H_total;
347  for (size_t k = 0; k < N; ++k) {
348  H[k] *= S;
349  }
350  }
351  }
352 }
353 
354 } // namespace details
355 
356 
357 Isochrones::Isochrones(std::shared_ptr<const Grid> grid,
358  std::shared_ptr<const stressbalance::StressBalance> stress_balance)
359  : Component(grid), m_stress_balance(stress_balance) {
360 
361  auto time = grid->ctx()->time();
362 
363  // Allocate storage for *one* isochronal layer just to ensure that this instance is in
364  // a valid state when the constrictor returns.
365  //
366  // Note: array::Array delays allocation until the last moment, so we can cheaply
367  // re-allocate storage if the number of "levels" used here turns out to be
368  // inappropriate.
372 }
373 
374 /*!
375  * When bootstrapping, we can put all the existing ice thickness into the bottom layer and
376  * then keep adding to it until we reach the next deposition time, *or*, we can distribute
377  * the ice thickness among N "bootstrapping" layers and then apply SMB to the layer `N+1`.
378  * The second option allows us to increase accuracy: the quality of the horizontal
379  * velocity approximation used to transport mass within layers is higher if the layers are
380  * thin.
381  */
382 void Isochrones::bootstrap(const array::Scalar &ice_thickness) {
383  using namespace details;
384  try {
385  if (regridp(*m_config)) {
386  File regrid_file(m_grid->com, m_config->get_string("input.regrid.file"), io::PISM_GUESS,
388 
389  auto last_record =
391 
392  initialize(regrid_file, (int)last_record, true);
393 
394  details::renormalize(ice_thickness, *m_layer_thickness);
395 
396  return;
397  }
398 
399  auto time = m_grid->ctx()->time();
400 
401  auto requested_times = deposition_times(*m_config, *time);
402 
403  auto N_bootstrap = static_cast<int>(m_config->get_number(N_boot_parameter));
404  auto N_max = static_cast<int>(m_config->get_number(N_max_parameter));
405  auto N_deposition_times = requested_times.size();
406 
407  if (N_bootstrap < 0) {
408  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "%s have to be non-negative (got %d)",
409  N_boot_parameter, N_bootstrap);
410  }
411 
412  m_log->message(2,
413  "* Bootstrapping the isochrone tracking model, adding %d isochronal layers...\n",
414  N_bootstrap);
415 
416  if (N_bootstrap + (int)N_deposition_times > N_max) {
417  auto times = m_config->get_string(times_parameter);
420  "%s (%d) + %s (%d) exceeds the allowed maximum (%s = %d)", N_boot_parameter,
421  (int)N_bootstrap, times_parameter, (int)N_deposition_times, N_max_parameter, (int)N_max);
422  }
423 
424  if (N_bootstrap > 0) {
425  // create N_bootstrap layers, all with the starting time as the earliest deposition
426  // time:
427  std::vector<double> deposition_times(N_bootstrap, time->start());
428  // add requested_times:
429  for (const auto &t : requested_times) {
430  deposition_times.push_back(t);
431  }
432 
433  // re-allocate storage
436 
437  array::AccessScope scope{ &ice_thickness, m_layer_thickness.get() };
438 
439  for (auto p = m_grid->points(); p; p.next()) {
440  const int i = p.i(), j = p.j();
441 
442  double H = ice_thickness(i, j);
443 
444  double *column = m_layer_thickness->get_column(i, j);
445  for (int k = 0; k < N_bootstrap; ++k) {
446  column[k] = H / static_cast<double>(N_bootstrap);
447  }
448  }
449  } else {
450 
453 
454  array::AccessScope scope{ &ice_thickness, m_layer_thickness.get() };
455 
456  for (auto p = m_grid->points(); p; p.next()) {
457  const int i = p.i(), j = p.j();
458  m_layer_thickness->get_column(i, j)[0] = ice_thickness(i, j);
459  }
460  }
461 
463  {
464  std::vector<std::string> dates;
465  for (auto t : m_layer_thickness->levels()) {
466  dates.push_back(time->date(t));
467  }
468  m_log->message(3, "Deposition times: %s\n", join(dates, ", ").c_str());
469  m_log->message(3, "Time: %s. Top layer index: %d\n", time->date(time->current()).c_str(),
470  (int)m_top_layer_index);
471  }
472  } catch (RuntimeError &e) {
473  e.add_context("bootstrapping the isochrone tracking model");
474  throw;
475  }
476 }
477 
478 /*!
479  * Initialize from the `input_file` using `record`. Uses regridding if `use_interpolation`
480  * is true.
481  */
482 void Isochrones::initialize(const File &input_file, int record, bool use_interpolation) {
483  try {
484  using namespace details;
485 
486  m_log->message(2, "* Initializing the isochrone tracking model from '%s'...\n",
487  input_file.filename().c_str());
488 
489  if (use_interpolation) {
490  m_log->message(2, " [using bilinear interpolation to read layer thicknesses]\n");
491  }
492 
493  const auto &time = m_grid->ctx()->time();
494 
495  {
496  std::shared_ptr<array::Array3D> tmp;
497 
498  if (use_interpolation) {
499  tmp = regrid_layer_thickness(m_grid, input_file, record);
500  } else {
501  tmp = read_layer_thickness(m_grid, input_file, record);
502  }
503 
506  }
508 
509  // set m_top_layer_index
511 
512  {
513  std::vector<std::string> dates;
514  for (auto t : m_layer_thickness->levels()) {
515  dates.push_back(time->date(t));
516  }
517  m_log->message(3, "Deposition times: %s\n", join(dates, ", ").c_str());
518  m_log->message(3, "Time: %s. Top layer index: %d\n", time->date(time->current()).c_str(),
519  (int)m_top_layer_index);
520  }
521 
522  } catch (RuntimeError &e) {
523  e.add_context("initializing the isochrone tracking model");
524  throw;
525  }
526 }
527 
528 /*!
529  * Re-start the model from a PISM output file.
530  */
531 void Isochrones::restart(const File &input_file, int record) {
532  if (details::regridp(*m_config)) {
533  File regrid_file(m_grid->com, m_config->get_string("input.regrid.file"), io::PISM_GUESS,
535 
536  auto last_record = regrid_file.nrecords(details::layer_thickness_variable_name, "", m_sys);
537 
538  initialize(regrid_file, (int)last_record, true);
539  } else {
540  initialize(input_file, record, false);
541  }
542 }
543 
544 /*!
545  * Update layer thicknesses.
546  *
547  * @param[in] t time step start, seconds
548  * @param[in] dt time step length, seconds
549  * @param[in] u x-component of the ice velocity, m/s
550  * @param[in] v y-component of the ice velocity, m/s
551  * @param[in] ice thickness, m
552  * @param[in] top_surface_mass_balance total top surface mass balance over the time step, meters
553  * @param[in] bottom_surface_mass_balance total bottom surface mass balance over the time step, meters
554  *
555  * For mass balance inputs, positive corresponds to mass gain.
556  */
557 void Isochrones::update(double t, double dt, const array::Array3D &u, const array::Array3D &v,
558  const array::Scalar &ice_thickness,
559  const array::Scalar &top_surface_mass_balance,
560  const array::Scalar &bottom_surface_mass_balance) {
561 
562  // apply top surface and basal mass balance terms:
563  {
564  array::AccessScope scope{ &top_surface_mass_balance, &bottom_surface_mass_balance,
565  m_layer_thickness.get() };
566 
567  for (auto p = m_grid->points(); p; p.next()) {
568  const int i = p.i(), j = p.j();
569 
570  double *H = m_layer_thickness->get_column(i, j);
571 
572  // apply the surface mass balance
573  {
574  double dH = top_surface_mass_balance(i, j);
575 
576  // apply thickness change to a layer, starting from the top-most
577  for (int k = (int)m_top_layer_index; k >= 0; --k) {
578  if (H[k] + dH >= 0.0) {
579  // thickness change is non-negative or does not remove the whole layer: apply to
580  // the current layer and stop
581  H[k] += dH;
582  break;
583  }
584 
585  dH += H[k];
586  H[k] = 0.0;
587  }
588  }
589  // apply the basal melt rate
590  {
591  double dH = bottom_surface_mass_balance(i, j);
592 
593  // apply thickness change to a layer, starting from the bottom
594  for (size_t k = 0; k <= m_top_layer_index; ++k) {
595  if (H[k] + dH >= 0.0) {
596  // thickness change is non-negative or does not remove the whole layer: apply to
597  // the current layer and stop
598  H[k] += dH;
599  break;
600  }
601 
602  dH += H[k];
603  H[k] = 0.0;
604  }
605  }
606  }
607  }
608 
609  // transport mass within layers:
610  {
611  // note: this updates ghosts of m_tmp
612  m_tmp->copy_from(*m_layer_thickness);
613 
614  array::AccessScope scope{ &u, &v, m_layer_thickness.get(), m_tmp.get(), &ice_thickness };
615 
616  double dx = m_grid->dx(), dy = m_grid->dy(),
617  H_min = m_config->get_number("geometry.ice_free_thickness_standard");
618 
619  // flux estimated using first-order upwinding
620  auto Q = [](double U, double f_n, double f_p) { return U * (U >= 0 ? f_n : f_p); };
621 
622  for (auto p = m_grid->points(); p; p.next()) {
623  const int i = p.i(), j = p.j();
624 
625  const double *d_c = m_tmp->get_column(i, j), *d_n = m_tmp->get_column(i, j + 1),
626  *d_e = m_tmp->get_column(i + 1, j), *d_s = m_tmp->get_column(i, j - 1),
627  *d_w = m_tmp->get_column(i - 1, j);
628 
629  double *d = m_layer_thickness->get_column(i, j);
630 
632  double d_total = 0.0;
633  for (size_t k = 0; k <= m_top_layer_index; ++k) {
634 
635  // Evaluate velocities in the *middle* (vertically) of the current layer. I am
636  // guessing that in many applications near the base of the ice layers get thin, so
637  // *sampling* is okay because a layer thickness is likely to be smaller than the
638  // vertical grid resolution used by the stress balance model. In the upper half of
639  // ice thickness, on the other hand, we have less variation of ice velocity in the
640  // vertical (du/dz is smaller), so we don't lose too much accuracy by linearizing
641  // u(z). For a linear f(x) we have
642  //
643  // (f(a) + f(b))/2 = f((a + b) / 2) = f(a + (b - a) / 2),
644  //
645  // which allows us to estimate the "average horizontal velocity in a layer" using
646  // *one* interpolation.
647  //
648  // Note, however, that the modeled depth of an isochrone is affected by depths of
649  // all the isochrones below it since the elevation of an isochrone above the bed is
650  // the sum of depths of all the layers below it.
651  //
652  // This implies that we should have at least a few layers *below* an isochrone we're
653  // interested in.
654 
655  double U = u.interpolate(i, j, z.c + 0.5 * d_c[k]),
656  U_e = u.interpolate(i + 1, j, z.e + 0.5 * d_e[k]),
657  U_w = u.interpolate(i - 1, j, z.w + 0.5 * d_w[k]);
658 
659  double V = v.interpolate(i, j, z.c + 0.5 * d_c[k]),
660  V_n = v.interpolate(i, j + 1, z.n + 0.5 * d_n[k]),
661  V_s = v.interpolate(i, j - 1, z.s + 0.5 * d_s[k]);
662 
663  double Q_n = Q(0.5 * (V + V_n), d_c[k], d_n[k]), Q_e = Q(0.5 * (U + U_e), d_c[k], d_e[k]),
664  Q_s = Q(0.5 * (V + V_s), d_s[k], d_c[k]), Q_w = Q(0.5 * (U + U_w), d_w[k], d_c[k]);
665 
666  d[k] = d_c[k] - dt * ((Q_e - Q_w) / dx + (Q_n - Q_s) / dy);
667 
668  assert(d[k] >= 0.0);
669 
670  // ensure non-negativity (should not be necessary, but still)
671  d[k] = std::max(d[k], 0.0);
672 
673  d_total += d[k];
674 
675  z.c += d_c[k];
676  z.n += d_n[k];
677  z.e += d_e[k];
678  z.s += d_s[k];
679  z.w += d_w[k];
680  }
681 
682  assert(ice_thickness(i, j) < H_min or d_total > 0.0);
683 
684  // re-scale so that the sum of layer thicknesses is equal to the ice_thickness
685  if (d_total > 0.0) {
686  double S = ice_thickness(i, j) / d_total;
687  for (size_t k = 0; k <= m_top_layer_index; ++k) {
688  d[k] *= S;
689  }
690  }
691  }
692  }
693 
694  // add one more layer if we reached the next deposition time
695  {
696  double T = t + dt;
697  const auto &deposition_times = m_layer_thickness->levels();
698  size_t N = deposition_times.size();
699 
700  // Find the index k such that deposition_times[k] <= T
701  //
702  // Note: `k` below will be strictly less than `N`, ensuring that the index "k"
703  // is valid.
704  //
705  // FIXME: consider using a gsl_interp_accel to speed this up
706  size_t k = gsl_interp_bsearch(deposition_times.data(), T, 0, N);
707 
708  double T_k = deposition_times[k];
709 
711  if (T_k > top_layer_deposition_time) {
712  // we reached the next requested deposition time
713 
714  if (m_top_layer_index < N - 1) {
715  // not too many layers yet: add one more
716  m_top_layer_index += 1;
717 
718  const auto &time = m_grid->ctx()->time();
719  m_log->message(2, " New isochronal layer %d at %s\n", (int)m_top_layer_index,
720  time->date(T).c_str());
721  } else {
722  // we have as many layers as we can handle: keep adding to the top layer
723  m_log->message(2,
724  "Isochrone tracking: reached isochrones.max_n_layers and can't add more.\n"
725  " SMB will contribute to the current top layer.");
726  }
727  }
728  }
729 }
730 
733 }
734 
736 
737  if (m_stress_balance == nullptr) {
739  "Isochrone tracking: no stress balance provided. "
740  "Cannot compute the maximum time step.");
741  }
742 
743  return MaxTimestep(m_stress_balance->max_timestep_cfl_3d().dt_max.value(), "isochrones");
744 }
745 
746 /*!
747  * Maximum time step we can take at time `t`.
748  *
749  * We can go up to the next deposition time.
750  */
752  auto deposition_times = m_layer_thickness->levels();
753 
754  double t0 = deposition_times[0];
755  if (t < t0) {
756  return { t0 - t, "isochrones" };
757  }
758 
759  if (t >= deposition_times.back()) {
760  return { "isochrones" };
761  }
762 
763  auto N = deposition_times.size();
764 
765  // Find the index k such that m_deposition_times[k] <= T < m_deposition_times[k + 1]
766  //
767  // Note: `k` below will be strictly less than `N - 1`, ensuring that the index "k + 1"
768  // is valid.
769  //
770  // FIXME: consider using gsl_interp_accel to speed this up
771  size_t k = gsl_interp_bsearch(deposition_times.data(), t, 0, N - 1);
772 
773  return { deposition_times[k + 1] - t, "isochrones" };
774 }
775 
776 /*!
777  * Define the model state in an output file.
778  *
779  * We are saving layer thicknesses, deposition times, and the number of active layers.
780  */
781 void Isochrones::define_model_state_impl(const File &output) const {
782  m_layer_thickness->define(output, io::PISM_DOUBLE);
783 }
784 
785 /*!
786  * Write the model state to an output file.
787  */
788 void Isochrones::write_model_state_impl(const File &output) const {
789  m_layer_thickness->write(output);
790 }
791 
793  return *m_layer_thickness;
794 }
795 
796 namespace diagnostics {
797 
798 /*! @brief Report isochrone depth below surface, in meters */
799 class IsochroneDepths : public Diag<Isochrones> {
800 public:
802  using namespace details;
803 
804  const auto &time = m_grid->ctx()->time();
805 
806  m_vars = { { m_sys, isochrone_depth_variable_name, model->layer_thicknesses().levels() } };
807 
808  auto description = pism::printf("depth below surface of isochrones for times in '%s'",
810 
811  m_vars[0].long_name(description).units("m");
812  auto &z = m_vars[0].z();
813  z.clear()
815  .long_name(
816  pism::printf("deposition times for isochrones in '%s'", isochrone_depth_variable_name))
817  .units(time->units_string());
818  z["calendar"] = time->calendar();
819  }
820 
821 protected:
822  std::shared_ptr<array::Array> compute_impl() const {
823 
824  const auto &layer_thicknesses = model->layer_thicknesses();
825 
826  auto result = layer_thicknesses.duplicate();
827  result->metadata(0) = m_vars[0];
828 
829  size_t N = result->levels().size();
830 
831  array::AccessScope scope{ &layer_thicknesses, result.get() };
832 
833  for (auto p = m_grid->points(); p; p.next()) {
834  const int i = p.i(), j = p.j();
835 
836  double *column = result->get_column(i, j);
837  const double *d = layer_thicknesses.get_column(i, j);
838 
839  double total_depth = 0.0;
840  for (int k = (int)N - 1; k >= 0; --k) {
841  total_depth += d[k];
842  column[k] = total_depth;
843  }
844  }
845 
846  return result;
847  }
848 };
849 
850 } // end of namespace diagnostics
851 
856 }
857 
858 } // end of namespace pism
859 
860 /*
861  * LocalWords: LocalWords deposition
862 */
const units::System::Ptr m_sys
unit system used by this component
Definition: Component.hh:160
const Time & time() const
Definition: Component.cc:109
std::shared_ptr< const Grid > grid() const
Definition: Component.cc:105
const Config::ConstPtr m_config
configuration database used by this component
Definition: Component.hh:158
const Logger::ConstPtr m_log
logger (for easy access)
Definition: Component.hh:162
const std::shared_ptr< const Grid > m_grid
grid used by this component
Definition: Component.hh:156
DiagnosticList diagnostics() const
Definition: Component.cc:89
A class defining a common interface for most PISM sub-models.
Definition: Component.hh:118
double get_number(const std::string &name, UseFlag flag=REMEMBER_THIS_USE) const
std::string get_string(const std::string &name, UseFlag flag=REMEMBER_THIS_USE) const
A class for storing and accessing PISM configuration flags and parameters.
A template derived from Diagnostic, adding a "Model".
Definition: Diagnostic.hh:166
static Ptr wrap(const T &input)
Definition: Diagnostic.hh:160
std::shared_ptr< Diagnostic > Ptr
Definition: Diagnostic.hh:65
void read_variable(const std::string &variable_name, const std::vector< unsigned int > &start, const std::vector< unsigned int > &count, double *ip) const
Definition: File.cc:747
VariableLookupData find_variable(const std::string &short_name, const std::string &std_name) const
Find a variable using its standard name and/or short name.
Definition: File.cc:361
unsigned int nrecords() const
Get the number of records. Uses the length of an unlimited dimension.
Definition: File.cc:313
std::string filename() const
Definition: File.cc:307
unsigned int dimension_length(const std::string &name) const
Get the length of a dimension.
Definition: File.cc:454
High-level PISM I/O class.
Definition: File.hh:56
std::shared_ptr< array::Array3D > m_layer_thickness
isochronal layer thicknesses
Definition: Isochrones.hh:73
void bootstrap(const array::Scalar &ice_thickness)
Definition: Isochrones.cc:382
MaxTimestep max_timestep_deposition_times(double t) const
Definition: Isochrones.cc:751
void update(double t, double dt, const array::Array3D &u, const array::Array3D &v, const array::Scalar &ice_thickness, const array::Scalar &top_surface_mass_balance, const array::Scalar &bottom_surface_mass_balance)
Definition: Isochrones.cc:557
MaxTimestep max_timestep_impl(double t) const
Definition: Isochrones.cc:731
std::shared_ptr< array::Array3D > m_tmp
temporary storage needed for time stepping
Definition: Isochrones.hh:76
void write_model_state_impl(const File &output) const
Definition: Isochrones.cc:788
std::shared_ptr< const stressbalance::StressBalance > m_stress_balance
Definition: Isochrones.hh:81
Isochrones(std::shared_ptr< const Grid > grid, std::shared_ptr< const stressbalance::StressBalance > stress_balance)
Definition: Isochrones.cc:357
DiagnosticList diagnostics_impl() const
Definition: Isochrones.cc:852
size_t m_top_layer_index
The index of the topmost isochronal layer.
Definition: Isochrones.hh:79
double top_layer_deposition_time() const
void define_model_state_impl(const File &output) const
Definition: Isochrones.cc:781
void restart(const File &input_file, int record)
Definition: Isochrones.cc:531
const array::Array3D & layer_thicknesses() const
Definition: Isochrones.cc:792
void initialize(const File &input_file, int record, bool use_interpolation)
Definition: Isochrones.cc:482
MaxTimestep max_timestep_cfl() const
Definition: Isochrones.cc:735
std::array< int, 4 > count
std::array< int, 4 > start
std::shared_ptr< Interpolation > z
Combines the max. time step with the flag indicating if a restriction is active. Makes is possible to...
Definition: MaxTimestep.hh:31
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::string units_string() const
Internal time units as a string.
Definition: Time.cc:477
double start() const
Definition: Time.cc:469
double end() const
Definition: Time.cc:473
double current() const
Current time, in seconds.
Definition: Time.cc:465
std::vector< double > parse_times(const std::string &spec) const
Definition: Time.cc:510
std::string date(double T) const
Returns the date corresponding to time T.
Definition: Time.cc:834
std::string calendar() const
Returns the calendar string.
Definition: Time.cc:486
Time management class.
Definition: Time.hh:55
Makes sure that we call begin_access() and end_access() for all accessed array::Arrays.
Definition: Array.hh:65
double interpolate(int i, int j, double z) const
Return value of scalar quantity at level z (m) above base of ice (by linear interpolation).
Definition: Array3D.cc:88
std::shared_ptr< Array3D > duplicate(Kind ghostedp=WITHOUT_GHOSTS) const
Definition: Array3D.cc:240
double * get_column(int i, int j)
Definition: Array3D.cc:120
A virtual class collecting methods common to ice and bedrock 3D fields.
Definition: Array3D.hh:33
const std::vector< double > & levels() const
Definition: Array.cc:140
const std::string & get_name() const
Get the name of an Array object.
Definition: Array.cc:383
std::shared_ptr< const Grid > grid() const
Definition: Array.cc:132
IsochroneDepths(const Isochrones *m)
Definition: Isochrones.cc:801
std::shared_ptr< array::Array > compute_impl() const
Definition: Isochrones.cc:822
Report isochrone depth below surface, in meters.
Definition: Isochrones.cc:799
Contains parameters of an input file grid.
Definition: Grid.hh:65
double * get()
Definition: Vec.cc:54
Wrapper around VecGetArray and VecRestoreArray.
Definition: Vec.hh:44
#define PISM_ERROR_LOCATION
double max(const array::Scalar &input)
Finds maximum over all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:165
@ WITH_GHOSTS
Definition: Array.hh:62
@ WITHOUT_GHOSTS
Definition: Array.hh:62
double min(const array::Scalar &input)
Finds minimum over all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:193
static const char * N_max_parameter
Definition: Isochrones.cc:46
static std::vector< double > prune_deposition_times(const Time &time, const std::vector< double > &times)
Definition: Isochrones.cc:162
std::vector< double > deposition_times(const Config &config, const Time &time)
Definition: Isochrones.cc:196
static void renormalize(const array::Scalar &ice_thickness, array::Array3D &layer_thickness)
Definition: Isochrones.cc:327
static const char * isochrone_depth_variable_name
Definition: Isochrones.cc:43
static std::shared_ptr< array::Array3D > allocate_layer_thickness(std::shared_ptr< const Grid > grid, const std::vector< double > &times)
Definition: Isochrones.cc:72
static size_t n_active_layers(std::vector< double > deposition_times, double current_time)
Definition: Isochrones.cc:293
static std::shared_ptr< array::Array3D > allocate_layer_thickness(const array::Array3D &input, double T_start, const std::vector< double > &requested_times)
Definition: Isochrones.cc:111
static const char * layer_thickness_variable_name
Definition: Isochrones.cc:41
static const char * N_boot_parameter
Definition: Isochrones.cc:47
static const char * deposition_time_variable_name
Definition: Isochrones.cc:42
static std::shared_ptr< array::Array3D > read_layer_thickness(std::shared_ptr< const Grid > grid, const File &input_file, int record)
Definition: Isochrones.cc:279
static bool regridp(const Config &config)
Definition: Isochrones.cc:307
static const char * times_parameter
Definition: Isochrones.cc:45
static std::shared_ptr< array::Array3D > regrid_layer_thickness(std::shared_ptr< const Grid > grid, const File &input_file, int record)
Definition: Isochrones.cc:237
static std::vector< double > deposition_times(const File &input_file)
Definition: Isochrones.cc:180
static bool non_decreasing(const std::vector< double > &a)
Checks if a vector of doubles is not decreasing.
Definition: Isochrones.cc:51
static Vector3 column(const double A[3][3], size_t k)
Definition: Element.cc:51
@ PISM_GUESS
Definition: IO_Flags.hh:56
void regrid_spatial_variable(SpatialVariableMetadata &variable, const Grid &internal_grid, const LocalInterpCtx &lic, const File &file, double *output)
Regrid from a NetCDF file into a distributed array output.
Definition: io_helpers.cc:793
@ PISM_READONLY
open an existing file for reading only
Definition: IO_Flags.hh:72
@ PISM_DOUBLE
Definition: IO_Flags.hh:52
@ T_AXIS
Definition: IO_Flags.hh:33
@ Z_AXIS
Definition: IO_Flags.hh:33
std::string printf(const char *format,...)
std::map< std::string, Diagnostic::Ptr > DiagnosticList
Definition: Diagnostic.hh:125
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.
bool member(const std::string &string, const std::set< std::string > &set)
std::string join(const std::vector< std::string > &strings, const std::string &separator)
Concatenate strings, inserting separator between elements.
Star stencil points (in the map-plane).
Definition: stencils.hh:30
static double S(unsigned n)
Definition: test_cube.c:58