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