PISM, A Parallel Ice Sheet Model 2.2.1-cd005eec8 committed by Constantine Khrulev on 2025-03-07
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
EmptyingProblem.cc
Go to the documentation of this file.
1/* Copyright (C) 2019, 2020, 2022, 2023, 2024 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
26namespace pism {
27namespace hydrology {
28
29namespace diagnostics {
30
31/*! Compute the map of sinks.
32 *
33 * This field is purely diagnostic: it can be used to diagnose failures of the code
34 * filling dips to eliminate sinks (and get a better estimate of the steady state flow
35 * direction).
36 */
37static void compute_sinks(const array::Scalar &domain_mask,
38 const array::Scalar1 &psi,
39 array::Scalar &result) {
40
41 auto grid = result.grid();
42
43 array::AccessScope list{&psi, &domain_mask, &result};
44
45 for (auto p = grid->points(); p; p.next()) {
46 const int i = p.i(), j = p.j();
47
48 auto P = psi.star(i, j);
49
50 double
51 v_n = - (P.n - P.c),
52 v_e = - (P.e - P.c),
53 v_s = - (P.c - P.s),
54 v_w = - (P.c - P.w);
55
56 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) {
57 result(i, j) = 1.0;
58 } else {
59 result(i, j) = 0.0;
60 }
61 }
62}
63
64static void effective_water_velocity(const Geometry &geometry,
65 const array::Vector &water_flux,
66 array::Vector &result) {
67
68 auto grid = result.grid();
69
70 const auto &cell_type = geometry.cell_type;
71 const auto &bed_elevation = geometry.bed_elevation;
72 const auto &ice_thickness = geometry.ice_thickness;
73 const auto &sea_level_elevation = geometry.sea_level_elevation;
74
76 {&ice_thickness, &bed_elevation, &cell_type, &sea_level_elevation,
77 &water_flux, &result};
78
79 double
80 grid_spacing = 0.5 * (grid->dx() + grid->dy()),
81 eps = 1.0; // q_sg regularization
82
83 for (auto p = grid->points(); p; p.next()) {
84 const int i = p.i(), j = p.j();
85
86 if (cell_type.icy(i, j)) {
87 // Convert subglacial water flux (m^2/s) to an "effective subglacial freshwater
88 // velocity" or flux per unit area of ice front in m/day (see Xu et al 2013, section
89 // 2, paragraph 11).
90 //
91 // [flux] = m^2 / s, so
92 // [flux * grid_spacing] = m^3 / s, so
93 // [flux * grid_spacing / submerged_front_area] = m / s, and
94 // [flux * grid_spacing * (s / day) / submerged_front_area] = m / day
95
96 double water_depth = std::max(sea_level_elevation(i, j) - bed_elevation(i, j), 0.0),
97 submerged_front_area = water_depth * grid_spacing;
98
99 if (submerged_front_area > 0.0) {
100 auto Q_sg = water_flux(i, j) * grid_spacing;
101 auto q_sg = Q_sg / std::max(submerged_front_area, eps);
102
103 result(i, j) = q_sg;
104 } else {
105 result(i, j) = 0.0;
106 }
107 } else {
108 result(i, j) = 0.0;
109 }
110 } // end of the loop over grid points
111}
112
113} // end of namespace diagnostics
114
115EmptyingProblem::EmptyingProblem(std::shared_ptr<const Grid> grid)
116 : Component(grid),
117 m_potential(grid, "hydraulic_potential"),
118 m_tmp(grid, "temporary_storage"),
119 m_bottom_surface(grid, "ice_bottom_surface"),
120 m_W(grid, "remaining_water_thickness"),
121 m_Vstag(grid, "V_staggered"),
122 m_Qsum(grid, "flux_total"),
123 m_domain_mask(grid, "domain_mask"),
124 m_Q(grid, "_water_flux"),
125 m_q_sg(grid, "_effective_water_velocity"),
126 m_adjustment(grid, "hydraulic_potential_adjustment"),
127 m_sinks(grid, "sinks") {
128
131
133 .long_name("estimate of the steady state hydraulic potential in the steady hydrology model")
134 .units("Pa");
135
136 m_bottom_surface.metadata(0).long_name("ice bottom surface elevation").units("m");
137
138 m_W.metadata(0)
139 .long_name(
140 "scaled water thickness in the steady state hydrology model (has no physical meaning)")
141 .units("m");
142
144 .long_name("water velocity on the staggered grid")
145 .units("m s^-1");
146
147 m_domain_mask.metadata(0).long_name("mask defining the domain");
148
149 m_Q.metadata(0).long_name("steady state water flux").units("m^2 s^-1");
150
152 .long_name("x-component of the effective water velocity in the steady-state hydrology model")
153 .units("m s^-1")
154 .output_units("m day^-1");
156 .long_name("y-component of the effective water velocity in the steady-state hydrology model")
157 .units("m s^-1")
158 .output_units("m day^-1");
159
161 .long_name("map of sinks in the domain (for debugging)");
162
164 .long_name(
165 "potential adjustment needed to fill sinks when computing an estimate of the steady-state hydraulic potential")
166 .units("Pa");
167
168 m_eps_gradient = 1e-2;
169 m_speed = 1.0;
170
171 m_dx = m_grid->dx();
172 m_dy = m_grid->dy();
173 m_tau = m_config->get_number("hydrology.steady.input_rate_scaling");
174}
175
176/*!
177 * Compute steady state water flux.
178 *
179 * @param[in] geometry ice geometry
180 * @param[in] no_model_mask no model mask
181 * @param[in] water_input_rate water input rate in m/s
182 */
184 const array::Scalar *no_model_mask,
185 const array::Scalar &water_input_rate,
186 bool recompute_potential) {
187
188 const double
189 eps = 1e-16,
190 cell_area = m_grid->cell_area(),
191 u_max = m_speed,
192 v_max = m_speed,
193 dt = 0.5 / (u_max / m_dx + v_max / m_dy), // CFL condition
194 volume_ratio = m_config->get_number("hydrology.steady.volume_ratio");
195
196 const int n_iterations = m_config->get_number("hydrology.steady.n_iterations");
197
198 if (recompute_potential) {
200
201 compute_mask(geometry.cell_type, no_model_mask, m_domain_mask);
202
203 // updates ghosts of m_potential
208
209 // diagnostics
210 {
212
214
216 }
217 }
218
219 // set initial state and compute initial volume
220 double volume_0 = 0.0;
221 {
222 array::AccessScope list{&geometry.cell_type, &m_W, &water_input_rate};
223
224 for (auto p = m_grid->points(); p; p.next()) {
225 const int i = p.i(), j = p.j();
226
227 if (geometry.cell_type.icy(i, j)) {
228 m_W(i, j) = m_tau * water_input_rate(i, j);
229 } else {
230 m_W(i, j) = 0.0;
231 }
232
233 volume_0 += m_W(i, j);
234 }
235 volume_0 = cell_area * GlobalSum(m_grid->com, volume_0);
236 }
238
239 // uses ghosts of m_potential and m_domain_mask, updates ghosts of m_Vstag
241
242 m_Qsum.set(0.0);
243
244 // no input means no flux
245 if (volume_0 == 0.0) {
246 m_Q.set(0.0);
247 m_q_sg.set(0.0);
248 return;
249 }
250
251 double volume = 0.0;
252 int step_counter = 0;
253
255
256 for (step_counter = 0; step_counter < n_iterations; ++step_counter) {
257 volume = 0.0;
258
259 for (auto p = m_grid->points(); p; p.next()) {
260 const int i = p.i(), j = p.j();
261
262 auto v = m_Vstag.star(i, j);
263 auto w = m_W.star(i, j);
264
265 double
266 q_n = v.n * (v.n >= 0.0 ? w.c : w.n),
267 q_e = v.e * (v.e >= 0.0 ? w.c : w.e),
268 q_s = v.s * (v.s >= 0.0 ? w.s : w.c),
269 q_w = v.w * (v.w >= 0.0 ? w.w : w.c),
270 divQ = (q_e - q_w) / m_dx + (q_n - q_s) / m_dy;
271
272 // update water thickness
273 if (m_domain_mask(i, j) > 0.5) {
274 m_tmp(i, j) = w.c + dt * (- divQ);
275 } else {
276 m_tmp(i, j) = 0.0;
277 }
278
279 if (m_tmp(i, j) < -eps) {
280 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "W(%d, %d) = %f < 0",
281 i, j, m_tmp(i, j));
282 }
283
284 // accumulate the water flux
285 m_Qsum(i, j, 0) += dt * q_e;
286 m_Qsum(i, j, 1) += dt * q_n;
287
288 // compute volume
289 volume += m_tmp(i, j);
290 }
291
293 volume = cell_area * GlobalSum(m_grid->com, volume);
294
295 if (volume / volume_0 <= volume_ratio) {
296 break;
297 }
298 m_log->message(3, "%04d V = %f\n", step_counter, volume / volume_0);
299 } // end of the loop
300
301 m_log->message(3, "Emptying problem: stopped after %d iterations. V = %f\n",
302 step_counter, volume / volume_0);
303
304 double epsilon = volume / volume_0;
305
307 staggered_to_regular(geometry.cell_type, m_Qsum,
308 true, // include floating ice
309 m_Q);
310 m_Q.scale(1.0 / (m_tau * (1.0 - epsilon)));
311
313}
314
315/*! Compute the unmodified hydraulic potential (with sinks).
316 *
317 * @param[in] H ice thickness
318 * @param[in] b ice bottom surface elevation
319 * @param[out] result simplified hydraulic potential used by to compute velocity
320 */
322 const array::Scalar &b,
323 array::Scalar &result) const {
324 const double
325 g = m_config->get_number("constants.standard_gravity"),
326 rho_i = m_config->get_number("constants.ice.density"),
327 rho_w = m_config->get_number("constants.fresh_water.density");
328
329 array::AccessScope list({&H, &b, &result});
330
331 for (auto p = m_grid->points(); p; p.next()) {
332 const int i = p.i(), j = p.j();
333
334 result(i, j) = rho_i * g * H(i, j) + rho_w * g * b(i, j);
335 }
336
337 result.update_ghosts();
338}
339
340/*!
341 * FIXME: uses "result" as temporary storage with ghosts.
342 */
345 const array::Scalar &domain_mask,
346 array::Scalar1 &result) {
347 array::Scalar &psi_new = m_tmp;
348
349 double delta = m_config->get_number("hydrology.steady.potential_delta");
350
351 int n_iterations = m_config->get_number("hydrology.steady.potential_n_iterations");
352 int step_counter = 0;
353 int n_sinks = 0;
354 int n_sinks_remaining = 0;
355
356 compute_raw_potential(ice_thickness, ice_bottom_surface, result);
357
358 array::AccessScope list{&result, &psi_new, &domain_mask};
359 for (step_counter = 0; step_counter < n_iterations; ++step_counter) {
360
361 n_sinks_remaining = 0;
362 for (auto p = m_grid->points(); p; p.next()) {
363 const int i = p.i(), j = p.j();
364
365 if (domain_mask(i, j) > 0.5) {
366 auto P = result.star(i, j);
367
368 double
369 v_n = - (P.n - P.c),
370 v_e = - (P.e - P.c),
371 v_s = - (P.c - P.s),
372 v_w = - (P.c - P.w);
373
374 if (v_e <= 0.0 and v_w >= 0.0 and v_n <= 0.0 and v_s >= 0.0) {
375 ++n_sinks_remaining;
376
377 psi_new(i, j) = 0.25 * (P.n + P.e + P.s + P.w) + delta;
378 } else {
379 psi_new(i, j) = result(i, j);
380 }
381 } else {
382 psi_new(i, j) = result(i, j);
383 }
384 }
385 // this call updates ghosts of result
386 result.copy_from(psi_new);
387
388 n_sinks_remaining = GlobalSum(m_grid->com, n_sinks_remaining);
389
390 // remember the original number of sinks
391 if (step_counter == 0) {
392 n_sinks = n_sinks_remaining;
393 }
394
395 if (n_sinks_remaining == 0) {
396 m_log->message(3, "Emptying problem: filled %d sinks after %d iterations.\n",
397 n_sinks - n_sinks_remaining, step_counter);
398 break;
399 }
400 } // end of loop over step_counter
401
402 if (n_sinks_remaining > 0) {
403 m_log->message(2, "WARNING: %d sinks left.\n", n_sinks_remaining);
404 }
405}
406
407
408static double K(double psi_x, double psi_y, double speed, double epsilon) {
409 return speed / std::max(Vector2d(psi_x, psi_y).magnitude(), epsilon);
410}
411
412/*!
413 * Compute water velocity on the staggered grid.
414 */
416 const array::Scalar1 &domain_mask,
417 array::Staggered &result) const {
418
419 array::AccessScope list{&psi, &result, &domain_mask};
420
421 for (auto p = m_grid->points(); p; p.next()) {
422 const int i = p.i(), j = p.j();
423
424 for (int o = 0; o < 2; ++o) {
425 double psi_x, psi_y;
426 if (o == 0) {
427 psi_x = (psi(i + 1, j) - psi(i, j)) / m_dx;
428 psi_y = (psi(i + 1, j + 1) + psi(i, j + 1) - psi(i + 1, j - 1) - psi(i, j - 1)) / (4.0 * m_dy);
429 result(i, j, o) = - K(psi_x, psi_y, m_speed, m_eps_gradient) * psi_x;
430 } else {
431 psi_x = (psi(i + 1, j + 1) + psi(i + 1, j) - psi(i - 1, j + 1) - psi(i - 1, j)) / (4.0 * m_dx);
432 psi_y = (psi(i, j + 1) - psi(i, j)) / m_dy;
433 result(i, j, o) = - K(psi_x, psi_y, m_speed, m_eps_gradient) * psi_y;
434 }
435
436 auto M = domain_mask.star(i, j);
437
438 if (M.c == 0 and M.e == 0) {
439 result(i, j, 0) = 0.0;
440 }
441
442 if (M.c == 0 and M.n == 0) {
443 result(i, j, 1) = 0.0;
444 }
445 }
446 }
447 result.update_ghosts();
448}
449
450/*!
451 * Compute the mask that defines the domain: ones in the domain, zeroes elsewhere.
452 */
454 const array::Scalar *no_model_mask,
455 array::Scalar &result) const {
456
457 array::AccessScope list{&cell_type, &result};
458
459 if (no_model_mask != nullptr) {
460 list.add(*no_model_mask);
461 }
462
463 for (auto p = m_grid->points(); p; p.next()) {
464 const int i = p.i(), j = p.j();
465
466 if (not cell_type.ice_free_ocean(i, j)) {
467 result(i, j) = 1.0;
468 } else {
469 result(i, j) = 0.0;
470 }
471
472 if ((no_model_mask != nullptr) and no_model_mask->as_int(i, j) == 1) {
473 result(i, j) = 0.0;
474 }
475 }
476
477 result.update_ghosts();
478}
479
480
481
483 return {{"steady_state_hydraulic_potential", Diagnostic::wrap(m_potential)},
484 {"hydraulic_potential_adjustment", Diagnostic::wrap(m_adjustment)},
485 {"hydraulic_sinks", Diagnostic::wrap(m_sinks)},
486 {"remaining_water_thickness", Diagnostic::wrap(m_W)},
487 {"effective_water_velocity", Diagnostic::wrap(m_q_sg)}};
488}
489
490
491/*!
492 * Remaining water thickness.
493 *
494 * This field can be used to get an idea of where water is accumulated. (This affects the
495 * quality of the estimate of the water flux).
496 */
500
501/*!
502 * Steady state water flux.
503 */
505 return m_Q;
506}
507
508/*!
509 * Effective water velocity (flux per unit area of the front).
510 */
514
515/*!
516 * Hydraulic potential used to determine flow direction.
517 */
519 return m_potential;
520}
521
522/*!
523 * Map of sinks.
524 */
526 return m_sinks;
527}
528
529/*!
530 * Adjustment applied to the unmodified hydraulic potential to eliminate sinks.
531 */
535
536} // end of namespace hydrology
537} // end of namespace pism
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
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:64
void copy_from(const Array2D< T > &source)
Definition Array2D.hh:73
stencils::Star< T > star(int i, int j) const
Definition Array2D.hh:79
void add(double alpha, const Array2D< T > &x)
Definition Array2D.hh:65
void set_interpolation_type(InterpolationType type)
Definition Array.cc:178
void scale(double alpha)
Result: v <- v * alpha. Calls VecScale.
Definition Array.cc:224
std::shared_ptr< const Grid > grid() const
Definition Array.cc:131
void set(double c)
Result: v[j] <- c for all j.
Definition Array.cc:629
void update_ghosts()
Updates ghost points.
Definition Array.cc:615
SpatialVariableMetadata & metadata(unsigned int N=0)
Returns a reference to the SpatialVariableMetadata object containing metadata for the compoment N.
Definition Array.cc:476
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:218
void GlobalSum(MPI_Comm comm, double *local, double *result, int count)