PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
EmptyingProblem.cc
Go to the documentation of this file.
1/* Copyright (C) 2019, 2020, 2022, 2023, 2024, 2025 PISM Authors
2 *
3 * This file is part of PISM.
4 *
5 * PISM is free software; you can redistribute it and/or modify it under the
6 * terms of the GNU General Public License as published by the Free Software
7 * Foundation; either version 3 of the License, or (at your option) any later
8 * version.
9 *
10 * PISM is distributed in the hope that it will be useful, but WITHOUT ANY
11 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 * details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with PISM; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include "pism/hydrology/EmptyingProblem.hh"
21
22#include "pism/geometry/Geometry.hh"
23#include "pism/util/Interpolation1D.hh"
24#include "pism/util/pism_utilities.hh"
25#include "pism/util/Logger.hh"
26
27namespace pism {
28namespace hydrology {
29
30namespace diagnostics {
31
32/*! Compute the map of sinks.
33 *
34 * This field is purely diagnostic: it can be used to diagnose failures of the code
35 * filling dips to eliminate sinks (and get a better estimate of the steady state flow
36 * direction).
37 */
38static void compute_sinks(const array::Scalar &domain_mask,
39 const array::Scalar1 &psi,
40 array::Scalar &result) {
41
42 auto grid = result.grid();
43
44 array::AccessScope list{&psi, &domain_mask, &result};
45
46 for (auto p : grid->points()) {
47 const int i = p.i(), j = p.j();
48
49 auto P = psi.star(i, j);
50
51 double
52 v_n = - (P.n - P.c),
53 v_e = - (P.e - P.c),
54 v_s = - (P.c - P.s),
55 v_w = - (P.c - P.w);
56
57 if (domain_mask(i, j) > 0.5 and v_e <= 0.0 and v_w >= 0.0 and v_n <= 0.0 and v_s >= 0.0) {
58 result(i, j) = 1.0;
59 } else {
60 result(i, j) = 0.0;
61 }
62 }
63}
64
65static void effective_water_velocity(const Geometry &geometry,
66 const array::Vector &water_flux,
67 array::Vector &result) {
68
69 auto grid = result.grid();
70
71 const auto &cell_type = geometry.cell_type;
72 const auto &bed_elevation = geometry.bed_elevation;
73 const auto &ice_thickness = geometry.ice_thickness;
74 const auto &sea_level_elevation = geometry.sea_level_elevation;
75
77 {&ice_thickness, &bed_elevation, &cell_type, &sea_level_elevation,
78 &water_flux, &result};
79
80 double
81 grid_spacing = 0.5 * (grid->dx() + grid->dy()),
82 eps = 1.0; // q_sg regularization
83
84 for (auto p : grid->points()) {
85 const int i = p.i(), j = p.j();
86
87 if (cell_type.icy(i, j)) {
88 // Convert subglacial water flux (m^2/s) to an "effective subglacial freshwater
89 // velocity" or flux per unit area of ice front in m/day (see Xu et al 2013, section
90 // 2, paragraph 11).
91 //
92 // [flux] = m^2 / s, so
93 // [flux * grid_spacing] = m^3 / s, so
94 // [flux * grid_spacing / submerged_front_area] = m / s, and
95 // [flux * grid_spacing * (s / day) / submerged_front_area] = m / day
96
97 double water_depth = std::max(sea_level_elevation(i, j) - bed_elevation(i, j), 0.0),
98 submerged_front_area = water_depth * grid_spacing;
99
100 if (submerged_front_area > 0.0) {
101 auto Q_sg = water_flux(i, j) * grid_spacing;
102 auto q_sg = Q_sg / std::max(submerged_front_area, eps);
103
104 result(i, j) = q_sg;
105 } else {
106 result(i, j) = 0.0;
107 }
108 } else {
109 result(i, j) = 0.0;
110 }
111 } // end of the loop over grid points
112}
113
114} // end of namespace diagnostics
115
116EmptyingProblem::EmptyingProblem(std::shared_ptr<const Grid> grid)
117 : Component(grid),
118 m_potential(grid, "hydraulic_potential"),
119 m_tmp(grid, "temporary_storage"),
120 m_bottom_surface(grid, "ice_bottom_surface"),
121 m_W(grid, "remaining_water_thickness"),
122 m_Vstag(grid, "V_staggered"),
123 m_Qsum(grid, "flux_total"),
124 m_domain_mask(grid, "domain_mask"),
125 m_Q(grid, "_water_flux"),
126 m_q_sg(grid, "_effective_water_velocity"),
127 m_adjustment(grid, "hydraulic_potential_adjustment"),
128 m_sinks(grid, "sinks") {
129
132
134 .long_name("estimate of the steady state hydraulic potential in the steady hydrology model")
135 .units("Pa");
136
137 m_bottom_surface.metadata(0).long_name("ice bottom surface elevation").units("m");
138
139 m_W.metadata(0)
140 .long_name(
141 "scaled water thickness in the steady state hydrology model (has no physical meaning)")
142 .units("m");
143
145 .long_name("water velocity on the staggered grid")
146 .units("m s^-1");
147
148 m_domain_mask.metadata(0).long_name("mask defining the domain");
149
150 m_Q.metadata(0).long_name("steady state water flux").units("m^2 s^-1");
151
153 .long_name("x-component of the effective water velocity in the steady-state hydrology model")
154 .units("m s^-1")
155 .output_units("m day^-1");
157 .long_name("y-component of the effective water velocity in the steady-state hydrology model")
158 .units("m s^-1")
159 .output_units("m day^-1");
160
162 .long_name("map of sinks in the domain (for debugging)");
163
165 .long_name(
166 "potential adjustment needed to fill sinks when computing an estimate of the steady-state hydraulic potential")
167 .units("Pa");
168
169 m_eps_gradient = 1e-2;
170 m_speed = 1.0;
171
172 m_dx = m_grid->dx();
173 m_dy = m_grid->dy();
174 m_tau = m_config->get_number("hydrology.steady.input_rate_scaling");
175}
176
177/*!
178 * Compute steady state water flux.
179 *
180 * @param[in] geometry ice geometry
181 * @param[in] no_model_mask no model mask
182 * @param[in] water_input_rate water input rate in m/s
183 */
185 const array::Scalar *no_model_mask,
186 const array::Scalar &water_input_rate,
187 bool recompute_potential) {
188
189 const double
190 eps = 1e-16,
191 cell_area = m_grid->cell_area(),
192 u_max = m_speed,
193 v_max = m_speed,
194 dt = 0.5 / (u_max / m_dx + v_max / m_dy), // CFL condition
195 volume_ratio = m_config->get_number("hydrology.steady.volume_ratio");
196
197 const int n_iterations = m_config->get_number("hydrology.steady.n_iterations");
198
199 if (recompute_potential) {
201
202 compute_mask(geometry.cell_type, no_model_mask, m_domain_mask);
203
204 // updates ghosts of m_potential
209
210 // diagnostics
211 {
213
215
217 }
218 }
219
220 // set initial state and compute initial volume
221 double volume_0 = 0.0;
222 {
223 array::AccessScope list{&geometry.cell_type, &m_W, &water_input_rate};
224
225 for (auto p : m_grid->points()) {
226 const int i = p.i(), j = p.j();
227
228 if (geometry.cell_type.icy(i, j)) {
229 m_W(i, j) = m_tau * water_input_rate(i, j);
230 } else {
231 m_W(i, j) = 0.0;
232 }
233
234 volume_0 += m_W(i, j);
235 }
236 volume_0 = cell_area * GlobalSum(m_grid->com, volume_0);
237 }
239
240 // uses ghosts of m_potential and m_domain_mask, updates ghosts of m_Vstag
242
243 m_Qsum.set(0.0);
244
245 // no input means no flux
246 if (volume_0 == 0.0) {
247 m_Q.set(0.0);
248 m_q_sg.set(0.0);
249 return;
250 }
251
252 double volume = 0.0;
253 int step_counter = 0;
254
256
257 for (step_counter = 0; step_counter < n_iterations; ++step_counter) {
258 volume = 0.0;
259
260 for (auto p : m_grid->points()) {
261 const int i = p.i(), j = p.j();
262
263 auto v = m_Vstag.star(i, j);
264 auto w = m_W.star(i, j);
265
266 double
267 q_n = v.n * (v.n >= 0.0 ? w.c : w.n),
268 q_e = v.e * (v.e >= 0.0 ? w.c : w.e),
269 q_s = v.s * (v.s >= 0.0 ? w.s : w.c),
270 q_w = v.w * (v.w >= 0.0 ? w.w : w.c),
271 divQ = (q_e - q_w) / m_dx + (q_n - q_s) / m_dy;
272
273 // update water thickness
274 if (m_domain_mask(i, j) > 0.5) {
275 m_tmp(i, j) = w.c + dt * (- divQ);
276 } else {
277 m_tmp(i, j) = 0.0;
278 }
279
280 if (m_tmp(i, j) < -eps) {
281 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "W(%d, %d) = %f < 0",
282 i, j, m_tmp(i, j));
283 }
284
285 // accumulate the water flux
286 m_Qsum(i, j, 0) += dt * q_e;
287 m_Qsum(i, j, 1) += dt * q_n;
288
289 // compute volume
290 volume += m_tmp(i, j);
291 }
292
294 volume = cell_area * GlobalSum(m_grid->com, volume);
295
296 if (volume / volume_0 <= volume_ratio) {
297 break;
298 }
299 m_log->message(3, "%04d V = %f\n", step_counter, volume / volume_0);
300 } // end of the loop
301
302 m_log->message(3, "Emptying problem: stopped after %d iterations. V = %f\n",
303 step_counter, volume / volume_0);
304
305 double epsilon = volume / volume_0;
306
308 staggered_to_regular(geometry.cell_type, m_Qsum,
309 true, // include floating ice
310 m_Q);
311 m_Q.scale(1.0 / (m_tau * (1.0 - epsilon)));
312
314}
315
316/*! Compute the unmodified hydraulic potential (with sinks).
317 *
318 * @param[in] H ice thickness
319 * @param[in] b ice bottom surface elevation
320 * @param[out] result simplified hydraulic potential used by to compute velocity
321 */
323 const array::Scalar &b,
324 array::Scalar &result) const {
325 const double
326 g = m_config->get_number("constants.standard_gravity"),
327 rho_i = m_config->get_number("constants.ice.density"),
328 rho_w = m_config->get_number("constants.fresh_water.density");
329
330 array::AccessScope list({&H, &b, &result});
331
332 for (auto p : m_grid->points()) {
333 const int i = p.i(), j = p.j();
334
335 result(i, j) = rho_i * g * H(i, j) + rho_w * g * b(i, j);
336 }
337
338 result.update_ghosts();
339}
340
341/*!
342 * FIXME: uses "result" as temporary storage with ghosts.
343 */
346 const array::Scalar &domain_mask,
347 array::Scalar1 &result) {
348 array::Scalar &psi_new = m_tmp;
349
350 double delta = m_config->get_number("hydrology.steady.potential_delta");
351
352 int n_iterations = m_config->get_number("hydrology.steady.potential_n_iterations");
353 int step_counter = 0;
354 int n_sinks = 0;
355 int n_sinks_remaining = 0;
356
357 compute_raw_potential(ice_thickness, ice_bottom_surface, result);
358
359 array::AccessScope list{&result, &psi_new, &domain_mask};
360 for (step_counter = 0; step_counter < n_iterations; ++step_counter) {
361
362 n_sinks_remaining = 0;
363 for (auto p : m_grid->points()) {
364 const int i = p.i(), j = p.j();
365
366 if (domain_mask(i, j) > 0.5) {
367 auto P = result.star(i, j);
368
369 double
370 v_n = - (P.n - P.c),
371 v_e = - (P.e - P.c),
372 v_s = - (P.c - P.s),
373 v_w = - (P.c - P.w);
374
375 if (v_e <= 0.0 and v_w >= 0.0 and v_n <= 0.0 and v_s >= 0.0) {
376 ++n_sinks_remaining;
377
378 psi_new(i, j) = 0.25 * (P.n + P.e + P.s + P.w) + delta;
379 } else {
380 psi_new(i, j) = result(i, j);
381 }
382 } else {
383 psi_new(i, j) = result(i, j);
384 }
385 }
386 // this call updates ghosts of result
387 result.copy_from(psi_new);
388
389 n_sinks_remaining = GlobalSum(m_grid->com, n_sinks_remaining);
390
391 // remember the original number of sinks
392 if (step_counter == 0) {
393 n_sinks = n_sinks_remaining;
394 }
395
396 if (n_sinks_remaining == 0) {
397 m_log->message(3, "Emptying problem: filled %d sinks after %d iterations.\n",
398 n_sinks - n_sinks_remaining, step_counter);
399 break;
400 }
401 } // end of loop over step_counter
402
403 if (n_sinks_remaining > 0) {
404 m_log->message(2, "WARNING: %d sinks left.\n", n_sinks_remaining);
405 }
406}
407
408
409static double K(double psi_x, double psi_y, double speed, double epsilon) {
410 return speed / std::max(Vector2d(psi_x, psi_y).magnitude(), epsilon);
411}
412
413/*!
414 * Compute water velocity on the staggered grid.
415 */
417 const array::Scalar1 &domain_mask,
418 array::Staggered &result) const {
419
420 array::AccessScope list{&psi, &result, &domain_mask};
421
422 for (auto p : m_grid->points()) {
423 const int i = p.i(), j = p.j();
424
425 for (int o = 0; o < 2; ++o) {
426 double psi_x, psi_y;
427 if (o == 0) {
428 psi_x = (psi(i + 1, j) - psi(i, j)) / m_dx;
429 psi_y = (psi(i + 1, j + 1) + psi(i, j + 1) - psi(i + 1, j - 1) - psi(i, j - 1)) / (4.0 * m_dy);
430 result(i, j, o) = - K(psi_x, psi_y, m_speed, m_eps_gradient) * psi_x;
431 } else {
432 psi_x = (psi(i + 1, j + 1) + psi(i + 1, j) - psi(i - 1, j + 1) - psi(i - 1, j)) / (4.0 * m_dx);
433 psi_y = (psi(i, j + 1) - psi(i, j)) / m_dy;
434 result(i, j, o) = - K(psi_x, psi_y, m_speed, m_eps_gradient) * psi_y;
435 }
436
437 auto M = domain_mask.star(i, j);
438
439 if (M.c == 0 and M.e == 0) {
440 result(i, j, 0) = 0.0;
441 }
442
443 if (M.c == 0 and M.n == 0) {
444 result(i, j, 1) = 0.0;
445 }
446 }
447 }
448 result.update_ghosts();
449}
450
451/*!
452 * Compute the mask that defines the domain: ones in the domain, zeroes elsewhere.
453 */
455 const array::Scalar *no_model_mask,
456 array::Scalar &result) const {
457
458 array::AccessScope list{&cell_type, &result};
459
460 if (no_model_mask != nullptr) {
461 list.add(*no_model_mask);
462 }
463
464 for (auto p : m_grid->points()) {
465 const int i = p.i(), j = p.j();
466
467 if (not cell_type.ice_free_ocean(i, j)) {
468 result(i, j) = 1.0;
469 } else {
470 result(i, j) = 0.0;
471 }
472
473 if ((no_model_mask != nullptr) and no_model_mask->as_int(i, j) == 1) {
474 result(i, j) = 0.0;
475 }
476 }
477
478 result.update_ghosts();
479}
480
481
482
484 return {{"steady_state_hydraulic_potential", Diagnostic::wrap(m_potential)},
485 {"hydraulic_potential_adjustment", Diagnostic::wrap(m_adjustment)},
486 {"hydraulic_sinks", Diagnostic::wrap(m_sinks)},
487 {"remaining_water_thickness", Diagnostic::wrap(m_W)},
488 {"effective_water_velocity", Diagnostic::wrap(m_q_sg)}};
489}
490
491
492/*!
493 * Remaining water thickness.
494 *
495 * This field can be used to get an idea of where water is accumulated. (This affects the
496 * quality of the estimate of the water flux).
497 */
501
502/*!
503 * Steady state water flux.
504 */
506 return m_Q;
507}
508
509/*!
510 * Effective water velocity (flux per unit area of the front).
511 */
515
516/*!
517 * Hydraulic potential used to determine flow direction.
518 */
520 return m_potential;
521}
522
523/*!
524 * Map of sinks.
525 */
527 return m_sinks;
528}
529
530/*!
531 * Adjustment applied to the unmodified hydraulic potential to eliminate sinks.
532 */
536
537} // end of namespace hydrology
538} // end of namespace pism
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
static Ptr wrap(const T &input)
array::Scalar1 sea_level_elevation
Definition Geometry.hh:48
array::CellType2 cell_type
Definition Geometry.hh:55
array::Scalar2 ice_thickness
Definition Geometry.hh:51
array::Scalar2 bed_elevation
Definition Geometry.hh:47
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
VariableMetadata & long_name(const std::string &input)
VariableMetadata & units(const std::string &input)
VariableMetadata & output_units(const std::string &input)
This class represents a 2D vector field (such as ice velocity) at a certain grid point.
Definition Vector2d.hh:29
Makes sure that we call begin_access() and end_access() for all accessed array::Arrays.
Definition Array.hh:66
void copy_from(const Array2D< T > &source)
Definition Array2D.hh:101
stencils::Star< T > star(int i, int j) const
Definition Array2D.hh:107
void add(double alpha, const Array2D< T > &x)
Definition Array2D.hh:93
void set_interpolation_type(InterpolationType type)
Definition Array.cc:181
void scale(double alpha)
Result: v <- v * alpha. Calls VecScale.
Definition Array.cc:227
std::shared_ptr< const Grid > grid() const
Definition Array.cc:134
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
VariableMetadata & metadata(unsigned int N=0)
Returns a reference to the VariableMetadata object containing metadata for the compoment N.
Definition Array.cc:467
bool ice_free_ocean(int i, int j) const
Definition CellType.hh:58
bool icy(int i, int j) const
Definition CellType.hh:42
"Cell type" mask. Adds convenience methods to array::Scalar.
Definition CellType.hh:30
int as_int(int i, int j) const
Definition Scalar.hh:45
stencils::Star< double > star(int i, int j) const
Returns the values at interfaces of the cell i,j using the staggered grid.
Definition Staggered.hh:75
A class for storing and accessing internal staggered-grid 2D fields. Uses dof=2 storage....
Definition Staggered.hh:37
void compute_velocity(const array::Scalar &hydraulic_potential, const array::Scalar1 &mask, array::Staggered &result) const
const array::Scalar & sinks() const
const array::Vector & flux() const
const array::Scalar & adjustment() const
void compute_mask(const array::CellType &cell_type, const array::Scalar *no_model_mask, array::Scalar &result) const
DiagnosticList diagnostics() const
const array::Scalar & potential() const
void compute_potential(const array::Scalar &ice_thickness, const array::Scalar &ice_bottom_surface, const array::Scalar &domain_mask, array::Scalar1 &result)
EmptyingProblem(std::shared_ptr< const Grid > g)
const array::Scalar & remaining_water_thickness() const
virtual void compute_raw_potential(const array::Scalar &ice_thickness, const array::Scalar &ice_bottom_surface, array::Scalar &result) const
void update(const Geometry &geometry, const array::Scalar *no_model_mask, const array::Scalar &water_input_rate, bool recompute_potential=true)
const array::Vector & effective_water_velocity() const
#define PISM_ERROR_LOCATION
static void compute_sinks(const array::Scalar &domain_mask, const array::Scalar1 &psi, array::Scalar &result)
static void effective_water_velocity(const Geometry &geometry, const array::Vector &water_flux, array::Vector &result)
static double K(double psi_x, double psi_y, double speed, double epsilon)
static const double g
Definition exactTestP.cc:36
std::map< std::string, Diagnostic::Ptr > DiagnosticList
void ice_bottom_surface(const Geometry &geometry, array::Scalar &result)
Definition Geometry.cc:241
void GlobalSum(MPI_Comm comm, double *local, double *result, int count)