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