PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
SIAFD.cc
Go to the documentation of this file.
1// Copyright (C) 2004--2023, 2025 Jed Brown, Craig Lingle, Ed Bueler and Constantine Khroulev
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#include <cstdlib>
20#include <cassert>
21
22#include "pism/stressbalance/sia/BedSmoother.hh"
23#include "pism/stressbalance/sia/SIAFD.hh"
24#include "pism/geometry/Geometry.hh"
25#include "pism/rheology/FlowLawFactory.hh"
26#include "pism/rheology/grain_size_vostok.hh"
27#include "pism/stressbalance/StressBalance.hh"
28#include "pism/util/EnthalpyConverter.hh"
29#include "pism/util/Grid.hh"
30#include "pism/util/Profiling.hh"
31#include "pism/util/Time.hh"
32#include "pism/util/array/CellType.hh"
33#include "pism/util/array/Scalar.hh"
34#include "pism/util/error_handling.hh"
35#include "pism/util/pism_utilities.hh"
36#include "pism/util/array/Vector.hh"
37#include "pism/util/Logger.hh"
38
39namespace pism {
40namespace stressbalance {
41
42SIAFD::SIAFD(std::shared_ptr<const Grid> g)
43 : SSB_Modifier(std::move(g)),
44 m_stencil_width(m_config->get_number("grid.max_stencil_width")),
45 m_work_2d_0(m_grid, "work_vector_2d_0"),
46 m_work_2d_1(m_grid, "work_vector_2d_1"),
47 m_h_x(m_grid, "h_x"),
48 m_h_y(m_grid, "h_y"),
49 m_D(m_grid, "diffusivity"),
50 m_delta_0(m_grid, "delta_0", array::WITH_GHOSTS, m_grid->z()),
51 m_delta_1(m_grid, "delta_1", array::WITH_GHOSTS, m_grid->z()),
52 m_work_3d_0(m_grid, "work_3d_0", array::WITH_GHOSTS, m_grid->z()),
53 m_work_3d_1(m_grid, "work_3d_1", array::WITH_GHOSTS, m_grid->z()) {
54 // bed smoother
56
57 m_seconds_per_year = units::convert(m_sys, 1, "second", "years");
58
59 m_e_factor = m_config->get_number("stress_balance.sia.enhancement_factor");
61 m_config->get_number("stress_balance.sia.enhancement_factor_interglacial");
62
63 {
65 m_flow_law = ice_factory.create(m_config->get_string("stress_balance.sia.flow_law"),
66 m_config->get_number("stress_balance.sia.Glen_exponent"));
67 }
68
69 const bool compute_grain_size_using_age =
70 m_config->get_flag("stress_balance.sia.grain_size_age_coupling");
71 const bool age_model_enabled = m_config->get_flag("age.enabled");
72 const bool e_age_coupling = m_config->get_flag("stress_balance.sia.e_age_coupling");
73
74 if (compute_grain_size_using_age) {
75 if (not FlowLawUsesGrainSize(*m_flow_law)) {
77 "flow law %s does not use grain size "
78 "but sia.grain_size_age_coupling was set",
79 m_flow_law->name().c_str());
80 }
81
82 if (not age_model_enabled) {
84 "SIAFD: age model is not active but\n"
85 "age is needed for grain-size-based flow law %s",
86 m_flow_law->name().c_str());
87 }
88 }
89
90 if (e_age_coupling and not age_model_enabled) {
91 throw RuntimeError(PISM_ERROR_LOCATION, "SIAFD: age model is not active but\n"
92 "age is needed for age-dependent flow enhancement");
93 }
94
95 m_eemian_start = m_config->get_number("time.eemian_start", "seconds");
96 m_eemian_end = m_config->get_number("time.eemian_end", "seconds");
97 m_holocene_start = m_config->get_number("time.holocene_start", "seconds");
98}
99
101 delete m_bed_smoother;
102}
103
104//! \brief Initialize the SIA module.
106
108
109 m_log->message(2, "* Initializing the SIA stress balance modifier...\n");
110 m_log->message(2, " [using the %s flow law]\n", m_flow_law->name().c_str());
111
112
113 // implements an option e.g. described in @ref Greve97Greenland that is the
114 // enhancement factor is coupled to the age of the ice
115 if (m_config->get_flag("stress_balance.sia.e_age_coupling")) {
116 m_log->message(2,
117 " using age-dependent enhancement factor:\n"
118 " e=%f for ice accumulated during interglacial periods\n"
119 " e=%f for ice accumulated during glacial periods\n",
121 }
122}
123
124//! \brief Do the update; if full_update == false skip the update of 3D velocities and strain
125//! heating.
126void SIAFD::update(const array::Vector &sliding_velocity, const Inputs &inputs, bool full_update) {
127
128 // Check if the smoothed bed computed by BedSmoother is out of date and
129 // recompute if necessary.
130 if (inputs.new_bed_elevation) {
131 profiling().begin("sia.bed_smoother");
133 profiling().end("sia.bed_smoother");
134 }
135
136 profiling().begin("sia.gradient");
138 profiling().end("sia.gradient");
139
140 profiling().begin("sia.flux");
141 compute_diffusivity(full_update, *inputs.geometry, inputs.enthalpy, inputs.age, m_h_x, m_h_y,
142 m_D);
144 profiling().end("sia.flux");
145
146 if (full_update) {
147 profiling().begin("sia.3d_velocity");
148 compute_3d_horizontal_velocity(*inputs.geometry, m_h_x, m_h_y, sliding_velocity, m_u, m_v);
149 profiling().end("sia.3d_velocity");
150 }
151}
152
153
154//! \brief Compute the ice surface gradient for the SIA.
155/*!
156 There are three methods for computing the surface gradient. Which method is
157 controlled by configuration parameter `sia.surface_gradient_method` which can
158 have values `haseloff`, `mahaffy`, or `eta`.
159
160 The most traditional method is to directly differentiate the surface
161 elevation \f$h\f$ by the Mahaffy method [\ref Mahaffy]. The `haseloff` method,
162 suggested by Marianne Haseloff, modifies the Mahaffy method only where
163 ice-free adjacent bedrock points are above the ice surface, and in those
164 cases the returned gradient component is zero.
165
166 The alternative method, when `sia.surface_gradient_method` = `eta`, transforms
167 the thickness to something more regular and differentiates that. We get back
168 to the gradient of the surface by applying the chain rule. In particular, as
169 shown in [\ref Vazquezetal2003] for the flat bed and \f$n=3\f$ case, if we define
170
171 \f[\eta = H^{(2n+2)/n}\f]
172
173 then \f$\eta\f$ is more regular near the margin than \f$H\f$. So we compute
174 the surface gradient by
175
176 \f[\nabla h = \frac{n}{(2n+2)} \eta^{(-n-2)/(2n+2)} \nabla \eta + \nabla b,\f]
177
178 recalling that \f$h = H + b\f$. This method is only applied when \f$\eta >
179 0\f$ at a given point; otherwise \f$\nabla h = \nabla b\f$.
180
181 In all cases we are computing the gradient by finite differences onto a
182 staggered grid. In the method with \f$\eta\f$ we apply centered differences
183 using (roughly) the same method for \f$\eta\f$ and \f$b\f$ that applies
184 directly to the surface elevation \f$h\f$ in the `mahaffy` and `haseloff`
185 methods.
186
187 \param[out] h_x the X-component of the surface gradient, on the staggered grid
188 \param[out] h_y the Y-component of the surface gradient, on the staggered grid
189*/
191 array::Staggered1 &h_y) {
192
193 const std::string method = m_config->get_string("stress_balance.sia.surface_gradient_method");
194
195 if (method == "eta") {
196
198
199 } else if (method == "haseloff") {
200
202 h_x, h_y);
203
204 } else if (method == "mahaffy") {
205
207
208 } else {
211 "value of sia.surface_gradient_method, option '-gradient %s', is not valid",
212 method.c_str());
213 }
214}
215
216//! \brief Compute the ice surface gradient using the eta-transformation.
218 const array::Scalar2 &bed_elevation, array::Staggered1 &h_x,
219 array::Staggered1 &h_y) {
220 const double n = m_flow_law->exponent(), // presumably 3.0
221 etapow = (2.0 * n + 2.0) / n, // = 8/3 if n = 3
222 invpow = 1.0 / etapow, dinvpow = (-n - 2.0) / (2.0 * n + 2.0);
223 const double dx = m_grid->dx(), dy = m_grid->dy(); // convenience
224
226
227 // compute eta = H^{8/3}, which is more regular, on reg grid
228
229 array::AccessScope list{ &eta, &ice_thickness, &h_x, &h_y, &bed_elevation };
230
231 auto GHOSTS = eta.stencil_width();
232
233 for (auto p : m_grid->points_with_ghosts(GHOSTS)) {
234 const int i = p.i(), j = p.j();
235
236 eta(i, j) = pow(ice_thickness(i, j), etapow);
237 }
238
239 // now use Mahaffy on eta to get grad h on staggered;
240 // note grad h = (3/8) eta^{-5/8} grad eta + grad b because h = H + b
241
242 assert(eta.stencil_width() >= 2);
243 assert(h_x.stencil_width() >= 1);
244 assert(h_y.stencil_width() >= 1);
245
246 for (auto p : m_grid->points_with_ghosts(1)) {
247 const int i = p.i(), j = p.j();
248
249 auto b = bed_elevation.box(i, j);
250 auto e = eta.box(i, j);
251
252 // i-offset
253 {
254 double mean_eta = 0.5 * (e.e + e.c);
255 if (mean_eta > 0.0) {
256 double factor = invpow * pow(mean_eta, dinvpow);
257 h_x(i, j, 0) = factor * (e.e - e.c) / dx;
258 h_y(i, j, 0) = factor * (e.ne + e.n - e.se - e.s) / (4.0 * dy);
259 } else {
260 h_x(i, j, 0) = 0.0;
261 h_y(i, j, 0) = 0.0;
262 }
263 // now add bed slope to get actual h_x, h_y
264 h_x(i, j, 0) += (b.e - b.c) / dx;
265 h_y(i, j, 0) += (b.ne + b.n - b.se - b.s) / (4.0 * dy);
266 }
267
268 // j-offset
269 {
270 double mean_eta = 0.5 * (e.n + e.c);
271 if (mean_eta > 0.0) {
272 double factor = invpow * pow(mean_eta, dinvpow);
273 h_x(i, j, 1) = factor * (e.ne + e.e - e.nw - e.w) / (4.0 * dx);
274 h_y(i, j, 1) = factor * (e.n - e.c) / dy;
275 } else {
276 h_x(i, j, 1) = 0.0;
277 h_y(i, j, 1) = 0.0;
278 }
279 // now add bed slope to get actual h_x, h_y
280 h_x(i, j, 1) += (b.ne + b.e - b.nw - b.w) / (4.0 * dx);
281 h_y(i, j, 1) += (b.n - b.c) / dy;
282 }
283 } // end of the loop over grid points
284}
285
286
287//! \brief Compute the ice surface gradient using the Mary Anne Mahaffy method;
288//! see [\ref Mahaffy].
289void SIAFD::surface_gradient_mahaffy(const array::Scalar &ice_surface_elevation,
291 const double dx = m_grid->dx(), dy = m_grid->dy(); // convenience
292
293 const array::Scalar &h = ice_surface_elevation;
294
295 array::AccessScope list{ &h_x, &h_y, &h };
296
297 // h_x and h_y have to have ghosts
298 assert(h_x.stencil_width() >= 1);
299 assert(h_y.stencil_width() >= 1);
300 // surface elevation needs more ghosts
301 assert(h.stencil_width() >= 2);
302
303 for (auto p : m_grid->points_with_ghosts(1)) {
304 const int i = p.i(), j = p.j();
305
306 // I-offset
307 h_x(i, j, 0) = (h(i + 1, j) - h(i, j)) / dx;
308 h_y(i, j, 0) = (+h(i + 1, j + 1) + h(i, j + 1) - h(i + 1, j - 1) - h(i, j - 1)) / (4.0 * dy);
309 // J-offset
310 h_y(i, j, 1) = (h(i, j + 1) - h(i, j)) / dy;
311 h_x(i, j, 1) = (+h(i + 1, j + 1) + h(i + 1, j) - h(i - 1, j + 1) - h(i - 1, j)) / (4.0 * dx);
312 }
313}
314
315//! \brief Compute the ice surface gradient using a modification of Marianne Haseloff's approach.
316/*!
317 * The original code deals correctly with adjacent ice-free points with bed
318 * elevations which are above the surface of the ice nearby. This is done by
319 * setting surface gradient at the margin to zero at such locations.
320 *
321 * This code also deals with shelf fronts: sharp surface elevation change at
322 * the ice shelf front would otherwise cause abnormally high diffusivity
323 * values, which forces PISM to take shorter time-steps than necessary. (Note
324 * that the mass continuity code does not use SIA fluxes in floating areas.)
325 * This is done by assuming that the ice surface near shelf fronts is
326 * horizontal (i.e. here the surface gradient is set to zero also).
327 *
328 * The code below uses an interpretation of the standard Mahaffy scheme. We
329 * compute components of the surface gradient at staggered grid locations. The
330 * field h_x stores the x-component on the i-offset and j-offset grids, h_y ---
331 * the y-component.
332 *
333 * The Mahaffy scheme for the x-component at grid points on the i-offset grid
334 * (offset in the x-direction) is just the centered finite difference using
335 * adjacent regular-grid points. (Similarly for the y-component at j-offset
336 * locations.)
337 *
338 * Mahaffy's prescription for computing the y-component on the i-offset can be
339 * interpreted as:
340 *
341 * - compute the y-component at four surrounding j-offset staggered grid locations,
342 * - compute the average of these four.
343 *
344 * The code below does just that.
345 *
346 * - The first double for-loop computes x-components at i-offset
347 * locations and y-components at j-offset locations. Each computed
348 * number is assigned a weight (w_i and w_j) that is used below
349 *
350 * - The second double for-loop computes x-components at j-offset
351 * locations and y-components at i-offset locations as averages of
352 * quantities computed earlier. The weight are used to keep track of
353 * the number of values used in the averaging process.
354 *
355 * This method communicates ghost values of h_x and h_y. They cannot be
356 * computed locally because the first loop uses width=2 stencil of surface,
357 * mask, and bed to compute values at all grid points including width=1 ghosts,
358 * then the second loop uses width=1 stencil to compute local values. (In other
359 * words, a purely local computation would require width=3 stencil of surface,
360 * mask, and bed fields.)
361 */
362void SIAFD::surface_gradient_haseloff(const array::Scalar2 &ice_surface_elevation,
363 const array::CellType2 &cell_type, array::Staggered1 &h_x,
364 array::Staggered1 &h_y) {
365 const double dx = m_grid->dx(),
366 dy = m_grid->dy(); // convenience
367 const array::Scalar2 &h = ice_surface_elevation;
369 &w_j = m_work_2d_1; // averaging weights
370
371 const auto &mask = cell_type;
372
373 array::AccessScope list{ &h_x, &h_y, &w_i, &w_j, &h, &mask };
374
375 assert(mask.stencil_width() >= 2);
376 assert(h.stencil_width() >= 2);
377 assert(h_x.stencil_width() >= 1);
378 assert(h_y.stencil_width() >= 1);
379 assert(w_i.stencil_width() >= 1);
380 assert(w_j.stencil_width() >= 1);
381
382 for (auto p : m_grid->points_with_ghosts(1)) {
383 const int i = p.i(), j = p.j();
384
385 // x-derivative, i-offset
386 {
387 if ((mask.floating_ice(i, j) && mask.ice_free_ocean(i + 1, j)) ||
388 (mask.ice_free_ocean(i, j) && mask.floating_ice(i + 1, j))) {
389 // marine margin
390 h_x(i, j, 0) = 0;
391 w_i(i, j) = 0;
392 } else if ((mask.icy(i, j) && mask.ice_free(i + 1, j) && h(i + 1, j) > h(i, j)) ||
393 (mask.ice_free(i, j) && mask.icy(i + 1, j) && h(i, j) > h(i + 1, j))) {
394 // ice next to a "cliff"
395 h_x(i, j, 0) = 0.0;
396 w_i(i, j) = 0;
397 } else {
398 // default case
399 h_x(i, j, 0) = (h(i + 1, j) - h(i, j)) / dx;
400 w_i(i, j) = 1;
401 }
402 }
403
404 // y-derivative, j-offset
405 {
406 if ((mask.floating_ice(i, j) && mask.ice_free_ocean(i, j + 1)) ||
407 (mask.ice_free_ocean(i, j) && mask.floating_ice(i, j + 1))) {
408 // marine margin
409 h_y(i, j, 1) = 0.0;
410 w_j(i, j) = 0.0;
411 } else if ((mask.icy(i, j) && mask.ice_free(i, j + 1) && h(i, j + 1) > h(i, j)) ||
412 (mask.ice_free(i, j) && mask.icy(i, j + 1) && h(i, j) > h(i, j + 1))) {
413 // ice next to a "cliff"
414 h_y(i, j, 1) = 0.0;
415 w_j(i, j) = 0.0;
416 } else {
417 // default case
418 h_y(i, j, 1) = (h(i, j + 1) - h(i, j)) / dy;
419 w_j(i, j) = 1.0;
420 }
421 }
422 }
423
424 for (auto p : m_grid->points()) {
425 const int i = p.i(), j = p.j();
426
427 // x-derivative, j-offset
428 {
429 if (w_j(i, j) > 0) {
430 double W = w_i(i, j) + w_i(i - 1, j) + w_i(i - 1, j + 1) + w_i(i, j + 1);
431 if (W > 0) {
432 h_x(i, j, 1) =
433 1.0 / W * (h_x(i, j, 0) + h_x(i - 1, j, 0) + h_x(i - 1, j + 1, 0) + h_x(i, j + 1, 0));
434 } else {
435 h_x(i, j, 1) = 0.0;
436 }
437 } else {
438 if (mask.icy(i, j)) {
439 double W = w_i(i, j) + w_i(i - 1, j);
440 if (W > 0) {
441 h_x(i, j, 1) = 1.0 / W * (h_x(i, j, 0) + h_x(i - 1, j, 0));
442 } else {
443 h_x(i, j, 1) = 0.0;
444 }
445 } else {
446 double W = w_i(i, j + 1) + w_i(i - 1, j + 1);
447 if (W > 0) {
448 h_x(i, j, 1) = 1.0 / W * (h_x(i - 1, j + 1, 0) + h_x(i, j + 1, 0));
449 } else {
450 h_x(i, j, 1) = 0.0;
451 }
452 }
453 }
454 } // end of "x-derivative, j-offset"
455
456 // y-derivative, i-offset
457 {
458 if (w_i(i, j) > 0) {
459 double W = w_j(i, j) + w_j(i, j - 1) + w_j(i + 1, j - 1) + w_j(i + 1, j);
460 if (W > 0) {
461 h_y(i, j, 0) =
462 1.0 / W * (h_y(i, j, 1) + h_y(i, j - 1, 1) + h_y(i + 1, j - 1, 1) + h_y(i + 1, j, 1));
463 } else {
464 h_y(i, j, 0) = 0.0;
465 }
466 } else {
467 if (mask.icy(i, j)) {
468 double W = w_j(i, j) + w_j(i, j - 1);
469 if (W > 0) {
470 h_y(i, j, 0) = 1.0 / W * (h_y(i, j, 1) + h_y(i, j - 1, 1));
471 } else {
472 h_y(i, j, 0) = 0.0;
473 }
474 } else {
475 double W = w_j(i + 1, j - 1) + w_j(i + 1, j);
476 if (W > 0) {
477 h_y(i, j, 0) = 1.0 / W * (h_y(i + 1, j - 1, 1) + h_y(i + 1, j, 1));
478 } else {
479 h_y(i, j, 0) = 0.0;
480 }
481 }
482 }
483 } // end of "y-derivative, i-offset"
484 }
485
486 h_x.update_ghosts();
487 h_y.update_ghosts();
488}
489
490
491//! \brief Compute the SIA diffusivity. If full_update, also store delta on the staggered grid.
492/*!
493 * Recall that \f$ Q = -D \nabla h \f$ is the diffusive flux in the mass-continuity equation
494 *
495 * \f[ \frac{\partial H}{\partial t} = M - S - \nabla \cdot (Q + \mathbf{U}_b H),\f]
496 *
497 * where \f$h\f$ is the ice surface elevation, \f$M\f$ is the top surface
498 * accumulation/ablation rate, \f$S\f$ is the basal mass balance and
499 * \f$\mathbf{U}_b\f$ is the thickness-advective (in PISM: usually SSA) ice
500 * velocity.
501 *
502 * Recall also that at any particular point in the map-plane (i.e. if \f$x\f$
503 * and \f$y\f$ are fixed)
504 *
505 * \f[ D = 2\int_b^h F(z)P(z)(h-z)dz, \f]
506 *
507 * where \f$F(z)\f$ is a constitutive function and \f$P(z)\f$ is the pressure
508 * at a level \f$z\f$.
509 *
510 * By defining
511 *
512 * \f[ \delta(z) = 2F(z)P(z) \f]
513 *
514 * one can write
515 *
516 * \f[D = \int_b^h\delta(z)(h-z)dz. \f]
517 *
518 * The advantage is that it is then possible to avoid re-evaluating
519 * \f$F(z)\f$ (which is computationally expensive) in the horizontal ice
520 * velocity (see compute_3d_horizontal_velocity()) computation.
521 *
522 * This method computes \f$D\f$ and stores \f$\delta\f$ in delta[0,1] if full_update is true.
523 *
524 * The trapezoidal rule is used to approximate the integral.
525 *
526 * \param[in] full_update the flag specitying if we're doing a "full" update.
527 * \param[in] h_x x-component of the surface gradient, on the staggered grid
528 * \param[in] h_y y-component of the surface gradient, on the staggered grid
529 * \param[out] result diffusivity of the SIA flow
530 */
531void SIAFD::compute_diffusivity(bool full_update, const Geometry &geometry,
532 const array::Array3D *enthalpy, const array::Array3D *age,
533 const array::Staggered1 &h_x, const array::Staggered1 &h_y,
534 array::Staggered1 &result) {
535 array::Scalar2 &thk_smooth = m_work_2d_0, &theta = m_work_2d_1;
536
537 array::Array3D *delta[] = { &m_delta_0, &m_delta_1 };
538
539 result.set(0.0);
540
541 const double current_time = time().current(),
542 D_limit = m_config->get_number("stress_balance.sia.max_diffusivity");
543
544 const bool compute_grain_size_using_age =
545 m_config->get_flag("stress_balance.sia.grain_size_age_coupling"),
546 e_age_coupling = m_config->get_flag("stress_balance.sia.e_age_coupling"),
547 limit_diffusivity = m_config->get_flag("stress_balance.sia.limit_diffusivity"),
548 use_age = compute_grain_size_using_age or e_age_coupling;
549
551
552 // get "theta" from Schoof (2003) bed smoothness calculation and the
553 // thickness relative to the smoothed bed; each array::Scalar involved must
554 // have stencil width WIDE_GHOSTS for this too work
556
558 geometry.cell_type, thk_smooth);
559
560 array::AccessScope list{ &result, &theta, &thk_smooth, &h_x, &h_y, enthalpy };
561
562 if (use_age) {
563 assert(age->stencil_width() >= 2);
564 list.add(*age);
565 }
566
567 if (full_update) {
568 list.add({ delta[0], delta[1] });
569 assert(m_delta_0.stencil_width() >= 1);
570 assert(m_delta_1.stencil_width() >= 1);
571 }
572
573 assert(theta.stencil_width() >= 2);
574 assert(thk_smooth.stencil_width() >= 2);
575 assert(result.stencil_width() >= 1);
576 assert(h_x.stencil_width() >= 1);
577 assert(h_y.stencil_width() >= 1);
578 assert(enthalpy->stencil_width() >= 2);
579
580 const std::vector<double> &z = m_grid->z();
581 const unsigned int Mx = m_grid->Mx(), My = m_grid->My(), Mz = m_grid->Mz();
582
583 std::vector<double> depth(Mz), stress(Mz), pressure(Mz), E(Mz), flow(Mz);
584 std::vector<double> delta_ij(Mz);
585 std::vector<double> A(Mz),
586 ice_grain_size(Mz, m_config->get_number("constants.ice.grain_size", "m"));
587 std::vector<double> e_factor(Mz, m_e_factor);
588
589 double D_max = 0.0;
590 int high_diffusivity_counter = 0;
591 for (int o = 0; o < 2; o++) {
592 ParallelSection loop(m_grid->com);
593 try {
594 for (auto p : m_grid->points_with_ghosts(1)) {
595 const int i = p.i(), j = p.j();
596
597 // staggered point: o=0 is i+1/2, o=1 is j+1/2, (i, j) and (i+oi, j+oj)
598 // are regular grid neighbors of a staggered point:
599 const int oi = 1 - o, oj = o;
600
601 const double thk = 0.5 * (thk_smooth(i, j) + thk_smooth(i + oi, j + oj));
602
603 // zero thickness case:
604 if (thk == 0.0) {
605 result(i, j, o) = 0.0;
606 if (full_update) {
607 delta[o]->set_column(i, j, 0.0);
608 }
609 continue;
610 }
611
612 const int ks = m_grid->kBelowHeight(thk);
613
614 for (int k = 0; k <= ks; ++k) {
615 depth[k] = thk - z[k];
616 }
617
618 // pressure added by the ice (i.e. pressure difference between the
619 // current level and the top of the column)
620 m_EC->pressure(depth, ks, pressure); // FIXME issue #15
621
622 if (use_age) {
623 const double *age_ij = age->get_column(i, j),
624 *age_offset = age->get_column(i + oi, j + oj);
625
626 for (int k = 0; k <= ks; ++k) {
627 A[k] = 0.5 * (age_ij[k] + age_offset[k]);
628 }
629
630 if (compute_grain_size_using_age) {
631 for (int k = 0; k <= ks; ++k) {
632 // convert age from seconds to years:
633 ice_grain_size[k] = gs_vostok(A[k] * m_seconds_per_year);
634 }
635 }
636
637 if (e_age_coupling) {
638 for (int k = 0; k <= ks; ++k) {
639 const double accumulation_time = current_time - A[k];
640 if (interglacial(accumulation_time)) {
641 e_factor[k] = m_e_factor_interglacial;
642 } else {
643 e_factor[k] = m_e_factor;
644 }
645 }
646 }
647 }
648
649 {
650 const double *E_ij = enthalpy->get_column(i, j),
651 *E_offset = enthalpy->get_column(i + oi, j + oj);
652 for (int k = 0; k <= ks; ++k) {
653 E[k] = 0.5 * (E_ij[k] + E_offset[k]);
654 }
655 }
656
657 const double alpha = sqrt(PetscSqr(h_x(i, j, o)) + PetscSqr(h_y(i, j, o)));
658 for (int k = 0; k <= ks; ++k) {
659 stress[k] = alpha * pressure[k];
660 }
661
662 m_flow_law->flow_n(stress.data(), E.data(), pressure.data(), ice_grain_size.data(), ks + 1,
663 flow.data());
664
665 const double theta_local = 0.5 * (theta(i, j) + theta(i + oi, j + oj));
666 for (int k = 0; k <= ks; ++k) {
667 delta_ij[k] = e_factor[k] * theta_local * 2.0 * pressure[k] * flow[k];
668 }
669
670 double D = 0.0; // diffusivity for deformational SIA flow
671 {
672 for (int k = 1; k <= ks; ++k) {
673 // trapezoidal rule
674 const double dz = z[k] - z[k - 1];
675 D += 0.5 * dz * ((depth[k] + dz) * delta_ij[k - 1] + depth[k] * delta_ij[k]);
676 }
677 // finish off D with (1/2) dz (0 + (H-z[ks])*delta_ij[ks]), but dz=H-z[ks]:
678 const double dz = thk - z[ks];
679 D += 0.5 * dz * dz * delta_ij[ks];
680 }
681
682 // Override diffusivity at the edges of the domain. (At these
683 // locations PISM uses ghost cells *beyond* the boundary of
684 // the computational domain. This does not matter if the ice
685 // does not extend all the way to the domain boundary, as in
686 // whole-ice-sheet simulations. In a regional setup, though,
687 // this adjustment lets us avoid taking very small time-steps
688 // because of the possible thickness and bed elevation
689 // "discontinuities" at the boundary.)
690 {
691 if ((i < 0 or i >= (int)Mx - 1) and not(m_grid->periodicity() & grid::X_PERIODIC)) {
692 D = 0.0;
693 }
694 if ((j < 0 or j >= (int)My - 1) and not(m_grid->periodicity() & grid::Y_PERIODIC)) {
695 D = 0.0;
696 }
697 }
698
699 if (limit_diffusivity and D >= D_limit) {
700 D = D_limit;
701 high_diffusivity_counter += 1;
702 }
703
704 D_max = std::max(D_max, D);
705
706 result(i, j, o) = D;
707
708 // if doing the full update, fill the delta column above the ice and
709 // store it:
710 if (full_update) {
711 for (unsigned int k = ks + 1; k < Mz; ++k) {
712 delta_ij[k] = 0.0;
713 }
714 delta[o]->set_column(i, j, delta_ij.data());
715 }
716 } // i, j-loop
717 } catch (...) {
718 loop.failed();
719 }
720 loop.check();
721 } // o-loop
722
723 m_D_max = GlobalMax(m_grid->com, D_max);
724
725 high_diffusivity_counter = GlobalSum(m_grid->com, high_diffusivity_counter);
726
727 if (m_D_max > D_limit) {
728 // This can happen only if stress_balance.sia.limit_diffusivity is false (m_D_max <=
729 // D_limit when limiting is enabled).
730
733 "Maximum diffusivity of SIA flow (%f m2/s) is too high.\n"
734 "This probably means that the bed elevation or the ice thickness is "
735 "too rough.\n"
736 "Increase stress_balance.sia.max_diffusivity to suppress this message.",
737 m_D_max);
738 }
739
740 if (high_diffusivity_counter > 0) {
741 // This can happen only if stress_balance.sia.limit_diffusivity is true and this
742 // limiting mechanism was active (high_diffusivity_counter is incremented only if
743 // limit_diffusivity is true).
744
745 m_log->message(2, " SIA diffusivity was capped at %.2f m2/s at %d locations.\n", D_limit,
746 high_diffusivity_counter);
747 }
748}
749
751 const array::Staggered &diffusivity, array::Staggered &result) {
752
753 array::AccessScope list{ &diffusivity, &h_x, &h_y, &result };
754
755 for (int o = 0; o < 2; o++) {
756 ParallelSection loop(m_grid->com);
757 try {
758 for (auto p : m_grid->points_with_ghosts(1)) {
759 const int i = p.i(), j = p.j();
760
761 const double slope = (o == 0) ? h_x(i, j, o) : h_y(i, j, o);
762
763 result(i, j, o) = -diffusivity(i, j, o) * slope;
764 }
765 } catch (...) {
766 loop.failed();
767 }
768 loop.check();
769 } // o-loop
770}
771
772//! \brief Compute I.
773/*!
774 * This computes
775 * \f[ I(z) = \int_b^z\delta(s)ds.\f]
776 *
777 * Uses the trapezoidal rule to approximate the integral.
778 *
779 * See compute_diffusive_flux() for the definition of \f$\delta\f$.
780 *
781 * The result is stored in work_3d[0,1] and is used to compute the SIA component
782 * of the 3D-distributed horizontal ice velocity.
783 */
784void SIAFD::compute_I(const Geometry &geometry) {
785
786 array::Scalar &thk_smooth = m_work_2d_0;
788 array::Array3D *delta[] = { &m_delta_0, &m_delta_1 };
789
790 const array::Scalar &h = geometry.ice_surface_elevation, &H = geometry.ice_thickness;
791
792 const auto &mask = geometry.cell_type;
793
794 m_bed_smoother->smoothed_thk(h, H, mask, thk_smooth);
795
796 array::AccessScope list{ delta[0], delta[1], I[0], I[1], &thk_smooth };
797
798 assert(I[0]->stencil_width() >= 1);
799 assert(I[1]->stencil_width() >= 1);
800 assert(delta[0]->stencil_width() >= 1);
801 assert(delta[1]->stencil_width() >= 1);
802 assert(thk_smooth.stencil_width() >= 2);
803
804 const unsigned int Mz = m_grid->Mz();
805
806 std::vector<double> dz(Mz);
807 for (unsigned int k = 1; k < Mz; ++k) {
808 dz[k] = m_grid->z(k) - m_grid->z(k - 1);
809 }
810
811 for (int o = 0; o < 2; ++o) {
812 ParallelSection loop(m_grid->com);
813 try {
814 for (auto p : m_grid->points_with_ghosts(1)) {
815 const int i = p.i(), j = p.j();
816
817 const int oi = 1 - o, oj = o;
818 const double thk = 0.5 * (thk_smooth(i, j) + thk_smooth(i + oi, j + oj));
819
820 const double *delta_ij = delta[o]->get_column(i, j);
821 double *I_ij = I[o]->get_column(i, j);
822
823 const unsigned int ks = m_grid->kBelowHeight(thk);
824
825 // within the ice:
826 I_ij[0] = 0.0;
827 double I_current = 0.0;
828 for (unsigned int k = 1; k <= ks; ++k) {
829 // trapezoidal rule
830 I_current += 0.5 * dz[k] * (delta_ij[k - 1] + delta_ij[k]);
831 I_ij[k] = I_current;
832 }
833
834 // above the ice:
835 for (unsigned int k = ks + 1; k < Mz; ++k) {
836 I_ij[k] = I_current;
837 }
838 }
839 } catch (...) {
840 loop.failed();
841 }
842 loop.check();
843 } // o-loop
844}
845
846//! \brief Compute horizontal components of the SIA velocity (in 3D).
847/*!
848 * Recall that
849 *
850 * \f[ \mathbf{U}(z) = -2 \nabla h \int_b^z F(s)P(s)ds + \mathbf{U}_b,\f]
851 *
852 * which can be written in terms of \f$I(z)\f$ defined in compute_I():
853 *
854 * \f[ \mathbf{U}(z) = -I(z) \nabla h + \mathbf{U}_b. \f]
855 *
856 * \note This is one of the places where "hybridization" is done.
857 *
858 * \param[in] h_x the X-component of the surface gradient, on the staggered grid
859 * \param[in] h_y the Y-component of the surface gradient, on the staggered grid
860 * \param[in] sliding_velocity the thickness-advective velocity from the underlying stress balance module
861 * \param[out] u_out the X-component of the resulting horizontal velocity field
862 * \param[out] v_out the Y-component of the resulting horizontal velocity field
863 */
865 const array::Staggered &h_y,
866 const array::Vector &sliding_velocity,
867 array::Array3D &u_out, array::Array3D &v_out) {
868
869 compute_I(geometry);
870 // after the compute_I() call work_3d[0,1] contains I on the staggered grid
872
873 array::AccessScope list{ &u_out, &v_out, &h_x, &h_y, &sliding_velocity, I[0], I[1] };
874
875 const unsigned int Mz = m_grid->Mz();
876
877 for (auto p : m_grid->points()) {
878 const int i = p.i(), j = p.j();
879
880 const double
881 *I_e = I[0]->get_column(i, j),
882 *I_w = I[0]->get_column(i - 1, j),
883 *I_n = I[1]->get_column(i, j),
884 *I_s = I[1]->get_column(i, j - 1);
885
886 // Fetch values from 2D fields *outside* of the k-loop:
887 const double
888 h_x_w = h_x(i - 1, j, 0),
889 h_x_e = h_x(i, j, 0),
890 h_x_n = h_x(i, j, 1),
891 h_x_s = h_x(i, j - 1, 1);
892
893 const double
894 h_y_w = h_y(i - 1, j, 0),
895 h_y_e = h_y(i, j, 0),
896 h_y_n = h_y(i, j, 1),
897 h_y_s = h_y(i, j - 1, 1);
898
899 const double
900 sliding_velocity_u = sliding_velocity(i, j).u,
901 sliding_velocity_v = sliding_velocity(i, j).v;
902
903 double
904 *u_ij = u_out.get_column(i, j),
905 *v_ij = v_out.get_column(i, j);
906
907 // split into two loops to encourage auto-vectorization
908 for (unsigned int k = 0; k < Mz; ++k) {
909 u_ij[k] = sliding_velocity_u - 0.25 * (I_e[k] * h_x_e + I_w[k] * h_x_w +
910 I_n[k] * h_x_n + I_s[k] * h_x_s);
911 }
912 for (unsigned int k = 0; k < Mz; ++k) {
913 v_ij[k] = sliding_velocity_v - 0.25 * (I_e[k] * h_y_e + I_w[k] * h_y_w +
914 I_n[k] * h_y_n + I_s[k] * h_y_s);
915 }
916 }
917
918 // Communicate to get ghosts:
919 u_out.update_ghosts();
920 v_out.update_ghosts();
921}
922
923//! Determine if `accumulation_time` corresponds to an interglacial period.
924bool SIAFD::interglacial(double accumulation_time) const {
925 if (accumulation_time < m_eemian_start) {
926 return false;
927 }
928
929 if (accumulation_time < m_eemian_end) {
930 return true;
931 }
932
933 return (accumulation_time >= m_holocene_start);
934}
935
937 return m_h_x;
938}
939
941 return m_h_y;
942}
943
945 return m_D;
946}
947
949 return *m_bed_smoother;
950}
951
952
953} // end of namespace stressbalance
954} // end of namespace pism
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 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
const Profiling & profiling() const
Definition Component.cc:115
std::shared_ptr< const Logger > m_log
logger (for easy access)
Definition Component.hh:164
array::Scalar2 ice_surface_elevation
Definition Geometry.hh:57
array::CellType2 cell_type
Definition Geometry.hh:55
array::Scalar2 ice_thickness
Definition Geometry.hh:51
array::Scalar2 bed_elevation
Definition Geometry.hh:47
void failed()
Indicates a failure of a parallel section.
void begin(const char *name) const
Definition Profiling.cc:75
void end(const char *name) const
Definition Profiling.cc:91
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
double current() const
Current time, in seconds.
Definition Time.cc:461
Makes sure that we call begin_access() and end_access() for all accessed array::Arrays.
Definition Array.hh:66
stencils::Box< T > box(int i, int j) const
Definition Array2D.hh:121
void set_column(int i, int j, double c)
Set all values of scalar quantity to given a single value in a particular column.
Definition Array3D.cc:56
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
void set(double c)
Result: v[j] <- c for all j.
Definition Array.cc:659
void update_ghosts()
Updates ghost points.
Definition Array.cc:645
unsigned int stencil_width() const
Get the stencil width of the current Array. Returns 0 if ghosts are not available.
Definition Array.cc:305
A class for storing and accessing internal staggered-grid 2D fields. Uses dof=2 storage....
Definition Staggered.hh:37
std::shared_ptr< FlowLaw > create(const std::string &type_name, double exponent)
A relationship between the age of the ice and the grain size from the Vostok core.
void smoothed_thk(const array::Scalar &usurf, const array::Scalar &thk, const array::CellType2 &mask, array::Scalar &thksmooth) const
Computes a smoothed thickness map.
void theta(const array::Scalar &usurf, array::Scalar &result) const
void preprocess_bed(const array::Scalar &topg)
PISM bed smoother, plus bed roughness parameterization, based on Schoof (2003).
const array::Array3D * age
const array::Array3D * enthalpy
virtual void compute_diffusivity(bool full_update, const Geometry &geometry, const array::Array3D *enthalpy, const array::Array3D *age, const array::Staggered1 &h_x, const array::Staggered1 &h_y, array::Staggered1 &result)
Compute the SIA diffusivity. If full_update, also store delta on the staggered grid.
Definition SIAFD.cc:531
virtual void compute_surface_gradient(const Inputs &inputs, array::Staggered1 &h_x, array::Staggered1 &h_y)
Compute the ice surface gradient for the SIA.
Definition SIAFD.cc:190
array::Staggered1 m_D
Definition SIAFD.hh:116
virtual void surface_gradient_eta(const array::Scalar2 &ice_thickness, const array::Scalar2 &bed_elevation, array::Staggered1 &h_x, array::Staggered1 &h_y)
Compute the ice surface gradient using the eta-transformation.
Definition SIAFD.cc:217
virtual void update(const array::Vector &sliding_velocity, const Inputs &inputs, bool full_update)
Do the update; if full_update == false skip the update of 3D velocities and strain heating.
Definition SIAFD.cc:126
array::Staggered1 m_h_y
Definition SIAFD.hh:116
array::Scalar2 m_work_2d_0
temporary storage for eta, theta and the smoothed thickness
Definition SIAFD.hh:113
const array::Staggered & surface_gradient_x() const
Definition SIAFD.cc:936
bool interglacial(double accumulation_time) const
Determine if accumulation_time corresponds to an interglacial period.
Definition SIAFD.cc:924
virtual void surface_gradient_haseloff(const array::Scalar2 &ice_surface_elevation, const array::CellType2 &cell_type, array::Staggered1 &h_x, array::Staggered1 &h_y)
Compute the ice surface gradient using a modification of Marianne Haseloff's approach.
Definition SIAFD.cc:362
array::Staggered1 m_h_x
temporary storage for the surface gradient and the diffusivity
Definition SIAFD.hh:116
const BedSmoother & bed_smoother() const
Definition SIAFD.cc:948
array::Array3D m_work_3d_1
Definition SIAFD.hh:122
const array::Staggered1 & diffusivity() const
Definition SIAFD.cc:944
virtual void compute_3d_horizontal_velocity(const Geometry &geometry, const array::Staggered &h_x, const array::Staggered &h_y, const array::Vector &sliding_velocity, array::Array3D &u_out, array::Array3D &v_out)
Compute horizontal components of the SIA velocity (in 3D).
Definition SIAFD.cc:864
virtual void surface_gradient_mahaffy(const array::Scalar &ice_surface_elevation, array::Staggered1 &h_x, array::Staggered1 &h_y)
Compute the ice surface gradient using the Mary Anne Mahaffy method; see [Mahaffy].
Definition SIAFD.cc:289
array::Array3D m_work_3d_0
temporary storage used to store I and strain_heating on the staggered grid
Definition SIAFD.hh:121
array::Array3D m_delta_0
temporary storage for delta on the staggered grid
Definition SIAFD.hh:118
BedSmoother * m_bed_smoother
Definition SIAFD.hh:124
virtual void init()
Initialize the SIA module.
Definition SIAFD.cc:105
array::Array3D m_delta_1
Definition SIAFD.hh:119
virtual void compute_I(const Geometry &geometry)
Compute I.
Definition SIAFD.cc:784
array::Scalar2 m_work_2d_1
Definition SIAFD.hh:114
virtual void compute_diffusive_flux(const array::Staggered &h_x, const array::Staggered &h_y, const array::Staggered &diffusivity, array::Staggered &result)
Definition SIAFD.cc:750
SIAFD(std::shared_ptr< const Grid > g)
Definition SIAFD.cc:42
const array::Staggered & surface_gradient_y() const
Definition SIAFD.cc:940
std::shared_ptr< EnthalpyConverter > m_EC
std::shared_ptr< rheology::FlowLaw > m_flow_law
Shallow stress balance modifier (such as the non-sliding SIA).
#define PISM_ERROR_LOCATION
#define n
Definition exactTestM.c:37
double convert(System::Ptr system, double input, const std::string &spec1, const std::string &spec2)
Convert a quantity from unit1 to unit2.
Definition Units.cc:70
static const double g
Definition exactTestP.cc:36
void GlobalMax(MPI_Comm comm, double *local, double *result, int count)
static const double k
Definition exactTestP.cc:42
void GlobalSum(MPI_Comm comm, double *local, double *result, int count)