PISM, A Parallel Ice Sheet Model  stable v2.1-1-g6902d5502 committed by Ed Bueler on 2023-12-20 08:38:27 -0800
BedSmoother.cc
Go to the documentation of this file.
1 // Copyright (C) 2010--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 
21 #include "pism/stressbalance/sia/BedSmoother.hh"
22 #include "pism/util/Context.hh"
23 #include "pism/util/Grid.hh"
24 #include "pism/util/Logger.hh"
25 #include "pism/util/array/CellType.hh"
26 #include "pism/util/error_handling.hh"
27 #include "pism/util/petscwrappers/Vec.hh"
28 #include "pism/util/pism_utilities.hh"
29 #include "pism/util/VariableMetadata.hh"
30 
31 namespace pism {
32 namespace stressbalance {
33 
34 BedSmoother::BedSmoother(std::shared_ptr<const Grid> g)
35  : m_grid(g),
36  m_config(g->ctx()->config()),
37  m_topgsmooth(m_grid, "topgsmooth"),
38  m_maxtl(m_grid, "maxtl"),
39  m_C2(m_grid, "C2bedsmooth"),
40  m_C3(m_grid, "C3bedsmooth"),
41  m_C4(m_grid, "C3bedsmooth")
42 {
43 
44  const Logger &log = *m_grid->ctx()->log();
45 
46  {
47  // allocate Vecs that live on all procs; all have to be as "wide" as any of
48  // their prospective uses
50  .long_name("smoothed bed elevation, in bed roughness parameterization")
51  .units("m");
52  m_maxtl.metadata(0)
53  .long_name("maximum elevation in local topography patch, in bed roughness parameterization")
54  .units("m");
55  m_C2.metadata(0)
56  .long_name("polynomial coeff of H^-2, in bed roughness parameterization")
57  .units("m2");
58  m_C3.metadata(0)
59  .long_name("polynomial coeff of H^-3, in bed roughness parameterization")
60  .units("m3");
61  m_C4.metadata(0)
62  .long_name("polynomial coeff of H^-4, in bed roughness parameterization")
63  .units("m4");
64 
65  // allocate Vecs that live on processor 0:
72  }
73 
74  m_Glen_exponent = m_config->get_number("stress_balance.sia.Glen_exponent"); // choice is SIA; see #285
75  m_smoothing_range = m_config->get_number("stress_balance.sia.bed_smoother.range");
76 
77  if (m_smoothing_range > 0.0) {
78  log.message(2,
79  "* Initializing bed smoother object with %.3f km half-width ...\n",
80  units::convert(m_grid->ctx()->unit_system(), m_smoothing_range, "m", "km"));
81  }
82 
83  // Make sure that Nx and Ny are initialized. In most cases SIAFD::update() will call
84  // preprocess_bed() and set appropriate values, but in a zero-length (-y 0) run IceModel does not
85  // call SIAFD::update()... We may need to re-structure this class so that everything is
86  // initialized right after construction and users don't have to call preprocess_bed() manually.
87  m_Nx = -1;
88  m_Ny = -1;
89 }
90 
91 
92 /*!
93 Input lambda gives physical half-width (in m) of square over which to do the
94 average. Only square smoothing domains are allowed with this call, which is the
95 default case.
96  */
98 
99  if (m_smoothing_range <= 0.0) {
100  // smoothing completely inactive. we transfer the original bed topg,
101  // including ghosts, to public member topgsmooth ...
102  m_topgsmooth.copy_from(topg);
103  // and we tell theta() to return theta=1
104  m_Nx = -1;
105  m_Ny = -1;
106  return;
107  }
108 
109  // determine Nx, Ny, which are always at least one if m_smoothing_range > 0
110  m_Nx = static_cast<int>(ceil(m_smoothing_range / m_grid->dx()));
111  m_Ny = static_cast<int>(ceil(m_smoothing_range / m_grid->dy()));
112  if (m_Nx < 1) {
113  m_Nx = 1;
114  }
115  if (m_Ny < 1) {
116  m_Ny = 1;
117  }
118 
119  preprocess_bed(topg, m_Nx, m_Ny);
120 }
121 
123  return m_topgsmooth;
124 }
125 
126 /*!
127 Inputs Nx,Ny gives half-width in number of grid points, over which to do the
128 average.
129  */
131  unsigned int Nx, unsigned int Ny) {
132 
133  if ((Nx >= m_grid->Mx()) || (Ny >= m_grid->My())) {
134  throw RuntimeError(PISM_ERROR_LOCATION, "input Nx, Ny in bed smoother is too large because\n"
135  "domain of smoothing exceeds Grid domain");
136  }
137  m_Nx = Nx;
138  m_Ny = Ny;
139 
140  topg.put_on_proc0(*m_topgp0);
142  // next call *does indeed* fill ghosts in topgsmooth
144 
146  // following calls *do* fill the ghosts
151 }
152 
153 
154 //! Computes the smoothed bed by a simple average over a rectangle of grid points.
156 
157  ParallelSection rank0(m_grid->com);
158  try {
159  if (m_grid->rank() == 0) {
160  const int Mx = (int)m_grid->Mx();
161  const int My = (int)m_grid->My();
162 
164  b0(*m_topgp0, Mx, My),
165  bs(*m_topgsmoothp0, Mx, My);
166 
167  for (int j=0; j < My; j++) {
168  for (int i=0; i < Mx; i++) {
169  // average only over those points which are in the grid; do
170  // not wrap periodically
171  double sum = 0.0, count = 0.0;
172  for (int r = -m_Nx; r <= m_Nx; r++) {
173  for (int s = -m_Ny; s <= m_Ny; s++) {
174  if ((i+r >= 0) and (i+r < Mx) and (j+s >= 0) and (j+s < My)) {
175  sum += b0(i+r, j+s);
176  count += 1.0;
177  }
178  }
179  }
180  // unprotected division by count but r=0,s=0 case guarantees count>=1
181  bs(i, j) = sum / count;
182  }
183  }
184  }
185  } catch (...) {
186  rank0.failed();
187  }
188  rank0.check();
189 }
190 
191 
193 
194  const unsigned int Mx = m_grid->Mx(), My = m_grid->My();
195 
196  ParallelSection rank0(m_grid->com);
197  try {
198  if (m_grid->rank() == 0) {
200  b0(*m_topgp0, Mx, My),
201  bs(*m_topgsmoothp0, Mx, My),
202  mt(*m_maxtlp0, Mx, My),
203  c2(*m_C2p0, Mx, My),
204  c3(*m_C3p0, Mx, My),
205  c4(*m_C4p0, Mx, My);
206 
207  for (int j=0; j < (int)My; j++) {
208  for (int i=0; i < (int)Mx; i++) {
209  // average only over those points which are in the grid
210  // do not wrap periodically
211  double
212  topgs = bs(i, j),
213  maxtltemp = 0.0,
214  sum2 = 0.0,
215  sum3 = 0.0,
216  sum4 = 0.0,
217  count = 0.0;
218 
219  for (int r = -m_Nx; r <= m_Nx; r++) {
220  for (int s = -m_Ny; s <= m_Ny; s++) {
221  if ((i+r >= 0) && (i+r < (int)Mx) && (j+s >= 0) && (j+s < (int)My)) {
222  // tl is elevation of local topography at a pt in patch
223  const double tl = b0(i+r, j+s) - topgs;
224  maxtltemp = std::max(maxtltemp, tl);
225  // accumulate 2nd, 3rd, and 4th powers with only 3 multiplications
226  const double tl2 = tl * tl;
227  sum2 += tl2;
228  sum3 += tl2 * tl;
229  sum4 += tl2 * tl2;
230  count += 1.0;
231  }
232  }
233  }
234  mt(i, j) = maxtltemp;
235 
236  // unprotected division by count but r=0,s=0 case guarantees count>=1
237  c2(i, j) = sum2 / count;
238  c3(i, j) = sum3 / count;
239  c4(i, j) = sum4 / count;
240  }
241  }
242 
243  // scale the coeffs in Taylor series
244  const double
245  n = m_Glen_exponent,
246  k = (n + 2) / n,
247  s2 = k * (2 * n + 2) / (2 * n),
248  s3 = s2 * (3 * n + 2) / (3 * n),
249  s4 = s3 * (4 * n + 2) / (4 * n);
250 
251  PetscErrorCode ierr;
252  ierr = VecScale(*m_C2p0,s2);
253  PISM_CHK(ierr, "VecScale");
254 
255  ierr = VecScale(*m_C3p0,s3);
256  PISM_CHK(ierr, "VecScale");
257 
258  ierr = VecScale(*m_C4p0,s4);
259  PISM_CHK(ierr, "VecScale");
260  }
261  } catch (...) {
262  rank0.failed();
263  }
264  rank0.check();
265 }
266 
267 
268 //! Computes a smoothed thickness map.
269 /*!
270 The result `thksmooth` is the difference between the given upper surface
271 elevation (`usurf`) and the stored smoothed bed topography (`topgsmooth`),
272 except where the given original thickness (`thk`) is zero. In places where
273 the original thickness is zero, the result `thksmooth` is also set to zero.
274 
275 Ghosted values are updated directly and no communication occurs. In fact,
276 we \e assume `usurf`, `thk`, and `thksmooth` all have stencil width at least
277 equal to GHOSTS. We \e check whether `topgsmooth`, which has stencil width
278 maxGHOSTS, has at least GHOSTS stencil width, and throw an error if not.
279 
280 Call preprocess_bed() first.
281  */
283  const array::Scalar &thk,
284  const array::CellType2 &mask,
285  array::Scalar &result) const {
286 
287  array::AccessScope list{&mask, &m_maxtl, &result, &thk, &m_topgsmooth, &usurf};
288 
289  auto GHOSTS = result.stencil_width();
290  assert(mask.stencil_width() >= GHOSTS);
291  assert(m_maxtl.stencil_width() >= GHOSTS);
292  assert(thk.stencil_width() >= GHOSTS);
293  assert(m_topgsmooth.stencil_width() >= GHOSTS);
294  assert(usurf.stencil_width() >= GHOSTS);
295 
296  ParallelSection loop(m_grid->com);
297  try {
298  for (auto p = m_grid->points(GHOSTS); p; p.next()) {
299  const int i = p.i(), j = p.j();
300 
301  if (thk(i, j) < 0.0) {
302  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "BedSmoother detects negative original thickness\n"
303  "at location (i, j) = (%d, %d) ... ending", i, j);
304  }
305 
306  if (thk(i, j) == 0.0) {
307  result(i, j) = 0.0;
308  } else if (m_maxtl(i, j) >= thk(i, j)) {
309  result(i, j) = thk(i, j);
310  } else {
311  if (mask.grounded(i, j)) {
312  // if grounded, compute smoothed thickness as the difference of ice surface
313  // elevation and smoothed bed elevation, making sure the result is non-negative
314  result(i, j) = std::max(usurf(i, j) - m_topgsmooth(i, j), 0.0);
315  } else {
316  // if floating, use original thickness (note: surface elevation was
317  // computed using this thickness and the sea level elevation)
318  result(i, j) = thk(i, j);
319  }
320  }
321  }
322  } catch (...) {
323  loop.failed();
324  }
325  loop.check();
326 }
327 
328 
329 /*!
330 Implements the strategy for computing \f$\theta(h,x,y)\f$ from previously-
331 stored coefficients, described on [Bed roughness parameterization](@ref bedrough) page and in [\ref
332 Schoofbasaltopg2003].
333 
334 Specifically, \f$\theta = \omega^{-n}\f$ where \f$\omega\f$ is a local average
335 of a rational function of surface elevation, approximated here by a Taylor polynomial:
336  \f[ \omega = \fint \left(1 - \frac{\tilde b(x_1,x_2,\xi_1,\xi_2)}{H}
337  \right)^{-(n+2)/n}\,d\xi_1\,d\xi_2
338  \approx 1 + C_2 H^{-2} + C_3 H^{-3} + C_4 H^{-4} \f]
339 where \f$h =\f$ usurf, \f$H = h -\f$ topgsmooth and \f$\tilde b\f$ is the local
340 bed topography, a function with mean zero. The coefficients \f$C_2,C_3,C_4\f$,
341 which depend on \f$x,y\f$, are precomputed by `preprocess_bed()`.
342 
343 Ghosted values are updated directly and no communication occurs. In fact,
344 we \e assume `usurf` and `theta` have stencil width at least
345 equal to GHOSTS. We \e check whether `topgsmooth`, which has stencil width
346 maxGHOSTS, has at least GHOSTS stencil width, and throw an error if not.
347 
348 Call preprocess_bed() first.
349  */
350 void BedSmoother::theta(const array::Scalar &usurf, array::Scalar &result) const {
351 
352  if ((m_Nx < 0) || (m_Ny < 0)) {
353  result.set(1.0);
354  return;
355  }
356 
357  array::AccessScope list{&m_C2, &m_C3, &m_C4, &m_maxtl, &result, &m_topgsmooth, &usurf};
358 
359  unsigned int GHOSTS = result.stencil_width();
360  assert(m_C2.stencil_width() >= GHOSTS);
361  assert(m_C3.stencil_width() >= GHOSTS);
362  assert(m_C4.stencil_width() >= GHOSTS);
363  assert(m_maxtl.stencil_width() >= GHOSTS);
364  assert(m_topgsmooth.stencil_width() >= GHOSTS);
365  assert(usurf.stencil_width() >= GHOSTS);
366 
367  const double
368  theta_min = m_config->get_number("stress_balance.sia.bed_smoother.theta_min"),
369  theta_max = 1.0;
370 
371  ParallelSection loop(m_grid->com);
372  try {
373  for (auto p = m_grid->points(GHOSTS); p; p.next()) {
374  const int i = p.i(), j = p.j();
375 
376  const double H = usurf(i, j) - m_topgsmooth(i, j);
377  if (H > m_maxtl(i, j)) {
378  // thickness exceeds maximum variation in patch of local topography,
379  // so ice buries local topography; note maxtl >= 0 always
380  const double Hinv = 1.0 / std::max(H, 1.0);
381  double omega = 1.0 + Hinv*Hinv * (m_C2(i, j) + Hinv * (m_C3(i, j) + Hinv*m_C4(i, j)));
382  if (omega <= 0) { // this check *should not* be necessary: p4(s) > 0
384  "omega is negative for i=%d, j=%d\n"
385  "in BedSmoother.theta()", i, j);
386  }
387 
388  if (omega < 0.001) { // this check *should not* be necessary
389  omega = 0.001;
390  }
391 
392  result(i, j) = pow(omega, -m_Glen_exponent);
393  } else {
394  result(i, j) = 0.00;
395  }
396 
397  result(i, j) = clip(result(i, j), theta_min, theta_max);
398  }
399  } catch (...) {
400  loop.failed();
401  }
402  loop.check();
403 }
404 
405 
406 } // end of namespace stressbalance
407 } // end of namespace pism
void message(int threshold, const char format[],...) const __attribute__((format(printf
Print a message to the log.
Definition: Logger.cc:49
A basic logging class.
Definition: Logger.hh:40
void failed()
Indicates a failure of a parallel section.
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
VariableMetadata & long_name(const std::string &input)
VariableMetadata & units(const std::string &input)
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 get_from_proc0(petsc::Vec &onp0)
Gets a local Array2 from processor 0.
Definition: Array.cc:1095
void set(double c)
Result: v[j] <- c for all j.
Definition: Array.cc:707
std::shared_ptr< petsc::Vec > allocate_proc0_copy() const
Definition: Array.cc:963
void put_on_proc0(petsc::Vec &onp0) const
Puts a local array::Scalar on processor 0.
Definition: Array.cc:1052
unsigned int stencil_width() const
Get the stencil width of the current Array. Returns 0 if ghosts are not available.
Definition: Array.cc:331
SpatialVariableMetadata & metadata(unsigned int N=0)
Returns a reference to the SpatialVariableMetadata object containing metadata for the compoment N.
Definition: Array.cc:553
bool grounded(int i, int j) const
Definition: CellType.hh:38
Wrapper around VecGetArray2d and VecRestoreArray2d.
Definition: Vec.hh:55
std::shared_ptr< petsc::Vec > m_C3p0
Definition: BedSmoother.hh:121
BedSmoother(std::shared_ptr< const Grid > g)
Definition: BedSmoother.cc:34
void smoothed_thk(const array::Scalar &usurf, const array::Scalar &thk, const array::CellType2 &mask, array::Scalar &thksmooth) const
Computes a smoothed thickness map.
Definition: BedSmoother.cc:282
std::shared_ptr< petsc::Vec > m_topgp0
original bed elevation on processor 0
Definition: BedSmoother.hh:117
array::Scalar2 m_topgsmooth
smoothed bed elevation; set by calling preprocess_bed()
Definition: BedSmoother.hh:105
std::shared_ptr< petsc::Vec > m_maxtlp0
maximum elevation at (i,j) of local topography (nearby patch)
Definition: BedSmoother.hh:121
void smooth_the_bed_on_proc0()
Computes the smoothed bed by a simple average over a rectangle of grid points.
Definition: BedSmoother.cc:155
const array::Scalar & smoothed_bed() const
Definition: BedSmoother.cc:122
void theta(const array::Scalar &usurf, array::Scalar &result) const
Definition: BedSmoother.cc:350
const Config::ConstPtr m_config
Definition: BedSmoother.hh:102
std::shared_ptr< petsc::Vec > m_topgsmoothp0
smoothed bed elevation on processor 0
Definition: BedSmoother.hh:119
std::shared_ptr< petsc::Vec > m_C4p0
Definition: BedSmoother.hh:121
std::shared_ptr< const Grid > m_grid
Definition: BedSmoother.hh:101
void preprocess_bed(const array::Scalar &topg)
Definition: BedSmoother.cc:97
std::shared_ptr< petsc::Vec > m_C2p0
Definition: BedSmoother.hh:121
#define PISM_CHK(errcode, name)
#define PISM_ERROR_LOCATION
static const double b0
Definition: exactTestL.cc:41
#define n
Definition: exactTestM.c:37
double max(const array::Scalar &input)
Finds maximum over all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:165
double sum(const array::Scalar &input)
Sums up all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:150
double convert(System::Ptr system, double input, const std::string &spec1, const std::string &spec2)
Convert a quantity from unit1 to unit2.
Definition: Units.cc:70
static const double g
Definition: exactTestP.cc:36
T clip(T x, T a, T b)
static const double c2
Definition: exactTestP.cc:45
static const double k
Definition: exactTestP.cc:42
int count
Definition: test_cube.c:16