PISM, A Parallel Ice Sheet Model  stable v2.1-1-g6902d5502 committed by Ed Bueler on 2023-12-20 08:38:27 -0800
LingleClarkSerial.cc
Go to the documentation of this file.
1 // Copyright (C) 2004-2009, 2011, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2023 Ed Bueler and Constantine Khroulev
2 //
3 // This file is part of PISM.
4 //
5 // PISM is free software; you can redistribute it and/or modify it under the
6 // terms of the GNU General Public License as published by the Free Software
7 // Foundation; either version 3 of the License, or (at your option) any later
8 // version.
9 //
10 // PISM is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with PISM; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 
19 #include <cassert>
20 #include <cmath> // sqrt
21 #include <fftw3.h>
22 #include <gsl/gsl_math.h> // M_PI
23 
24 #include "pism/earth/matlablike.hh"
25 #include "pism/earth/greens.hh"
26 #include "pism/earth/LingleClarkSerial.hh"
27 
28 #include "pism/util/ConfigInterface.hh"
29 #include "pism/util/error_handling.hh"
30 #include "pism/util/petscwrappers/Vec.hh"
31 #include "pism/util/fftw_utilities.hh"
32 
33 namespace pism {
34 namespace bed {
35 
36 /*!
37  * @param[in] config configuration database
38  * @param[in] include_elastic include elastic deformation component
39  * @param[in] Mx grid size in the X direction
40  * @param[in] My grid size in the Y direction
41  * @param[in] dx grid spacing in the X direction
42  * @param[in] dy grid spacing in the Y direction
43  * @param[in] Nx extended grid size in the X direction
44  * @param[in] Ny extended grid size in the Y direction
45  */
47  const Config &config,
48  bool include_elastic,
49  int Mx, int My,
50  double dx, double dy,
51  int Nx, int Ny)
52  : m_log(log) {
53 
54  // set parameters
55  m_include_elastic = include_elastic;
56 
57  if (include_elastic) {
58  // check if the extended grid is large enough (it has to be at least twice the size of
59  // the physical grid so that the load in one corner of the domain affects the grid
60  // point in the opposite corner).
61 
62  if (config.get_number("bed_deformation.lc.grid_size_factor") < 2) {
64  "bed_deformation.lc.elastic_model"
65  " requires bed_deformation.lc.grid_size_factor > 1");
66  }
67  }
68 
69  // grid parameters
70  m_Mx = Mx;
71  m_My = My;
72  m_dx = dx;
73  m_dy = dy;
74  m_Nx = Nx;
75  m_Ny = Ny;
76 
77  m_load_density = config.get_number("constants.ice.density");
78  m_mantle_density = config.get_number("bed_deformation.mantle_density");
79  m_eta = config.get_number("bed_deformation.mantle_viscosity");
80  m_D = config.get_number("bed_deformation.lithosphere_flexural_rigidity");
81 
82  m_standard_gravity = config.get_number("constants.standard_gravity");
83 
84  // derive more parameters
85  m_Lx = 0.5 * (m_Nx - 1.0) * m_dx;
86  m_Ly = 0.5 * (m_Ny - 1.0) * m_dy;
87  m_i0_offset = (Nx - Mx) / 2;
88  m_j0_offset = (Ny - My) / 2;
89 
90  // memory allocation
91  PetscErrorCode ierr = 0;
92 
93  // total displacement
94  ierr = VecCreateSeq(PETSC_COMM_SELF, m_Mx * m_My, m_U.rawptr());;
95  PISM_CHK(ierr, "VecCreateSeq");
96 
97  // elastic displacement
98  ierr = VecCreateSeq(PETSC_COMM_SELF, m_Mx * m_My, m_Ue.rawptr());;
99  PISM_CHK(ierr, "VecCreateSeq");
100 
101  // viscous displacement
102  ierr = VecCreateSeq(PETSC_COMM_SELF, m_Nx * m_Ny, m_Uv.rawptr());
103  PISM_CHK(ierr, "VecCreateSeq");
104 
105  // setup fftw stuff: FFTW builds "plans" based on observed performance
106  m_fftw_input = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * m_Nx * m_Ny);
107  m_fftw_output = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * m_Nx * m_Ny);
108  m_loadhat = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * m_Nx * m_Ny);
109  m_lrm_hat = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * m_Nx * m_Ny);
110 
112  m_dft_forward = fftw_plan_dft_2d(m_Nx, m_Ny, m_fftw_input, m_fftw_output,
113  FFTW_FORWARD, FFTW_ESTIMATE);
114  m_dft_inverse = fftw_plan_dft_2d(m_Nx, m_Ny, m_fftw_input, m_fftw_output,
115  FFTW_BACKWARD, FFTW_ESTIMATE);
116 
117  // Note: FFTW is weird. If a malloc() call fails it will just call
118  // abort() on you without giving you a chance to recover or tell the
119  // user what happened. This is why we don't check return values of
120  // fftw_malloc() and fftw_plan_dft_2d() calls here...
121  //
122  // (Constantine Khroulev, February 1, 2015)
123 
125 }
126 
128  fftw_destroy_plan(m_dft_forward);
129  fftw_destroy_plan(m_dft_inverse);
130  fftw_free(m_fftw_input);
131  fftw_free(m_fftw_output);
132  fftw_free(m_loadhat);
133  fftw_free(m_lrm_hat);
134 }
135 
136 /*!
137  * Return total displacement.
138  */
140  return m_U;
141 }
142 
143 /*!
144  * Return viscous plate displacement.
145  */
147  return m_Uv;
148 }
149 
150 /*!
151  * Return elastic plate displacement.
152  */
154  return m_Ue;
155 }
156 
158 
159  FFTWArray LRM(output, m_Nx, m_Ny);
160 
162  ge_data ge_data {m_dx, m_dy, 0, 0, &G};
163 
164  int Nx2 = m_Nx / 2;
165  int Ny2 = m_Ny / 2;
166 
167  // Top half
168  for (int j = 0; j <= Ny2; ++j) {
169  // Top left quarter
170  for (int i = 0; i <= Nx2; ++i) {
171  ge_data.p = Nx2 - i;
172  ge_data.q = Ny2 - j;
173 
174  LRM(i, j) = dblquad_cubature(ge_integrand,
175  -m_dx / 2, m_dx / 2,
176  -m_dy / 2, m_dy / 2,
177  1.0e-8, &ge_data);
178  }
179 
180  // Top right quarter
181  //
182  // Note: Nx2 = m_Nx / 2 (using integer division!), so
183  //
184  // - If m_Nx is even then 2 * Nx2 == m_Nx. So i < m_Nx implies i < 2 * Nx2 and
185  // 2 * Nx2 - i > 0.
186  //
187  // - If m_Nx is odd then 2 * Nx2 == m_Nx - 1 or m_Nx == 2 * Nx2 + 1. So i < m_Nx
188  // implies i < 2 * Nx2 + 1, which is the same as 2 * Nx2 - i > -1 or
189  // 2 * Nx2 - i >= 0.
190  //
191  // Also, i == Nx2 + 1 gives 2 * Nx2 - i == Nx2 - 1
192  //
193  // So, in both cases (even and odd) 0 <= 2 * Nx2 - i <= Nx2 - 1.
194  //
195  // This means that LRM(2 * Nx2 - i, j) will not use indexes that are out of bounds
196  // *and* will only use values computed in the for loop above.
197  for (int i = Nx2 + 1; i < m_Nx; ++i) {
198  assert(2 * Nx2 - i >= 0);
199  LRM(i, j) = LRM(2 * Nx2 - i, j);
200  }
201  } // End of the loop over the top half
202 
203  // Bottom half
204  //
205  // See the comment above the "top right quarter" loop.
206  for (int j = Ny2 + 1; j < m_Ny; ++j) {
207  for (int i = 0; i < m_Nx; ++i) {
208  assert(2 * Ny2 - j >= 0);
209  LRM(i, j) = LRM(i, 2 * Ny2 - j);
210  }
211  }
212 }
213 
214 /**
215  * Pre-compute coefficients used by the model.
216  */
218 
219  // Coefficients for Fourier spectral method Laplacian
220  // MATLAB version: cx=(pi/Lx)*[0:Nx/2 Nx/2-1:-1:1]
221  m_cx = fftfreq(m_Nx, m_Lx / (m_Nx * M_PI));
222  m_cy = fftfreq(m_Ny, m_Ly / (m_Ny * M_PI));
223 
224  // compare geforconv.m
225  if (m_include_elastic) {
226  m_log->message(2, " computing spherical elastic load response matrix ...");
227  {
229  // Compute fft2(LRM) and save it in m_lrm_hat
230  fftw_execute(m_dft_forward);
232  }
233  m_log->message(2, " done\n");
234  }
235 }
236 
237 /*!
238  * Solve
239  *
240  * @f$ 2 \nu |\nabla| \diff{u}{t} + \rho_r g U + D\nabla^4 U = \sigma_{zz}@f$
241  *
242  * for @f$ U @f$, treating @f$ \diff{u}{t} @f$ and @f$ \sigma_{zz} @f$ as known.
243  *
244  * @param[in] load_thickness load thickness, meters
245  * @param[in] bed_uplift bed uplift, m/second
246  *
247  * Here `load_thickness` is used to compute the load @f$ \sigma_{zz} @f$ and `bed_uplift` is
248  * @f$ \diff{u}{t} @f$ itself.
249  *
250  */
252  petsc::Vec &bed_uplift,
253  petsc::Vec &output) {
254 
255  // Compute fft2(-load_density * g * load_thickness)
256  {
258  set_real_part(load_thickness, - m_load_density * m_standard_gravity,
260  m_fftw_input);
261  fftw_execute(m_dft_forward);
262  // Save fft2(-load_density * g * load_thickness) in loadhat.
264  }
265 
266  // fft2(uplift)
267  {
269  set_real_part(bed_uplift, 1.0, m_Mx, m_My, m_Nx, m_Ny, m_i0_offset, m_j0_offset,
270  m_fftw_input);
271  fftw_execute(m_dft_forward);
272  }
273 
274  {
275  FFTWArray
276  u0_hat(m_fftw_input, m_Nx, m_Ny),
277  load_hat(m_loadhat, m_Nx, m_Ny),
278  uplift_hat(m_fftw_output, m_Nx, m_Ny);
279 
280  for (int i = 0; i < m_Nx; i++) {
281  for (int j = 0; j < m_Ny; j++) {
282  const double
283  C = m_cx[i]*m_cx[i] + m_cy[j]*m_cy[j],
284  A = - 2.0 * m_eta * sqrt(C),
286 
287  u0_hat(i, j) = (load_hat(i, j) + A * uplift_hat(i, j)) / B;
288  }
289  }
290  }
291 
292  fftw_execute(m_dft_inverse);
293  get_real_part(m_fftw_output, 1.0 / (m_Nx * m_Ny), m_Nx, m_Ny, m_Nx, m_Ny, 0, 0, output);
294 
295  tweak(load_thickness, output, m_Nx, m_Ny, 0.0);
296 }
297 
298 /*! Initialize using provided load thickness and the bed uplift rate.
299  *
300  * Here we solve:
301  *
302  * rho_r g U + D grad^4 U = -rho g H - 2 eta |grad| uplift
303  *
304  * Compare equation (16) in Bueler, Lingle, Brown (2007) "Fast computation of a viscoelastic
305  * deformable Earth model for ice sheet simulations", Ann. Glaciol. 46, 97--105
306  *
307  * @note Probable sign error in eqn (16)?: load "rho g H" should be "- rho g H"]
308  *
309  * This initialization method is used to "bootstrap" the model. It should not be used to re-start a
310  * stopped modeling run.
311  *
312  * @param[in] thickness load thickness, meters
313  * @param[in] uplift initial bed uplift on the PISM grid
314  *
315  * Sets m_Uv, m_Ue, m_U.
316  */
318 
319  // compute viscous displacement
320  uplift_problem(thickness, uplift, m_Uv);
321 
322  if (m_include_elastic) {
323  compute_elastic_response(thickness, m_Ue);
324  } else {
325  PetscErrorCode ierr = VecSet(m_Ue, 0.0); PISM_CHK(ierr, "VecSet");
326  }
327 
329 }
330 
331 /*!
332  * Initialize using provided plate displacement.
333  *
334  * @param[in] viscous_displacement initial viscous plate displacement (meters) on the extended grid
335  * @param[in] elastic_displacement initial viscous plate displacement (meters) on the regular grid
336  *
337  * Sets m_Uv, m_Ue, m_U.
338  */
339 void LingleClarkSerial::init(petsc::Vec &viscous_displacement,
340  petsc::Vec &elastic_displacement) {
341  PetscErrorCode ierr = 0;
342 
343  ierr = VecCopy(viscous_displacement, m_Uv); PISM_CHK(ierr, "VecCopy");
344 
345  if (m_include_elastic) {
346  ierr = VecCopy(elastic_displacement, m_Ue); PISM_CHK(ierr, "VecCopy");
347  } else {
348  ierr = VecSet(m_Ue, 0.0); PISM_CHK(ierr, "VecSet");
349  }
350 
352 }
353 
354 /*!
355  * Perform a time step.
356  *
357  * @param[in] dt time step length
358  * @param[in] H load thickness on the physical (Mx*My) grid
359  */
360 void LingleClarkSerial::step(double dt, petsc::Vec &H) {
361  // solves:
362  // (2 eta |grad| U^{n+1}) + (dt/2) * (rho_r g U^{n+1} + D grad^4 U^{n+1})
363  // = (2 eta |grad| U^n) - (dt/2) * (rho_r g U^n + D grad^4 U^n) - dt * rho g H_start
364  // where U=plate displacement; see equation (7) in
365  // Bueler, Lingle, Brown (2007) "Fast computation of a viscoelastic
366  // deformable Earth model for ice sheet simulations", Ann. Glaciol. 46, 97--105
367 
368  // Compute viscous displacement if dt > 0 and bypass this computation if dt == 0.
369  //
370  // This makes it easier to test the elastic part of the model.
371  if (dt > 0.0) {
372  // Non-zero time step: include the viscous part of the model.
373 
374  // Compute fft2(-load_density * g * dt * H)
375  {
377  set_real_part(H,
380  m_fftw_input);
381  fftw_execute(m_dft_forward);
382 
383  // Save fft2(-load_density * g * H * dt) in loadhat.
385  }
386 
387  // Compute fft2(u).
388  // no need to clear fftw_input: all values are overwritten
389  {
390  set_real_part(m_Uv, 1.0, m_Nx, m_Ny, m_Nx, m_Ny, 0, 0, m_fftw_input);
391  fftw_execute(m_dft_forward);
392  }
393 
394  // frhs = right.*fft2(uun) + fft2(dt*sszz);
395  // uun1 = real(ifft2(frhs./left));
396  {
397  FFTWArray input(m_fftw_input, m_Nx, m_Ny),
398  u_hat(m_fftw_output, m_Nx, m_Ny), load_hat(m_loadhat, m_Nx, m_Ny);
399  for (int i = 0; i < m_Nx; i++) {
400  for (int j = 0; j < m_Ny; j++) {
401  const double
402  C = m_cx[i]*m_cx[i] + m_cy[j]*m_cy[j],
403  part1 = 2.0 * m_eta * sqrt(C),
404  part2 = (dt / 2.0) * (m_mantle_density * m_standard_gravity + m_D * C * C),
405  A = part1 - part2,
406  B = part1 + part2;
407 
408  input(i, j) = (load_hat(i, j) + A * u_hat(i, j)) / B;
409  }
410  }
411  }
412 
413  fftw_execute(m_dft_inverse);
414  get_real_part(m_fftw_output, 1.0 / (m_Nx * m_Ny), m_Nx, m_Ny, m_Nx, m_Ny, 0, 0, m_Uv);
415 
416  // Now tweak. (See the "correction" in section 5 of BuelerLingleBrown.)
417  //
418  // Here 1e16 approximates t = \infty.
419  tweak(H, m_Uv, m_Nx, m_Ny, 1e16);
420  } else {
421  // zero time step: viscous displacement is zero
422  PetscErrorCode ierr = VecSet(m_Uv, 0.0); PISM_CHK(ierr, "VecSet");
423  }
424 
425  // now compute elastic response if desired
426  if (m_include_elastic) {
428  }
429 
431 }
432 
433 /*!
434  * Compute elastic response to the load H
435  *
436  * @param[in] H load thickness (ice equivalent meters)
437  * @param[out] dE elastic plate displacement
438  */
440 
441  // Compute fft2(load_density * H)
442  //
443  // Note that here the load is placed in the corner of the array on the extended grid
444  // (offsets i0 and j0 are zero).
445  {
448  fftw_execute(m_dft_forward);
449  }
450 
451  // fft2(m_response_matrix) * fft2(load_density*H)
452  //
453  // Compute the product of Fourier transforms of the LRM and the load. This uses C++'s
454  // native support for complex arithmetic.
455  {
456  FFTWArray
457  input(m_fftw_input, m_Nx, m_Ny),
458  LRM_hat(m_lrm_hat, m_Nx, m_Ny),
459  load_hat(m_fftw_output, m_Nx, m_Ny);
460  for (int i = 0; i < m_Nx; i++) {
461  for (int j = 0; j < m_Ny; j++) {
462  input(i, j) = LRM_hat(i, j) * load_hat(i, j);
463  }
464  }
465  }
466 
467  // Compute the inverse transform and extract the elastic response.
468  //
469  // Here the offsets are:
470  // i0 = m_Nx / 2,
471  // j0 = m_Ny / 2.
472  fftw_execute(m_dft_inverse);
474  m_Nx/2, m_Ny/2, dE);
475 }
476 
477 /*! Compute total displacement by combining viscous and elastic contributions.
478  *
479  * @param[in] V viscous displacement
480  * @param[in] dE elastic displacement
481  * @param[out] dU total displacement
482  */
484  // PISM grid
486  u(U, m_Mx, m_My),
487  u_elastic(Ue, m_Mx, m_My);
488  // extended grid
490  u_viscous(Uv, m_Nx, m_Ny, m_i0_offset, m_j0_offset);
491 
492  for (int i = 0; i < m_Mx; i++) {
493  for (int j = 0; j < m_My; j++) {
494  u(i, j) = u_viscous(i, j) + u_elastic(i, j);
495  }
496  }
497 }
498 
499 
500 /*!
501  * Modify the plate displacement to correct for the effect of imposing periodic boundary conditions
502  * at a finite distance.
503  *
504  * See Section 5 in [@ref BuelerLingleBrown].
505  *
506  * @param[in] load_thickness thickness of the load (used to compute the corresponding disc volume)
507  * @param[in,out] U viscous plate displacement
508  * @param[in] Nx grid size
509  * @param[in] Ny grid size
510  * @param[in] time time, seconds (usually 0 or a large number approximating \infty)
511  */
512 void LingleClarkSerial::tweak(petsc::Vec &load_thickness, petsc::Vec &U, int Nx, int Ny, double time) {
513  PetscErrorCode ierr = 0;
514  petsc::VecArray2D u(U, Nx, Ny);
515 
516  // find average value along "distant" boundary of [-Lx, Lx]X[-Ly, Ly]
517  // note domain is periodic, so think of cut locus of torus (!)
518  // (will remove it: uun1=uun1-(sum(uun1(1, :))+sum(uun1(:, 1)))/(2*N);)
519  double average = 0.0;
520  for (int i = 0; i < Nx; i++) {
521  average += u(i, 0);
522  }
523 
524  for (int j = 0; j < Ny; j++) {
525  average += u(0, j);
526  }
527 
528  average /= (double) (Nx + Ny);
529 
530  double shift = 0.0;
531 
532  if (time > 0.0) {
533  // tweak continued: replace far field with value for an equivalent disc load which has
534  // R0=Lx*(2/3)=L/3 (instead of 1000km in MATLAB code: H0 = dx*dx*sum(sum(H))/(pi*1e6^2); %
535  // trapezoid rule)
536  const double L_average = (m_Lx + m_Ly) / 2.0;
537  const double R = L_average * (2.0 / 3.0);
538 
539  double H_sum = 0.0;
540  ierr = VecSum(load_thickness, &H_sum); PISM_CHK(ierr, "VecSum");
541 
542  // compute disc thickness by dividing its volume by the area
543  const double H = (H_sum * m_dx * m_dy) / (M_PI * R * R);
544 
545  shift = viscDisc(time, // time in seconds
546  H, // disc thickness
547  R, // disc radius
548  L_average, // compute deflection at this radius
549  m_mantle_density, m_load_density, // mantle and load densities
551  m_D, // flexural rigidity
552  m_eta); // mantle viscosity
553  }
554 
555  ierr = VecShift(U, shift - average); PISM_CHK(ierr, "VecShift");
556 }
557 
558 } // end of namespace bed
559 } // end of namespace pism
double get_number(const std::string &name, UseFlag flag=REMEMBER_THIS_USE) const
A class for storing and accessing PISM configuration flags and parameters.
std::shared_ptr< const Logger > ConstPtr
Definition: Logger.hh:46
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
T * rawptr()
Definition: Wrapper.hh:39
void uplift_problem(petsc::Vec &load_thickness, petsc::Vec &bed_uplift, petsc::Vec &output)
void update_displacement(petsc::Vec &V, petsc::Vec &dE, petsc::Vec &dU)
LingleClarkSerial(Logger::ConstPtr log, const Config &config, bool include_elastic, int Mx, int My, double dx, double dy, int Nx, int Ny)
void step(double dt_seconds, petsc::Vec &H)
const petsc::Vec & viscous_displacement() const
void tweak(petsc::Vec &load_thickness, petsc::Vec &U, int Nx, int Ny, double time)
double m_D
lithosphere flexural rigidity
void bootstrap(petsc::Vec &thickness, petsc::Vec &uplift)
void compute_elastic_response(petsc::Vec &H, petsc::Vec &dE)
double m_mantle_density
mantle density
void compute_load_response_matrix(fftw_complex *output)
void init(petsc::Vec &viscous_displacement, petsc::Vec &elastic_displacement)
const petsc::Vec & elastic_displacement() const
const petsc::Vec & total_displacement() const
double m_eta
mantle viscosity
double m_load_density
load density (for computing load from its thickness)
Wrapper around VecGetArray2d and VecRestoreArray2d.
Definition: Vec.hh:55
#define PISM_CHK(errcode, name)
#define PISM_ERROR_LOCATION
const double G
Definition: exactTestK.c:40
double dblquad_cubature(integrand f, double ax, double bx, double ay, double by, double reqRelError, void *fdata)
Definition: matlablike.cc:22
double ge_integrand(unsigned ndim, const double *args, void *data)
The integrand in defining the elastic Green's function from the [Farrell] earth model.
Definition: greens.cc:30
double viscDisc(double t, double H0, double R0, double r, double rho, double rho_ice, double grav, double D, double eta)
Actually compute the response of the viscous half-space model in LingleClark, to a disc load.
Definition: greens.cc:135
void clear_fftw_array(fftw_complex *input, int Nx, int Ny)
Fill input with zeros.
void copy_fftw_array(fftw_complex *source, fftw_complex *destination, int Nx, int Ny)
Copy source to destination.
void get_real_part(fftw_complex *input, double normalization, int Mx, int My, int Nx, int Ny, int i0, int j0, petsc::Vec &output)
Get the real part of input and put it in output.
void set_real_part(petsc::Vec &input, double normalization, int Mx, int My, int Nx, int Ny, int i0, int j0, fftw_complex *output)
std::vector< double > fftfreq(int M, double normalization)
const int C[]
Definition: ssafd_code.cc:44
Parameters used to access elastic Green's function from the [Farrell] earth model.
Definition: greens.hh:56