PISM, A Parallel Ice Sheet Model  stable v2.1.1 committed by Constantine Khrulev on 2024-12-04 13:36:58 -0900
SSAFEM.cc
Go to the documentation of this file.
1 // Copyright (C) 2009--2024 Jed Brown and Ed Bueler and Constantine Khroulev and David Maxwell
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 "pism/util/Grid.hh"
20 #include "pism/stressbalance/ssa/SSAFEM.hh"
21 #include "pism/util/fem/FEM.hh"
22 #include "pism/util/Mask.hh"
23 #include "pism/basalstrength/basal_resistance.hh"
24 #include "pism/rheology/FlowLaw.hh"
25 #include "pism/util/pism_options.hh"
26 #include "pism/util/error_handling.hh"
27 #include "pism/util/Vars.hh"
28 #include "pism/stressbalance/StressBalance.hh"
29 #include "pism/geometry/Geometry.hh"
30 
31 #include "pism/util/node_types.hh"
32 #include "pism/util/pism_utilities.hh" // average_water_column_pressure()
33 #include "pism/util/interpolation.hh"
34 #include "pism/util/petscwrappers/DM.hh"
35 #include "pism/util/petscwrappers/Vec.hh"
36 #include "pism/util/petscwrappers/Viewer.hh"
37 
38 namespace pism {
39 namespace stressbalance {
40 
41 /** The Q1 finite element SSA solver.
42  *
43  *
44  *
45  */
46 SSAFEM::SSAFEM(std::shared_ptr<const Grid> grid)
47  : SSA(grid),
48  m_bc_mask(grid, "bc_mask"),
49  m_bc_values(grid, "_bc"),
50  m_gc(*m_config),
51  m_coefficients(grid, "ssa_coefficients", array::WITH_GHOSTS, 1),
52  m_node_type(m_grid, "node_type"),
53  m_boundary_integral(m_grid, "boundary_integral"),
54  m_element_index(*grid),
55  m_q1_element(*grid, fem::Q1Quadrature4()) {
58 
59  const double ice_density = m_config->get_number("constants.ice.density");
60  m_alpha = 1 - ice_density / m_config->get_number("constants.sea_water.density");
61  m_rho_g = ice_density * m_config->get_number("constants.standard_gravity");
62 
63  m_driving_stress_x = NULL;
64  m_driving_stress_y = NULL;
65 
66  PetscErrorCode ierr;
67 
68  m_dirichletScale = 1.0;
69  m_beta_ice_free_bedrock = m_config->get_number("basal_resistance.beta_ice_free_bedrock");
70 
71  ierr = SNESCreate(m_grid->com, m_snes.rawptr());
72  PISM_CHK(ierr, "SNESCreate");
73 
74  // Set the SNES callbacks to call into our compute_local_function and compute_local_jacobian.
76  m_callback_data.ssa = this;
77 
78  ierr = DMDASNESSetFunctionLocal(*m_da, INSERT_VALUES,
79 #if PETSC_VERSION_LT(3,21,0)
80  (DMDASNESFunction)function_callback,
81 #else
82  (DMDASNESFunctionFn*)function_callback,
83 #endif
85  PISM_CHK(ierr, "DMDASNESSetFunctionLocal");
86 
87  ierr = DMDASNESSetJacobianLocal(*m_da,
88 #if PETSC_VERSION_LT(3,21,0)
89  (DMDASNESJacobian)jacobian_callback,
90 #else
91  (DMDASNESJacobianFn*)jacobian_callback,
92 #endif
94  PISM_CHK(ierr, "DMDASNESSetJacobianLocal");
95 
96  ierr = DMSetMatType(*m_da, "baij");
97  PISM_CHK(ierr, "DMSetMatType");
98 
99  ierr = DMSetApplicationContext(*m_da, &m_callback_data);
100  PISM_CHK(ierr, "DMSetApplicationContext");
101 
102  ierr = SNESSetDM(m_snes, *m_da);
103  PISM_CHK(ierr, "SNESSetDM");
104 
105  // Default of maximum 200 iterations; possibly overridden by command line options
106  int snes_max_it = 200;
107  ierr = SNESSetTolerances(m_snes, PETSC_DEFAULT, PETSC_DEFAULT, PETSC_DEFAULT, snes_max_it,
108  PETSC_DEFAULT);
109  PISM_CHK(ierr, "SNESSetTolerances");
110 
111  ierr = SNESSetFromOptions(m_snes);
112  PISM_CHK(ierr, "SNESSetFromOptions");
113 
115  "node types: interior, boundary, exterior"); // no units or standard name
116 
117  // Element::nodal_values() expects a ghosted array::Scalar. Ghosts if this field are
118  // never assigned to and not communicated, though.
120  "residual contribution from lateral boundaries"); // no units or standard name
121 }
122 
123 SSA *SSAFEMFactory(std::shared_ptr<const Grid> g) {
124  return new SSAFEM(g);
125 }
126 
127 // Initialize the solver, called once by the client before use.
129 
130  SSA::init_impl();
131 
132  // Use explicit driving stress if provided.
133  if (m_grid->variables().is_available("ssa_driving_stress_x") and
134  m_grid->variables().is_available("ssa_driving_stress_y")) {
135  m_driving_stress_x = m_grid->variables().get_2d_scalar("ssa_driving_stress_x");
136  m_driving_stress_y = m_grid->variables().get_2d_scalar("ssa_driving_stress_y");
137  }
138 
139  m_log->message(2, " [using the SNES-based finite element method implementation]\n");
140 
141  // process command-line options
142  {
143  m_dirichletScale = 1.0e9;
144  m_dirichletScale = options::Real(m_sys, "-ssa_fe_dirichlet_scale",
145  "Enforce Dirichlet conditions with this additional scaling",
146  "1", m_dirichletScale);
147  }
148 
149  // On restart, SSA::init() reads the SSA velocity from a PISM output file
150  // into array::Vector "velocity". We use that field as an initial guess.
151  // If we are not restarting from a PISM file, "velocity" is identically zero,
152  // and the call below clears m_velocity_global.
153 
155 }
156 
157 /** Solve the SSA system of equations.
158 
159  The FEM solver computes values of the various coefficients (most notably: the vertically-averaged
160  ice hardness) at each of the element nodes *only once*.
161 
162  When running in an ice-model context, at each time step, SSA::update() is called, which calls
163  SSAFEM::solve(). Since coefficients have generally changed between time steps, we need to recompute
164  coefficients. On the other hand, in the context of inversion, coefficients will not change between
165  iteration and there is no need to recompute their values.
166 
167  So there are two different solve methods, SSAFEM::solve() and SSAFEM::solve_nocache(). The only
168  difference is that SSAFEM::solve() recomputes the cached values of the coefficients before calling
169  SSAFEM::solve_nocache().
170  */
171 void SSAFEM::solve(const Inputs &inputs) {
172 
173  auto reason = solve_with_reason(inputs);
174  if (reason->failed()) {
176  "SSAFEM solve failed to converge (SNES reason %s)",
177  reason->description().c_str());
178  }
179 
180  if (m_log->get_threshold() > 2) {
181  m_stdout_ssa += "SSAFEM converged (SNES reason " + reason->description() + ")";
182  }
183 }
184 
185 std::shared_ptr<TerminationReason> SSAFEM::solve_with_reason(const Inputs &inputs) {
186 
187  // Set up the system to solve.
188  cache_inputs(inputs);
189 
190  return solve_nocache();
191 }
192 
193 //! Solve the SSA without first recomputing the values of coefficients at quad
194 //! points. See the disccusion of SSAFEM::solve for more discussion.
195 std::shared_ptr<TerminationReason> SSAFEM::solve_nocache() {
196  PetscErrorCode ierr;
197 
198  m_epsilon_ssa = m_config->get_number("stress_balance.ssa.epsilon");
199 
200  options::String filename("-ssa_view", "");
201  if (filename.is_set()) {
202  petsc::Viewer viewer;
203  ierr = PetscViewerASCIIOpen(m_grid->com, filename->c_str(), viewer.rawptr());
204  PISM_CHK(ierr, "PetscViewerASCIIOpen");
205 
206  ierr = PetscViewerASCIIPrintf(viewer, "SNES before SSASolve_FE\n");
207  PISM_CHK(ierr, "PetscViewerASCIIPrintf");
208 
209  ierr = SNESView(m_snes, viewer);
210  PISM_CHK(ierr, "SNESView");
211 
212  ierr = PetscViewerASCIIPrintf(viewer, "solution vector before SSASolve_FE\n");
213  PISM_CHK(ierr, "PetscViewerASCIIPrintf");
214 
215  ierr = VecView(m_velocity_global.vec(), viewer);
216  PISM_CHK(ierr, "VecView");
217  }
218 
219  m_stdout_ssa.clear();
220  if (m_log->get_threshold() >= 2) {
221  m_stdout_ssa = " SSA: ";
222  }
223 
224  // Solve:
225  ierr = SNESSolve(m_snes, NULL, m_velocity_global.vec());
226  PISM_CHK(ierr, "SNESSolve");
227 
228  // See if it worked.
229  SNESConvergedReason snes_reason;
230  ierr = SNESGetConvergedReason(m_snes, &snes_reason); PISM_CHK(ierr, "SNESGetConvergedReason");
231 
232  std::shared_ptr<TerminationReason> reason(new SNESTerminationReason(snes_reason));
233  if (not reason->failed()) {
234 
235  // Extract the solution back from SSAX to velocity and communicate.
238 
239  bool view_solution = options::Bool("-ssa_view_solution", "view solution of the SSA system");
240  if (view_solution) {
241  petsc::Viewer viewer;
242  ierr = PetscViewerASCIIOpen(m_grid->com, filename->c_str(), viewer.rawptr());
243  PISM_CHK(ierr, "PetscViewerASCIIOpen");
244 
245  ierr = PetscViewerASCIIPrintf(viewer, "solution vector after SSASolve\n");
246  PISM_CHK(ierr, "PetscViewerASCIIPrintf");
247 
248  ierr = VecView(m_velocity_global.vec(), viewer);
249  PISM_CHK(ierr, "VecView");
250  }
251 
252  }
253 
254  return reason;
255 }
256 
257 //! Initialize stored data from the coefficients in the SSA. Called by SSAFEM::solve.
258 /**
259  This method is should be called after SSAFEM::init and whenever any geometry or temperature
260  related coefficients have changed. The method stores the values of the coefficients the nodes of
261  each element so that these are available to the residual and Jacobian evaluation methods.
262 
263  We store the vertical average of the ice hardness to avoid re-doing this computation every
264  iteration.
265 
266  In addition to coefficients at element nodes we store "node types" used to identify interior
267  elements, exterior elements, and boundary faces.
268 */
269 void SSAFEM::cache_inputs(const Inputs &inputs) {
270 
271  // Make copies of BC mask and BC values: they are needed in SNES callbacks and
272  // inputs.bc_{mask,values} are not available there.
273  if (inputs.bc_mask and inputs.bc_values) {
274  m_bc_mask.copy_from(*inputs.bc_mask);
276  } else {
277  m_bc_mask.set(0.0);
278  }
279 
280  const std::vector<double> &z = m_grid->z();
281 
283  inputs.enthalpy,
284  &inputs.geometry->ice_thickness,
285  &inputs.geometry->bed_elevation,
286  &inputs.geometry->sea_level_elevation,
287  inputs.basal_yield_stress};
288 
289  bool use_explicit_driving_stress = (m_driving_stress_x != NULL) && (m_driving_stress_y != NULL);
290  if (use_explicit_driving_stress) {
292  }
293 
294  ParallelSection loop(m_grid->com);
295  try {
296  for (auto p = m_grid->points(); p; p.next()) {
297  const int i = p.i(), j = p.j();
298 
299  double thickness = inputs.geometry->ice_thickness(i, j);
300 
301  Vector2d tau_d;
302  if (use_explicit_driving_stress) {
303  tau_d.u = (*m_driving_stress_x)(i, j);
304  tau_d.v = (*m_driving_stress_y)(i, j);
305  } else {
306  // tau_d above is set to zero by the Vector2d
307  // constructor, but is not used
308  }
309 
310  const double *enthalpy = inputs.enthalpy->get_column(i, j);
311  double hardness = rheology::averaged_hardness(*m_flow_law, thickness,
312  m_grid->kBelowHeight(thickness),
313  z.data(), enthalpy);
314 
315  Coefficients c;
316  c.thickness = thickness;
317  c.bed = inputs.geometry->bed_elevation(i, j);
318  c.sea_level = inputs.geometry->sea_level_elevation(i, j);
319  c.tauc = (*inputs.basal_yield_stress)(i, j);
320  c.hardness = hardness;
321  c.driving_stress = tau_d;
322 
323  m_coefficients(i, j) = c;
324  } // loop over owned grid points
325  } catch (...) {
326  loop.failed();
327  }
328  loop.check();
329 
330  m_coefficients.update_ghosts();
331 
332  const bool use_cfbc = m_config->get_flag("stress_balance.calving_front_stress_bc");
333  if (use_cfbc) {
334  // Note: the call below uses ghosts of inputs.geometry->ice_thickness.
336  m_config->get_number("stress_balance.ice_free_thickness_standard"),
337  m_node_type);
338  } else {
340  }
341 
342  cache_residual_cfbc(inputs);
343 
344 }
345 
346 //! Compute quadrature point values of various coefficients given a quadrature `Q` and nodal values.
348  const Coefficients *x,
349  int *mask,
350  double *thickness,
351  double *tauc,
352  double *hardness) const {
353  // quadrature size
354  unsigned int n = E.n_pts();
355 
356  for (unsigned int q = 0; q < n; q++) {
357  double
358  bed = 0.0,
359  sea_level = 0.0;
360 
361  thickness[q] = 0.0;
362  tauc[q] = 0.0;
363  hardness[q] = 0.0;
364 
365  for (int k = 0; k < E.n_chi(); k++) {
366  const fem::Germ &psi = E.chi(q, k);
367 
368  thickness[q] += psi.val * x[k].thickness;
369  bed += psi.val * x[k].bed;
370  sea_level += psi.val * x[k].sea_level;
371  tauc[q] += psi.val * x[k].tauc;
372  hardness[q] += psi.val * x[k].hardness;
373  }
374 
375  mask[q] = m_gc.mask(sea_level, bed, thickness[q]);
376  }
377 }
378 
379 //! Compute gravitational driving stress at quadrature points.
380 //! Uses explicitly-provided nodal values.
382  const Coefficients *x,
383  Vector2d *result) const {
384  const unsigned int n = E.n_pts();
385 
386  for (unsigned int q = 0; q < n; q++) {
387  result[q] = 0.0;
388 
389  for (int k = 0; k < E.n_chi(); k++) {
390  const fem::Germ &psi = E.chi(q, k);
391  result[q] += psi.val * x[k].driving_stress;
392  }
393  }
394 }
395 
396 /** Compute the the driving stress at quadrature points.
397  The surface elevation @f$ h @f$ is defined by
398  @f{equation}{
399  h =
400  \begin{cases}
401  b + H, & \mathrm{grounded},\\
402  z_{sl} + \alpha H, & \mathrm{shelf},
403  \end{cases}
404  @f}
405  where @f$ b @f$ is the bed elevation, @f$ H @f$ is the ice thickness, and @f$ z_{sl} @f$ is the
406  sea level, and @f$ \alpha @f$ is the ratio of densities of ice and sea water.
407 
408  Note that if @f$ b @f$, @f$ H @f$, and @f$ z_{sl} @f$ are bilinear (or @f$ C^{1} @f$ in
409  general), @f$ h @f$ is continuous but not continuously differentiable. In
410  other words, the gradient of @f$ h @f$ is not continuous, so near the
411  grounding line we cannot compute the gravitational driving stress
412  @f$ \tau_{d} = - \rho g H \nabla h @f$ using the @f$ Q_1 @f$ or @f$ P_1 @f$ FE basis
413  expansion of @f$ h @f$.
414 
415  We differentiate the equation above instead, getting
416  @f{equation}{
417  \nabla h =
418  \begin{cases}
419  \nabla b + \nabla H, & \mathrm{grounded},\\
420  \nabla z_{sl} + \alpha \nabla H, & \mathrm{shelf}.
421  \end{cases}
422  @f}
423 
424  and use basis expansions of @f$ \nabla b @f$ and @f$ \nabla H @f$.
425 
426  Overall, we have
427 
428  @f{align*}{
429  \tau_{d} &= \rho g H \nabla h\\
430  &=
431  - \rho g H
432  \begin{cases}
433  \nabla b + \nabla H, & \mathrm{grounded},\\
434  \alpha \nabla H, & \mathrm{shelf},
435  \end{cases}
436  @f}
437 
438  because @f$ z = z_{sl} @f$ defines the geoid surface and so *its gradient
439  does not contribute to the driving stress*.
440 */
442  const Coefficients *x,
443  Vector2d *result) const {
444  const unsigned int n = E.n_pts();
445 
446  for (unsigned int q = 0; q < n; q++) {
447  double
448  sea_level = 0.0,
449  b = 0.0,
450  b_x = 0.0,
451  b_y = 0.0,
452  H = 0.0,
453  H_x = 0.0,
454  H_y = 0.0;
455 
456  result[q] = 0.0;
457 
458  for (int k = 0; k < E.n_chi(); k++) {
459  const fem::Germ &psi = E.chi(q, k);
460 
461  b += psi.val * x[k].bed;
462  b_x += psi.dx * x[k].bed;
463  b_y += psi.dy * x[k].bed;
464 
465  H += psi.val * x[k].thickness;
466  H_x += psi.dx * x[k].thickness;
467  H_y += psi.dy * x[k].thickness;
468 
469  sea_level += psi.val * x[k].sea_level;
470  }
471 
472  const int M = m_gc.mask(sea_level, b, H);
473  const bool grounded = mask::grounded(M);
474 
475  const double
476  pressure = m_rho_g * H,
477  h_x = grounded ? b_x + H_x : m_alpha * H_x,
478  h_y = grounded ? b_y + H_y : m_alpha * H_y;
479 
480  result[q].u = - pressure * h_x;
481  result[q].v = - pressure * h_y;
482  }
483 }
484 
485 
486 /** @brief Compute the "(regularized effective viscosity) x (ice thickness)" and effective viscous
487  * bed strength from the current solution, at a single quadrature point.
488  *
489  * @param[in] thickness ice thickness
490  * @param[in] hardness ice hardness
491  * @param[in] mask cell type mask
492  * @param[in] tauc basal yield stress
493  * @param[in] U the value of the solution
494  * @param[in] U_x x-derivatives of velocity components
495  * @param[in] U_y y-derivatives of velocity components
496  * @param[out] nuH product of the ice viscosity and thickness @f$ \nu H @f$
497  * @param[out] dnuH derivative of @f$ \nu H @f$ with respect to the
498  * second invariant @f$ \gamma @f$. Set to NULL if
499  * not desired.
500  * @param[out] beta basal drag coefficient @f$ \beta @f$
501  * @param[out] dbeta derivative of @f$ \beta @f$ with respect to the
502  * second invariant @f$ \gamma @f$. Set to NULL if
503  * not desired.
504  */
505 void SSAFEM::PointwiseNuHAndBeta(double thickness,
506  double hardness,
507  int mask,
508  double tauc,
509  const Vector2d &U,
510  const Vector2d &U_x,
511  const Vector2d &U_y,
512  double *nuH, double *dnuH,
513  double *beta, double *dbeta) {
514 
515  if (thickness < strength_extension->get_min_thickness()) {
517  if (dnuH) {
518  *dnuH = 0;
519  }
520  } else {
521  m_flow_law->effective_viscosity(hardness, secondInvariant_2D(U_x, U_y),
522  nuH, dnuH);
523 
524  *nuH = m_epsilon_ssa + *nuH * thickness;
525 
526  if (dnuH) {
527  *dnuH *= thickness;
528  }
529  }
530 
531  if (mask::grounded_ice(mask)) {
532  m_basal_sliding_law->drag_with_derivative(tauc, U.u, U.v, beta, dbeta);
533  } else {
534  *beta = 0;
535 
536  if (mask::ice_free_land(mask)) {
537  *beta = m_beta_ice_free_bedrock;
538  }
539 
540  if (dbeta) {
541  *dbeta = 0;
542  }
543  }
544 }
545 
546 
547 //! Compute and cache residual contributions from the integral over the lateral boundary.
548 /**
549 
550  This method computes FIXME.
551 
552  */
553 void SSAFEM::cache_residual_cfbc(const Inputs &inputs) {
554 
555  // Compute 1D (linear) shape function values at quadrature points on element boundaries.
556  // These values do not depend on the element type or dimensions of a particular physical
557  // element.
558  //
559  // Note: the number of quadrature points (2) is hard-wired below.
560  const unsigned int Nq = 2;
561  double chi_b[Nq][Nq];
562  {
563  // interval length does not matter here
564  fem::Gaussian2 Q(1.0);
565 
566  for (int i : {0, 1}) { // 2 functions
567  for (int j : {0, 1}) { // 2 quadrature points
568  chi_b[i][j] = fem::linear::chi(i, Q.point(j)).val;
569  }
570  }
571  }
572 
573  const unsigned int Nk = fem::q1::n_chi;
574 
575  using fem::P1Element2;
576  fem::P1Quadrature3 Q_p1;
577  P1Element2 p1_element[Nk] = {P1Element2(*m_grid, Q_p1, 0),
578  P1Element2(*m_grid, Q_p1, 1),
579  P1Element2(*m_grid, Q_p1, 2),
580  P1Element2(*m_grid, Q_p1, 3)};
581 
582  using mask::ocean;
583 
584  const bool
585  use_cfbc = m_config->get_flag("stress_balance.calving_front_stress_bc");
586 
587  const double
588  ice_density = m_config->get_number("constants.ice.density"),
589  ocean_density = m_config->get_number("constants.sea_water.density"),
590  standard_gravity = m_config->get_number("constants.standard_gravity");
591 
592  // Reset the boundary integral so that all values are overwritten.
594 
595  if (not use_cfbc) {
596  // If CFBC is not used then we're done.
597  return;
598  }
599 
601  &inputs.geometry->ice_thickness,
602  &inputs.geometry->bed_elevation,
603  &inputs.geometry->sea_level_elevation,
605 
606  // Iterate over the elements.
607  const int
608  xs = m_element_index.xs,
609  xm = m_element_index.xm,
610  ys = m_element_index.ys,
611  ym = m_element_index.ym;
612 
613  ParallelSection loop(m_grid->com);
614  try {
615  for (int j = ys; j < ys + ym; j++) {
616  for (int i = xs; i < xs + xm; i++) {
617  // Initialize the map from global to element degrees of freedom.
618  m_q1_element.reset(i, j);
619 
620  int node_type[Nk];
622 
623  fem::Element2 *E = nullptr;
624  {
625  auto type = fem::element_type(node_type);
626 
627  switch (type) {
629  continue;
630  case fem::ELEMENT_Q:
631  E = &m_q1_element;
632  break;
633  default:
634  E = &p1_element[type];
635 
636  E->reset(i, j);
637 
638  // get node types *again*, this time at the nodes of this P1 element
639  E->nodal_values(m_node_type, node_type);
640  }
641  }
642 
643  // residual contributions at element nodes
644  std::vector<Vector2d> I(Nk);
645 
646  double H_nodal[Nk];
647  E->nodal_values(inputs.geometry->ice_thickness.array(), H_nodal);
648 
649  double b_nodal[Nk];
650  E->nodal_values(inputs.geometry->bed_elevation.array(), b_nodal);
651 
652  double sl_nodal[Nk];
653  E->nodal_values(inputs.geometry->sea_level_elevation.array(), sl_nodal);
654 
655  // storage for values of test functions on a side of the element
656  double psi[2] = {0.0, 0.0};
657 
658  const unsigned int n_sides = E->n_sides();
659  // loop over element sides
660  for (unsigned int s = 0; s < n_sides; ++s) {
661 
662  // initialize the quadrature and compute quadrature weights
663  fem::Gaussian2 Q(E->side_length(s));
664 
665  // nodes incident to the current side
666  const int
667  n0 = s,
668  n1 = (n0 + 1) % n_sides;
669 
670  if (not (node_type[n0] == NODE_BOUNDARY and
671  node_type[n1] == NODE_BOUNDARY)) {
672  // not a boundary side; skip it
673  continue;
674  }
675 
676  for (unsigned int q = 0; q < Nq; ++q) {
677 
678  double W = Q.weight(q);
679 
680  // test functions at nodes incident to the current side, evaluated at the
681  // quadrature point q
682  psi[0] = chi_b[0][q];
683  psi[1] = chi_b[1][q];
684 
685  // Compute ice thickness and bed elevation at a quadrature point. This uses a 1D basis
686  // expansion on the current side.
687  const double
688  H = H_nodal[n0] * psi[0] + H_nodal[n1] * psi[1],
689  bed = b_nodal[n0] * psi[0] + b_nodal[n1] * psi[1],
690  sea_level = sl_nodal[n0] * psi[0] + sl_nodal[n1] * psi[1];
691 
692  // vertically averaged pressures at a quadrature point
693  double
694  P_ice = 0.5 * ice_density * standard_gravity * H,
695  P_water = average_water_column_pressure(H, bed, sea_level,
696  ice_density, ocean_density,
697  standard_gravity);
698 
699  // vertical integral of the pressure difference
700  double dP = H * (P_ice - P_water);
701  // FIXME: implement melange pressure forcing
702 
703  // This integral contributes to the residual at 2 nodes (the ones incident to the
704  // current side). This is is written in a way that allows *adding* (... += ...) the
705  // boundary contribution in the residual computation.
706  //
707  // FIXME: I need to include the special case corresponding to ice margins next
708  // to fjord walls, nunataks, etc. In this case dP == 0.
709  //
710  // FIXME: set pressure difference to zero at grounded locations at domain
711  // boundaries.
712  I[n0] += W * (- psi[0] * dP) * E->normal(s);
713  I[n1] += W * (- psi[1] * dP) * E->normal(s);
714  } // q-loop
715 
716  } // loop over element sides
717 
719 
720  } // i-loop
721  } // j-loop
722  } catch (...) {
723  loop.failed();
724  }
725  loop.check();
726 }
727 
728 
729 //! Implements the callback for computing the residual.
730 /*!
731  * Compute the residual \f[r_{ij}= G(x, \psi_{ij}) \f] where \f$G\f$
732  * is the weak form of the SSA, \f$x\f$ is the current approximate
733  * solution, and the \f$\psi_{ij}\f$ are test functions.
734  *
735  * The weak form of the SSA system is
736  */
737 void SSAFEM::compute_local_function(Vector2d const *const *const velocity_global,
738  Vector2d **residual_global) {
739 
740  const bool use_explicit_driving_stress = (m_driving_stress_x != NULL) && (m_driving_stress_y != NULL);
741 
742  const bool use_cfbc = m_config->get_flag("stress_balance.calving_front_stress_bc");
743 
744  const unsigned int Nk = fem::q1::n_chi;
745  const unsigned int Nq_max = fem::MAX_QUADRATURE_SIZE;
746 
747  using fem::P1Element2;
748  fem::P1Quadrature3 Q_p1;
749  P1Element2 p1_element[Nk] = {P1Element2(*m_grid, Q_p1, 0),
750  P1Element2(*m_grid, Q_p1, 1),
751  P1Element2(*m_grid, Q_p1, 2),
752  P1Element2(*m_grid, Q_p1, 3)};
753 
755 
756  // Set the boundary contribution of the residual. This is computed at the nodes, so we don't want
757  // to set it using Element::add_contribution() because that would lead to
758  // double-counting. Also note that without CFBC m_boundary_integral is exactly zero.
759  for (auto p = m_grid->points(); p; p.next()) {
760  const int i = p.i(), j = p.j();
761 
762  residual_global[j][i] = m_boundary_integral(i, j);
763  }
764 
765  // Start access to Dirichlet data if present.
767 
768  // Storage for the current solution and its derivatives at quadrature points.
769  Vector2d U[Nq_max], U_x[Nq_max], U_y[Nq_max];
770 
771  // Iterate over the elements.
772  const int
773  xs = m_element_index.xs,
774  xm = m_element_index.xm,
775  ys = m_element_index.ys,
776  ym = m_element_index.ym;
777 
778  ParallelSection loop(m_grid->com);
779  try {
780  for (int j = ys; j < ys + ym; j++) {
781  for (int i = xs; i < xs + xm; i++) {
782 
783  fem::Element2 *E = nullptr;
784  {
785  m_q1_element.reset(i, j);
786 
787  int node_type[Nk];
789 
790  auto type = fem::element_type(node_type);
791 
792  if (use_cfbc) {
793  if (type == fem::ELEMENT_EXTERIOR) {
794  // skip exterior elements
795  continue;
796  }
797 
798  if (type == fem::ELEMENT_Q) {
799  E = &m_q1_element;
800  } else {
801  E = &p1_element[type];
802 
803  E->reset(i, j);
804  }
805  } else {
806  // if use_cfbc == false all elements are interior and Q1
807  E = &m_q1_element;
808  }
809  }
810 
811  // Number of quadrature points.
812  const unsigned int Nq = E->n_pts();
813 
814  // Storage for the solution and residuals at element nodes.
815  Vector2d residual[Nk];
816 
817  int mask[Nq_max];
818  double thickness[Nq_max];
819  double tauc[Nq_max];
820  double hardness[Nq_max];
821  Vector2d tau_d[Nq_max];
822 
823  {
824  Coefficients coeffs[Nk];
825  E->nodal_values(m_coefficients.array(), coeffs);
826 
827  quad_point_values(*E, coeffs, mask, thickness, tauc, hardness);
828 
829  if (use_explicit_driving_stress) {
830  explicit_driving_stress(*E, coeffs, tau_d);
831  } else {
832  driving_stress(*E, coeffs, tau_d);
833  }
834  }
835 
836  {
837  // Obtain the value of the solution at the nodes
838  Vector2d velocity_nodal[Nk];
839  E->nodal_values(velocity_global, velocity_nodal);
840 
841  // These values now need to be adjusted if some nodes in the element have Dirichlet data.
842  if (dirichlet_data) {
843  // Set elements of velocity_nodal that correspond to Dirichlet nodes to prescribed
844  // values.
845  dirichlet_data.enforce(*E, velocity_nodal);
846  // mark Dirichlet nodes in E so that they are not touched by
847  // add_contribution() below
848  dirichlet_data.constrain(*E);
849  }
850 
851  // Compute the solution values and its gradient at the quadrature points.
852  E->evaluate(velocity_nodal, // input
853  U, U_x, U_y); // outputs
854  }
855 
856  // Zero out the element-local residual in preparation for updating it.
857  for (unsigned int k = 0; k < Nk; k++) {
858  residual[k] = 0;
859  }
860 
861  // loop over quadrature points:
862  for (unsigned int q = 0; q < Nq; q++) {
863 
864  auto W = E->weight(q);
865 
866  double eta = 0.0, beta = 0.0;
867  PointwiseNuHAndBeta(thickness[q], hardness[q], mask[q], tauc[q],
868  U[q], U_x[q], U_y[q], // inputs
869  &eta, NULL, &beta, NULL); // outputs
870 
871  // The next few lines compute the actual residual for the element.
872  const Vector2d tau_b = U[q] * (- beta); // basal shear stress
873 
874  const double
875  u_x = U_x[q].u,
876  v_y = U_y[q].v,
877  u_y_plus_v_x = U_y[q].u + U_x[q].v;
878 
879  // Loop over test functions.
880  for (int k = 0; k < E->n_chi(); k++) {
881  const fem::Germ &psi = E->chi(q, k);
882 
883  residual[k].u += W * (eta * (psi.dx * (4.0 * u_x + 2.0 * v_y) + psi.dy * u_y_plus_v_x)
884  - psi.val * (tau_b.u + tau_d[q].u));
885  residual[k].v += W * (eta * (psi.dx * u_y_plus_v_x + psi.dy * (2.0 * u_x + 4.0 * v_y))
886  - psi.val * (tau_b.v + tau_d[q].v));
887  } // k (test functions)
888  } // q (quadrature points)
889 
890  E->add_contribution(residual, residual_global);
891  } // i-loop
892  } // j-loop
893  } catch (...) {
894  loop.failed();
895  }
896  loop.check();
897 
898  // Until now we have not touched rows in the residual corresponding to Dirichlet data.
899  // We fix this now.
900  if (dirichlet_data) {
901  dirichlet_data.fix_residual(velocity_global, residual_global);
902  }
903 
904  if (use_cfbc) {
905  // Prescribe homogeneous Dirichlet B.C. at ice-free nodes. This uses the fact that m_node_type
906  // can be used as a "Dirichlet B.C. mask", i.e. ice-free nodes (and only ice-free nodes) are
907  // marked with ones.
908  fem::DirichletData_Vector dirichlet_ice_free(&m_node_type, NULL, m_dirichletScale);
909  dirichlet_ice_free.fix_residual_homogeneous(residual_global);
910  }
911 
912  monitor_function(velocity_global, residual_global);
913 }
914 
915 void SSAFEM::monitor_function(Vector2d const *const *const velocity_global,
916  Vector2d const *const *const residual_global) {
917  PetscErrorCode ierr;
918  bool monitorFunction = options::Bool("-ssa_monitor_function", "monitor the SSA residual");
919  if (not monitorFunction) {
920  return;
921  }
922 
923  ierr = PetscPrintf(m_grid->com,
924  "SSA Solution and Function values (pointwise residuals)\n");
925  PISM_CHK(ierr, "PetscPrintf");
926 
927  ParallelSection loop(m_grid->com);
928  try {
929  for (auto p = m_grid->points(); p; p.next()) {
930  const int i = p.i(), j = p.j();
931 
932  ierr = PetscSynchronizedPrintf(m_grid->com,
933  "[%2d, %2d] u=(%12.10e, %12.10e) f=(%12.4e, %12.4e)\n",
934  i, j,
935  velocity_global[j][i].u, velocity_global[j][i].v,
936  residual_global[j][i].u, residual_global[j][i].v);
937  PISM_CHK(ierr, "PetscSynchronizedPrintf");
938  }
939  } catch (...) {
940  loop.failed();
941  }
942  loop.check();
943 
944  ierr = PetscSynchronizedFlush(m_grid->com, NULL);
945  PISM_CHK(ierr, "PetscSynchronizedFlush");
946 }
947 
948 
949 //! Implements the callback for computing the Jacobian.
950 /*!
951  Compute the Jacobian
952 
953  @f[ J_{ij}{kl} \frac{d r_{ij}}{d x_{kl}}= G(x, \psi_{ij}) @f]
954 
955  where \f$G\f$ is the weak form of the SSA, \f$x\f$ is the current
956  approximate solution, and the \f$\psi_{ij}\f$ are test functions.
957 
958 */
959 void SSAFEM::compute_local_jacobian(Vector2d const *const *const velocity_global, Mat Jac) {
960 
961  const bool use_cfbc = m_config->get_flag("stress_balance.calving_front_stress_bc");
962 
963  const unsigned int Nk = fem::q1::n_chi;
964  const unsigned int Nq_max = fem::MAX_QUADRATURE_SIZE;
965 
966  using fem::P1Element2;
967  fem::P1Quadrature3 Q_p1;
968  P1Element2 p1_element[Nk] = {P1Element2(*m_grid, Q_p1, 0),
969  P1Element2(*m_grid, Q_p1, 1),
970  P1Element2(*m_grid, Q_p1, 2),
971  P1Element2(*m_grid, Q_p1, 3)};
972 
973  // Zero out the Jacobian in preparation for updating it.
974  PetscErrorCode ierr = MatZeroEntries(Jac);
975  PISM_CHK(ierr, "MatZeroEntries");
976 
978 
979  // Start access to Dirichlet data if present.
981 
982  // Storage for the current solution at quadrature points.
983  Vector2d U[Nq_max], U_x[Nq_max], U_y[Nq_max];
984 
985  // Loop through all the elements.
986  int
987  xs = m_element_index.xs,
988  xm = m_element_index.xm,
989  ys = m_element_index.ys,
990  ym = m_element_index.ym;
991 
992  ParallelSection loop(m_grid->com);
993  try {
994  for (int j = ys; j < ys + ym; j++) {
995  for (int i = xs; i < xs + xm; i++) {
996 
997  fem::Element2 *E = nullptr;
998  {
999  m_q1_element.reset(i, j);
1000 
1001  int node_type[Nk];
1003 
1004  auto type = fem::element_type(node_type);
1005 
1006  if (use_cfbc) {
1007  if (type == fem::ELEMENT_EXTERIOR) {
1008  // skip exterior elements
1009  continue;
1010  }
1011 
1012  if (type == fem::ELEMENT_Q) {
1013  E = &m_q1_element;
1014  } else {
1015  E = &p1_element[type];
1016 
1017  E->reset(i, j);
1018  }
1019  } else {
1020  // if use_cfbc == false all elements are interior and Q1
1021  E = &m_q1_element;
1022  }
1023  }
1024 
1025  // Number of quadrature points.
1026  const unsigned int
1027  Nq = E->n_pts(),
1028  n_chi = E->n_chi();
1029 
1030  int mask[Nq_max];
1031  double thickness[Nq_max];
1032  double tauc[Nq_max];
1033  double hardness[Nq_max];
1034 
1035  {
1036  Coefficients coeffs[Nk];
1037  E->nodal_values(m_coefficients.array(), coeffs);
1038 
1039  quad_point_values(*E, coeffs,
1040  mask, thickness, tauc, hardness);
1041  }
1042 
1043  {
1044  // Values of the solution at the nodes of the current element.
1045  Vector2d velocity_nodal[Nk];
1046  E->nodal_values(velocity_global, velocity_nodal);
1047 
1048  // These values now need to be adjusted if some nodes in the element have
1049  // Dirichlet data.
1050  if (dirichlet_data) {
1051  dirichlet_data.enforce(*E, velocity_nodal);
1052  dirichlet_data.constrain(*E);
1053  }
1054  // Compute the values of the solution at the quadrature points.
1055  E->evaluate(velocity_nodal, U, U_x, U_y);
1056  }
1057 
1058  // Element-local Jacobian matrix (there are Nk vector valued degrees
1059  // of freedom per element, for a total of (2*Nk)*(2*Nk) = 64
1060  // entries in the local Jacobian.
1061  double K[2*Nk][2*Nk];
1062  // Build the element-local Jacobian.
1063  ierr = PetscMemzero(K, sizeof(K));
1064  PISM_CHK(ierr, "PetscMemzero");
1065 
1066  for (unsigned int q = 0; q < Nq; q++) {
1067 
1068  const double
1069  W = E->weight(q),
1070  u = U[q].u,
1071  v = U[q].v,
1072  u_x = U_x[q].u,
1073  v_y = U_y[q].v,
1074  u_y_plus_v_x = U_y[q].u + U_x[q].v;
1075 
1076  double eta = 0.0, deta = 0.0, beta = 0.0, dbeta = 0.0;
1077  PointwiseNuHAndBeta(thickness[q], hardness[q], mask[q], tauc[q],
1078  U[q], U_x[q], U_y[q],
1079  &eta, &deta, &beta, &dbeta);
1080 
1081  for (unsigned int l = 0; l < n_chi; l++) { // Trial functions
1082 
1083  // Current trial function and its derivatives:
1084  const fem::Germ &phi = E->chi(q, l);
1085 
1086  // Derivatives of \gamma with respect to u_l and v_l:
1087  const double
1088  gamma_u = (2.0 * u_x + v_y) * phi.dx + 0.5 * u_y_plus_v_x * phi.dy,
1089  gamma_v = 0.5 * u_y_plus_v_x * phi.dx + (u_x + 2.0 * v_y) * phi.dy;
1090 
1091  // Derivatives of \eta = \nu*H with respect to u_l and v_l:
1092  const double
1093  eta_u = deta * gamma_u,
1094  eta_v = deta * gamma_v;
1095 
1096  // Derivatives of the basal shear stress term (\tau_b):
1097  const double
1098  taub_xu = -dbeta * u * u * phi.val - beta * phi.val, // x-component, derivative with respect to u_l
1099  taub_xv = -dbeta * u * v * phi.val, // x-component, derivative with respect to u_l
1100  taub_yu = -dbeta * v * u * phi.val, // y-component, derivative with respect to v_l
1101  taub_yv = -dbeta * v * v * phi.val - beta * phi.val; // y-component, derivative with respect to v_l
1102 
1103  for (unsigned int k = 0; k < n_chi; k++) { // Test functions
1104 
1105  // Current test function and its derivatives:
1106 
1107  const fem::Germ &psi = E->chi(q, k);
1108 
1109  if (eta == 0) {
1110  ierr = PetscPrintf(PETSC_COMM_SELF, "eta=0 i %d j %d q %d k %d\n", i, j, q, k);
1111  PISM_CHK(ierr, "PetscPrintf");
1112  }
1113 
1114  // u-u coupling
1115  K[k*2 + 0][l*2 + 0] += W * (eta_u * (psi.dx * (4 * u_x + 2 * v_y) + psi.dy * u_y_plus_v_x)
1116  + eta * (4 * psi.dx * phi.dx + psi.dy * phi.dy) - psi.val * taub_xu);
1117  // u-v coupling
1118  K[k*2 + 0][l*2 + 1] += W * (eta_v * (psi.dx * (4 * u_x + 2 * v_y) + psi.dy * u_y_plus_v_x)
1119  + eta * (2 * psi.dx * phi.dy + psi.dy * phi.dx) - psi.val * taub_xv);
1120  // v-u coupling
1121  K[k*2 + 1][l*2 + 0] += W * (eta_u * (psi.dx * u_y_plus_v_x + psi.dy * (2 * u_x + 4 * v_y))
1122  + eta * (psi.dx * phi.dy + 2 * psi.dy * phi.dx) - psi.val * taub_yu);
1123  // v-v coupling
1124  K[k*2 + 1][l*2 + 1] += W * (eta_v * (psi.dx * u_y_plus_v_x + psi.dy * (2 * u_x + 4 * v_y))
1125  + eta * (psi.dx * phi.dx + 4 * psi.dy * phi.dy) - psi.val * taub_yv);
1126 
1127  } // l
1128  } // k
1129  } // q
1130  E->add_contribution(&K[0][0], Jac);
1131  } // j
1132  } // i
1133  } catch (...) {
1134  loop.failed();
1135  }
1136  loop.check();
1137 
1138 
1139  // Until now, the rows and columns correspoinding to Dirichlet data
1140  // have not been set. We now put an identity block in for these
1141  // unknowns. Note that because we have takes steps to not touching
1142  // these columns previously, the symmetry of the Jacobian matrix is
1143  // preserved.
1144  if (dirichlet_data) {
1145  dirichlet_data.fix_jacobian(Jac);
1146  }
1147 
1148  if (use_cfbc) {
1149  // Prescribe homogeneous Dirichlet B.C. at ice-free nodes. This uses the fact that m_node_type
1150  // can be used as a "Dirichlet B.C. mask", i.e. ice-free nodes (and only ice-free nodes) are
1151  // marked with ones.
1152  fem::DirichletData_Vector dirichlet_ice_free(&m_node_type, NULL, m_dirichletScale);
1153  dirichlet_ice_free.fix_jacobian(Jac);
1154  }
1155 
1156  ierr = MatAssemblyBegin(Jac, MAT_FINAL_ASSEMBLY);
1157  PISM_CHK(ierr, "MatAssemblyBegin");
1158 
1159  ierr = MatAssemblyEnd(Jac, MAT_FINAL_ASSEMBLY);
1160  PISM_CHK(ierr, "MatAssemblyEnd");
1161 
1162  ierr = MatSetOption(Jac, MAT_NEW_NONZERO_LOCATION_ERR, PETSC_TRUE);
1163  PISM_CHK(ierr, "MatSetOption");
1164 
1165  ierr = MatSetOption(Jac, MAT_SYMMETRIC, PETSC_TRUE);
1166  PISM_CHK(ierr, "MatSetOption");
1167 
1168  monitor_jacobian(Jac);
1169 }
1170 
1172  PetscErrorCode ierr;
1173  bool mon_jac = options::Bool("-ssa_monitor_jacobian", "monitor the SSA Jacobian");
1174 
1175  if (not mon_jac) {
1176  return;
1177  }
1178 
1179  // iter has to be a PetscInt because it is used in the
1180  // SNESGetIterationNumber() call below.
1181  PetscInt iter = 0;
1182  ierr = SNESGetIterationNumber(m_snes, &iter);
1183  PISM_CHK(ierr, "SNESGetIterationNumber");
1184 
1185  auto file_name = pism::printf("PISM_SSAFEM_J%d.m", (int)iter);
1186 
1187  m_log->message(2,
1188  "writing Matlab-readable file for SSAFEM system A xsoln = rhs to file `%s' ...\n",
1189  file_name.c_str());
1190 
1191  petsc::Viewer viewer(m_grid->com);
1192 
1193  ierr = PetscViewerSetType(viewer, PETSCVIEWERASCII);
1194  PISM_CHK(ierr, "PetscViewerSetType");
1195 
1196  ierr = PetscViewerPushFormat(viewer, PETSC_VIEWER_ASCII_MATLAB);
1197  PISM_CHK(ierr, "PetscViewerPushFormat");
1198 
1199  ierr = PetscViewerFileSetName(viewer, file_name.c_str());
1200  PISM_CHK(ierr, "PetscViewerFileSetName");
1201 
1202  ierr = PetscObjectSetName((PetscObject) Jac, "A");
1203  PISM_CHK(ierr, "PetscObjectSetName");
1204 
1205  ierr = MatView(Jac, viewer);
1206  PISM_CHK(ierr, "MatView");
1207 
1208  ierr = PetscViewerPopFormat(viewer);
1209  PISM_CHK(ierr, "PetscViewerPopFormat");
1210 }
1211 
1212 //!
1213 PetscErrorCode SSAFEM::function_callback(DMDALocalInfo *info,
1214  Vector2d const *const *const velocity,
1215  Vector2d **residual,
1216  CallbackData *fe) {
1217  try {
1218  (void) info;
1219  fe->ssa->compute_local_function(velocity, residual);
1220  } catch (...) {
1221  MPI_Comm com = MPI_COMM_SELF;
1222  PetscErrorCode ierr = PetscObjectGetComm((PetscObject)fe->da, &com); CHKERRQ(ierr);
1223  handle_fatal_errors(com);
1224  SETERRQ(com, 1, "A PISM callback failed");
1225  }
1226  return 0;
1227 }
1228 
1229 PetscErrorCode SSAFEM::jacobian_callback(DMDALocalInfo *info,
1230  Vector2d const *const *const velocity,
1231  Mat A, Mat J, CallbackData *fe) {
1232  try {
1233  (void) A;
1234  (void) info;
1236  } catch (...) {
1237  MPI_Comm com = MPI_COMM_SELF;
1238  PetscErrorCode ierr = PetscObjectGetComm((PetscObject)fe->da, &com); CHKERRQ(ierr);
1239  handle_fatal_errors(com);
1240  SETERRQ(com, 1, "A PISM callback failed");
1241  }
1242  return 0;
1243 }
1244 
1245 } // end of namespace stressbalance
1246 } // end of namespace pism
const units::System::Ptr m_sys
unit system used by this component
Definition: Component.hh:160
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
int mask(double sea_level, double bed, double thickness) const
Definition: Mask.hh:138
array::Scalar1 sea_level_elevation
Definition: Geometry.hh:48
array::Scalar2 ice_thickness
Definition: Geometry.hh:51
array::Scalar2 bed_elevation
Definition: Geometry.hh:47
virtual void drag_with_derivative(double tauc, double vx, double vy, double *drag, double *ddrag) const
Compute the drag coefficient and its derivative with respect to .
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)
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
void add(double alpha, const Array2D< T > &x)
Definition: Array2D.hh:65
double * get_column(int i, int j)
Definition: Array3D.cc:120
void set_interpolation_type(InterpolationType type)
Definition: Array.cc:179
petsc::Vec & vec() const
Definition: Array.cc:339
void set(double c)
Result: v[j] <- c for all j.
Definition: Array.cc:707
void update_ghosts()
Updates ghost points.
Definition: Array.cc:693
SpatialVariableMetadata & metadata(unsigned int N=0)
Returns a reference to the SpatialVariableMetadata object containing metadata for the compoment N.
Definition: Array.cc:553
void fix_residual(Vector2d const *const *const x_global, Vector2d **r_global)
void enforce(const Element2 &element, Vector2d *x_e)
void fix_residual_homogeneous(Vector2d **r)
void constrain(Element2 &element)
Constrain element, i.e. ensure that quadratures do not contribute to Dirichlet nodes by marking corre...
void evaluate(const T *x, T *vals, T *dx, T *dy)
Given nodal values, compute the values and partial derivatives at the quadrature points.
Definition: Element.hh:195
void reset(int i, int j)
Initialize the Element to element (i, j) for the purposes of inserting into global residual and Jacob...
Definition: Element.cc:187
double side_length(unsigned int side) const
Definition: Element.hh:185
void add_contribution(const T *local, T **y_global) const
Add the values of element-local contributions y to the global vector y_global.
Definition: Element.hh:231
void nodal_values(const array::Scalar &x_global, int *result) const
Get nodal values of an integer mask.
Definition: Element.cc:176
unsigned int n_sides() const
Definition: Element.hh:181
Vector2d normal(unsigned int side) const
Definition: Element.hh:176
int xm
total number of elements to loop over in the x-direction.
int ym
total number of elements to loop over in the y-direction.
int ys
y-coordinate of the first element to loop over.
int xs
x-coordinate of the first element to loop over.
const Germ & chi(unsigned int q, unsigned int k) const
Definition: Element.hh:73
int n_pts() const
Number of quadrature points.
Definition: Element.hh:80
int n_chi() const
Definition: Element.hh:65
double weight(unsigned int q) const
Weight of the quadrature point q
Definition: Element.hh:85
The mapping from global to local degrees of freedom.
Definition: Element.hh:61
P1 element embedded in Q1Element2.
Definition: Element.hh:266
double weight(int k) const
Definition: Quadrature.hh:71
QuadPoint point(int k) const
Definition: Quadrature.hh:67
bool is_set() const
Definition: options.hh:35
const array::Scalar * basal_yield_stress
const array::Scalar * bc_mask
const array::Array3D * enthalpy
const array::Vector * bc_values
void compute_local_function(Vector2d const *const *const velocity, Vector2d **residual)
Implements the callback for computing the residual.
Definition: SSAFEM.cc:737
std::shared_ptr< TerminationReason > solve_with_reason(const Inputs &inputs)
Definition: SSAFEM.cc:185
void PointwiseNuHAndBeta(double thickness, double hardness, int mask, double tauc, const Vector2d &U, const Vector2d &U_x, const Vector2d &U_y, double *nuH, double *dnuH, double *beta, double *dbeta)
Compute the "(regularized effective viscosity) x (ice thickness)" and effective viscous bed strength ...
Definition: SSAFEM.cc:505
void quad_point_values(const fem::Element &Q, const Coefficients *x, int *mask, double *thickness, double *tauc, double *hardness) const
Compute quadrature point values of various coefficients given a quadrature Q and nodal values.
Definition: SSAFEM.cc:347
const array::Scalar * m_driving_stress_x
Definition: SSAFEM.hh:146
static PetscErrorCode function_callback(DMDALocalInfo *info, Vector2d const *const *const velocity, Vector2d **residual, CallbackData *fe)
SNES callbacks.
Definition: SSAFEM.cc:1213
void cache_inputs(const Inputs &inputs)
Initialize stored data from the coefficients in the SSA. Called by SSAFEM::solve.
Definition: SSAFEM.cc:269
void compute_local_jacobian(Vector2d const *const *const velocity, Mat J)
Implements the callback for computing the Jacobian.
Definition: SSAFEM.cc:959
SSAFEM(std::shared_ptr< const Grid > g)
Definition: SSAFEM.cc:46
CallbackData m_callback_data
Definition: SSAFEM.hh:126
array::Vector1 m_boundary_integral
Boundary integral (CFBC contribution to the residual).
Definition: SSAFEM.hh:133
void monitor_jacobian(Mat Jac)
Definition: SSAFEM.cc:1171
void explicit_driving_stress(const fem::Element &E, const Coefficients *x, Vector2d *driving_stress) const
Definition: SSAFEM.cc:381
static PetscErrorCode jacobian_callback(DMDALocalInfo *info, Vector2d const *const *const xg, Mat A, Mat J, CallbackData *fe)
Definition: SSAFEM.cc:1229
array::Scalar1 m_bc_mask
Definition: SSAFEM.hh:69
virtual void solve(const Inputs &inputs)
Definition: SSAFEM.cc:171
fem::Q1Element2 m_q1_element
Definition: SSAFEM.hh:140
GeometryCalculator m_gc
Definition: SSAFEM.hh:72
virtual void init_impl()
Initialize a generic regular-grid SSA solver.
Definition: SSAFEM.cc:128
std::shared_ptr< TerminationReason > solve_nocache()
Definition: SSAFEM.cc:195
void cache_residual_cfbc(const Inputs &inputs)
Compute and cache residual contributions from the integral over the lateral boundary.
Definition: SSAFEM.cc:553
fem::ElementIterator m_element_index
Definition: SSAFEM.hh:139
const array::Scalar * m_driving_stress_y
Definition: SSAFEM.hh:147
void monitor_function(Vector2d const *const *const velocity_global, Vector2d const *const *const residual_global)
Definition: SSAFEM.cc:915
array::Scalar1 m_node_type
Storage for node types (interior, boundary, exterior).
Definition: SSAFEM.hh:131
array::Vector1 m_bc_values
Definition: SSAFEM.hh:70
array::Array2D< Coefficients > m_coefficients
Definition: SSAFEM.hh:76
PISM's SSA solver: the finite element method implementation written by Jed and David.
Definition: SSAFEM.hh:40
double get_notional_strength() const
Returns strength = (viscosity times thickness).
Definition: SSA.cc:61
array::Vector m_velocity_global
Definition: SSA.hh:150
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
const array::Vector & driving_stress() const
Definition: SSA.cc:389
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 phi
Definition: exactTestK.c:41
#define n
Definition: exactTestM.c:37
@ WITH_GHOSTS
Definition: Array.hh:62
Germ chi(unsigned int k, const QuadPoint &pt)
Linear basis functions on the interval [-1, -1].
Definition: FEM.cc:29
const int n_chi
Definition: FEM.hh:177
const int n_sides
Definition: FEM.hh:185
const int n_chi
Definition: FEM.hh:191
const unsigned int MAX_QUADRATURE_SIZE
Definition: FEM.hh:173
@ ELEMENT_Q
Definition: FEM.hh:203
@ ELEMENT_EXTERIOR
Definition: FEM.hh:205
ElementType element_type(int node_type[q1::n_chi])
Definition: FEM.cc:104
static double K(double psi_x, double psi_y, double speed, double epsilon)
bool grounded_ice(int M)
Definition: Mask.hh:51
bool ice_free_land(int M)
Definition: Mask.hh:64
bool grounded(int M)
Grounded cell (grounded ice or ice-free).
Definition: Mask.hh:44
bool ocean(int M)
An ocean cell (floating ice or ice-free).
Definition: Mask.hh:40
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 * SSAFEMFactory(std::shared_ptr< const Grid > g)
Factory function for constructing a new SSAFEM.
Definition: SSAFEM.cc:123
@ NODE_BOUNDARY
Definition: node_types.hh:35
@ NODE_INTERIOR
Definition: node_types.hh:34
static double secondInvariant_2D(const Vector2d &U_x, const Vector2d &U_y)
Definition: FlowLaw.hh:45
void compute_node_types(const array::Scalar1 &ice_thickness, double thickness_threshold, array::Scalar &result)
Definition: node_types.cc:62
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,...)
static const double k
Definition: exactTestP.cc:42
void handle_fatal_errors(MPI_Comm com)
const int J[]
Definition: ssafd_code.cc:34
const int I[]
Definition: ssafd_code.cc:24
double dy
Function derivative with respect to y.
Definition: FEM.hh:157
double val
Function value.
Definition: FEM.hh:153
double dx
Function deriviative with respect to x.
Definition: FEM.hh:155
Struct for gathering the value and derivative of a function at a point.
Definition: FEM.hh:151
Adaptor for gluing SNESDAFormFunction callbacks to an SSAFEM.
Definition: SSAFEM.hh:120
double tauc
basal yield stress
Definition: SSAFEM.hh:62
Vector2d driving_stress
prescribed gravitational driving stress
Definition: SSAFEM.hh:66