PISM, A Parallel Ice Sheet Model  stable v2.1.1 committed by Constantine Khrulev on 2024-12-04 13:36:58 -0900
SSAFD.cc
Go to the documentation of this file.
1 // Copyright (C) 2004--2023 Constantine Khroulev, Ed Bueler and Jed Brown
2 //
3 // This file is part of PISM.
4 //
5 // PISM is free software; you can redistribute it and/or modify it under the
6 // terms of the GNU General Public License as published by the Free Software
7 // Foundation; either version 3 of the License, or (at your option) any later
8 // version.
9 //
10 // PISM is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with PISM; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 
19 #include <cassert>
20 #include <stdexcept>
21 
22 #include "pism/basalstrength/basal_resistance.hh"
23 #include "pism/geometry/Geometry.hh"
24 #include "pism/rheology/FlowLaw.hh"
25 #include "pism/stressbalance/StressBalance.hh"
26 #include "pism/stressbalance/ssa/SSAFD.hh"
27 #include "pism/stressbalance/ssa/SSAFD_diagnostics.hh"
28 #include "pism/util/Grid.hh"
29 #include "pism/util/Mask.hh"
30 #include "pism/util/array/CellType.hh"
31 #include "pism/util/petscwrappers/DM.hh"
32 #include "pism/util/petscwrappers/Vec.hh"
33 #include "pism/util/pism_options.hh"
34 #include "pism/util/pism_utilities.hh"
35 
36 namespace pism {
37 namespace stressbalance {
38 
39 SSAFD::KSPFailure::KSPFailure(const char* reason)
40  : RuntimeError(ErrorLocation(), std::string("SSAFD KSP (linear solver) failed: ") + reason){
41  // empty
42 }
43 
44 SSAFD::PicardFailure::PicardFailure(const std::string &message)
45  : RuntimeError(ErrorLocation(), "SSAFD Picard iterations failed: " + message) {
46  // empty
47 }
48 
49 SSA* SSAFDFactory(std::shared_ptr<const Grid> g) {
50  return new SSAFD(g);
51 }
52 
53 /*!
54 Because the FD implementation of the SSA uses Picard iteration, a PETSc KSP
55 and Mat are used directly. In particular we set up \f$A\f$
56 (Mat m_A) and a \f$b\f$ (= Vec m_b) and iteratively solve
57 linear systems
58  \f[ A x = b \f]
59 where \f$x\f$ (= Vec SSAX). A PETSc SNES object is never created.
60  */
61 SSAFD::SSAFD(std::shared_ptr<const Grid> grid)
62  : SSA(grid),
63  m_hardness(grid, "hardness"),
64  m_nuH(grid, "nuH"),
65  m_nuH_old(grid, "nuH_old"),
66  m_work(grid, "work_vector", array::WITH_GHOSTS,
67  2 /* stencil width */),
68  m_b(grid, "right_hand_side"),
69  m_velocity_old(grid, "velocity_old"),
70  m_scaling(1e9) // comparable to typical beta for an ice stream;
71 {
72 
74  .long_name("old SSA velocity field; used for re-trying with a different epsilon")
75  .units("m s-1");
76 
78  .long_name("vertically-averaged ice hardness")
79  .set_units_without_validation(pism::printf("Pa s^(1/%f)", m_flow_law->exponent()));
80 
81  m_nuH.metadata(0)
82  .long_name("ice thickness times effective viscosity")
83  .units("Pa s m");
84 
86  .long_name("ice thickness times effective viscosity (before an update)")
87  .units("Pa s m");
88 
89  m_work.metadata(0).long_name("temporary storage used to compute nuH");
90 
91  // The nuH viewer:
92  m_view_nuh = false;
93  m_nuh_viewer_size = 300;
94 
95  // PETSc objects and settings
96  {
97  PetscErrorCode ierr;
98  ierr = DMSetMatType(*m_da, MATAIJ);
99  PISM_CHK(ierr, "DMSetMatType");
100 
101  ierr = DMCreateMatrix(*m_da, m_A.rawptr());
102  PISM_CHK(ierr, "DMCreateMatrix");
103 
104  ierr = KSPCreate(m_grid->com, m_KSP.rawptr());
105  PISM_CHK(ierr, "KSPCreate");
106 
107  ierr = KSPSetOptionsPrefix(m_KSP, "ssafd_");
108  PISM_CHK(ierr, "KSPSetOptionsPrefix");
109 
110  // Use non-zero initial guess (i.e. SSA velocities from the last
111  // solve() call).
112  ierr = KSPSetInitialGuessNonzero(m_KSP, PETSC_TRUE);
113  PISM_CHK(ierr, "KSPSetInitialGuessNonzero");
114 
115  // Use the initial residual norm.
116  ierr = KSPConvergedDefaultSetUIRNorm(m_KSP);
117  PISM_CHK(ierr, "KSPConvergedDefaultSetUIRNorm");
118  }
119 }
120 
121 //! @note Uses `PetscErrorCode` *intentionally*.
123  PetscErrorCode ierr;
124  PC pc;
125 
126  ierr = KSPSetType(m_KSP, KSPGMRES);
127  PISM_CHK(ierr, "KSPSetType");
128 
129  ierr = KSPSetOperators(m_KSP, m_A, m_A);
130  PISM_CHK(ierr, "KSPSetOperators");
131 
132  // Get the PC from the KSP solver:
133  ierr = KSPGetPC(m_KSP, &pc);
134  PISM_CHK(ierr, "KSPGetPC");
135 
136  // Set the PC type:
137  ierr = PCSetType(pc, PCBJACOBI);
138  PISM_CHK(ierr, "PCSetType");
139 
140  // Process options:
141  ierr = KSPSetFromOptions(m_KSP);
142  PISM_CHK(ierr, "KSPSetFromOptions");
143 }
144 
145 //! @note Uses `PetscErrorCode` *intentionally*.
147  PetscErrorCode ierr;
148  PC pc, sub_pc;
149 
150  // Set parameters equivalent to
151  // -ksp_type gmres -ksp_norm_type unpreconditioned -ksp_pc_side right -pc_type asm -sub_pc_type lu
152 
153  ierr = KSPSetType(m_KSP, KSPGMRES);
154  PISM_CHK(ierr, "KSPSetType");
155 
156  ierr = KSPSetOperators(m_KSP, m_A, m_A);
157  PISM_CHK(ierr, "KSPSetOperators");
158 
159  // Switch to using the "unpreconditioned" norm.
160  ierr = KSPSetNormType(m_KSP, KSP_NORM_UNPRECONDITIONED);
161  PISM_CHK(ierr, "KSPSetNormType");
162 
163  // Switch to "right" preconditioning.
164  ierr = KSPSetPCSide(m_KSP, PC_RIGHT);
165  PISM_CHK(ierr, "KSPSetPCSide");
166 
167  // Get the PC from the KSP solver:
168  ierr = KSPGetPC(m_KSP, &pc);
169  PISM_CHK(ierr, "KSPGetPC");
170 
171  // Set the PC type:
172  ierr = PCSetType(pc, PCASM);
173  PISM_CHK(ierr, "PCSetType");
174 
175  // Set the sub-KSP object to "preonly"
176  KSP *sub_ksp;
177  ierr = PCSetUp(pc);
178  PISM_CHK(ierr, "PCSetUp");
179 
180  ierr = PCASMGetSubKSP(pc, NULL, NULL, &sub_ksp);
181  PISM_CHK(ierr, "PCASMGetSubKSP");
182 
183  ierr = KSPSetType(*sub_ksp, KSPPREONLY);
184  PISM_CHK(ierr, "KSPSetType");
185 
186  // Set the PC of the sub-KSP to "LU".
187  ierr = KSPGetPC(*sub_ksp, &sub_pc);
188  PISM_CHK(ierr, "KSPGetPC");
189 
190  ierr = PCSetType(sub_pc, PCLU);
191  PISM_CHK(ierr, "PCSetType");
192 
193  // Let the user override all this:
194  // Process options:
195  ierr = KSPSetFromOptions(m_KSP);
196  PISM_CHK(ierr, "KSPSetFromOptions");
197 }
198 
200  SSA::init_impl();
201 
202  m_log->message(2, " [using the KSP-based finite difference implementation]\n");
203 
204  // options
205  options::Integer viewer_size("-ssa_nuh_viewer_size", "nuH viewer size", m_nuh_viewer_size);
206  m_nuh_viewer_size = viewer_size;
207  m_view_nuh = options::Bool("-ssa_view_nuh", "Enable the SSAFD nuH runtime viewer");
208 
209  if (m_config->get_flag("stress_balance.calving_front_stress_bc")) {
210  m_log->message(2, " using PISM-PIK calving-front stress boundary condition ...\n");
211  }
212 
215 }
216 
217 //! \brief Computes the right-hand side ("rhs") of the linear problem for the
218 //! Picard iteration and finite-difference implementation of the SSA equations.
219 /*!
220 The right side of the SSA equations is just the driving stress term
221  \f[ - \rho g H \nabla h. \f]
222 The basal stress is put on the left side of the system. This method builds the
223 discrete approximation of the right side. For more about the discretization
224 of the SSA equations, see comments for assemble_matrix().
225 
226 The values of the driving stress on the i,j grid come from a call to
227 compute_driving_stress().
228 
229 In the case of Dirichlet boundary conditions, the entries on the right-hand side
230 come from known velocity values. The fields m_bc_values and m_bc_mask are used for
231 this.
232  */
233 void SSAFD::assemble_rhs(const Inputs &inputs) {
234  using mask::ice_free;
235  using mask::ice_free_land;
236  using mask::ice_free_ocean;
237 
238  const array::Scalar1 &bed = inputs.geometry->bed_elevation;
239  const array::Scalar &thickness = inputs.geometry->ice_thickness,
240  &surface = inputs.geometry->ice_surface_elevation,
241  &sea_level = inputs.geometry->sea_level_elevation,
242  *water_column_pressure = inputs.water_column_pressure;
243 
244  const double dx = m_grid->dx(), dy = m_grid->dy(),
245  standard_gravity = m_config->get_number("constants.standard_gravity"),
246  rho_ocean = m_config->get_number("constants.sea_water.density"),
247  rho_ice = m_config->get_number("constants.ice.density");
248 
249  // This constant is for debugging: simulations should not depend on the choice of
250  // velocity used in ice-free areas.
251  const Vector2d ice_free_velocity(0.0, 0.0);
252 
253  const bool use_cfbc = m_config->get_flag("stress_balance.calving_front_stress_bc"),
254  flow_line_mode = m_config->get_flag("stress_balance.ssa.fd.flow_line_mode");
255 
256  // FIXME: bedrock_boundary is a misleading name
257  bool bedrock_boundary = m_config->get_flag("stress_balance.ssa.dirichlet_bc");
258 
260  m_mask, inputs.no_model_mask, m_taud);
261 
262  array::AccessScope list{ &m_taud, &m_b };
263 
264  if (inputs.bc_values != nullptr and inputs.bc_mask != nullptr) {
265  list.add({ inputs.bc_values, inputs.bc_mask });
266  }
267 
268  if (use_cfbc) {
269  list.add({ &thickness, &bed, &surface, &m_mask, &sea_level });
270  }
271 
272  if (use_cfbc and (water_column_pressure != nullptr)) {
273  list.add(*water_column_pressure);
274  }
275 
276  m_b.set(0.0);
277 
278  for (auto p = m_grid->points(); p; p.next()) {
279  const int i = p.i(), j = p.j();
280 
281  Vector2d taud = m_taud(i, j);
282 
283  if (flow_line_mode) {
284  // no cross-flow driving stress in the flow line mode
285  taud.v = 0.0;
286  }
287 
288  if ((inputs.bc_values != nullptr) and inputs.bc_mask->as_int(i, j) == 1) {
289  m_b(i, j).u = m_scaling * (*inputs.bc_values)(i, j).u;
290  m_b(i, j).v = m_scaling * (*inputs.bc_values)(i, j).v;
291  continue;
292  }
293 
294  if (use_cfbc) {
295  double H_ij = thickness(i, j);
296 
297  auto M = m_mask.star_int(i, j);
298 
299  // Note: this sets velocities at both ice-free ocean and ice-free
300  // bedrock to zero. This means that we need to set boundary conditions
301  // at both ice/ice-free-ocean and ice/ice-free-bedrock interfaces below
302  // to be consistent.
303  if (ice_free(M.c)) {
304  m_b(i, j) = m_scaling * ice_free_velocity;
305  continue;
306  }
307 
308  if (is_marginal(i, j, bedrock_boundary)) {
309  // weights at the west, east, south, and north cell faces
310  int W = 0, E = 0, S = 0, N = 0;
311  // direct neighbors
312  // NOLINTBEGIN(readability-braces-around-statements)
313  if (bedrock_boundary) {
314  if (ice_free_ocean(M.e))
315  E = 1;
316  if (ice_free_ocean(M.w))
317  W = 1;
318  if (ice_free_ocean(M.n))
319  N = 1;
320  if (ice_free_ocean(M.s))
321  S = 1;
322  } else {
323  if (ice_free(M.e))
324  E = 1;
325  if (ice_free(M.w))
326  W = 1;
327  if (ice_free(M.n))
328  N = 1;
329  if (ice_free(M.s))
330  S = 1;
331  }
332  // NOLINTEND(readability-braces-around-statements)
333 
334  double P_ice = 0.5 * rho_ice * standard_gravity * H_ij, P_water = 0.0;
335 
336  if (water_column_pressure != nullptr) {
337  P_water = (*water_column_pressure)(i, j);
338  } else {
339  P_water = pism::average_water_column_pressure(H_ij, bed(i, j), sea_level(i, j), rho_ice,
340  rho_ocean, standard_gravity);
341  }
342 
343  double delta_p = H_ij * (P_ice - P_water);
344 
345  if (grid::domain_edge(*m_grid, i, j) and not(flow_line_mode or mask::grounded(M.c))) {
346  // In regional setups grounded ice may extend to the edge of the domain. This
347  // condition ensures that at a domain edge the ice behaves as if it extends past
348  // the edge without a change in geometry.
349  delta_p = 0.0;
350  }
351 
352  {
353  // fjord walls, nunataks, etc
354  //
355  // Override weights if we are at the margin and the grid cell on the other side
356  // of the interface is ice-free and above the level of the ice surface.
357  //
358  // This effectively sets the pressure difference at the corresponding interface
359  // to zero, which is exactly what we need.
360  auto b = bed.star(i, j);
361  double h = surface(i, j);
362 
363  if (ice_free(M.n) and b.n > h) {
364  N = 0;
365  }
366  if (ice_free(M.e) and b.e > h) {
367  E = 0;
368  }
369  if (ice_free(M.s) and b.s > h) {
370  S = 0;
371  }
372  if (ice_free(M.w) and b.w > h) {
373  W = 0;
374  }
375  }
376 
377  // Note that if the current cell is "marginal" but not a CFBC
378  // location, the following two lines are equaivalent to the "usual
379  // case" below.
380  //
381  // Note: signs below (+E, -W, etc) are explained by directions of outward
382  // normal vectors at corresponding cell faces.
383  m_b(i, j).u = taud.u + (E - W) * delta_p / dx;
384  m_b(i, j).v = taud.v + (N - S) * delta_p / dy;
385 
386  continue;
387  } // end of "if (is_marginal(i, j))"
388 
389  // If we reached this point, then CFBC are enabled, but we are in the
390  // interior of a sheet or shelf. See "usual case" below.
391 
392  } // end of "if (use_cfbc)"
393 
394  // usual case: use already computed driving stress
395  m_b(i, j) = taud;
396  }
397 }
398 
399 static void set_diagonal_matrix_entry(Mat A, int i, int j, int component, double value) {
400  MatStencil row, col;
401 
402  row.i = i;
403  row.j = j;
404  row.c = component;
405 
406  col.i = i;
407  col.j = j;
408  col.c = component;
409 
410  PetscErrorCode ierr = MatSetValuesStencil(A, 1, &row, 1, &col, &value, INSERT_VALUES);
411  PISM_CHK(ierr, "MatSetValuesStencil");
412 }
413 
414 
415 //! \brief Assemble the left-hand side matrix for the KSP-based, Picard iteration,
416 //! and finite difference implementation of the SSA equations.
417 /*!
418 Recall the SSA equations are
419 \f{align*}
420  - 2 \left[\nu H \left(2 u_x + v_y\right)\right]_x
421  - \left[\nu H \left(u_y + v_x\right)\right]_y
422  - \tau_{(b)1} &= - \rho g H h_x, \\
423  - \left[\nu H \left(u_y + v_x\right)\right]_x
424  - 2 \left[\nu H \left(u_x + 2 v_y\right)\right]_y
425  - \tau_{(b)2} &= - \rho g H h_y,
426 \f}
427 where \f$u\f$ is the \f$x\f$-component of the velocity and \f$v\f$ is the
428 \f$y\f$-component of the velocity.
429 
430 The coefficient \f$\nu\f$ is the vertically-averaged effective viscosity.
431 (The product \f$\nu H\f$ is computed by compute_nuH_staggered().)
432 The Picard iteration idea is that, to solve the nonlinear equations in which
433 the effective viscosity depends on the velocity, we freeze the effective
434 viscosity using its value at the current estimate of the velocity and we solve
435 the linear equations which come from this viscosity. In abstract symbols, the
436 Picard iteration replaces the above nonlinear SSA equations by a sequence of
437 linear problems
438 
439 \f[ A(U^{(k)}) U^{(k+1)} = b \f]
440 
441 where \f$A(U)\f$ is the matrix from discretizing the SSA equations supposing
442 the viscosity is a function of known velocities \f$U\f$, and where \f$U^{(k)}\f$
443 denotes the \f$k\f$th iterate in the process. The current method assembles \f$A(U)\f$.
444 
445 For ice shelves \f$\tau_{(b)i} = 0\f$ [\ref MacAyealetal].
446 For ice streams with a basal till modelled as a plastic material,
447 \f$\tau_{(b)i} = - \tau_c u_i/|\mathbf{u}|\f$ where
448 \f$\mathbf{u} = (u,v)\f$, \f$|\mathbf{u}| = \left(u^2 + v^2\right)^{1/2}\f$,
449 where \f$\tau_c(t,x,y)\f$ is the yield stress of the till [\ref SchoofStream].
450 More generally, ice streams can be modeled with a pseudo-plastic basal till;
451 see IceModel::initBasalTillModel() and IceModel::updateYieldStressUsingBasalWater()
452 and reference [\ref BKAJS]. The pseudo-plastic till model includes all power law
453 sliding relations and the linearly-viscous model for sliding,
454 \f$\tau_{(b)i} = - \beta u_i\f$ where \f$\beta\f$ is the basal drag
455 (friction) parameter [\ref MacAyeal]. In any case, PISM assumes that the basal shear
456 stress can be factored this way, *even if the coefficient depends on the
457 velocity*, \f$\beta(u,v)\f$. Such factoring is possible even in the case of
458 (regularized) plastic till. This scalar coefficient \f$\beta\f$ is what is
459 returned by IceBasalResistancePlasticLaw::drag().
460 
461 Note that the basal shear stress appears on the \em left side of the
462 linear system we actually solve. We believe this is crucial, because
463 of its effect on the spectrum of the linear approximations of each
464 stage. The effect on spectrum is clearest in the linearly-viscous till
465 case but there seems to be an analogous effect in the plastic till case.
466 
467 This method assembles the matrix for the left side of the above SSA equations.
468 The numerical method is finite difference. Suppose we use difference notation
469 \f$\delta_{+x}f^{i,j} = f^{i+1,j}-f^{i,j}\f$,
470 \f$\delta_{-x}f^{i,j} = f^{i,j}-f^{i-1,j}\f$, and
471 \f$\Delta_{x}f^{i,j} = f^{i+1,j}-f^{i-1,j}\f$, and corresponding notation for
472 \f$y\f$ differences, and that we write \f$N = \nu H\f$ then the first of the
473 two "concrete" SSA equations above has this discretization:
474 \f{align*}
475 - &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] \\
476 &\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}.
477 \f}
478 As a picture, see Figure \ref ssastencil.
479 
480 \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."
481 \anchor ssastencil
482 
483 It follows immediately that the matrix we assemble in the current method has
484 13 nonzeros entries per row because, for this first SSA equation, there are 5
485 grid values of \f$u\f$ and 8 grid values of \f$v\f$ used in this scheme. For
486 the second equation we also have 13 nonzeros per row.
487 
488 FIXME: document use of DAGetMatrix and MatStencil and MatSetValuesStencil
489 
490 */
491 void SSAFD::assemble_matrix(const Inputs &inputs, bool include_basal_shear, Mat A) {
492  using mask::grounded_ice;
493  using mask::ice_free;
494  using mask::ice_free_land;
495  using mask::ice_free_ocean;
496  using mask::icy;
497 
498  const int diag_u = 4;
499  const int diag_v = 13;
500 
501  PetscErrorCode ierr = 0;
502 
503  // shortcut:
504  const array::Vector &vel = m_velocity;
505 
506  const array::Scalar1 &thickness = inputs.geometry->ice_thickness,
507  &bed = inputs.geometry->bed_elevation;
508  const array::Scalar &surface = inputs.geometry->ice_surface_elevation,
509  &grounded_fraction = inputs.geometry->cell_grounded_fraction,
510  &tauc = *inputs.basal_yield_stress;
511 
512  const double dx = m_grid->dx(), dy = m_grid->dy(),
513  beta_lateral_margin = m_config->get_number("basal_resistance.beta_lateral_margin"),
514  beta_ice_free_bedrock =
515  m_config->get_number("basal_resistance.beta_ice_free_bedrock");
516 
517  const bool
518  // FIXME: bedrock_boundary is a misleading name
519  bedrock_boundary = m_config->get_flag("stress_balance.ssa.dirichlet_bc"),
520  flow_line_mode = m_config->get_flag("stress_balance.ssa.fd.flow_line_mode"),
521  use_cfbc = m_config->get_flag("stress_balance.calving_front_stress_bc"),
522  replace_zero_diagonal_entries =
523  m_config->get_flag("stress_balance.ssa.fd.replace_zero_diagonal_entries");
524 
525  ierr = MatZeroEntries(A);
526  PISM_CHK(ierr, "MatZeroEntries");
527 
528  array::AccessScope list{ &m_nuH, &tauc, &vel, &m_mask, &bed, &surface };
529 
530  if (inputs.bc_values != nullptr && inputs.bc_mask != nullptr) {
531  list.add(*inputs.bc_mask);
532  }
533 
534  const bool sub_gl = m_config->get_flag("geometry.grounded_cell_fraction");
535  if (sub_gl) {
536  list.add(grounded_fraction);
537  }
538 
539  // handles friction of the ice cell along ice-free bedrock margins when bedrock higher than ice
540  // surface (in simplified setups)
541  bool lateral_drag_enabled = m_config->get_flag("stress_balance.ssa.fd.lateral_drag.enabled");
542  if (lateral_drag_enabled) {
543  list.add({ &thickness, &bed, &surface });
544  }
545  double lateral_drag_viscosity =
546  m_config->get_number("stress_balance.ssa.fd.lateral_drag.viscosity");
547  double HminFrozen = 0.0;
548 
549  /* matrix assembly loop */
550  ParallelSection loop(m_grid->com);
551  try {
552  for (auto p = m_grid->points(); p; p.next()) {
553  const int i = p.i(), j = p.j();
554 
555  // Handle the easy case: provided Dirichlet boundary conditions
556  if (inputs.bc_values != nullptr && inputs.bc_mask != nullptr &&
557  inputs.bc_mask->as_int(i, j) == 1) {
558  // set diagonal entry to one (scaled); RHS entry will be known velocity;
561  continue;
562  }
563 
564  /* Provide shorthand for the following staggered coefficients nu H:
565  * c_n
566  * c_w c_e
567  * c_s
568  */
569  // const
570  double c_w = m_nuH(i - 1, j, 0);
571  double c_e = m_nuH(i, j, 0);
572  double c_s = m_nuH(i, j - 1, 1);
573  double c_n = m_nuH(i, j, 1);
574 
575  if (lateral_drag_enabled) {
576  // if option is set, the viscosity at ice-bedrock boundary layer will
577  // be prescribed and is a temperature-independent free (user determined) parameter
578 
579  // direct neighbors
580  auto M = m_mask.star_int(i, j);
581  auto H = thickness.star(i, j);
582  auto b = bed.star(i, j);
583  double h = surface(i, j);
584 
585  if (H.c > HminFrozen) {
586  if (b.w > h and ice_free_land(M.w)) {
587  c_w = lateral_drag_viscosity * 0.5 * (H.c + H.w);
588  }
589  if (b.e > h and ice_free_land(M.e)) {
590  c_e = lateral_drag_viscosity * 0.5 * (H.c + H.e);
591  }
592  if (b.n > h and ice_free_land(M.n)) {
593  c_n = lateral_drag_viscosity * 0.5 * (H.c + H.n);
594  }
595  if (b.s > h and ice_free_land(M.s)) {
596  c_s = lateral_drag_viscosity * 0.5 * (H.c + H.s);
597  }
598  }
599  }
600 
601  // We use DAGetMatrix to obtain the SSA matrix, which means that all 18
602  // non-zeros get allocated, even though we use only 13 (or 14). The
603  // remaining 5 (or 4) coefficients are zeros, but we set them anyway,
604  // because this makes the code easier to understand.
605  const int n_nonzeros = 18;
606  MatStencil row, col[n_nonzeros];
607 
608  // |-----+-----+---+-----+-----|
609  // | NW | NNW | N | NNE | NE |
610  // | WNW | | | | | ENE |
611  // | W |-----|-o-|-----| E |
612  // | WSW | | | | | ESE |
613  // | SW | SSW | S | SSE | SE |
614  // |-----+-----+---+-----+-----|
615  //
616  // We use compass rose notation for weights corresponding to interfaces between
617  // cells around the current one (i, j). Here N corresponds to the interface between
618  // the cell (i, j) and the one to the north of it.
619  int N = 1, E = 1, S = 1, W = 1;
620 
621  // Similarly, we use compass rose notation for weights used to switch between
622  // centered and one-sided finite differences. Here NNE is the interface between
623  // cells N and NE, ENE - between E and NE, etc.
624  int NNW = 1, NNE = 1, SSW = 1, SSE = 1;
625  int WNW = 1, ENE = 1, WSW = 1, ESE = 1;
626 
627  int M_ij = m_mask.as_int(i, j);
628 
629  if (use_cfbc) {
630  auto M = m_mask.box_int(i, j);
631 
632  // Note: this sets velocities at both ice-free ocean and ice-free
633  // bedrock to zero. This means that we need to set boundary conditions
634  // at both ice/ice-free-ocean and ice/ice-free-bedrock interfaces below
635  // to be consistent.
636  if (ice_free(M.c)) {
639  continue;
640  }
641 
642  if (is_marginal(i, j, bedrock_boundary)) {
643  // If at least one of the following four conditions is "true", we're
644  // at a CFBC location.
645  // NOLINTBEGIN(readability-braces-around-statements)
646  if (bedrock_boundary) {
647 
648  if (ice_free_ocean(M.e))
649  E = 0;
650  if (ice_free_ocean(M.w))
651  W = 0;
652  if (ice_free_ocean(M.n))
653  N = 0;
654  if (ice_free_ocean(M.s))
655  S = 0;
656 
657  // decide whether to use centered or one-sided differences
658  if (ice_free_ocean(M.n) || ice_free_ocean(M.ne))
659  NNE = 0;
660  if (ice_free_ocean(M.e) || ice_free_ocean(M.ne))
661  ENE = 0;
662  if (ice_free_ocean(M.e) || ice_free_ocean(M.se))
663  ESE = 0;
664  if (ice_free_ocean(M.s) || ice_free_ocean(M.se))
665  SSE = 0;
666  if (ice_free_ocean(M.s) || ice_free_ocean(M.sw))
667  SSW = 0;
668  if (ice_free_ocean(M.w) || ice_free_ocean(M.sw))
669  WSW = 0;
670  if (ice_free_ocean(M.w) || ice_free_ocean(M.nw))
671  WNW = 0;
672  if (ice_free_ocean(M.n) || ice_free_ocean(M.nw))
673  NNW = 0;
674 
675  } else { // if (not bedrock_boundary)
676 
677  if (ice_free(M.e))
678  E = 0;
679  if (ice_free(M.w))
680  W = 0;
681  if (ice_free(M.n))
682  N = 0;
683  if (ice_free(M.s))
684  S = 0;
685 
686  // decide whether to use centered or one-sided differences
687  if (ice_free(M.n) || ice_free(M.ne))
688  NNE = 0;
689  if (ice_free(M.e) || ice_free(M.ne))
690  ENE = 0;
691  if (ice_free(M.e) || ice_free(M.se))
692  ESE = 0;
693  if (ice_free(M.s) || ice_free(M.se))
694  SSE = 0;
695  if (ice_free(M.s) || ice_free(M.sw))
696  SSW = 0;
697  if (ice_free(M.w) || ice_free(M.sw))
698  WSW = 0;
699  if (ice_free(M.w) || ice_free(M.nw))
700  WNW = 0;
701  if (ice_free(M.n) || ice_free(M.nw))
702  NNW = 0;
703 
704  } // end of the else clause following "if (bedrock_boundary)"
705  // NOLINTEND(readability-braces-around-statements)
706  } // end of "if (is_marginal(i, j, bedrock_boundary))"
707  } // end of "if (use_cfbc)"
708 
709  /* begin Maxima-generated code */
710  const double dx2 = dx*dx, dy2 = dy*dy, d4 = 4*dx*dy, d2 = 2*dx*dy;
711 
712  /* Coefficients of the discretization of the first equation; u first, then v. */
713  double eq1[] = {
714  0, -c_n*N/dy2, 0,
715  -4*c_w*W/dx2, (c_n*N+c_s*S)/dy2+(4*c_e*E+4*c_w*W)/dx2, -4*c_e*E/dx2,
716  0, -c_s*S/dy2, 0,
717  c_w*W*WNW/d2+c_n*NNW*N/d4, (c_n*NNE*N-c_n*NNW*N)/d4+(c_w*W*N-c_e*E*N)/d2, -c_e*E*ENE/d2-c_n*NNE*N/d4,
718  (c_w*W*WSW-c_w*W*WNW)/d2+(c_n*W*N-c_s*W*S)/d4, (c_n*E*N-c_n*W*N-c_s*E*S+c_s*W*S)/d4+(c_e*E*N-c_w*W*N-c_e*E*S+c_w*W*S)/d2, (c_e*E*ENE-c_e*E*ESE)/d2+(c_s*E*S-c_n*E*N)/d4,
719  -c_w*W*WSW/d2-c_s*SSW*S/d4, (c_s*SSW*S-c_s*SSE*S)/d4+(c_e*E*S-c_w*W*S)/d2, c_e*E*ESE/d2+c_s*SSE*S/d4,
720  };
721 
722  /* Coefficients of the discretization of the second equation; u first, then v. */
723  double eq2[] = {
724  c_w*W*WNW/d4+c_n*NNW*N/d2, (c_n*NNE*N-c_n*NNW*N)/d2+(c_w*W*N-c_e*E*N)/d4, -c_e*E*ENE/d4-c_n*NNE*N/d2,
725  (c_w*W*WSW-c_w*W*WNW)/d4+(c_n*W*N-c_s*W*S)/d2, (c_n*E*N-c_n*W*N-c_s*E*S+c_s*W*S)/d2+(c_e*E*N-c_w*W*N-c_e*E*S+c_w*W*S)/d4, (c_e*E*ENE-c_e*E*ESE)/d4+(c_s*E*S-c_n*E*N)/d2,
726  -c_w*W*WSW/d4-c_s*SSW*S/d2, (c_s*SSW*S-c_s*SSE*S)/d2+(c_e*E*S-c_w*W*S)/d4, c_e*E*ESE/d4+c_s*SSE*S/d2,
727  0, -4*c_n*N/dy2, 0,
728  -c_w*W/dx2, (4*c_n*N+4*c_s*S)/dy2+(c_e*E+c_w*W)/dx2, -c_e*E/dx2,
729  0, -4*c_s*S/dy2, 0,
730  };
731 
732  /* i indices */
733  const int I[] = {
734  i-1, i, i+1,
735  i-1, i, i+1,
736  i-1, i, i+1,
737  i-1, i, i+1,
738  i-1, i, i+1,
739  i-1, i, i+1,
740  };
741 
742  /* j indices */
743  const int J[] = {
744  j+1, j+1, j+1,
745  j, j, j,
746  j-1, j-1, j-1,
747  j+1, j+1, j+1,
748  j, j, j,
749  j-1, j-1, j-1,
750  };
751 
752  /* component indices */
753  const int C[] = {
754  0, 0, 0,
755  0, 0, 0,
756  0, 0, 0,
757  1, 1, 1,
758  1, 1, 1,
759  1, 1, 1,
760  };
761  /* end Maxima-generated code */
762 
763  /* Dragging ice experiences friction at the bed determined by the
764  * IceBasalResistancePlasticLaw::drag() methods. These may be a plastic,
765  * pseudo-plastic, or linear friction law. Dragging is done implicitly
766  * (i.e. on left side of SSA eqns). */
767  double beta_u = 0.0, beta_v = 0.0;
768  if (include_basal_shear) {
769  double beta = 0.0;
770  switch (M_ij) {
771  case MASK_ICE_FREE_BEDROCK: {
772  // apply drag even in this case, to help with margins; note ice free areas may
773  // already have a strength extension
774  beta = beta_ice_free_bedrock;
775  break;
776  }
777  case MASK_FLOATING: {
778  double scaling = sub_gl ? grounded_fraction(i, j) : 0.0;
779  beta = scaling * m_basal_sliding_law->drag(tauc(i, j), vel(i, j).u, vel(i, j).v);
780  break;
781  }
782  case MASK_GROUNDED: {
783  double scaling = sub_gl ? grounded_fraction(i, j) : 1.0;
784  beta = scaling * m_basal_sliding_law->drag(tauc(i, j), vel(i, j).u, vel(i, j).v);
785  break;
786  }
787  case MASK_ICE_FREE_OCEAN:
788  default:
789  beta = 0.0;
790  }
791 
792  beta_u = beta;
793  beta_v = beta;
794  }
795 
796  {
797  // Set very high basal drag *in the direction along the boundary* at locations
798  // bordering "fjord walls".
799 
800  auto M = m_mask.star_int(i, j);
801  auto b = bed.star(i, j);
802  double h = surface(i, j);
803 
804  if ((ice_free(M.n) and b.n > h) or (ice_free(M.s) and b.s > h)) {
805  beta_u += beta_lateral_margin;
806  }
807 
808  if ((ice_free(M.e) and b.e > h) or (ice_free(M.w) and b.w > h)) {
809  beta_v += beta_lateral_margin;
810  }
811  }
812 
813  // add beta to diagonal entries
814  eq1[diag_u] += beta_u;
815  eq2[diag_v] += beta_v;
816 
817  if (flow_line_mode) {
818  // set values corresponding to a trivial equation v = 0
819  for (int k = 0; k < n_nonzeros; ++k) {
820  eq2[k] = 0.0;
821  }
822  eq2[diag_v] = m_scaling;
823  }
824 
825  // check diagonal entries:
826  const double eps = 1e-16;
827  if (fabs(eq1[diag_u]) < eps) {
828  if (replace_zero_diagonal_entries) {
829  eq1[diag_u] = beta_ice_free_bedrock;
830  } else {
832  "first (X) equation in the SSAFD system:"
833  " zero diagonal entry at a regular (not Dirichlet B.C.)"
834  " location: i = %d, j = %d\n",
835  i, j);
836  }
837  }
838  if (fabs(eq2[diag_v]) < eps) {
839  if (replace_zero_diagonal_entries) {
840  eq2[diag_v] = beta_ice_free_bedrock;
841  } else {
843  "second (Y) equation in the SSAFD system:"
844  " zero diagonal entry at a regular (not Dirichlet B.C.)"
845  " location: i = %d, j = %d\n",
846  i, j);
847  }
848  }
849 
850  row.i = i;
851  row.j = j;
852  for (int m = 0; m < n_nonzeros; m++) {
853  col[m].i = I[m];
854  col[m].j = J[m];
855  col[m].c = C[m];
856  }
857 
858  // set coefficients of the first equation:
859  row.c = 0;
860  ierr = MatSetValuesStencil(A, 1, &row, n_nonzeros, col, eq1, INSERT_VALUES);
861  PISM_CHK(ierr, "MatSetValuesStencil");
862 
863  // set coefficients of the second equation:
864  row.c = 1;
865  ierr = MatSetValuesStencil(A, 1, &row, n_nonzeros, col, eq2, INSERT_VALUES);
866  PISM_CHK(ierr, "MatSetValuesStencil");
867  } // i,j-loop
868  } catch (...) {
869  loop.failed();
870  }
871  loop.check();
872 
873  ierr = MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY);
874  PISM_CHK(ierr, "MatAssemblyBegin");
875 
876  ierr = MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY);
877  PISM_CHK(ierr, "MatAssemblyEnd");
878 #if (Pism_DEBUG == 1)
879  ierr = MatSetOption(A, MAT_NEW_NONZERO_LOCATION_ERR, PETSC_TRUE);
880  PISM_CHK(ierr, "MatSetOption");
881 #endif
882 }
883 
884 //! \brief Compute the vertically-averaged horizontal velocity from the shallow
885 //! shelf approximation.
886 /*!
887 This is the main procedure in the SSAFD. It manages the nonlinear solve process
888 and the Picard iteration.
889 
890 The outer loop (over index `k`) is the nonlinear iteration. In this loop the effective
891 viscosity is computed by compute_nuH_staggered() and then the linear system is
892 set up and solved.
893 
894 Specifically, we call the PETSc procedure KSPSolve() to solve the linear system.
895 Solving the linear system is also a loop, an iteration, but it occurs
896 inside KSPSolve(). The user has full control of the KSP solve through the PETSc
897 interface. The default choicess for KSP type `-ksp_type` and preconditioner type
898 `-pc_type` are GMRES(30) for the former and block Jacobi with ILU on the
899 blocks for the latter. The defaults usually work. These choices are important
900 but poorly understood. The eigenvalues of the linearized
901 SSA vary with ice sheet geometry and temperature in ways that are not well-studied.
902 Nonetheless these eigenvalues determine the convergence of
903 this (inner) linear iteration. A well-chosen preconditioner can put the eigenvalues
904 in the right place so that the KSP can converge quickly.
905 
906 The preconditioner will behave differently on different numbers of
907 processors. If the user wants the results of SSA calculations to be
908 independent of the number of processors, then `-pc_type none` could
909 be used, but performance will be poor.
910 
911 If you want to test different KSP methods, it may be helpful to see how many
912 iterations were necessary. Use `-ksp_monitor`.
913 Initial testing implies that CGS takes roughly half the iterations of
914 GMRES(30), but is not significantly faster because the iterations are each
915 roughly twice as slow. The outputs of PETSc options `-ksp_monitor_singular_value`,
916 `-ksp_compute_eigenvalues` and `-ksp_plot_eigenvalues -draw_pause N`
917 (the last holds plots for N seconds) may be useful to diagnose.
918 
919 The outer loop terminates when the effective viscosity times thickness
920 is no longer changing much, according to the tolerance set by the
921 option `-ssafd_picard_rtol`. The outer loop also terminates when a maximum
922 number of iterations is exceeded. We save the velocity from the last
923 time step in order to have a better estimate of the effective
924 viscosity than the u=v=0 result.
925 
926 In truth there is an "outer outer" loop (over index `l`). This attempts to
927 over-regularize the effective viscosity if the nonlinear iteration (the "outer"
928 loop over `k`) is not converging with the default regularization. The same
929 over-regularization is attempted if the KSP object reports that it has not
930 converged.
931 
932 (An alternative for recovery in the KSP diverged case, suggested by Jed, is to
933 revert to a direct linear solve, either for the whole domain (not scalable) or
934 on the subdomains. This recovery alternative requires a more nontrivial choice
935 but it may be worthwhile. Note the user can already do `-pc_type asm
936 -sub_pc_type lu` at the command line, forcing subdomain direct solves.)
937 
938 FIXME: update this doxygen comment
939 */
940 void SSAFD::solve(const Inputs &inputs) {
941 
942  // Store away old SSA velocity (it might be needed in case a solver
943  // fails).
945 
946  // These computations do not depend on the solution, so they need to
947  // be done once.
948  {
949  assemble_rhs(inputs);
950  compute_hardav_staggered(inputs);
951  }
952 
953  for (unsigned int k = 0; k < 3; ++k) {
954  try {
955  if (k == 0) {
956  // default strategy
957  picard_iteration(inputs, m_config->get_number("stress_balance.ssa.epsilon"), 1.0);
958 
959  break;
960  }
961  if (k == 1) {
962  // try underrelaxing the iteration
963  const double underrelax =
964  m_config->get_number("stress_balance.ssa.fd.nuH_iter_failure_underrelaxation");
965  m_log->message(
966  1, " re-trying with effective viscosity under-relaxation (parameter = %.2f) ...\n",
967  underrelax);
968  picard_iteration(inputs, m_config->get_number("stress_balance.ssa.epsilon"), underrelax);
969 
970  break;
971  }
972  if (k == 2) {
973  // try over-regularization
975 
976  break;
977  }
978 
979  // if we reached this, then all strategies above failed
980  write_system_petsc("all_strategies_failed");
981  throw RuntimeError(PISM_ERROR_LOCATION, "all SSAFD strategies failed");
982  } catch (PicardFailure &f) {
983  // proceed to the next strategy
984  }
985  }
986 
987  if (m_config->get_flag("stress_balance.ssa.fd.extrapolate_at_margins")) {
989  }
990 
991  // Post-process velocities if the user asked for it:
992  if (m_config->get_flag("stress_balance.ssa.fd.brutal_sliding")) {
993  const double brutal_sliding_scaleFactor =
994  m_config->get_number("stress_balance.ssa.fd.brutal_sliding_scale");
995  m_velocity.scale(brutal_sliding_scaleFactor);
996 
998  }
999 }
1000 
1001 void SSAFD::picard_iteration(const Inputs &inputs, double nuH_regularization,
1002  double nuH_iter_failure_underrelax) {
1003 
1005  // Give BJACOBI another shot if we haven't tried it enough yet
1006 
1007  try {
1008  pc_setup_bjacobi();
1009  picard_manager(inputs, nuH_regularization, nuH_iter_failure_underrelax);
1010 
1011  } catch (KSPFailure &f) {
1012 
1014 
1015  m_log->message(1, " re-trying using the Additive Schwarz preconditioner...\n");
1016 
1017  pc_setup_asm();
1018 
1020 
1021  picard_manager(inputs, nuH_regularization, nuH_iter_failure_underrelax);
1022  }
1023 
1024  } else {
1025  // otherwise use ASM
1026  pc_setup_asm();
1027 
1028  picard_manager(inputs, nuH_regularization, nuH_iter_failure_underrelax);
1029  }
1030 }
1031 
1032 //! \brief Manages the Picard iteration loop.
1033 void SSAFD::picard_manager(const Inputs &inputs, double nuH_regularization,
1034  double nuH_iter_failure_underrelax) {
1035  PetscErrorCode ierr;
1036  double nuH_norm, nuH_norm_change;
1037  // ksp_iterations should be a PetscInt because it is used in the
1038  // KSPGetIterationNumber() call below
1039  PetscInt ksp_iterations, ksp_iterations_total = 0, outer_iterations;
1040  KSPConvergedReason reason;
1041 
1042  int max_iterations =
1043  static_cast<int>(m_config->get_number("stress_balance.ssa.fd.max_iterations"));
1044  double ssa_relative_tolerance =
1045  m_config->get_number("stress_balance.ssa.fd.relative_convergence");
1046  bool verbose = m_log->get_threshold() >= 2, very_verbose = m_log->get_threshold() > 2;
1047 
1048  // set the initial guess:
1050 
1051  m_stdout_ssa.clear();
1052 
1053  bool use_cfbc = m_config->get_flag("stress_balance.calving_front_stress_bc");
1054 
1055  if (use_cfbc) {
1057  nuH_regularization, m_nuH);
1058  } else {
1060  nuH_regularization, m_nuH);
1061  }
1063 
1064  // outer loop
1065  for (int k = 0; k < max_iterations; ++k) {
1066 
1067  if (very_verbose) {
1068  m_stdout_ssa += pism::printf(" %2d:", k);
1069  }
1070 
1071  // in preparation of measuring change of effective viscosity:
1073 
1074  // assemble (or re-assemble) matrix, which depends on updated viscosity
1075  assemble_matrix(inputs, true, m_A);
1076 
1077  if (very_verbose) {
1078 
1079  m_stdout_ssa += "A:";
1080  }
1081 
1082  // Call PETSc to solve linear system by iterative method; "inner iteration":
1083  ierr = KSPSetOperators(m_KSP, m_A, m_A);
1084  PISM_CHK(ierr, "KSPSetOperator");
1085 
1086  ierr = KSPSolve(m_KSP, m_b.vec(), m_velocity_global.vec());
1087  PISM_CHK(ierr, "KSPSolve");
1088 
1089  // Check if diverged; report to standard out about iteration
1090  ierr = KSPGetConvergedReason(m_KSP, &reason);
1091  PISM_CHK(ierr, "KSPGetConvergedReason");
1092 
1093  if (reason < 0) {
1094  // KSP diverged
1095  m_log->message(1, "PISM WARNING: KSPSolve() reports 'diverged'; reason = %d = '%s'\n",
1096  reason, KSPConvergedReasons[reason]);
1097 
1098  write_system_petsc("kspdivergederror");
1099 
1100  // Tell the caller that we failed. (The caller might try again,
1101  // though.)
1102  throw KSPFailure(KSPConvergedReasons[reason]);
1103  }
1104 
1105  // report on KSP success; the "inner" iteration is done
1106  ierr = KSPGetIterationNumber(m_KSP, &ksp_iterations);
1107  PISM_CHK(ierr, "KSPGetIterationNumber");
1108 
1109  ksp_iterations_total += ksp_iterations;
1110 
1111  if (very_verbose) {
1112  m_stdout_ssa += pism::printf("S:%d,%d: ", (int)ksp_iterations, reason);
1113  }
1114 
1115  // limit ice speed
1116  {
1117  auto max_speed = m_config->get_number("stress_balance.ssa.fd.max_speed", "m second-1");
1118  int high_speed_counter = 0;
1119 
1121 
1122  for (auto p = m_grid->points(); p; p.next()) {
1123  const int i = p.i(), j = p.j();
1124 
1125  auto speed = m_velocity_global(i, j).magnitude();
1126 
1127  if (speed > max_speed) {
1128  m_velocity_global(i, j) *= max_speed / speed;
1129  high_speed_counter += 1;
1130  }
1131  }
1132 
1133  high_speed_counter = GlobalSum(m_grid->com, high_speed_counter);
1134 
1135  if (high_speed_counter > 0) {
1136  m_log->message(2, " SSA speed was capped at %d locations\n", high_speed_counter);
1137  }
1138  }
1139 
1140  // Communicate so that we have stencil width for evaluation of effective
1141  // viscosity on next "outer" iteration (and geometry etc. if done):
1142  // Note that copy_from() updates ghosts of m_velocity.
1144 
1145  // update viscosity and check for viscosity convergence
1146  if (use_cfbc) {
1148  nuH_regularization, m_nuH);
1149  } else {
1151  nuH_regularization, m_nuH);
1152  }
1153 
1154  if (nuH_iter_failure_underrelax != 1.0) {
1155  m_nuH.scale(nuH_iter_failure_underrelax);
1156  m_nuH.add(1.0 - nuH_iter_failure_underrelax, m_nuH_old);
1157  }
1158  compute_nuH_norm(nuH_norm, nuH_norm_change);
1159 
1161 
1162  if (very_verbose) {
1163  m_stdout_ssa += pism::printf("|nu|_2, |Delta nu|_2/|nu|_2 = %10.3e %10.3e\n", nuH_norm,
1164  nuH_norm_change / nuH_norm);
1165 
1166  // assume that high verbosity shows interest in immediate
1167  // feedback about SSA iterations
1168  m_log->message(2, m_stdout_ssa);
1169 
1170  m_stdout_ssa.clear();
1171  }
1172 
1173  outer_iterations = k + 1;
1174 
1175  if (nuH_norm == 0 || nuH_norm_change / nuH_norm < ssa_relative_tolerance) {
1176  goto done;
1177  }
1178 
1179  } // outer loop (k)
1180 
1181  // If we're here, it means that we exceeded max_iterations and still
1182  // failed.
1183 
1184  throw PicardFailure(pism::printf("effective viscosity not converged after %d iterations\n"
1185  "with nuH_regularization=%8.2e.",
1186  max_iterations, nuH_regularization));
1187 
1188 done:
1189 
1190  if (very_verbose) {
1191  auto tempstr =
1192  pism::printf("... =%5d outer iterations, ~%3.1f KSP iterations each\n",
1193  (int)outer_iterations, ((double)ksp_iterations_total) / outer_iterations);
1194  m_stdout_ssa += tempstr;
1195  } else if (verbose) {
1196  // at default verbosity, just record last nuH_norm_change and iterations
1197  auto tempstr =
1198  pism::printf("%5d outer iterations, ~%3.1f KSP iterations each\n", (int)outer_iterations,
1199  ((double)ksp_iterations_total) / outer_iterations);
1200 
1201  m_stdout_ssa += tempstr;
1202  }
1203 
1204  if (verbose) {
1205  m_stdout_ssa = " SSA: " + m_stdout_ssa;
1206  }
1207 }
1208 
1209 //! Old SSAFD recovery strategy: increase the SSA regularization parameter.
1211  // this has no units; epsilon goes up by this ratio when previous value failed
1212  const double DEFAULT_EPSILON_MULTIPLIER_SSA = 4.0;
1213  double nuH_regularization = m_config->get_number("stress_balance.ssa.epsilon");
1214  unsigned int k = 0, max_tries = 5;
1215 
1216  if (nuH_regularization <= 0.0) {
1217  throw PicardFailure("will not attempt over-regularization of nuH\n"
1218  "because nuH_regularization == 0.0.");
1219  }
1220 
1221  while (k < max_tries) {
1223  m_log->message(1, " re-trying with nuH_regularization multiplied by %8.2f...\n",
1224  DEFAULT_EPSILON_MULTIPLIER_SSA);
1225 
1226  nuH_regularization *= DEFAULT_EPSILON_MULTIPLIER_SSA;
1227 
1228  try {
1229  // 1.0 is the under-relaxation parameter
1230  picard_iteration(inputs, nuH_regularization, 1.0);
1231  // if this call succeeded, stop over-regularizing
1232  break;
1233  } catch (PicardFailure &f) {
1234  k += 1;
1235 
1236  if (k == max_tries) {
1237  throw PicardFailure("exceeded the max. number of nuH over-regularization attempts");
1238  }
1239  }
1240  }
1241 }
1242 
1243 //! \brief Compute the norm of nu H and the change in nu H.
1244 /*!
1245 Verification and PST experiments
1246 suggest that an \f$L^1\f$ criterion for convergence is best. For verification
1247 there seems to be little difference, presumably because the solutions are smooth
1248 and the norms are roughly equivalent on a subspace of smooth functions. For PST,
1249 the \f$L^1\f$ criterion gives faster runs with essentially the same results.
1250 Presumably that is because rapid (temporal and spatial) variation in
1251 \f$\nu H\f$ occurs at margins, occupying very few horizontal grid cells.
1252 For the significant (e.g.~in terms of flux) parts of the flow, it is o.k. to ignore
1253 a bit of bad behavior at these few places, and \f$L^1\f$ ignores it more than
1254 \f$L^2\f$ (much less \f$L^\infty\f$, which might not work at all).
1255  */
1256 void SSAFD::compute_nuH_norm(double &norm, double &norm_change) {
1257 
1258  const double area = m_grid->cell_area();
1259  const NormType MY_NORM = NORM_1;
1260 
1261  // Test for change in nu
1262  m_nuH_old.add(-1, m_nuH);
1263 
1264  std::vector<double> nuNorm = m_nuH.norm(MY_NORM), nuChange = m_nuH_old.norm(MY_NORM);
1265 
1266  nuChange[0] *= area;
1267  nuChange[1] *= area;
1268  nuNorm[0] *= area;
1269  nuNorm[1] *= area;
1270 
1271  norm_change = sqrt(PetscSqr(nuChange[0]) + PetscSqr(nuChange[1]));
1272  norm = sqrt(PetscSqr(nuNorm[0]) + PetscSqr(nuNorm[1]));
1273 }
1274 
1275 //! \brief Computes vertically-averaged ice hardness on the staggered grid.
1277  const array::Scalar &thickness = inputs.geometry->ice_thickness;
1278 
1279  const array::Array3D &enthalpy = *inputs.enthalpy;
1280 
1281  const double *E_ij = NULL, *E_offset = NULL;
1282 
1283  auto Mz = m_grid->Mz();
1284  std::vector<double> E(Mz);
1285 
1286  array::AccessScope list{ &thickness, &enthalpy, &m_hardness, &m_mask };
1287 
1288  ParallelSection loop(m_grid->com);
1289  try {
1290  for (auto p = m_grid->points(); p; p.next()) {
1291  const int i = p.i(), j = p.j();
1292 
1293  E_ij = enthalpy.get_column(i, j);
1294  for (int o = 0; o < 2; o++) {
1295  const int oi = 1 - o, oj = o;
1296  double H;
1297 
1298  if (m_mask.icy(i, j) && m_mask.icy(i + oi, j + oj)) {
1299  H = 0.5 * (thickness(i, j) + thickness(i + oi, j + oj));
1300  } else if (m_mask.icy(i, j)) {
1301  H = thickness(i, j);
1302  } else {
1303  H = thickness(i + oi, j + oj);
1304  }
1305 
1306  if (H == 0) {
1307  m_hardness(i, j, o) = -1e6; // an obviously impossible value
1308  continue;
1309  }
1310 
1311  E_offset = enthalpy.get_column(i + oi, j + oj);
1312  // build a column of enthalpy values a the current location:
1313  for (unsigned int k = 0; k < Mz; ++k) {
1314  E[k] = 0.5 * (E_ij[k] + E_offset[k]);
1315  }
1316 
1317  m_hardness(i, j, o) = rheology::averaged_hardness(*m_flow_law, H, m_grid->kBelowHeight(H),
1318  m_grid->z().data(), E.data());
1319  } // o
1320  } // loop over points
1321  } catch (...) {
1322  loop.failed();
1323  }
1324  loop.check();
1325 
1327 }
1328 
1329 
1330 /*! @brief Correct vertically-averaged hardness using a
1331  parameterization of the fracture-induced softening.
1332 
1333  See T. Albrecht, A. Levermann; Fracture-induced softening for
1334  large-scale ice dynamics; (2013), The Cryosphere Discussions 7;
1335  4501-4544; DOI:10.5194/tcd-7-4501-2013
1336 
1337  Note that this paper proposes an adjustment of the enhancement factor:
1338 
1339  \f[E_{\text{effective}} = E \cdot (1 - (1-\epsilon) \phi)^{-n}.\f]
1340 
1341  Let \f$E_{\text{effective}} = E\cdot C\f$, where \f$C\f$ is the
1342  factor defined by the formula above.
1343 
1344  Recall that the effective viscosity is defined by
1345 
1346  \f[\nu(D) = \frac12 B D^{(1-n)/(2n)}\f]
1347 
1348  and the viscosity form of the flow law is
1349 
1350  \f[\sigma'_{ij} = E_{\text{effective}}^{-\frac1n}2\nu(D) D_{ij}.\f]
1351 
1352  Then
1353 
1354  \f[\sigma'_{ij} = E_{\text{effective}}^{-\frac1n}BD^{(1-n)/(2n)}D_{ij}.\f]
1355 
1356  Using the fact that \f$E_{\text{effective}} = E\cdot C\f$, this can be rewritten as
1357 
1358  \f[\sigma'_{ij} = E^{-\frac1n} \left(C^{-\frac1n}B\right) D^{(1-n)/(2n)}D_{ij}.\f]
1359 
1360  So scaling the enhancement factor by \f$C\f$ is equivalent to scaling
1361  ice hardness \f$B\f$ by \f$C^{-\frac1n}\f$.
1362 */
1363 void SSAFD::fracture_induced_softening(const array::Scalar *fracture_density) {
1364  if (fracture_density == nullptr) {
1365  return;
1366  }
1367 
1368  const double epsilon = m_config->get_number("fracture_density.softening_lower_limit"),
1369  n_glen = m_flow_law->exponent();
1370 
1371  array::AccessScope list{ &m_hardness, fracture_density };
1372 
1373  for (auto p = m_grid->points(); p; p.next()) {
1374  const int i = p.i(), j = p.j();
1375 
1376  for (int o = 0; o < 2; o++) {
1377  const int oi = 1 - o, oj = o;
1378 
1379  const double
1380  // fracture density on the staggered grid:
1381  phi = 0.5 * ((*fracture_density)(i, j) + (*fracture_density)(i + oi, j + oj)),
1382  // the line below implements equation (6) in the paper
1383  softening = pow((1.0 - (1.0 - epsilon) * phi), -n_glen);
1384 
1385  m_hardness(i, j, o) *= pow(softening, -1.0 / n_glen);
1386  }
1387  }
1388 }
1389 
1390 //! \brief Compute the product of ice thickness and effective viscosity (on the
1391 //! staggered grid).
1392 /*!
1393 In PISM the product \f$\nu H\f$ can be
1394  - constant, or
1395  - can be computed with a constant ice hardness \f$\bar B\f$ (temperature-independent)
1396  but with dependence of the viscosity on the strain rates, or
1397  - it can depend on the strain rates \e and have a vertically-averaged ice
1398  hardness depending on temperature or enthalpy.
1399 
1400 The flow law in ice stream and ice shelf regions must, for now, be a
1401 (temperature-dependent) Glen law. This is the only flow law we know how to
1402 invert to ``viscosity form''. More general forms like Goldsby-Kohlstedt are
1403 not yet inverted.
1404 
1405 The viscosity form of a Glen law is
1406  \f[ \nu(T^*,D) = \frac{1}{2} B(T^*) D^{(1/n)-1}\, D_{ij} \f]
1407 where
1408  \f[ D_{ij} = \frac{1}{2} \left(\frac{\partial U_i}{\partial x_j} +
1409  \frac{\partial U_j}{\partial x_i}\right) \f]
1410 is the strain rate tensor and \f$B\f$ is an ice hardness related to
1411 the ice softness \f$A(T^*)\f$ by
1412  \f[ B(T^*)=A(T^*)^{-1/n} \f]
1413 in the case of a temperature dependent Glen-type law. (Here \f$T^*\f$ is the
1414 pressure-adjusted temperature.)
1415 
1416 The effective viscosity is then
1417  \f[ \nu = \frac{\bar B}{2} \left[\left(\frac{\partial u}{\partial x}\right)^2 +
1418  \left(\frac{\partial v}{\partial y}\right)^2 +
1419  \frac{\partial u}{\partial x} \frac{\partial v}{\partial y} +
1420  \frac{1}{4} \left(\frac{\partial u}{\partial y}
1421  + \frac{\partial v}{\partial x}\right)^2
1422  \right]^{(1-n)/(2n)} \f]
1423 where in the temperature-dependent case
1424  \f[ \bar B = \frac{1}{H}\,\int_b^h B(T^*)\,dz\f]
1425 This integral is approximately computed by the trapezoid rule.
1426 
1427 The result of this procedure is \f$\nu H\f$, not just \f$\nu\f$, this it is
1428 a vertical integral, not average, of viscosity.
1429 
1430 The resulting effective viscosity times thickness is regularized by ensuring that
1431 its minimum is at least \f$\epsilon\f$. This regularization constant is an argument.
1432 
1433 In this implementation we set \f$\nu H\f$ to a constant anywhere the ice is
1434 thinner than a certain minimum. See SSAStrengthExtension and compare how this
1435 issue is handled when -cfbc is set.
1436 */
1438  const array::Vector1 &velocity, const array::Staggered &hardness,
1439  double nuH_regularization, array::Staggered &result) {
1440 
1441  const array::Vector &uv = velocity; // shortcut
1442 
1443  array::AccessScope list{ &result, &uv, &hardness, &ice_thickness };
1444 
1445  double n_glen = m_flow_law->exponent(),
1446  nu_enhancement_scaling = 1.0 / pow(m_e_factor, 1.0 / n_glen);
1447 
1448  const double dx = m_grid->dx(), dy = m_grid->dy();
1449 
1450  for (int o = 0; o < 2; ++o) {
1451  const int oi = 1 - o, oj = o;
1452  for (auto p = m_grid->points(); p; p.next()) {
1453  const int i = p.i(), j = p.j();
1454 
1455  const double H = 0.5 * (ice_thickness(i, j) + ice_thickness(i + oi, j + oj));
1456 
1457  if (H < strength_extension->get_min_thickness()) {
1458  result(i, j, o) = strength_extension->get_notional_strength();
1459  continue;
1460  }
1461 
1462  double u_x, u_y, v_x, v_y;
1463  // Check the offset to determine how to differentiate velocity
1464  if (o == 0) {
1465  u_x = (uv(i + 1, j).u - uv(i, j).u) / dx;
1466  u_y =
1467  (uv(i, j + 1).u + uv(i + 1, j + 1).u - uv(i, j - 1).u - uv(i + 1, j - 1).u) / (4 * dy);
1468  v_x = (uv(i + 1, j).v - uv(i, j).v) / dx;
1469  v_y =
1470  (uv(i, j + 1).v + uv(i + 1, j + 1).v - uv(i, j - 1).v - uv(i + 1, j - 1).v) / (4 * dy);
1471  } else {
1472  u_x =
1473  (uv(i + 1, j).u + uv(i + 1, j + 1).u - uv(i - 1, j).u - uv(i - 1, j + 1).u) / (4 * dx);
1474  u_y = (uv(i, j + 1).u - uv(i, j).u) / dy;
1475  v_x =
1476  (uv(i + 1, j).v + uv(i + 1, j + 1).v - uv(i - 1, j).v - uv(i - 1, j + 1).v) / (4 * dx);
1477  v_y = (uv(i, j + 1).v - uv(i, j).v) / dy;
1478  }
1479 
1480  double nu = 0.0;
1481  m_flow_law->effective_viscosity(hardness(i, j, o),
1482  secondInvariant_2D({ u_x, v_x }, { u_y, v_y }), &nu, NULL);
1483 
1484  result(i, j, o) = nu * H;
1485 
1486  // include the SSA enhancement factor; in most cases m_e_factor is 1
1487  result(i, j, o) *= nu_enhancement_scaling;
1488 
1489  // We ensure that nuH is bounded below by a positive constant.
1490  result(i, j, o) += nuH_regularization;
1491 
1492  } // i,j-loop
1493  } // o-loop
1494 
1495 
1496  // Some communication
1497  result.update_ghosts();
1498 }
1499 
1500 /**
1501  * @brief Compute the product of ice viscosity and thickness on the
1502  * staggered grid. Used when CFBC is enabled.
1503  *
1504  * @param[out] result nu*H product
1505  * @param[in] nuH_regularization regularization parameter (added to nu*H to keep it away from zero)
1506  *
1507  * @return 0 on success
1508  */
1510  const array::CellType2 &mask, const array::Vector1 &velocity,
1511  const array::Staggered &hardness, double nuH_regularization,
1512  array::Staggered &result) {
1513 
1514  const auto &thickness = ice_thickness;
1515 
1516  const array::Vector &uv = velocity; // shortcut
1517 
1518  double n_glen = m_flow_law->exponent(),
1519  nu_enhancement_scaling = 1.0 / pow(m_e_factor, 1.0 / n_glen);
1520 
1521  const double dx = m_grid->dx(), dy = m_grid->dy();
1522 
1523  array::AccessScope list{ &mask, &m_work, &uv };
1524 
1525  assert(uv.stencil_width() >= 2);
1526  assert(mask.stencil_width() >= 2);
1527  assert(m_work.stencil_width() >= 1);
1528 
1529  for (auto p = m_grid->points(1); p; p.next()) {
1530  const int i = p.i(), j = p.j();
1531 
1532  // x-derivative, i-offset
1533  {
1534  if (mask.icy(i, j) && mask.icy(i + 1, j)) {
1535  m_work(i, j).u_x = (uv(i + 1, j).u - uv(i, j).u) / dx; // u_x
1536  m_work(i, j).v_x = (uv(i + 1, j).v - uv(i, j).v) / dx; // v_x
1537  m_work(i, j).w_i = 1.0;
1538  } else {
1539  m_work(i, j).u_x = 0.0;
1540  m_work(i, j).v_x = 0.0;
1541  m_work(i, j).w_i = 0.0;
1542  }
1543  }
1544 
1545  // y-derivative, j-offset
1546  {
1547  if (mask.icy(i, j) && mask.icy(i, j + 1)) {
1548  m_work(i, j).u_y = (uv(i, j + 1).u - uv(i, j).u) / dy; // u_y
1549  m_work(i, j).v_y = (uv(i, j + 1).v - uv(i, j).v) / dy; // v_y
1550  m_work(i, j).w_j = 1.0;
1551  } else {
1552  m_work(i, j).u_y = 0.0;
1553  m_work(i, j).v_y = 0.0;
1554  m_work(i, j).w_j = 0.0;
1555  }
1556  }
1557  }
1558 
1559  list.add({ &result, &hardness, &thickness });
1560 
1561  for (auto p = m_grid->points(); p; p.next()) {
1562  const int i = p.i(), j = p.j();
1563 
1564  double u_x, u_y, v_x, v_y, H, nu, W;
1565  // i-offset
1566  {
1567  if (mask.icy(i, j) && mask.icy(i + 1, j)) {
1568  H = 0.5 * (thickness(i, j) + thickness(i + 1, j));
1569  } else if (mask.icy(i, j)) {
1570  H = thickness(i, j);
1571  } else {
1572  H = thickness(i + 1, j);
1573  }
1574 
1575  if (H >= strength_extension->get_min_thickness()) {
1576  u_x = m_work(i, j).u_x;
1577  v_x = m_work(i, j).v_x;
1578 
1579  W = m_work(i, j).w_j + m_work(i, j - 1).w_j + m_work(i + 1, j - 1).w_j +
1580  m_work(i + 1, j).w_j;
1581  if (W > 0) {
1582  u_y = 1.0 / W *
1583  (m_work(i, j).u_y + m_work(i, j - 1).u_y + m_work(i + 1, j - 1).u_y +
1584  m_work(i + 1, j).u_y);
1585  v_y = 1.0 / W *
1586  (m_work(i, j).v_y + m_work(i, j - 1).v_y + m_work(i + 1, j - 1).v_y +
1587  m_work(i + 1, j).v_y);
1588  } else {
1589  u_y = 0.0;
1590  v_y = 0.0;
1591  }
1592 
1593  m_flow_law->effective_viscosity(hardness(i, j, 0),
1594  secondInvariant_2D({ u_x, v_x }, { u_y, v_y }), &nu, NULL);
1595  result(i, j, 0) = nu * H;
1596  } else {
1597  result(i, j, 0) = strength_extension->get_notional_strength();
1598  }
1599  }
1600 
1601  // j-offset
1602  {
1603  if (mask.icy(i, j) && mask.icy(i, j + 1)) {
1604  H = 0.5 * (thickness(i, j) + thickness(i, j + 1));
1605  } else if (mask.icy(i, j)) {
1606  H = thickness(i, j);
1607  } else {
1608  H = thickness(i, j + 1);
1609  }
1610 
1611  if (H >= strength_extension->get_min_thickness()) {
1612  u_y = m_work(i, j).u_y;
1613  v_y = m_work(i, j).v_y;
1614 
1615  W = m_work(i, j).w_i + m_work(i - 1, j).w_i + m_work(i - 1, j + 1).w_i +
1616  m_work(i, j + 1).w_i;
1617  if (W > 0.0) {
1618  u_x = 1.0 / W *
1619  (m_work(i, j).u_x + m_work(i - 1, j).u_x + m_work(i - 1, j + 1).u_x +
1620  m_work(i, j + 1).u_x);
1621  v_x = 1.0 / W *
1622  (m_work(i, j).v_x + m_work(i - 1, j).v_x + m_work(i - 1, j + 1).v_x +
1623  m_work(i, j + 1).v_x);
1624  } else {
1625  u_x = 0.0;
1626  v_x = 0.0;
1627  }
1628 
1629  m_flow_law->effective_viscosity(hardness(i, j, 1),
1630  secondInvariant_2D({ u_x, v_x }, { u_y, v_y }), &nu, NULL);
1631  result(i, j, 1) = nu * H;
1632  } else {
1633  result(i, j, 1) = strength_extension->get_notional_strength();
1634  }
1635  }
1636 
1637  // adjustments:
1638  for (int o = 0; o < 2; ++o) {
1639  // include the SSA enhancement factor; in most cases ssa_enhancement_factor is 1
1640  result(i, j, o) *= nu_enhancement_scaling;
1641 
1642  // We ensure that nuH is bounded below by a positive constant.
1643  result(i, j, o) += nuH_regularization;
1644  }
1645  }
1646 
1647  // Some communication
1648  result.update_ghosts();
1649 }
1650 
1651 //! Update the nuH viewer, which shows log10(nu H).
1653 
1654  if (not m_view_nuh) {
1655  return;
1656  }
1657 
1658  array::Scalar tmp(m_grid, "nuH");
1659  tmp.metadata(0)
1660  .long_name("log10 of (viscosity * thickness)")
1661  .units("Pa s m");
1662 
1663  array::AccessScope list{&m_nuH, &tmp};
1664 
1665  for (auto p = m_grid->points(); p; p.next()) {
1666  const int i = p.i(), j = p.j();
1667 
1668  double avg_nuH = 0.5 * (m_nuH(i,j,0) + m_nuH(i,j,1));
1669  if (avg_nuH > 1.0e14) {
1670  tmp(i,j) = log10(avg_nuH);
1671  } else {
1672  tmp(i,j) = 14.0;
1673  }
1674  }
1675 
1676  if (not m_nuh_viewer) {
1677  m_nuh_viewer.reset(new petsc::Viewer(m_grid->com, "nuH", m_nuh_viewer_size,
1678  m_grid->Lx(), m_grid->Ly()));
1679  }
1680 
1681  tmp.view({m_nuh_viewer});
1682 }
1683 
1684 //! \brief Checks if a cell is near or at the ice front.
1685 /*!
1686  * You need to create array::AccessScope object and add mask to it.
1687  *
1688  * Note that a cell is a CFBC location of one of four direct neighbors is ice-free.
1689  *
1690  * If one of the diagonal neighbors is ice-free we don't use the CFBC, but we
1691  * do need to compute weights used in the SSA discretization (see
1692  * assemble_matrix()) to avoid differencing across interfaces between icy
1693  * and ice-free cells.
1694  *
1695  * This method ensures that checks in assemble_rhs() and assemble_matrix() are
1696  * consistent.
1697  */
1698 bool SSAFD::is_marginal(int i, int j, bool ssa_dirichlet_bc) {
1699 
1700  auto M = m_mask.box_int(i, j);
1701 
1702  using mask::ice_free;
1703  using mask::ice_free_ocean;
1704  using mask::icy;
1705 
1706  if (ssa_dirichlet_bc) {
1707  return icy(M.c) &&
1708  (ice_free(M.e) || ice_free(M.w) || ice_free(M.n) || ice_free(M.s) ||
1709  ice_free(M.ne) || ice_free(M.se) || ice_free(M.nw) || ice_free(M.sw));
1710  }
1711 
1712  return icy(M.c) &&
1713  (ice_free_ocean(M.e) || ice_free_ocean(M.w) ||
1714  ice_free_ocean(M.n) || ice_free_ocean(M.s) ||
1715  ice_free_ocean(M.ne) || ice_free_ocean(M.se) ||
1716  ice_free_ocean(M.nw) || ice_free_ocean(M.sw));
1717 }
1718 
1719 void SSAFD::write_system_petsc(const std::string &namepart) {
1720  PetscErrorCode ierr;
1721 
1722  // write a file with a fixed filename; avoid zillions of files
1723  std::string filename = "SSAFD_" + namepart + ".petsc";
1724  m_log->message(1,
1725  " writing linear system to PETSc binary file %s ...\n", filename.c_str());
1726 
1727  petsc::Viewer viewer; // will be destroyed automatically
1728  ierr = PetscViewerBinaryOpen(m_grid->com, filename.c_str(), FILE_MODE_WRITE,
1729  viewer.rawptr());
1730  PISM_CHK(ierr, "PetscViewerBinaryOpen");
1731 
1732  ierr = MatView(m_A, viewer);
1733  PISM_CHK(ierr, "MatView");
1734 
1735  ierr = VecView(m_b.vec(), viewer);
1736  PISM_CHK(ierr, "VecView");
1737 }
1738 
1740  m_vars = { { m_sys, "nuH[0]" }, { m_sys, "nuH[1]" } };
1741  m_vars[0]
1742  .long_name("ice thickness times effective viscosity, i-offset")
1743  .units("Pa s m")
1744  .output_units("kPa s m");
1745  m_vars[1]
1746  .long_name("ice thickness times effective viscosity, j-offset")
1747  .units("Pa s m")
1748  .output_units("kPa s m");
1749 }
1750 
1751 std::shared_ptr<array::Array> SSAFD_nuH::compute_impl() const {
1752  auto result = allocate<array::Staggered>("nuH");
1753 
1754  result->copy_from(model->integrated_viscosity());
1755 
1756  return result;
1757 }
1758 
1761 
1762  result["nuH"] = Diagnostic::Ptr(new SSAFD_nuH(this));
1763 
1764  return result;
1765 }
1766 
1768  return m_nuH;
1769 }
1770 
1771 
1772 } // end of namespace stressbalance
1773 } // end of namespace pism
std::shared_ptr< const Grid > grid() const
Definition: Component.cc:105
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
const SSAFD * model
Definition: Diagnostic.hh:172
A template derived from Diagnostic, adding a "Model".
Definition: Diagnostic.hh:166
const units::System::Ptr m_sys
the unit system
Definition: Diagnostic.hh:116
std::vector< SpatialVariableMetadata > m_vars
metadata corresponding to NetCDF variables
Definition: Diagnostic.hh:120
std::shared_ptr< Diagnostic > Ptr
Definition: Diagnostic.hh:65
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::CellType2 cell_type
Definition: Geometry.hh:55
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.
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 & set_units_without_validation(const std::string &value)
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
T * rawptr()
Definition: Wrapper.hh:39
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
double * get_column(int i, int j)
Definition: Array3D.cc:120
A virtual class collecting methods common to ice and bedrock 3D fields.
Definition: Array3D.hh:33
petsc::Vec & vec() const
Definition: Array.cc:339
void scale(double alpha)
Result: v <- v * alpha. Calls VecScale.
Definition: Array.cc:253
void view(std::vector< std::shared_ptr< petsc::Viewer > > viewers) const
View a 2D vector field using existing PETSc viewers.
Definition: Array.cc:1205
void set(double c)
Result: v[j] <- c for all j.
Definition: Array.cc:707
void add(double alpha, const Array &x)
Result: v <- v + alpha * x. Calls VecAXPY.
Definition: Array.cc:235
std::vector< double > norm(int n) const
Computes the norm of all the components of an Array.
Definition: Array.cc:746
void update_ghosts()
Updates ghost points.
Definition: Array.cc:693
unsigned int stencil_width() const
Get the stencil width of the current Array. Returns 0 if ghosts are not available.
Definition: Array.cc:331
SpatialVariableMetadata & metadata(unsigned int N=0)
Returns a reference to the SpatialVariableMetadata object containing metadata for the compoment N.
Definition: Array.cc:553
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 icy(int i, int j) const
Definition: CellType.hh:42
int as_int(int i, int j) const
Definition: Scalar.hh:45
void copy_from(const array::Staggered &input)
Definition: Staggered.cc:42
A class for storing and accessing internal staggered-grid 2D fields. Uses dof=2 storage....
Definition: Staggered.hh:35
const array::Scalar * basal_yield_stress
const array::Scalar * bc_mask
const array::Scalar * water_column_pressure
const array::Array3D * enthalpy
const array::Scalar2 * no_model_mask
const array::Scalar * fracture_density
const array::Vector * bc_values
KSPFailure(const char *reason)
Definition: SSAFD.cc:39
PicardFailure(const std::string &message)
Definition: SSAFD.cc:44
virtual std::shared_ptr< array::Array > compute_impl() const
Definition: SSAFD.cc:1751
SSAFD_nuH(const SSAFD *m)
Definition: SSAFD.cc:1739
Reports the nuH (viscosity times thickness) product on the staggered grid.
virtual void compute_nuH_norm(double &norm, double &norm_change)
Compute the norm of nu H and the change in nu H.
Definition: SSAFD.cc:1256
SSAFD(std::shared_ptr< const Grid > g)
Definition: SSAFD.cc:61
const array::Staggered & integrated_viscosity() const
Definition: SSAFD.cc:1767
virtual void init_impl()
Initialize a generic regular-grid SSA solver.
Definition: SSAFD.cc:199
virtual void picard_strategy_regularization(const Inputs &inputs)
Old SSAFD recovery strategy: increase the SSA regularization parameter.
Definition: SSAFD.cc:1210
array::Array2D< Work > m_work
Definition: SSAFD.hh:111
virtual void fracture_induced_softening(const array::Scalar *fracture_density)
Correct vertically-averaged hardness using a parameterization of the fracture-induced softening.
Definition: SSAFD.cc:1363
std::shared_ptr< petsc::Viewer > m_nuh_viewer
Definition: SSAFD.hh:124
array::Staggered m_hardness
Definition: SSAFD.hh:94
array::Vector1 m_velocity_old
Definition: SSAFD.hh:117
virtual void update_nuH_viewers()
Update the nuH viewer, which shows log10(nu H).
Definition: SSAFD.cc:1652
array::Staggered1 m_nuH
Definition: SSAFD.hh:95
virtual void compute_nuH_staggered_cfbc(const array::Scalar1 &ice_thickness, const array::CellType2 &mask, const array::Vector1 &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.
Definition: SSAFD.cc:1509
virtual void compute_nuH_staggered(const array::Scalar1 &ice_thickness, const array::Vector1 &velocity, const array::Staggered &hardness, double nuH_regularization, array::Staggered &result)
Compute the product of ice thickness and effective viscosity (on the staggered grid).
Definition: SSAFD.cc:1437
virtual void solve(const Inputs &inputs)
Compute the vertically-averaged horizontal velocity from the shallow shelf approximation.
Definition: SSAFD.cc:940
virtual void write_system_petsc(const std::string &namepart)
Definition: SSAFD.cc:1719
const double m_scaling
Definition: SSAFD.hh:118
virtual DiagnosticList diagnostics_impl() const
Definition: SSAFD.cc:1759
unsigned int m_default_pc_failure_max_count
Definition: SSAFD.hh:121
virtual void assemble_matrix(const Inputs &inputs, bool include_basal_shear, Mat A)
Assemble the left-hand side matrix for the KSP-based, Picard iteration, and finite difference impleme...
Definition: SSAFD.cc:491
virtual void pc_setup_asm()
Definition: SSAFD.cc:146
array::Vector m_b
Definition: SSAFD.hh:115
virtual bool is_marginal(int i, int j, bool ssa_dirichlet_bc)
Checks if a cell is near or at the ice front.
Definition: SSAFD.cc:1698
virtual void compute_hardav_staggered(const Inputs &inputs)
Computes vertically-averaged ice hardness on the staggered grid.
Definition: SSAFD.cc:1276
virtual void picard_iteration(const Inputs &inputs, double nuH_regularization, double nuH_iter_failure_underrelax)
Definition: SSAFD.cc:1001
virtual void picard_manager(const Inputs &inputs, double nuH_regularization, double nuH_iter_failure_underrelax)
Manages the Picard iteration loop.
Definition: SSAFD.cc:1033
virtual void pc_setup_bjacobi()
Definition: SSAFD.cc:122
unsigned int m_default_pc_failure_count
Definition: SSAFD.hh:120
array::Staggered1 m_nuH_old
Definition: SSAFD.hh:95
virtual void assemble_rhs(const Inputs &inputs)
Computes the right-hand side ("rhs") of the linear problem for the Picard iteration and finite-differ...
Definition: SSAFD.cc:233
PISM's SSA solver: the finite difference implementation.
Definition: SSAFD.hh:35
double get_notional_strength() const
Returns strength = (viscosity times thickness).
Definition: SSA.cc:61
double get_min_thickness() const
Returns minimum thickness to trigger use of extension.
Definition: SSA.cc:66
virtual DiagnosticList diagnostics_impl() const
Definition: SSA.cc:402
virtual 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, array::Vector &result) const
Compute the gravitational driving stress.
Definition: SSA.cc:232
array::Vector m_velocity_global
Definition: SSA.hh:150
void extrapolate_velocity(const array::CellType1 &cell_type, array::Vector1 &velocity) const
Definition: SSA.cc:352
array::Vector m_taud
Definition: SSA.hh:144
SSAStrengthExtension * strength_extension
Definition: SSA.hh:115
std::string m_stdout_ssa
Definition: SSA.hh:146
std::shared_ptr< petsc::DM > m_da
Definition: SSA.hh:149
array::CellType2 m_mask
Definition: SSA.hh:143
virtual void init_impl()
Initialize a generic regular-grid SSA solver.
Definition: SSA.cc:121
PISM's SSA solver.
Definition: SSA.hh:110
const array::Vector1 & velocity() const
Get the thickness-advective 2D velocity.
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
@ WITH_GHOSTS
Definition: Array.hh:62
static Vector3 row(const double A[3][3], size_t k)
Definition: Element.cc:46
bool domain_edge(const Grid &grid, int i, int j)
Definition: Grid.hh:396
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 Bool(const std::string &option, const std::string &description)
Definition: options.cc:240
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
SSA * SSAFDFactory(std::shared_ptr< const Grid > g)
Constructs a new SSAFD.
Definition: SSAFD.cc:49
static void set_diagonal_matrix_entry(Mat A, int i, int j, int component, double value)
Definition: SSAFD.cc:399
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)
static const double g
Definition: exactTestP.cc:36
std::string printf(const char *format,...)
std::map< std::string, Diagnostic::Ptr > DiagnosticList
Definition: Diagnostic.hh:125
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
void GlobalSum(MPI_Comm comm, double *local, double *result, int count)
const int J[]
Definition: ssafd_code.cc:34
double eq1[]
Definition: ssafd_code.cc:4
const int I[]
Definition: ssafd_code.cc:24
const double d2
Definition: ssafd_code.cc:1
const int C[]
Definition: ssafd_code.cc:44
const double d4
Definition: ssafd_code.cc:1
const double dy2
Definition: ssafd_code.cc:1
double eq2[]
Definition: ssafd_code.cc:14
const double dx2
Definition: ssafd_code.cc:1
static double S(unsigned n)
Definition: test_cube.c:58