Loading [MathJax]/extensions/tex2jax.js
PISM, A Parallel Ice Sheet Model 2.2.2-d6b3a29ca committed by Constantine Khrulev on 2025-03-28
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
SSAFDBase.cc
Go to the documentation of this file.
1/* Copyright (C) 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/stressbalance/ssa/SSAFDBase.hh"
21
22#include "pism/basalstrength/basal_resistance.hh" // IceBasalResistancePlasticLaw
23#include "pism/geometry/Geometry.hh" // Geometry
24#include "pism/rheology/FlowLaw.hh" // rheology::averaged_hardness()
25#include "pism/stressbalance/StressBalance.hh" // stressbalance::Inputs
26
27#include "pism/util/Mask.hh"
28
29#include "pism/util/pism_utilities.hh" // average_water_column_pressure()
30#include <cassert>
31
32namespace pism {
33namespace stressbalance {
34
35SSAFDBase::SSAFDBase(std::shared_ptr<const Grid> grid, bool regional_mode)
36 : SSA(grid),
37 m_work(grid, "work_vector", array::WITH_GHOSTS, 1 /* stencil width */),
38 m_hardness(grid, "ice_hardness"),
39 m_nuH(grid, "nuH"),
40 m_cell_type(m_grid, "ssafd_cell_type"),
41 m_rhs(grid, "right_hand_side"),
42 m_taud(m_grid, "taud"),
43 m_bc_scaling(1e9), // comparable to typical beta for an ice stream;
44 m_regional_mode(regional_mode) {
45
46 m_work.metadata(0).long_name("temporary storage used to compute nuH");
47
49 .long_name("vertically-averaged ice hardness")
50 .set_units_without_validation(pism::printf("Pa s^(1/%f)", m_flow_law->exponent()));
51
53 .long_name("ice thickness times effective viscosity")
54 .units("Pa s m");
55
56 // grounded_dragging_floating integer mask
58 .long_name("ice-type (ice-free/grounded/floating/ocean) integer mask");
61 m_cell_type.metadata()["flag_meanings"] = "ice_free_bedrock grounded_ice floating_ice ice_free_ocean";
62
64 .long_name("X-component of the driving shear stress at the base of ice")
65 .units("Pa");
67 .long_name("Y-component of the driving shear stress at the base of ice")
68 .units("Pa");
69}
70
71/*!
72 * Compute the weight used to determine if the difference between locations `i,j` and `n`
73 * (neighbor) should be used in the computation of the surface gradient in
74 * SSA::compute_driving_stress().
75 *
76 * We avoid differencing across
77 *
78 * - ice margins if stress boundary condition at ice margins (CFBC) is active
79 * - grounding lines
80 * - ice margins next to ice free locations above the surface elevation of the ice (fjord
81 * walls, nunataks, headwalls)
82 */
83static int weight(bool margin_bc, int M_ij, int M_n, double h_ij, double h_n, int N_ij, int N_n) {
85 using mask::grounded;
86 using mask::ice_free;
88 using mask::icy;
89
90 // grounding lines and calving fronts
91 if ((grounded(M_ij) and floating_ice(M_n)) or (floating_ice(M_ij) and grounded(M_n)) or
92 (floating_ice(M_ij) and ice_free_ocean(M_n))) {
93 return 0;
94 }
95
96 // fjord walls, nunataks, headwalls
97 if ((icy(M_ij) and ice_free(M_n) and h_n > h_ij) or
98 (ice_free(M_ij) and icy(M_n) and h_ij > h_n)) {
99 return 0;
100 }
101
102 // This condition has to match the one used to implement the calving front stress
103 // boundary condition in assemble_rhs().
104 if (margin_bc and ((icy(M_ij) and ice_free(M_n)) or (ice_free(M_ij) and icy(M_n)))) {
105 return 0;
106 }
107
108 // boundaries of the "no model" area
109 if ((N_ij == 0 and N_n == 1) or (N_ij == 1 and N_n == 0)) {
110 return 0;
111 }
112
113 return 1;
114}
115
116// Use "upwinded" (-ish) finite difference to approximate the surface elevation
117// difference.
118static double diff_uphill(double L, double C, double R) {
119 double dL = C - L, dR = R - C;
120
121 if (dR * dL > 0) {
122 // dL and dR have the same sign
123 //
124 // If dL < 0 then L > C > R and "dL = C - L" is the "uphill" difference.
125 //
126 // If dL > 0 then L < C < R and "dR = R - C" is the "uphill" difference.
127 return dL < 0.0 ? dL : dR;
128 }
129
130 // centered
131 return 0.5 * (dL + dR);
132}
133
134static double diff_centered(double L, double /* unused */, double R) {
135 return 0.5 * (R - L);
136}
137
138//! @brief Compute the gravitational driving stress.
139/*!
140Computes the gravitational driving stress at the base of the ice:
141\f[ \tau_d = - \rho g H \nabla h \f]
142 */
144 const array::Scalar1 &surface_elevation,
145 const array::CellType1 &cell_type,
146 const array::Scalar1 *no_model_mask,
147 const EnthalpyConverter &EC, array::Vector &result) const {
148
149 using mask::floating_ice;
151
152 bool cfbc = m_config->get_flag("stress_balance.calving_front_stress_bc");
153 bool surface_gradient_inward =
154 m_config->get_flag("stress_balance.ssa.compute_surface_gradient_inward");
155
156 auto diff_grounded = diff_centered;
157 if (m_config->get_flag("stress_balance.ssa.fd.upstream_surface_slope_approximation")) {
158 diff_grounded = diff_uphill;
159 }
160
161 double dx = m_grid->dx(), dy = m_grid->dy();
162
163 array::AccessScope list{ &surface_elevation, &cell_type, &ice_thickness, &result };
164
165 if (no_model_mask != nullptr) {
166 list.add(*no_model_mask);
167 }
168
169 for (auto p = m_grid->points(); p; p.next()) {
170 const int i = p.i(), j = p.j();
171
172 const double pressure = EC.pressure(ice_thickness(i, j)); // FIXME issue #15
173 if (pressure <= 0.0) {
174 result(i, j) = 0.0;
175 continue;
176 }
177
178 // Special case for verification tests.
179 if (surface_gradient_inward) {
180 double h_x = diff_x_p(surface_elevation, i, j), h_y = diff_y_p(surface_elevation, i, j);
181 result(i, j) = -pressure * Vector2d(h_x, h_y);
182 continue;
183 }
184
185 // To compute the x-derivative we use
186 //
187 // * away from the grounding line, ice margins, and no_model mask transitions -- 2nd
188 // order centered difference
189 //
190 // * at the grounded cell near the grounding line -- 1st order
191 // one-sided difference using the grounded neighbor
192 //
193 // * at the floating cell near the grounding line -- 1st order
194 // one-sided difference using the floating neighbor
195 //
196 // All these cases can be combined by writing h_x as the weighted
197 // average of one-sided differences, with weights of 0 if a finite
198 // difference is not used and 1 if it is.
199 //
200 // The y derivative is handled the same way.
201
202 auto M = cell_type.star_int(i, j);
203 auto h = surface_elevation.star(i, j);
205
206 if (no_model_mask != nullptr) {
207 N = no_model_mask->star_int(i, j);
208 }
209
210 // x-derivative
211 double h_x = 0.0;
212 {
213 int west = weight(cfbc, M.c, M.w, h.c, h.w, N.c, N.w),
214 east = weight(cfbc, M.c, M.e, h.c, h.e, N.c, N.e);
215
216 if (east + west == 2 and mask::grounded_ice(M.c)) {
217 // interior of the ice blob: use the "uphill-biased" difference
218 h_x = diff_grounded(h.w, h.c, h.e) / dx;
219 } else if (east + west > 0) {
220 h_x = 1.0 / ((west + east) * dx) * (west * (h.c - h.w) + east * (h.e - h.c));
221 if (floating_ice(M.c) and (ice_free_ocean(M.e) or ice_free_ocean(M.w))) {
222 // at the ice front: use constant extrapolation to approximate the value outside
223 // the ice extent (see the notes in the manual)
224 h_x /= 2.0;
225 }
226 } else {
227 h_x = 0.0;
228 }
229 }
230
231 // y-derivative
232 double h_y = 0.0;
233 {
234 int south = weight(cfbc, M.c, M.s, h.c, h.s, N.c, N.s),
235 north = weight(cfbc, M.c, M.n, h.c, h.n, N.c, N.n);
236
237 if (north + south == 2 and mask::grounded_ice(M.c)) {
238 // interior of the ice blob: use the "uphill-biased" difference
239 h_y = diff_grounded(h.s, h.c, h.n) / dy;
240 } else if (north + south > 0) {
241 h_y = 1.0 / ((south + north) * dy) * (south * (h.c - h.s) + north * (h.n - h.c));
242 if (floating_ice(M.c) and (ice_free_ocean(M.s) or ice_free_ocean(M.n))) {
243 // at the ice front: use constant extrapolation to approximate the value outside
244 // the ice extent
245 h_y /= 2.0;
246 }
247 } else {
248 h_y = 0.0;
249 }
250 }
251
252 result(i, j) = -pressure * Vector2d(h_x, h_y);
253 }
254}
255
256//! \brief Checks if a cell is near or at the ice front.
257/*!
258 * You need to create array::AccessScope object and add `cell_type` to it.
259 *
260 * Note that a cell is a CFBC location of one of four direct neighbors is ice-free.
261 *
262 * If one of the diagonal neighbors is ice-free we don't use the CFBC, but we
263 * do need to compute weights used in the SSA discretization (see
264 * assemble_matrix()) to avoid differencing across interfaces between icy
265 * and ice-free cells.
266 *
267 * This method ensures that checks in assemble_rhs() and assemble_matrix() are
268 * consistent.
269 */
270static bool is_marginal(int i, int j, const array::CellType1 &cell_type, bool ssa_dirichlet_bc) {
271
272 auto M = cell_type.box_int(i, j);
273
274 using mask::ice_free;
276 using mask::icy;
277
278 if (ssa_dirichlet_bc) {
279 return icy(M.c) && (ice_free(M.e) || ice_free(M.w) || ice_free(M.n) || ice_free(M.s) ||
280 ice_free(M.ne) || ice_free(M.se) || ice_free(M.nw) || ice_free(M.sw));
281 }
282
283 return icy(M.c) && (ice_free_ocean(M.e) || ice_free_ocean(M.w) || ice_free_ocean(M.n) ||
284 ice_free_ocean(M.s) || ice_free_ocean(M.ne) || ice_free_ocean(M.se) ||
285 ice_free_ocean(M.nw) || ice_free_ocean(M.sw));
286}
287
288//! \brief Computes the right-hand side ("rhs") of the linear problem for the
289//! Picard iteration and finite-difference implementation of the SSA equations.
290/*!
291The right side of the SSA equations is just the driving stress term
292 \f[ - \rho g H \nabla h. \f]
293The basal stress is put on the left side of the system. This method builds the
294discrete approximation of the right side. For more about the discretization
295of the SSA equations, see comments for assemble_matrix().
296
297The values of the driving stress on the i,j grid come from a call to
298compute_driving_stress().
299
300In the case of Dirichlet boundary conditions, the entries on the right-hand side
301come from known velocity values. The fields bc_values and m_bc_mask are used for
302this.
303 */
304void SSAFDBase::assemble_rhs(const Inputs &inputs, const array::CellType1 &cell_type,
305 const array::Vector &driving_stress, double bc_scaling,
306 array::Vector &result) const {
307
308 using mask::ice_free;
311
312 const array::Scalar1 &bed = inputs.geometry->bed_elevation;
313 const array::Scalar &thickness = inputs.geometry->ice_thickness,
314 &surface = inputs.geometry->ice_surface_elevation,
315 &sea_level = inputs.geometry->sea_level_elevation,
316 *water_column_pressure = inputs.water_column_pressure;
317
318 const double dx = m_grid->dx(), dy = m_grid->dy(),
319 standard_gravity = m_config->get_number("constants.standard_gravity"),
320 rho_ocean = m_config->get_number("constants.sea_water.density"),
321 rho_ice = m_config->get_number("constants.ice.density");
322
323 // This constant is for debugging: simulations should not depend on the choice of
324 // velocity used in ice-free areas.
325 const Vector2d ice_free_velocity(0.0, 0.0);
326
327 const bool use_cfbc = m_config->get_flag("stress_balance.calving_front_stress_bc"),
328 flow_line_mode = m_config->get_flag("stress_balance.ssa.fd.flow_line_mode");
329
330 // FIXME: bedrock_boundary is a misleading name
331 bool bedrock_boundary = m_config->get_flag("stress_balance.ssa.dirichlet_bc");
332
333 array::AccessScope list{ &driving_stress, &result };
334
335 bool use_bc = inputs.bc_values != nullptr and inputs.bc_mask != nullptr;
336
337 if (use_bc) {
338 list.add({ inputs.bc_values, inputs.bc_mask });
339 }
340
341 if (use_cfbc) {
342 list.add({ &thickness, &bed, &surface, &cell_type, &sea_level });
343 }
344
345 if (use_cfbc and (water_column_pressure != nullptr)) {
346 list.add(*water_column_pressure);
347 }
348
349 result.set(0.0);
350
351 for (auto p = m_grid->points(); p; p.next()) {
352 const int i = p.i(), j = p.j();
353
354 Vector2d taud = driving_stress(i, j);
355
356 if (flow_line_mode) {
357 // no cross-flow driving stress in the flow line mode
358 taud.v = 0.0;
359 }
360
361 if (use_bc and inputs.bc_mask->as_int(i, j) == 1) {
362 result(i, j).u = bc_scaling * (*inputs.bc_values)(i, j).u;
363 result(i, j).v = bc_scaling * (*inputs.bc_values)(i, j).v;
364 continue;
365 }
366
367 if (use_cfbc) {
368 double H_ij = thickness(i, j);
369
370 auto M = cell_type.star_int(i, j);
371
372 // Note: this sets velocities at both ice-free ocean and ice-free
373 // bedrock to zero. This means that we need to set boundary conditions
374 // at both ice/ice-free-ocean and ice/ice-free-bedrock interfaces below
375 // to be consistent.
376 if (ice_free(M.c)) {
377 result(i, j) = bc_scaling * ice_free_velocity;
378 continue;
379 }
380
381 if (is_marginal(i, j, cell_type, bedrock_boundary)) {
382 // weights at the west, east, south, and north cell faces
383 int W = 0, E = 0, S = 0, N = 0;
384 // direct neighbors
385 // NOLINTBEGIN(readability-braces-around-statements)
386 if (bedrock_boundary) {
387 if (ice_free_ocean(M.e))
388 E = 1;
389 if (ice_free_ocean(M.w))
390 W = 1;
391 if (ice_free_ocean(M.n))
392 N = 1;
393 if (ice_free_ocean(M.s))
394 S = 1;
395 } else {
396 if (ice_free(M.e))
397 E = 1;
398 if (ice_free(M.w))
399 W = 1;
400 if (ice_free(M.n))
401 N = 1;
402 if (ice_free(M.s))
403 S = 1;
404 }
405 // NOLINTEND(readability-braces-around-statements)
406
407 double P_ice = 0.5 * rho_ice * standard_gravity * H_ij, P_water = 0.0;
408
409 if (water_column_pressure != nullptr) {
410 P_water = (*water_column_pressure)(i, j);
411 } else {
412 P_water = pism::average_water_column_pressure(H_ij, bed(i, j), sea_level(i, j), rho_ice,
413 rho_ocean, standard_gravity);
414 }
415
416 double delta_p = H_ij * (P_ice - P_water);
417
418 if (grid::domain_edge(*m_grid, i, j) and not(flow_line_mode or mask::grounded(M.c))) {
419 // In regional setups grounded ice may extend to the edge of the domain. This
420 // condition ensures that at a domain edge the ice behaves as if it extends past
421 // the edge without a change in geometry.
422 delta_p = 0.0;
423 }
424
425 {
426 // fjord walls, nunataks, etc
427 //
428 // Override weights if we are at the margin and the grid cell on the other side
429 // of the interface is ice-free and above the level of the ice surface.
430 //
431 // This effectively sets the pressure difference at the corresponding interface
432 // to zero, which is exactly what we need.
433 auto b = bed.star(i, j);
434 double h = surface(i, j);
435
436 if (ice_free(M.n) and b.n > h) {
437 N = 0;
438 }
439 if (ice_free(M.e) and b.e > h) {
440 E = 0;
441 }
442 if (ice_free(M.s) and b.s > h) {
443 S = 0;
444 }
445 if (ice_free(M.w) and b.w > h) {
446 W = 0;
447 }
448 }
449
450 // Note that if the current cell is "marginal" but not a CFBC
451 // location, the following two lines are equaivalent to the "usual
452 // case" below.
453 //
454 // Note: signs below (+E, -W, etc) are explained by directions of outward
455 // normal vectors at corresponding cell faces.
456 result(i, j).u = taud.u + (E - W) * delta_p / dx;
457 result(i, j).v = taud.v + (N - S) * delta_p / dy;
458
459 continue;
460 } // end of "if (is_marginal(i, j))"
461
462 // If we reached this point, then CFBC are enabled, but we are in the
463 // interior of a sheet or shelf. See "usual case" below.
464
465 } // end of "if (use_cfbc)"
466
467 // usual case: use already computed driving stress
468 result(i, j) = taud;
469 }
470}
471
472
473//! \brief Assemble the left-hand side matrix for the KSP-based, Picard iteration,
474//! and finite difference implementation of the SSA equations.
475/*!
476Recall the SSA equations are
477\f{align*}
478 - 2 \left[\nu H \left(2 u_x + v_y\right)\right]_x
479 - \left[\nu H \left(u_y + v_x\right)\right]_y
480 - \tau_{(b)1} &= - \rho g H h_x, \\
481 - \left[\nu H \left(u_y + v_x\right)\right]_x
482 - 2 \left[\nu H \left(u_x + 2 v_y\right)\right]_y
483 - \tau_{(b)2} &= - \rho g H h_y,
484\f}
485where \f$u\f$ is the \f$x\f$-component of the velocity and \f$v\f$ is the
486\f$y\f$-component of the velocity.
487
488The coefficient \f$\nu\f$ is the vertically-averaged effective viscosity.
489(The product \f$\nu H\f$ is computed by compute_nuH().)
490The Picard iteration idea is that, to solve the nonlinear equations in which
491the effective viscosity depends on the velocity, we freeze the effective
492viscosity using its value at the current estimate of the velocity and we solve
493the linear equations which come from this viscosity. In abstract symbols, the
494Picard iteration replaces the above nonlinear SSA equations by a sequence of
495linear problems
496
497\f[ A(U^{(k)}) U^{(k+1)} = b \f]
498
499where \f$A(U)\f$ is the matrix from discretizing the SSA equations supposing
500the viscosity is a function of known velocities \f$U\f$, and where \f$U^{(k)}\f$
501denotes the \f$k\f$th iterate in the process. The current method assembles \f$A(U)\f$.
502
503For ice shelves \f$\tau_{(b)i} = 0\f$ [\ref MacAyealetal].
504For ice streams with a basal till modelled as a plastic material,
505\f$\tau_{(b)i} = - \tau_c u_i/|\mathbf{u}|\f$ where
506\f$\mathbf{u} = (u,v)\f$, \f$|\mathbf{u}| = \left(u^2 + v^2\right)^{1/2}\f$,
507where \f$\tau_c(t,x,y)\f$ is the yield stress of the till [\ref SchoofStream].
508More generally, ice streams can be modeled with a pseudo-plastic basal till;
509see IceModel::initBasalTillModel() and IceModel::updateYieldStressUsingBasalWater()
510and reference [\ref BKAJS]. The pseudo-plastic till model includes all power law
511sliding relations and the linearly-viscous model for sliding,
512\f$\tau_{(b)i} = - \beta u_i\f$ where \f$\beta\f$ is the basal drag
513(friction) parameter [\ref MacAyeal]. In any case, PISM assumes that the basal shear
514stress can be factored this way, *even if the coefficient depends on the
515velocity*, \f$\beta(u,v)\f$. Such factoring is possible even in the case of
516(regularized) plastic till. This scalar coefficient \f$\beta\f$ is what is
517returned by IceBasalResistancePlasticLaw::drag().
518
519Note that the basal shear stress appears on the \em left side of the
520linear system we actually solve. We believe this is crucial, because
521of its effect on the spectrum of the linear approximations of each
522stage. The effect on spectrum is clearest in the linearly-viscous till
523case but there seems to be an analogous effect in the plastic till case.
524
525This method assembles the matrix for the left side of the above SSA equations.
526The numerical method is finite difference. Suppose we use difference notation
527\f$\delta_{+x}f^{i,j} = f^{i+1,j}-f^{i,j}\f$,
528\f$\delta_{-x}f^{i,j} = f^{i,j}-f^{i-1,j}\f$, and
529\f$\Delta_{x}f^{i,j} = f^{i+1,j}-f^{i-1,j}\f$, and corresponding notation for
530\f$y\f$ differences, and that we write \f$N = \nu H\f$ then the first of the
531two "concrete" SSA equations above has this discretization:
532\f{align*}
533- &2 \frac{N^{i+\frac{1}{2},j}}{\Delta x} \left[2\frac{\delta_{+x}u^{i,j}}{\Delta x} + \frac{\Delta_{y} v^{i+1,j} + \Delta_{y} v^{i,j}}{4 \Delta y}\right] + 2 \frac{N^{i-\frac{1}{2},j}}{\Delta x} \left[2\frac{\delta_{-x}u^{i,j}}{\Delta x} + \frac{\Delta_y v^{i,j} + \Delta_y v^{i-1,j}}{4 \Delta y}\right] \\
534&\qquad- \frac{N^{i,j+\frac{1}{2}}}{\Delta y} \left[\frac{\delta_{+y} u^{i,j}}{\Delta y} + \frac{\Delta_x v^{i,j+1} + \Delta_x v^{i,j}}{4 \Delta x}\right] + \frac{N^{i,j-\frac{1}{2}}}{\Delta y} \left[\frac{\delta_{-y}u^{i,j}}{\Delta y} + \frac{\Delta_x v^{i,j} + \Delta_x v^{i,j-1}}{4 \Delta x}\right] - \tau_{(b)1}^{i,j} = - \rho g H^{i,j} \frac{\Delta_x h^{i,j}}{2\Delta x}.
535\f}
536As a picture, see Figure \ref ssastencil.
537
538\image html ssastencil.png "\b ssastencil: Stencil for our finite difference discretization of the first of the two scalar SSA equations. Triangles show staggered grid points where N = nu * H is evaluated. Circles and squares show where u and v are approximated, respectively."
539\anchor ssastencil
540
541It follows immediately that the matrix we assemble in the current method has
54213 nonzeros entries per row because, for this first SSA equation, there are 5
543grid values of \f$u\f$ and 8 grid values of \f$v\f$ used in this scheme. For
544the second equation we also have 13 nonzeros per row.
545
546FIXME: document use of DAGetMatrix and MatStencil and MatSetValuesStencil
547
548*/
549void SSAFDBase::fd_operator(const Geometry &geometry, const array::Scalar *bc_mask,
550 double bc_scaling, const array::Scalar &basal_yield_stress,
551 IceBasalResistancePlasticLaw *basal_sliding_law,
552 const pism::Vector2d *const *input_velocity,
553 const array::Staggered1 &nuH, const array::CellType1 &cell_type, Mat *A,
554 Vector2d **Ax) const {
555
556 using mask::grounded_ice;
557 using mask::ice_free;
560 using mask::icy;
561
562 const int diag_u = 4;
563 const int diag_v = 13;
564
565 PetscErrorCode ierr = 0;
566
567 const array::Scalar1 &thickness = geometry.ice_thickness, &bed = geometry.bed_elevation;
568
569 const array::Scalar &surface = geometry.ice_surface_elevation,
570 &grounded_fraction = geometry.cell_grounded_fraction;
571
572 const double dx = m_grid->dx(), dy = m_grid->dy(),
573 beta_lateral_margin = m_config->get_number("basal_resistance.beta_lateral_margin"),
574 beta_ice_free_bedrock =
575 m_config->get_number("basal_resistance.beta_ice_free_bedrock");
576
577 const bool
578 // FIXME: bedrock_boundary is a misleading name
579 bedrock_boundary = m_config->get_flag("stress_balance.ssa.dirichlet_bc"),
580 flow_line_mode = m_config->get_flag("stress_balance.ssa.fd.flow_line_mode"),
581 use_cfbc = m_config->get_flag("stress_balance.calving_front_stress_bc"),
582 replace_zero_diagonal_entries =
583 m_config->get_flag("stress_balance.ssa.fd.replace_zero_diagonal_entries");
584
585 if (A != nullptr) {
586 ierr = MatZeroEntries(*A);
587 PISM_CHK(ierr, "MatZeroEntries");
588 }
589
590 array::AccessScope list{ &nuH, &basal_yield_stress, &cell_type, &bed, &surface };
591
592 auto velocity = [&input_velocity](int i, int j) { return input_velocity[j][i]; };
593
594 if (bc_mask != nullptr) {
595 list.add(*bc_mask);
596 }
597
598 const bool sub_gl = m_config->get_flag("geometry.grounded_cell_fraction");
599 if (sub_gl) {
600 list.add(grounded_fraction);
601 }
602
603 // handles friction of the ice cell along ice-free bedrock margins when bedrock higher than ice
604 // surface (in simplified setups)
605 bool lateral_drag_enabled = m_config->get_flag("stress_balance.ssa.fd.lateral_drag.enabled");
606 if (lateral_drag_enabled) {
607 list.add({ &thickness, &bed, &surface });
608 }
609 double lateral_drag_viscosity =
610 m_config->get_number("stress_balance.ssa.fd.lateral_drag.viscosity");
611 double HminFrozen = 0.0;
612
613 /* matrix assembly loop */
614 ParallelSection loop(m_grid->com);
615 try {
616 for (auto p = m_grid->points(); p; p.next()) {
617 const int i = p.i(), j = p.j();
618
619 // Easy cases:
620 {
621 // Provided Dirichlet boundary conditions
622 bool bc_location = bc_mask != nullptr and bc_mask->as_int(i, j) == 1;
623 // Note: this sets velocities at both ice-free ocean and ice-free
624 // bedrock to zero. This means that we need to set boundary conditions
625 // at both ice/ice-free-ocean and ice/ice-free-bedrock interfaces below
626 // to be consistent.
627 bool ice_free_with_cfbc = use_cfbc and cell_type.ice_free(i, j);
628
629 if (bc_location or ice_free_with_cfbc) {
630 if (Ax != nullptr) {
631 Ax[j][i] = bc_scaling * velocity(i, j); // STORAGE_ORDER
632 }
633
634 // set diagonal entries to one (scaled); RHS entry will be known velocity
635 if (A != nullptr) {
636 MatStencil row[2] = { { -1, j, i, 0 }, { -1, j, i, 1 } };
637 double identity[4] = { bc_scaling, 0.0, 0.0, bc_scaling };
638 ierr = MatSetValuesStencil(*A, 2, row, 2, row, identity, INSERT_VALUES);
639 PISM_CHK(ierr, "MatSetValuesStencil");
640 }
641 continue;
642 }
643 }
644
645 /* Provide shorthand for the following staggered coefficients nu H:
646 * c_n
647 * c_w c_e
648 * c_s
649 */
650 stencils::Star<double> c = nuH.star(i, j);
651
652 if (lateral_drag_enabled) {
653 // if option is set, the viscosity at ice-bedrock boundary layer will
654 // be prescribed and is a temperature-independent free (user determined) parameter
655
656 // direct neighbors
657 auto M = cell_type.star_int(i, j);
658 auto H = thickness.star(i, j);
659 auto b = bed.star(i, j);
660 double h = surface(i, j);
661
662 if (H.c > HminFrozen) {
663 if (b.w > h and ice_free_land(M.w)) {
664 c.w = lateral_drag_viscosity * 0.5 * (H.c + H.w);
665 }
666 if (b.e > h and ice_free_land(M.e)) {
667 c.e = lateral_drag_viscosity * 0.5 * (H.c + H.e);
668 }
669 if (b.n > h and ice_free_land(M.n)) {
670 c.n = lateral_drag_viscosity * 0.5 * (H.c + H.n);
671 }
672 if (b.s > h and ice_free_land(M.s)) {
673 c.s = lateral_drag_viscosity * 0.5 * (H.c + H.s);
674 }
675 }
676 }
677
678 // We use DAGetMatrix to obtain the SSA matrix, which means that all 18
679 // non-zeros get allocated, even though we use only 13 (or 14). The
680 // remaining 5 (or 4) coefficients are zeros, but we set them anyway,
681 // because this makes the code easier to understand.
682 const int n_nonzeros = 18;
683
684 // |-----+-----+---+-----+-----|
685 // | NW | NNW | N | NNE | NE |
686 // | WNW | | | | | ENE |
687 // | W |-----|-o-|-----| E |
688 // | WSW | | | | | ESE |
689 // | SW | SSW | S | SSE | SE |
690 // |-----+-----+---+-----+-----|
691 //
692 // We use compass rose notation for weights corresponding to interfaces between
693 // cells around the current one (i, j). Here N corresponds to the interface between
694 // the cell (i, j) and the one to the north of it.
695 int N = 1, E = 1, S = 1, W = 1;
696
697 // Similarly, we use compass rose notation for weights used to switch between
698 // centered and one-sided finite differences. Here NNE is the interface between
699 // cells N and NE, ENE - between E and NE, etc.
700 int NNW = 1, NNE = 1, SSW = 1, SSE = 1;
701 int WNW = 1, ENE = 1, WSW = 1, ESE = 1;
702
703 int M_ij = cell_type.as_int(i, j);
704
705 if (use_cfbc) {
706 auto M = cell_type.box_int(i, j);
707
708 if (is_marginal(i, j, cell_type, bedrock_boundary)) {
709 // If at least one of the following four conditions is "true", we're
710 // at a CFBC location.
711 // NOLINTBEGIN(readability-braces-around-statements)
712 if (bedrock_boundary) {
713
714 if (ice_free_ocean(M.e))
715 E = 0;
716 if (ice_free_ocean(M.w))
717 W = 0;
718 if (ice_free_ocean(M.n))
719 N = 0;
720 if (ice_free_ocean(M.s))
721 S = 0;
722
723 // decide whether to use centered or one-sided differences
724 if (ice_free_ocean(M.n) || ice_free_ocean(M.ne))
725 NNE = 0;
726 if (ice_free_ocean(M.e) || ice_free_ocean(M.ne))
727 ENE = 0;
728 if (ice_free_ocean(M.e) || ice_free_ocean(M.se))
729 ESE = 0;
730 if (ice_free_ocean(M.s) || ice_free_ocean(M.se))
731 SSE = 0;
732 if (ice_free_ocean(M.s) || ice_free_ocean(M.sw))
733 SSW = 0;
734 if (ice_free_ocean(M.w) || ice_free_ocean(M.sw))
735 WSW = 0;
736 if (ice_free_ocean(M.w) || ice_free_ocean(M.nw))
737 WNW = 0;
738 if (ice_free_ocean(M.n) || ice_free_ocean(M.nw))
739 NNW = 0;
740
741 } else { // if (not bedrock_boundary)
742
743 if (ice_free(M.e))
744 E = 0;
745 if (ice_free(M.w))
746 W = 0;
747 if (ice_free(M.n))
748 N = 0;
749 if (ice_free(M.s))
750 S = 0;
751
752 // decide whether to use centered or one-sided differences
753 if (ice_free(M.n) || ice_free(M.ne))
754 NNE = 0;
755 if (ice_free(M.e) || ice_free(M.ne))
756 ENE = 0;
757 if (ice_free(M.e) || ice_free(M.se))
758 ESE = 0;
759 if (ice_free(M.s) || ice_free(M.se))
760 SSE = 0;
761 if (ice_free(M.s) || ice_free(M.sw))
762 SSW = 0;
763 if (ice_free(M.w) || ice_free(M.sw))
764 WSW = 0;
765 if (ice_free(M.w) || ice_free(M.nw))
766 WNW = 0;
767 if (ice_free(M.n) || ice_free(M.nw))
768 NNW = 0;
769
770 } // end of the else clause following "if (bedrock_boundary)"
771 // NOLINTEND(readability-braces-around-statements)
772 } // end of "if (is_marginal(i, j, bedrock_boundary))"
773 } // end of "if (use_cfbc)"
774
775 /* begin Maxima-generated code */
776 const double dx2 = dx * dx, dy2 = dy * dy, d4 = 4 * dx * dy, d2 = 2 * dx * dy;
777
778 /* Coefficients of the discretization of the first equation; u first, then v. */
779 double eq1[] = {
780 0,
781 -c.n * N / dy2,
782 0,
783 -4 * c.w * W / dx2,
784 (c.n * N + c.s * S) / dy2 + (4 * c.e * E + 4 * c.w * W) / dx2,
785 -4 * c.e * E / dx2,
786 0,
787 -c.s * S / dy2,
788 0,
789 c.w * W * WNW / d2 + c.n * NNW * N / d4,
790 (c.n * NNE * N - c.n * NNW * N) / d4 + (c.w * W * N - c.e * E * N) / d2,
791 -c.e * E * ENE / d2 - c.n * NNE * N / d4,
792 (c.w * W * WSW - c.w * W * WNW) / d2 + (c.n * W * N - c.s * W * S) / d4,
793 (c.n * E * N - c.n * W * N - c.s * E * S + c.s * W * S) / d4 +
794 (c.e * E * N - c.w * W * N - c.e * E * S + c.w * W * S) / d2,
795 (c.e * E * ENE - c.e * E * ESE) / d2 + (c.s * E * S - c.n * E * N) / d4,
796 -c.w * W * WSW / d2 - c.s * SSW * S / d4,
797 (c.s * SSW * S - c.s * SSE * S) / d4 + (c.e * E * S - c.w * W * S) / d2,
798 c.e * E * ESE / d2 + c.s * SSE * S / d4,
799 };
800
801 /* Coefficients of the discretization of the second equation; u first, then v. */
802 double eq2[] = {
803 c.w * W * WNW / d4 + c.n * NNW * N / d2,
804 (c.n * NNE * N - c.n * NNW * N) / d2 + (c.w * W * N - c.e * E * N) / d4,
805 -c.e * E * ENE / d4 - c.n * NNE * N / d2,
806 (c.w * W * WSW - c.w * W * WNW) / d4 + (c.n * W * N - c.s * W * S) / d2,
807 (c.n * E * N - c.n * W * N - c.s * E * S + c.s * W * S) / d2 +
808 (c.e * E * N - c.w * W * N - c.e * E * S + c.w * W * S) / d4,
809 (c.e * E * ENE - c.e * E * ESE) / d4 + (c.s * E * S - c.n * E * N) / d2,
810 -c.w * W * WSW / d4 - c.s * SSW * S / d2,
811 (c.s * SSW * S - c.s * SSE * S) / d2 + (c.e * E * S - c.w * W * S) / d4,
812 c.e * E * ESE / d4 + c.s * SSE * S / d2,
813 0,
814 -4 * c.n * N / dy2,
815 0,
816 -c.w * W / dx2,
817 (4 * c.n * N + 4 * c.s * S) / dy2 + (c.e * E + c.w * W) / dx2,
818 -c.e * E / dx2,
819 0,
820 -4 * c.s * S / dy2,
821 0,
822 };
823
824 /* i indices */
825 const int I[] = {
826 i - 1, i, i + 1, i - 1, i, i + 1, i - 1, i, i + 1,
827 i - 1, i, i + 1, i - 1, i, i + 1, i - 1, i, i + 1,
828 };
829
830 /* j indices */
831 const int J[] = {
832 j + 1, j + 1, j + 1, j, j, j, j - 1, j - 1, j - 1,
833 j + 1, j + 1, j + 1, j, j, j, j - 1, j - 1, j - 1,
834 };
835
836 /* component indices */
837 const int C[] = {
838 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
839 };
840 /* end Maxima-generated code */
841
842 /* Dragging ice experiences friction at the bed determined by the
843 * IceBasalResistancePlasticLaw::drag() methods. These may be a plastic,
844 * pseudo-plastic, or linear friction law. Dragging is done implicitly
845 * (i.e. on left side of SSA eqns). */
846 double beta_u = 0.0, beta_v = 0.0;
847 {
848 double beta = 0.0;
849 switch (M_ij) {
851 // apply drag even in this case, to help with margins; note ice free areas may
852 // already have a strength extension
853 beta = beta_ice_free_bedrock;
854 break;
855 }
856 case MASK_FLOATING: {
857 const Vector2d &v = velocity(i, j);
858 double scaling = sub_gl ? grounded_fraction(i, j) : 0.0;
859 beta = scaling * basal_sliding_law->drag(basal_yield_stress(i, j), v.u, v.v);
860 break;
861 }
862 case MASK_GROUNDED: {
863 const Vector2d &v = velocity(i, j);
864 double scaling = sub_gl ? grounded_fraction(i, j) : 1.0;
865 beta = scaling * basal_sliding_law->drag(basal_yield_stress(i, j), v.u, v.v);
866 break;
867 }
869 default:
870 beta = 0.0;
871 }
872
873 beta_u = beta;
874 beta_v = beta;
875 }
876
877 {
878 // Set very high basal drag *in the direction along the boundary* at locations
879 // bordering "fjord walls".
880
881 auto M = cell_type.star_int(i, j);
882 auto b = bed.star(i, j);
883 double h = surface(i, j);
884
885 if ((ice_free(M.n) and b.n > h) or (ice_free(M.s) and b.s > h)) {
886 beta_u += beta_lateral_margin;
887 }
888
889 if ((ice_free(M.e) and b.e > h) or (ice_free(M.w) and b.w > h)) {
890 beta_v += beta_lateral_margin;
891 }
892 }
893
894 // add beta to diagonal entries
895 eq1[diag_u] += beta_u;
896 eq2[diag_v] += beta_v;
897
898 if (flow_line_mode) {
899 // set values corresponding to a trivial equation v = 0
900 for (int k = 0; k < n_nonzeros; ++k) {
901 eq2[k] = 0.0;
902 }
903 eq2[diag_v] = bc_scaling;
904 }
905
906 // check diagonal entries:
907 const double eps = 1e-16;
908 if (fabs(eq1[diag_u]) < eps) {
909 if (replace_zero_diagonal_entries) {
910 eq1[diag_u] = beta_ice_free_bedrock;
911 } else {
913 "first (X) equation in the SSAFD system:"
914 " zero diagonal entry at a regular (not Dirichlet B.C.)"
915 " location: i = %d, j = %d\n",
916 i, j);
917 }
918 }
919 if (fabs(eq2[diag_v]) < eps) {
920 if (replace_zero_diagonal_entries) {
921 eq2[diag_v] = beta_ice_free_bedrock;
922 } else {
924 "second (Y) equation in the SSAFD system:"
925 " zero diagonal entry at a regular (not Dirichlet B.C.)"
926 " location: i = %d, j = %d\n",
927 i, j);
928 }
929 }
930
931 // compute the matrix-vector product
932 if (Ax != nullptr) {
933 Vector2d sum{0.0, 0.0};
934 for (int k = 0; k < n_nonzeros; ++k) {
935 const Vector2d &v = velocity(I[k], J[k]);
936 double u_or_v = v.u * (1 - C[k]) + v.v * C[k];
937 sum += Vector2d{ eq1[k], eq2[k] } * u_or_v;
938 }
939 Ax[j][i] = sum; // STORAGE_ORDER
940 }
941
942 // set matrix values
943 if (A != nullptr) {
944 MatStencil row, col[n_nonzeros];
945 row.i = i;
946 row.j = j;
947 for (int m = 0; m < n_nonzeros; m++) {
948 col[m].i = I[m];
949 col[m].j = J[m];
950 col[m].c = C[m];
951 }
952
953 // set coefficients of the first equation:
954 row.c = 0;
955 ierr = MatSetValuesStencil(*A, 1, &row, n_nonzeros, col, eq1, INSERT_VALUES);
956 PISM_CHK(ierr, "MatSetValuesStencil");
957
958 // set coefficients of the second equation:
959 row.c = 1;
960 ierr = MatSetValuesStencil(*A, 1, &row, n_nonzeros, col, eq2, INSERT_VALUES);
961 PISM_CHK(ierr, "MatSetValuesStencil");
962 }
963 } // i,j-loop
964 } catch (...) {
965 loop.failed();
966 }
967 loop.check();
968
969 if (A != nullptr) {
970 ierr = MatAssemblyBegin(*A, MAT_FINAL_ASSEMBLY);
971 PISM_CHK(ierr, "MatAssemblyBegin");
972
973 ierr = MatAssemblyEnd(*A, MAT_FINAL_ASSEMBLY);
974 PISM_CHK(ierr, "MatAssemblyEnd");
975#if (Pism_DEBUG == 1)
976 ierr = MatSetOption(*A, MAT_NEW_NONZERO_LOCATION_ERR, PETSC_TRUE);
977 PISM_CHK(ierr, "MatSetOption");
978#endif
979 }
980}
981
982//! @brief Computes vertically-averaged ice hardness on the staggered grid.
984 const array::Array3D &enthalpy,
985 const array::CellType1 &cell_type,
986 array::Staggered &result) const {
987
988 assert(enthalpy.stencil_width() >= 1);
989
990 const double *E_ij = nullptr, *E_offset = nullptr;
991
992 auto Mz = m_grid->Mz();
993 std::vector<double> E(Mz);
994
995 array::AccessScope list{ &ice_thickness, &enthalpy, &result, &cell_type };
996
997 ParallelSection loop(m_grid->com);
998 try {
999 for (auto p = m_grid->points(); p; p.next()) {
1000 const int i = p.i(), j = p.j();
1001
1002 E_ij = enthalpy.get_column(i, j);
1003 for (int o = 0; o < 2; o++) {
1004 const int oi = 1 - o, oj = o;
1005 double H;
1006
1007 if (cell_type.icy(i, j) && cell_type.icy(i + oi, j + oj)) {
1008 H = 0.5 * (ice_thickness(i, j) + ice_thickness(i + oi, j + oj));
1009 } else if (cell_type.icy(i, j)) {
1010 H = ice_thickness(i, j);
1011 } else {
1012 H = ice_thickness(i + oi, j + oj);
1013 }
1014
1015 if (H == 0) {
1016 result(i, j, o) = -1e6; // an obviously impossible value
1017 continue;
1018 }
1019
1020 E_offset = enthalpy.get_column(i + oi, j + oj);
1021 // build a column of enthalpy values a the current location:
1022 for (unsigned int k = 0; k < Mz; ++k) {
1023 E[k] = 0.5 * (E_ij[k] + E_offset[k]);
1024 }
1025
1026 result(i, j, o) = rheology::averaged_hardness(*m_flow_law, H, m_grid->kBelowHeight(H),
1027 m_grid->z().data(), E.data());
1028 } // o
1029 } // loop over points
1030 } catch (...) {
1031 loop.failed();
1032 }
1033 loop.check();
1034}
1035
1037 const array::Scalar1 &surface_elevation,
1038 const array::CellType1 &cell_type,
1039 const array::Scalar1 *no_model_mask,
1040 array::Vector &driving_stress) const {
1041
1042 auto weight = [](int M_ij, int M_n, double h_ij, double h_n) {
1043 // fjord walls, nunataks, headwalls
1044 if ((mask::icy(M_ij) and mask::ice_free(M_n) and h_n > h_ij) or
1045 (mask::ice_free(M_ij) and mask::icy(M_n) and h_ij > h_n)) {
1046 return 0;
1047 }
1048
1049 return 1;
1050 };
1051
1052
1053 double
1054 dx = m_grid->dx(),
1055 dy = m_grid->dy();
1056
1057 int
1058 Mx = m_grid->Mx(),
1059 My = m_grid->My();
1060
1061 array::AccessScope list{ &driving_stress, &cell_type, no_model_mask, &surface_elevation,
1062 &ice_thickness };
1063
1064 for (auto p = m_grid->points(); p; p.next()) {
1065 const int i = p.i(), j = p.j();
1066
1067 auto M = no_model_mask->star_int(i, j);
1068
1069 if (M.c == 0) {
1070 // this grid point is in the modeled area so we don't need to modify the driving
1071 // stress
1072 continue;
1073 }
1074
1075 double pressure = m_EC->pressure(ice_thickness(i, j));
1076 if (pressure <= 0) {
1077 driving_stress(i, j) = 0.0;
1078 continue;
1079 }
1080
1081 auto h = surface_elevation.star(i, j);
1082 auto CT = cell_type.star_int(i, j);
1083
1084 // x-derivative
1085 double h_x = 0.0;
1086 {
1087 double
1088 west = static_cast<double>(M.w == 1 and i > 0),
1089 east = static_cast<double>(M.e == 1 and i < Mx - 1);
1090
1091 // don't use differences spanning "cliffs"
1092 west *= weight(CT.c, CT.w, h.c, h.w);
1093 east *= weight(CT.c, CT.e, h.c, h.e);
1094
1095 if (east + west > 0) {
1096 h_x = 1.0 / ((west + east) * dx) * (west * (h.c - h.w) + east * (h.e - h.c));
1097 } else {
1098 h_x = 0.0;
1099 }
1100 }
1101
1102 // y-derivative
1103 double h_y = 0.0;
1104 {
1105 double
1106 south = static_cast<double>(M.s == 1 and j > 0),
1107 north = static_cast<double>(M.n == 1 and j < My - 1);
1108
1109 // don't use differences spanning "cliffs"
1110 south *= weight(CT.c, CT.s, h.c, h.s);
1111 north *= weight(CT.c, CT.n, h.c, h.n);
1112
1113 if (north + south > 0) {
1114 h_y = 1.0 / ((south + north) * dy) * (south * (h.c - h.s) + north * (h.n - h.c));
1115 } else {
1116 h_y = 0.0;
1117 }
1118 }
1119
1120 driving_stress(i, j) = - pressure * Vector2d(h_x, h_y);
1121 } // end of the loop over grid points
1122}
1123
1124/*!
1125 * These computations do not depend on the solution, so they need to be done only once.
1126 *
1127 * Updates m_cell_type, m_taud, m_hardness, m_rhs.
1128 */
1130 // update the cell type mask using the ice-free thickness threshold for stress balance
1131 // computations
1132 {
1133 const double H_threshold = m_config->get_number("stress_balance.ice_free_thickness_standard");
1135 gc.set_icefree_thickness(H_threshold);
1136
1138 inputs.geometry->ice_thickness, //
1139 m_cell_type); // output
1140 // note: compute_mask() updates ghosts without communication ("redundantly")
1141 }
1143 m_cell_type, inputs.no_model_mask, *m_EC,
1144 m_taud); // output
1145
1146 if (m_regional_mode) {
1150 inputs.no_model_mask, m_taud);
1151 }
1152
1154 m_hardness);
1155
1156 if (inputs.fracture_density != nullptr) {
1158 }
1159
1161}
1162
1163/*! @brief Correct vertically-averaged hardness using a
1164 parameterization of the fracture-induced softening.
1165
1166 See T. Albrecht, A. Levermann; Fracture-induced softening for
1167 large-scale ice dynamics; (2013), The Cryosphere Discussions 7;
1168 4501-4544; DOI:10.5194/tcd-7-4501-2013
1169
1170 Note that this paper proposes an adjustment of the enhancement factor:
1171
1172 \f[E_{\text{effective}} = E \cdot (1 - (1-\epsilon) \phi)^{-n}.\f]
1173
1174 Let \f$E_{\text{effective}} = E\cdot C\f$, where \f$C\f$ is the
1175 factor defined by the formula above.
1176
1177 Recall that the effective viscosity is defined by
1178
1179 \f[\nu(D) = \frac12 B D^{(1-n)/(2n)}\f]
1180
1181 and the viscosity form of the flow law is
1182
1183 \f[\sigma'_{ij} = E_{\text{effective}}^{-\frac1n}2\nu(D) D_{ij}.\f]
1184
1185 Then
1186
1187 \f[\sigma'_{ij} = E_{\text{effective}}^{-\frac1n}BD^{(1-n)/(2n)}D_{ij}.\f]
1188
1189 Using the fact that \f$E_{\text{effective}} = E\cdot C\f$, this can be rewritten as
1190
1191 \f[\sigma'_{ij} = E^{-\frac1n} \left(C^{-\frac1n}B\right) D^{(1-n)/(2n)}D_{ij}.\f]
1192
1193 So scaling the enhancement factor by \f$C\f$ is equivalent to scaling
1194 ice hardness \f$B\f$ by \f$C^{-\frac1n}\f$.
1195*/
1196void SSAFDBase::fracture_induced_softening(const array::Scalar1 &fracture_density, double n_glen,
1197 array::Staggered &ice_hardness) {
1198
1199 const double epsilon = m_config->get_number("fracture_density.softening_lower_limit");
1200
1201 array::AccessScope list{ &ice_hardness, &fracture_density };
1202
1203 for (auto p = m_grid->points(); p; p.next()) {
1204 const int i = p.i(), j = p.j();
1205
1206 for (int o = 0; o < 2; o++) {
1207 const int oi = 1 - o, oj = o;
1208
1209 const double
1210 // fracture density on the staggered grid:
1211 phi = 0.5 * (fracture_density(i, j) + fracture_density(i + oi, j + oj)),
1212 // the line below implements equation (6) in the paper
1213 softening = pow((1.0 - (1.0 - epsilon) * phi), -n_glen);
1214
1215 ice_hardness(i, j, o) *= pow(softening, -1.0 / n_glen);
1216 }
1217 }
1218}
1219
1220//! \brief Compute the product of ice thickness and effective viscosity (on the
1221//! staggered grid).
1222/*!
1223In PISM the product \f$\nu H\f$ can be
1224 - constant, or
1225 - can be computed with a constant ice hardness \f$\bar B\f$ (temperature-independent)
1226 but with dependence of the viscosity on the strain rates, or
1227 - it can depend on the strain rates \e and have a vertically-averaged ice
1228 hardness depending on temperature or enthalpy.
1229
1230The flow law in ice stream and ice shelf regions must, for now, be a
1231(temperature-dependent) Glen law. This is the only flow law we know how to
1232invert to ``viscosity form''. More general forms like Goldsby-Kohlstedt are
1233not yet inverted.
1234
1235The viscosity form of a Glen law is
1236 \f[ \nu(T^*,D) = \frac{1}{2} B(T^*) D^{(1/n)-1}\, D_{ij} \f]
1237where
1238 \f[ D_{ij} = \frac{1}{2} \left(\frac{\partial U_i}{\partial x_j} +
1239 \frac{\partial U_j}{\partial x_i}\right) \f]
1240is the strain rate tensor and \f$B\f$ is an ice hardness related to
1241the ice softness \f$A(T^*)\f$ by
1242 \f[ B(T^*)=A(T^*)^{-1/n} \f]
1243in the case of a temperature dependent Glen-type law. (Here \f$T^*\f$ is the
1244pressure-adjusted temperature.)
1245
1246The effective viscosity is then
1247 \f[ \nu = \frac{\bar B}{2} \left[\left(\frac{\partial u}{\partial x}\right)^2 +
1248 \left(\frac{\partial v}{\partial y}\right)^2 +
1249 \frac{\partial u}{\partial x} \frac{\partial v}{\partial y} +
1250 \frac{1}{4} \left(\frac{\partial u}{\partial y}
1251 + \frac{\partial v}{\partial x}\right)^2
1252 \right]^{(1-n)/(2n)} \f]
1253where in the temperature-dependent case
1254 \f[ \bar B = \frac{1}{H}\,\int_b^h B(T^*)\,dz\f]
1255This integral is approximately computed by the trapezoid rule.
1256
1257The result of this procedure is \f$\nu H\f$, not just \f$\nu\f$, this it is
1258a vertical integral, not average, of viscosity.
1259
1260The resulting effective viscosity times thickness is regularized by ensuring that
1261its minimum is at least \f$\epsilon\f$. This regularization constant is an argument.
1262
1263In this implementation we set \f$\nu H\f$ to a constant anywhere the ice is
1264thinner than a certain minimum. See SSAStrengthExtension and compare how this
1265issue is handled when -cfbc is set.
1266*/
1268 const pism::Vector2d *const *velocity,
1269 const array::Staggered &hardness, double nuH_regularization,
1270 array::Staggered &result) {
1271
1272 auto uv = [&velocity](int i, int j) { return velocity[j][i]; };
1273
1274 array::AccessScope list{ &result, &hardness, &ice_thickness };
1275
1276 double n_glen = m_flow_law->exponent(),
1277 nu_enhancement_scaling = 1.0 / pow(m_e_factor, 1.0 / n_glen);
1278
1279 const double dx = m_grid->dx(), dy = m_grid->dy();
1280
1281 for (auto p = m_grid->points(); p; p.next()) {
1282 const int i = p.i(), j = p.j();
1283
1284 const int
1285 E = i + 1,
1286 W = i - 1,
1287 N = j + 1,
1288 S = j - 1;
1289
1290 stencils::Box<Vector2d> V{ uv(i, j), uv(i, N), uv(W, N), uv(W, j), uv(W, S),
1291 uv(i, S), uv(E, S), uv(E, j), uv(E, N) };
1292
1293 for (int o = 0; o < 2; ++o) {
1294 const int oi = 1 - o, oj = o;
1295
1296 const double H = 0.5 * (ice_thickness(i, j) + ice_thickness(i + oi, j + oj));
1297
1298 if (H < strength_extension->get_min_thickness()) {
1299 result(i, j, o) = strength_extension->get_notional_strength();
1300 continue;
1301 }
1302
1303 double u_x, u_y, v_x, v_y;
1304 // Check the offset to determine how to differentiate velocity
1305 if (o == 0) {
1306 u_x = (V.e.u - V.c.u) / dx;
1307 v_x = (V.e.v - V.c.v) / dx;
1308 u_y = (V.n.u + V.ne.u - V.s.u - V.se.u) / (4 * dy);
1309 v_y = (V.n.v + V.ne.v - V.s.v - V.se.v) / (4 * dy);
1310 } else {
1311 u_x = (V.e.u + V.ne.u - V.w.u - V.nw.u) / (4 * dx);
1312 v_x = (V.e.v + V.ne.v - V.w.v - V.nw.v) / (4 * dx);
1313 u_y = (V.n.u - V.c.u) / dy;
1314 v_y = (V.n.v - V.c.v) / dy;
1315 }
1316
1317 double nu = 0.0;
1318 m_flow_law->effective_viscosity(hardness(i, j, o),
1319 secondInvariant_2D({ u_x, v_x }, { u_y, v_y }), &nu, nullptr);
1320
1321 result(i, j, o) = nu * H;
1322
1323 // include the SSA enhancement factor; in most cases m_e_factor is 1
1324 result(i, j, o) *= nu_enhancement_scaling;
1325
1326 // We ensure that nuH is bounded below by a positive constant.
1327 result(i, j, o) += nuH_regularization;
1328 } // o-loop
1329 } // i,j-loop
1330}
1331
1332/**
1333 * @brief Compute the product of ice viscosity and thickness on the
1334 * staggered grid. Used when CFBC is enabled.
1335 *
1336 * 1) Loops over all grid points and width=1 ghosts and estimates u_x and v_x at the
1337 * i-offset staggered grid locations and u_y and v_y at the j-offset staggered grid
1338 * locations. This requires width=2 ghosts of `velocity` and `cell_type`.
1339 *
1340 * 2) Loops over all grid points (excluding ghost points) and computes weighted averages
1341 * of quantities from step 1 to estimate (u_y, v_y) at i-offset locations and (u_x,
1342 * v_x) and j-offset locations. This uses width=1 ghost values set in step 1.
1343 *
1344 * 3) In the second loop, ice thickness, ice hardness and (u_x, u_y, v_x, v_y) at
1345 * staggered grid locations from steps 1 and 2 are used to estimate nuH.
1346 *
1347 * @param[out] result nu*H product
1348 * @param[in] nuH_regularization regularization parameter (added to nu*H to keep it away from zero)
1349 *
1350 * @return 0 on success
1351 */
1352void SSAFDBase::compute_nuH_cfbc(const array::Scalar1 &ice_thickness, const array::CellType2 &cell_type,
1353 const pism::Vector2d* const* velocity,
1354 const array::Staggered &hardness,
1355 double nuH_regularization, array::Staggered &result) {
1356
1357 double n_glen = m_flow_law->exponent(),
1358 nu_enhancement_scaling = 1.0 / pow(m_e_factor, 1.0 / n_glen);
1359
1360 const double dx = m_grid->dx(), dy = m_grid->dy();
1361
1362 array::AccessScope list{ &cell_type, &m_work };
1363
1364 assert(m_work.stencil_width() >= 1);
1365
1366 auto U = [&velocity](int i, int j) { return velocity[j][i].u; };
1367 auto V = [&velocity](int i, int j) { return velocity[j][i].v; };
1368
1369 for (auto p = m_grid->points(1); p; p.next()) {
1370 const int i = p.i(), j = p.j();
1371
1372 // x-derivative, i-offset
1373 {
1374 if (cell_type.icy(i, j) && cell_type.icy(i + 1, j)) {
1375 m_work(i, j).u_x = (U(i + 1, j) - U(i, j)) / dx; // u_x
1376 m_work(i, j).v_x = (V(i + 1, j) - V(i, j)) / dx; // v_x
1377 m_work(i, j).w_i = 1.0;
1378 } else {
1379 m_work(i, j).u_x = 0.0;
1380 m_work(i, j).v_x = 0.0;
1381 m_work(i, j).w_i = 0.0;
1382 }
1383 }
1384
1385 // y-derivative, j-offset
1386 {
1387 if (cell_type.icy(i, j) && cell_type.icy(i, j + 1)) {
1388 m_work(i, j).u_y = (U(i, j + 1) - U(i, j)) / dy; // u_y
1389 m_work(i, j).v_y = (V(i, j + 1) - V(i, j)) / dy; // v_y
1390 m_work(i, j).w_j = 1.0;
1391 } else {
1392 m_work(i, j).u_y = 0.0;
1393 m_work(i, j).v_y = 0.0;
1394 m_work(i, j).w_j = 0.0;
1395 }
1396 }
1397 } // end of the loop over grid points and width=1 ghosts
1398
1399 list.add({ &result, &hardness, &ice_thickness });
1400
1401 for (auto p = m_grid->points(); p; p.next()) {
1402 const int i = p.i(), j = p.j();
1403
1404 double u_x, u_y, v_x, v_y, H, nu, W;
1405 // i-offset
1406 {
1407 if (cell_type.icy(i, j) && cell_type.icy(i + 1, j)) {
1408 H = 0.5 * (ice_thickness(i, j) + ice_thickness(i + 1, j));
1409 } else if (cell_type.icy(i, j)) {
1410 H = ice_thickness(i, j);
1411 } else {
1412 H = ice_thickness(i + 1, j);
1413 }
1414
1416 u_x = m_work(i, j).u_x;
1417 v_x = m_work(i, j).v_x;
1418
1419 W = m_work(i, j).w_j + m_work(i, j - 1).w_j + m_work(i + 1, j - 1).w_j +
1420 m_work(i + 1, j).w_j;
1421 if (W > 0) {
1422 u_y = 1.0 / W *
1423 (m_work(i, j).u_y + m_work(i, j - 1).u_y + m_work(i + 1, j - 1).u_y +
1424 m_work(i + 1, j).u_y);
1425 v_y = 1.0 / W *
1426 (m_work(i, j).v_y + m_work(i, j - 1).v_y + m_work(i + 1, j - 1).v_y +
1427 m_work(i + 1, j).v_y);
1428 } else {
1429 u_y = 0.0;
1430 v_y = 0.0;
1431 }
1432
1433 m_flow_law->effective_viscosity(
1434 hardness(i, j, 0), secondInvariant_2D({ u_x, v_x }, { u_y, v_y }), &nu, nullptr);
1435 result(i, j, 0) = nu * H;
1436 } else {
1437 result(i, j, 0) = strength_extension->get_notional_strength();
1438 }
1439 }
1440
1441 // j-offset
1442 {
1443 if (cell_type.icy(i, j) && cell_type.icy(i, j + 1)) {
1444 H = 0.5 * (ice_thickness(i, j) + ice_thickness(i, j + 1));
1445 } else if (cell_type.icy(i, j)) {
1446 H = ice_thickness(i, j);
1447 } else {
1448 H = ice_thickness(i, j + 1);
1449 }
1450
1452 u_y = m_work(i, j).u_y;
1453 v_y = m_work(i, j).v_y;
1454
1455 W = m_work(i, j).w_i + m_work(i - 1, j).w_i + m_work(i - 1, j + 1).w_i +
1456 m_work(i, j + 1).w_i;
1457 if (W > 0.0) {
1458 u_x = 1.0 / W *
1459 (m_work(i, j).u_x + m_work(i - 1, j).u_x + m_work(i - 1, j + 1).u_x +
1460 m_work(i, j + 1).u_x);
1461 v_x = 1.0 / W *
1462 (m_work(i, j).v_x + m_work(i - 1, j).v_x + m_work(i - 1, j + 1).v_x +
1463 m_work(i, j + 1).v_x);
1464 } else {
1465 u_x = 0.0;
1466 v_x = 0.0;
1467 }
1468
1469 m_flow_law->effective_viscosity(hardness(i, j, 1),
1470 secondInvariant_2D({ u_x, v_x }, { u_y, v_y }), &nu, NULL);
1471 result(i, j, 1) = nu * H;
1472 } else {
1473 result(i, j, 1) = strength_extension->get_notional_strength();
1474 }
1475 }
1476
1477 // adjustments:
1478 for (int o = 0; o < 2; ++o) {
1479 // include the SSA enhancement factor; in most cases ssa_enhancement_factor is 1
1480 result(i, j, o) *= nu_enhancement_scaling;
1481
1482 // We ensure that nuH is bounded below by a positive constant.
1483 result(i, j, o) += nuH_regularization;
1484 }
1485 } // end of the loop over grid points
1486}
1487
1488void SSAFDBase::compute_nuH(const array::Scalar1 &ice_thickness, const array::CellType2 &cell_type,
1489 const pism::Vector2d *const *velocity, const array::Staggered &hardness,
1490 double nuH_regularization, array::Staggered1 &result) {
1491 if (m_config->get_flag("stress_balance.calving_front_stress_bc")) {
1492 compute_nuH_cfbc(ice_thickness, cell_type, velocity, hardness, nuH_regularization, result);
1493 } else {
1494 compute_nuH_everywhere(ice_thickness, velocity, m_hardness, nuH_regularization, result);
1495 }
1496 result.update_ghosts();
1497}
1498
1499
1500/*!
1501 * Compute the residual.
1502 *
1503 * `velocity` has to have width=2 ghosts
1504 */
1505void SSAFDBase::compute_residual(const Inputs &inputs, const pism::Vector2d *const *velocity,
1506 pism::Vector2d **result) {
1507 {
1509 m_config->get_number("stress_balance.ssa.epsilon"), m_nuH);
1510
1511 fd_operator(*inputs.geometry,
1512 inputs.bc_mask,
1514 *inputs.basal_yield_stress,
1516 velocity, m_nuH, m_cell_type, nullptr, result);
1517 }
1518
1519 array::AccessScope scope{&m_rhs};
1520 for (auto p = m_grid->points(); p; p.next()) {
1521 const int i = p.i(), j = p.j();
1522
1523 result[j][i] -= m_rhs(i, j); // STORAGE_ORDER
1524 }
1525}
1526
1527/*!
1528 * Compute the residual (as a diagnostic).
1529 */
1530void SSAFDBase::compute_residual(const Inputs &inputs, const array::Vector2 &velocity,
1531 array::Vector &result) {
1533
1534 array::AccessScope list{ &m_velocity, &result };
1535
1536 compute_residual(inputs, m_velocity.array(), result.array());
1537}
1538
1540 return m_nuH;
1541}
1542
1544 return m_taud;
1545}
1546
1547//! @brief Reports the nuH (viscosity times thickness) product on the staggered
1548//! grid.
1549class SSAFD_nuH : public Diag<SSAFDBase> {
1550public:
1552 m_vars = { { m_sys, "nuH[0]" }, { m_sys, "nuH[1]" } };
1553 m_vars[0]
1554 .long_name("ice thickness times effective viscosity, i-offset")
1555 .units("Pa s m")
1556 .output_units("kPa s m");
1557 m_vars[1]
1558 .long_name("ice thickness times effective viscosity, j-offset")
1559 .units("Pa s m")
1560 .output_units("kPa s m");
1561 }
1562
1563protected:
1564 virtual std::shared_ptr<array::Array> compute_impl() const {
1565 auto result = allocate<array::Staggered>("nuH");
1566
1567 result->copy_from(model->integrated_viscosity());
1568
1569 return result;
1570 }
1571};
1572
1573//! @brief Computes the driving shear stress at the base of ice
1574//! (diagnostically).
1575/*! This is *not* a duplicate of SSB_taud: SSAFD_taud::compute() uses
1576 SSAFD::compute_driving_stress(), which tries to be smarter near ice margins.
1577*/
1578class SSAFD_taud : public Diag<SSAFDBase> {
1579public:
1581
1582 // set metadata:
1583 m_vars = { { m_sys, "taud_x" }, { m_sys, "taud_y" } };
1584
1585 m_vars[0].long_name("X-component of the driving shear stress at the base of ice");
1586 m_vars[1].long_name("Y-component of the driving shear stress at the base of ice");
1587
1588 for (auto &v : m_vars) {
1589 v.units("Pa");
1590 v["comment"] = "this is the driving stress used by the SSAFD solver";
1591 }
1592 }
1593
1594protected:
1595 std::shared_ptr<array::Array> compute_impl() const {
1596 auto result = allocate<array::Vector>("taud");
1597
1598 result->copy_from(model->driving_stress());
1599
1600 return result;
1601 }
1602};
1603
1604//! @brief Computes the magnitude of the driving shear stress at the base of
1605//! ice (diagnostically).
1606class SSAFD_taud_mag : public Diag<SSAFDBase> {
1607public:
1609
1610 // set metadata:
1611 m_vars = { { m_sys, "taud_mag" } };
1612
1613 m_vars[0].long_name("magnitude of the driving shear stress at the base of ice").units("Pa");
1614 m_vars[0]["comment"] = "this is the magnitude of the driving stress used by the SSAFD solver";
1615 }
1616
1617protected:
1618 virtual std::shared_ptr<array::Array> compute_impl() const {
1619 auto result = allocate<array::Scalar>("taud_mag");
1620 result->metadata(0) = m_vars[0];
1621
1622 compute_magnitude(model->driving_stress(), *result);
1623
1624 return result;
1625 }
1626};
1627
1630
1631 // replace these diagnostics
1632 result["taud"] = Diagnostic::Ptr(new SSAFD_taud(this));
1633 result["taud_mag"] = Diagnostic::Ptr(new SSAFD_taud_mag(this));
1634 result["nuH"] = Diagnostic::Ptr(new SSAFD_nuH(this));
1635
1636 return result;
1637}
1638
1639} // namespace stressbalance
1640} // namespace pism
const Config::ConstPtr m_config
configuration database used by this component
Definition Component.hh:158
const std::shared_ptr< const Grid > m_grid
grid used by this component
Definition Component.hh:156
const SSAFDBase * model
A template derived from Diagnostic, adding a "Model".
const units::System::Ptr m_sys
the unit system
std::vector< SpatialVariableMetadata > m_vars
metadata corresponding to NetCDF variables
std::shared_ptr< Diagnostic > Ptr
Definition Diagnostic.hh:65
double pressure(double depth) const
Get pressure in ice from depth below surface using the hydrostatic assumption.
Converts between specific enthalpy and temperature or liquid content.
void compute_mask(const array::Scalar &sea_level, const array::Scalar &bed, const array::Scalar &thickness, array::Scalar &result) const
Definition Mask.cc:36
void set_icefree_thickness(double threshold)
Definition Mask.hh:81
array::Scalar1 sea_level_elevation
Definition Geometry.hh:48
array::Scalar cell_grounded_fraction
Definition Geometry.hh:56
array::Scalar2 ice_surface_elevation
Definition Geometry.hh:57
array::Scalar2 ice_thickness
Definition Geometry.hh:51
array::Scalar2 bed_elevation
Definition Geometry.hh:47
virtual double drag(double tauc, double vx, double vy) const
Compute the drag coefficient for the basal shear stress.
Class containing physical constants and the constitutive relation describing till for SSA.
void failed()
Indicates a failure of a parallel section.
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 & set_units_without_validation(const std::string &value)
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
double * get_column(int i, int j)
Definition Array3D.cc:121
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:629
void update_ghosts()
Updates ghost points.
Definition Array.cc:615
unsigned int stencil_width() const
Get the stencil width of the current Array. Returns 0 if ghosts are not available.
Definition Array.cc:302
SpatialVariableMetadata & metadata(unsigned int N=0)
Returns a reference to the SpatialVariableMetadata object containing metadata for the compoment N.
Definition Array.cc:476
stencils::Box< int > box_int(int i, int j) const
Definition Scalar.hh:84
stencils::Star< int > star_int(int i, int j) const
Definition Scalar.hh:72
bool ice_free(int i, int j) const
Definition CellType.hh:54
bool icy(int i, int j) const
Definition CellType.hh:42
stencils::Star< int > star_int(int i, int j) const
Definition Scalar.hh:72
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
const array::Scalar1 * fracture_density
const array::Scalar * basal_yield_stress
const array::Scalar * bc_mask
const array::Scalar * no_model_ice_thickness
const array::Scalar2 * no_model_surface_elevation
const array::Scalar * water_column_pressure
const array::Array3D * enthalpy
const array::Scalar2 * no_model_mask
const array::Vector * bc_values
void compute_driving_stress(const array::Scalar &ice_thickness, const array::Scalar1 &surface_elevation, const array::CellType1 &cell_type, const array::Scalar1 *no_model_mask, const EnthalpyConverter &EC, array::Vector &result) const
Compute the gravitational driving stress.
Definition SSAFDBase.cc:143
void assemble_rhs(const Inputs &inputs, const array::CellType1 &cell_type, const array::Vector &driving_stress, double bc_scaling, array::Vector &result) const
Computes the right-hand side ("rhs") of the linear problem for the Picard iteration and finite-differ...
Definition SSAFDBase.cc:304
array::Vector m_taud
driving stress
Definition SSAFDBase.hh:127
const array::Staggered & integrated_viscosity() const
void compute_nuH_cfbc(const array::Scalar1 &ice_thickness, const array::CellType2 &cell_type, const pism::Vector2d *const *velocity, const array::Staggered &hardness, double nuH_regularization, array::Staggered &result)
Compute the product of ice viscosity and thickness on the staggered grid. Used when CFBC is enabled.
array::Staggered m_hardness
ice hardness
Definition SSAFDBase.hh:116
void fd_operator(const Geometry &geometry, const array::Scalar *bc_mask, double bc_scaling, const array::Scalar &basal_yield_stress, IceBasalResistancePlasticLaw *basal_sliding_law, const pism::Vector2d *const *velocity, const array::Staggered1 &nuH, const array::CellType1 &cell_type, Mat *A, Vector2d **Ax) const
Assemble the left-hand side matrix for the KSP-based, Picard iteration, and finite difference impleme...
Definition SSAFDBase.cc:549
void initialize_iterations(const Inputs &inputs)
void fracture_induced_softening(const array::Scalar1 &fracture_density, double n_glen, array::Staggered &ice_hardness)
Correct vertically-averaged hardness using a parameterization of the fracture-induced softening.
array::Staggered1 m_nuH
viscosity times thickness
Definition SSAFDBase.hh:119
const double m_bc_scaling
scaling used for diagonal matrix elements at Dirichlet BC locations
Definition SSAFDBase.hh:130
void adjust_driving_stress(const array::Scalar &ice_thickness, const array::Scalar1 &surface_elevation, const array::CellType1 &cell_type, const array::Scalar1 *no_model_mask, array::Vector &driving_stress) const
void compute_average_ice_hardness(const array::Scalar1 &thickness, const array::Array3D &enthalpy, const array::CellType1 &cell_type, array::Staggered &result) const
Computes vertically-averaged ice hardness on the staggered grid.
Definition SSAFDBase.cc:983
DiagnosticList diagnostics_impl() const
SSAFDBase(std::shared_ptr< const Grid > g, bool regional_mode)
Definition SSAFDBase.cc:35
array::Vector m_rhs
right hand side
Definition SSAFDBase.hh:124
void compute_nuH(const array::Scalar1 &ice_thickness, const array::CellType2 &cell_type, const pism::Vector2d *const *velocity, const array::Staggered &hardness, double nuH_regularization, array::Staggered1 &result)
array::Array2D< Work > m_work
Definition SSAFDBase.hh:113
void compute_nuH_everywhere(const array::Scalar1 &ice_thickness, const pism::Vector2d *const *velocity, const array::Staggered &hardness, double nuH_regularization, array::Staggered &result)
Compute the product of ice thickness and effective viscosity (on the staggered grid).
void compute_residual(const Inputs &inputs, const array::Vector2 &velocity, array::Vector &result)
const array::Vector & driving_stress() const
virtual std::shared_ptr< array::Array > compute_impl() const
SSAFD_nuH(const SSAFDBase *m)
Reports the nuH (viscosity times thickness) product on the staggered grid.
virtual std::shared_ptr< array::Array > compute_impl() const
Computes the magnitude of the driving shear stress at the base of ice (diagnostically).
std::shared_ptr< array::Array > compute_impl() const
SSAFD_taud(const SSAFDBase *m)
Computes the driving shear stress at the base of ice (diagnostically).
double get_notional_strength() const
Returns strength = (viscosity times thickness).
Definition SSA.cc:59
double get_min_thickness() const
Returns minimum thickness to trigger use of extension.
Definition SSA.cc:64
SSAStrengthExtension * strength_extension
Definition SSA.hh:115
PISM's SSA solver.
Definition SSA.hh:110
const array::Vector1 & velocity() const
Get the thickness-advective 2D velocity.
virtual DiagnosticList diagnostics_impl() const
std::shared_ptr< rheology::FlowLaw > m_flow_law
IceBasalResistancePlasticLaw * m_basal_sliding_law
#define PISM_CHK(errcode, name)
#define PISM_ERROR_LOCATION
const double rho_ice
Definition exactTestK.c:31
const double phi
Definition exactTestK.c:41
static const double L
Definition exactTestL.cc:40
bool domain_edge(const Grid &grid, int i, int j)
Definition Grid.hh:409
bool icy(int M)
Ice-filled cell (grounded or floating).
Definition Mask.hh:48
bool grounded_ice(int M)
Definition Mask.hh:51
bool ice_free_land(int M)
Definition Mask.hh:64
bool ice_free_ocean(int M)
Definition Mask.hh:61
bool grounded(int M)
Grounded cell (grounded ice or ice-free).
Definition Mask.hh:44
bool ice_free(int M)
Ice-free cell (grounded or ocean).
Definition Mask.hh:58
bool floating_ice(int M)
Definition Mask.hh:54
double averaged_hardness(const FlowLaw &ice, double thickness, unsigned int kbelowH, const double *zlevels, const double *enthalpy)
Computes vertical average of B(E, p) ice hardness, namely .
Definition FlowLaw.cc:213
static int weight(bool margin_bc, int M_ij, int M_n, double h_ij, double h_n, int N_ij, int N_n)
Definition SSAFDBase.cc:83
static double diff_centered(double L, double, double R)
Definition SSAFDBase.cc:134
static bool is_marginal(int i, int j, const array::CellType1 &cell_type, bool ssa_dirichlet_bc)
Checks if a cell is near or at the ice front.
Definition SSAFDBase.cc:270
static double diff_uphill(double L, double C, double R)
Definition SSAFDBase.cc:118
static double secondInvariant_2D(const Vector2d &U_x, const Vector2d &U_y)
Definition FlowLaw.hh:45
double average_water_column_pressure(double ice_thickness, double bed, double floatation_level, double rho_ice, double rho_water, double g)
std::string printf(const char *format,...)
std::map< std::string, Diagnostic::Ptr > DiagnosticList
static const double k
Definition exactTestP.cc:42
@ MASK_FLOATING
Definition Mask.hh:34
@ MASK_ICE_FREE_OCEAN
Definition Mask.hh:35
@ MASK_ICE_FREE_BEDROCK
Definition Mask.hh:32
@ MASK_GROUNDED
Definition Mask.hh:33
Star stencil points (in the map-plane).
Definition stencils.hh:30
static double S(unsigned n)
Definition test_cube.c:58