PISM, A Parallel Ice Sheet Model  stable v2.1-1-g6902d5502 committed by Ed Bueler on 2023-12-20 08:38:27 -0800
EmptyingProblem.cc
Go to the documentation of this file.
1 /* Copyright (C) 2019, 2020, 2022, 2023 PISM Authors
2  *
3  * This file is part of PISM.
4  *
5  * PISM is free software; you can redistribute it and/or modify it under the
6  * terms of the GNU General Public License as published by the Free Software
7  * Foundation; either version 3 of the License, or (at your option) any later
8  * version.
9  *
10  * PISM is distributed in the hope that it will be useful, but WITHOUT ANY
11  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with PISM; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18  */
19 
20 #include "pism/hydrology/EmptyingProblem.hh"
21 
22 #include "pism/geometry/Geometry.hh"
23 #include "pism/util/interpolation.hh"
24 #include "pism/util/pism_utilities.hh"
25 
26 namespace pism {
27 namespace hydrology {
28 
29 namespace 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  */
37 static 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 
64 static 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 
115 EmptyingProblem::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 
143  m_Vstag.metadata(0)
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("m2 s-1");
150 
151  m_q_sg.metadata(0)
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");
155  m_q_sg.metadata(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 
160  m_sinks.metadata(0)
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  */
183 void EmptyingProblem::update(const Geometry &geometry,
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
207  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  }
237  m_W.update_ghosts();
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 
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 
408 static 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  */
498  return m_W;
499 }
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  */
512  return m_q_sg;
513 }
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  */
533  return m_adjustment;
534 }
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)
Definition: Diagnostic.hh:160
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 & output_units(const std::string &input)
VariableMetadata & 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:65
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:179
void scale(double alpha)
Result: v <- v * alpha. Calls VecScale.
Definition: Array.cc:253
std::shared_ptr< const Grid > grid() const
Definition: Array.cc:132
void set(double c)
Result: v[j] <- c for all j.
Definition: Array.cc:707
void update_ghosts()
Updates ghost points.
Definition: Array.cc:693
SpatialVariableMetadata & metadata(unsigned int N=0)
Returns a reference to the SpatialVariableMetadata object containing metadata for the compoment N.
Definition: Array.cc:553
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:73
A class for storing and accessing internal staggered-grid 2D fields. Uses dof=2 storage....
Definition: Staggered.hh:35
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
void staggered_to_regular(const array::CellType1 &cell_type, const array::Staggered1 &input, bool include_floating_ice, array::Scalar &result)
Definition: Staggered.cc:87
double max(const array::Scalar &input)
Finds maximum over all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:165
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
Definition: Diagnostic.hh:125
void ice_bottom_surface(const Geometry &geometry, array::Scalar &result)
Definition: Geometry.cc:217
void GlobalSum(MPI_Comm comm, double *local, double *result, int count)