PISM, A Parallel Ice Sheet Model  stable v2.1-1-g6902d5502 committed by Ed Bueler on 2023-12-20 08:38:27 -0800
ColumnSystem.cc
Go to the documentation of this file.
1 // Copyright (C) 2004-2023 PISM Authors
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 <cmath> // fabs()
20 #include <cassert>
21 #include <fstream>
22 #include <iostream>
23 #include <cstring> // memset
24 
25 #include "pism/util/pism_utilities.hh"
26 #include "pism/util/array/Array3D.hh"
27 #include "pism/util/ColumnSystem.hh"
28 
29 #include "pism/util/error_handling.hh"
30 #include "pism/util/ColumnInterpolation.hh"
31 
32 namespace pism {
33 
34 
35 //! Allocate a tridiagonal system of maximum size max_system_size.
36 /*!
37 Let N = `max_system_size`. Then allocated locations are like this:
38 \verbatim
39 D[0] U[0] 0 0 0 ...
40 L[1] D[1] U[1] 0 0 ...
41  0 L[2] D[2] U[2] 0 ...
42  0 0 L[3] D[3] U[3] ...
43 \endverbatim
44 with the last row
45 \verbatim
46 0 0 ... 0 L[N-1] D[N-1]
47 \endverbatim
48 Thus the index into the arrays L, D, U is always the row number.
49  */
51  const std::string &prefix)
52  : m_max_system_size(max_size), m_prefix(prefix) {
53  const unsigned int huge = 1e6;
54  assert(max_size >= 1 && max_size < huge);
55 
56  m_L.resize(m_max_system_size);
57  m_D.resize(m_max_system_size);
58  m_U.resize(m_max_system_size);
59  m_rhs.resize(m_max_system_size);
60  m_work.resize(m_max_system_size);
61 }
62 
63 //! Zero all entries.
65 #if Pism_DEBUG==1
66  memset(&m_L[0], 0, (m_max_system_size)*sizeof(double));
67  memset(&m_U[0], 0, (m_max_system_size)*sizeof(double));
68  memset(&m_D[0], 0, (m_max_system_size)*sizeof(double));
69  memset(&m_rhs[0], 0, (m_max_system_size)*sizeof(double));
70  memset(&m_work[0], 0, (m_max_system_size)*sizeof(double));
71 #endif
72 }
73 
74 //! Compute 1-norm, which is max sum of absolute values of columns.
75 double TridiagonalSystem::norm1(unsigned int system_size) const {
76  assert(system_size <= m_max_system_size);
77  if (system_size == 1) {
78  return fabs(m_D[0]); // only 1x1 case is special
79  }
80  double z = fabs(m_D[0]) + fabs(m_L[1]);
81  for (unsigned int k = 1; k < system_size; k++) { // k is column index (zero-based)
82  z = std::max(z, fabs(m_U[k-1])) + fabs(m_D[k]) + fabs(m_L[k+1]);
83  }
84  z = std::max(z, fabs(m_U[system_size-2]) + fabs(m_D[system_size-1]));
85  return z;
86 }
87 
88 
89 //! Compute diagonal-dominance ratio. If this is less than one then the matrix is strictly diagonally-dominant.
90 /*!
91 Let \f$A = (a_{ij})\f$ be the tridiagonal matrix
92 described by L, D, U for row indices 0 through `n`. The computed ratio is
93  \f[ \max_{j=1, \dots, n} \frac{|a_{j, j-1}|+|a_{j, j+1}|}{|a_{jj}|}, \f]
94 where \f$a_{1, 0}\f$ and \f$a_{n, n+1}\f$ are interpreted as zero.
95 
96 If this is smaller than one then it is a theorem that the tridiagonal solve will
97 succeed.
98 
99 We return -1.0 if the absolute value of any diagonal element is less than
100 1e-12 of the 1-norm of the matrix.
101  */
102 double TridiagonalSystem::ddratio(unsigned int system_size) const {
103  assert(system_size <= m_max_system_size);
104 
105  const double eps = 1.0e-12;
106  const double scale = norm1(system_size);
107 
108  if ((fabs(m_D[0]) / scale) < eps) {
109  return -1.0;
110  }
111  double z = fabs(m_U[0]) / fabs(m_D[0]);
112 
113  for (unsigned int k = 1; k < system_size - 1; k++) { // k is row index (zero-based)
114  if ((fabs(m_D[k]) / scale) < eps) {
115  return -1.0;
116  }
117  const double s = fabs(m_L[k]) + fabs(m_U[k]);
118  z = std::max(z, s / fabs(m_D[k]));
119  }
120 
121  if ((fabs(m_D[system_size - 1]) / scale) < eps) {
122  return -1.0;
123  }
124  z = std::max(z, fabs(m_L[system_size - 1]) / fabs(m_D[system_size - 1]));
125 
126  return z;
127 }
128 
129 //! Utility for simple ascii view of a vector (one-dimensional column) quantity.
130 /*!
131 Give first argument NULL to get standard out. No binary viewer.
132 
133 Give description string as `info` argument.
134 
135 Result should be executable as a Python (SciPy) script.
136 
137 Does not stop on non-fatal errors.
138  */
139 void TridiagonalSystem::save_vector(std::ostream &output,
140  const std::vector<double> &v,
141  unsigned int system_size,
142  const std::string &variable) {
143  assert(system_size >= 1);
144 
145  output << variable << " = numpy.array([";
146  for (unsigned int k = 0; k < system_size; k++) {
147  output << v[k] << ", ";
148  }
149  output << "])" << std::endl;
150 }
151 
152 
153 //! View the tridiagonal matrix.
154 void TridiagonalSystem::save_matrix(std::ostream &output,
155  unsigned int system_size,
156  const std::string &variable) const {
157 
158  if (system_size < 2) {
159  std::cout << "\n\n<nmax >= 2 required to view tri-diagonal matrix " << variable
160  << " ... skipping view" << std::endl;
161  return;
162  }
163 
164  save_vector(output, m_U, system_size, variable + "_U");
165  save_vector(output, m_D, system_size, variable + "_D");
166  save_vector(output, m_L, system_size, variable + "_L");
167 
168  // prepare to convert to a sparse matrix
169  output << variable << "_diagonals = ["
170  << variable << "_L[1:], "
171  << variable << "_D, "
172  << variable << "_U[:-1]]" << std::endl;
173  output << "import scipy.sparse" << std::endl;
174  output << variable << " = scipy.sparse.diags(" << variable << "_diagonals, [-1, 0, 1])" << std::endl;
175 }
176 
177 
178 //! View the tridiagonal system A x = b to an output stream, both A as a full matrix and b as a vector.
179 void TridiagonalSystem::save_system(std::ostream &output,
180  unsigned int system_size) const {
181  save_matrix(output, system_size, m_prefix + "_A");
182  save_vector(output, m_rhs, system_size, m_prefix + "_rhs");
183 }
184 
185 //! Write system matrix, right-hand-side, and (provided) solution into an already-named Python
186 //! script.
187 void TridiagonalSystem::save_system_with_solution(const std::string &filename,
188  unsigned int system_size,
189  const std::vector<double> &solution) {
190  std::ofstream output(filename.c_str());
191  output << "import numpy" << std::endl;
192  output << "# system has 1-norm = " << norm1(system_size)
193  << " and diagonal-dominance ratio = " << ddratio(system_size) << std::endl;
194 
195  save_system(output, system_size);
196  save_vector(output, solution, system_size, m_prefix + "_x");
197 }
198 
199 
200 void TridiagonalSystem::solve(unsigned int system_size, std::vector<double> &result) {
201  result.resize(m_max_system_size);
202 
203  solve(system_size, result.data());
204 }
205 
206 
207 //! The actual code for solving a tridiagonal system.
208 /*!
209 This is modified slightly from a Numerical Recipes version.
210 
211 Input size n is size of instance. Requires n <= TridiagonalSystem::m_max_system_size.
212 
213 Solution of system in x.
214  */
215 void TridiagonalSystem::solve(unsigned int system_size, double *result) {
216  assert(system_size >= 1);
217  assert(system_size <= m_max_system_size);
218 
219  if (m_D[0] == 0.0) {
220  throw RuntimeError(PISM_ERROR_LOCATION, "zero pivot at row 1");
221  }
222 
223  double b = m_D[0];
224 
225  result[0] = m_rhs[0] / b;
226  for (unsigned int k = 1; k < system_size; ++k) {
227  m_work[k] = m_U[k - 1] / b;
228 
229  b = m_D[k] - m_L[k] * m_work[k];
230 
231  if (b == 0.0) {
232  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "zero pivot at row %d", k + 1);
233  }
234 
235  result[k] = (m_rhs[k] - m_L[k] * result[k-1]) / b;
236  }
237 
238  for (int k = static_cast<int>(system_size) - 2; k >= 0; --k) {
239  result[k] -= m_work[k + 1] * result[k + 1];
240  }
241 }
242 
243 std::string TridiagonalSystem::prefix() const {
244  return m_prefix;
245 }
246 
247 //! A column system is a kind of a tridiagonal system.
248 columnSystemCtx::columnSystemCtx(const std::vector<double>& storage_grid,
249  const std::string &prefix,
250  double dx, double dy, double dt,
251  const array::Array3D &u3,
252  const array::Array3D &v3,
253  const array::Array3D &w3)
254  : m_dx(dx), m_dy(dy), m_dt(dt), m_u3(u3), m_v3(v3), m_w3(w3) {
255  assert(dx > 0.0);
256  assert(dy > 0.0);
257  assert(dt > 0.0);
258 
259  init_fine_grid(storage_grid);
260 
261  m_solver = new TridiagonalSystem(m_z.size(), prefix);
262 
263  m_interp = new ColumnInterpolation(storage_grid, m_z);
264 
265  m_u.resize(m_z.size());
266  m_v.resize(m_z.size());
267  m_w.resize(m_z.size());
268 }
269 
271  delete m_solver;
272  delete m_interp;
273 }
274 
275 unsigned int columnSystemCtx::ks() const {
276  return m_ks;
277 }
278 
279 double columnSystemCtx::dz() const {
280  return m_dz;
281 }
282 
283 const std::vector<double>& columnSystemCtx::z() const {
284  return m_z;
285 }
286 
287 void columnSystemCtx::fine_to_coarse(const std::vector<double> &input, int i, int j,
288  array::Array3D& output) const {
289  m_interp->fine_to_coarse(&input[0], output.get_column(i, j));
290 }
291 
292 void columnSystemCtx::coarse_to_fine(const array::Array3D &input, int i, int j,
293  double* output) const {
294  m_interp->coarse_to_fine(input.get_column(i, j), m_ks, output);
295 }
296 
297 void columnSystemCtx::init_fine_grid(const std::vector<double>& storage_grid) {
298  // Compute m_dz as the minimum vertical spacing in the coarse
299  // grid:
300  unsigned int Mz = storage_grid.size();
301  double Lz = storage_grid.back();
302  m_dz = Lz;
303  for (unsigned int k = 1; k < Mz; ++k) {
304  m_dz = std::min(m_dz, storage_grid[k] - storage_grid[k - 1]);
305  }
306 
307  size_t Mz_fine = static_cast<size_t>(ceil(Lz / m_dz) + 1);
308  m_dz = Lz / static_cast<double>(Mz_fine - 1);
309 
310  m_z.resize(Mz_fine);
311  // compute levels of the fine grid:
312  for (unsigned int k = 0; k < Mz_fine; ++k) {
313  m_z[k] = storage_grid[0] + k * m_dz;
314  }
315  // Note that it *is* allowed to go over Lz.
316 }
317 
319  double ice_thickness) {
320  m_i = i;
321  m_j = j;
322  m_ks = static_cast<unsigned int>(floor(ice_thickness / m_dz));
323 
324  // Force m_ks to be in the allowed range.
325  if (m_ks >= m_z.size()) {
326  m_ks = m_z.size() - 1;
327  }
328 
329  m_solver->reset();
330 
331  // post-condition
332 #if Pism_DEBUG==1
333  // check if m_ks is valid
334  if (m_ks >= m_z.size()) {
335  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "ks = %d computed at i = %d, j = %d is invalid,\n"
336  "possibly because of invalid ice thickness (%f meters) or dz (%f meters).",
337  m_ks, m_i, m_j, ice_thickness, m_dz);
338  }
339 #endif
340 }
341 
342 //! Write system matrix and right-hand-side into an Python script. The file name contains ZERO_PIVOT_ERROR.
344 
345  auto filename = pism::printf("%s_i%d_j%d_ZERO_PIVOT_ERROR.py",
346  m_solver->prefix().c_str(), m_i, m_j);
347 
348  std::ofstream output(filename);
349  output << "# system has 1-norm = " << m_solver->norm1(M)
350  << " and diagonal-dominance ratio = " << m_solver->ddratio(M) << std::endl;
351 
352  m_solver->save_system(output, M);
353 }
354 
355 
356 //! @brief Write system matrix, right-hand-side, and (provided)
357 //! solution into Python script. Constructs file name from m_prefix.
358 void columnSystemCtx::save_to_file(const std::vector<double> &x) {
359 
360  auto filename = pism::printf("%s_i%d_j%d.py", m_solver->prefix().c_str(), m_i, m_j);
361 
362  std::cout << "saving "
363  << m_solver->prefix() << " column system at (i,j)"
364  << " = (" << m_i << "," << m_j << ") to " << filename << " ...\n" << std::endl;
365 
366  this->save_to_file(filename, x);
367 }
368 
369 void columnSystemCtx::save_to_file(const std::string &filename,
370  const std::vector<double> &x) {
371  m_solver->save_system_with_solution(filename, m_z.size(), x);
372 }
373 
374 } // end of namespace pism
void coarse_to_fine(const double *input, unsigned int ks, double *result) const
void fine_to_coarse(const double *input, double *result) const
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
void save_matrix(std::ostream &output, unsigned int system_size, const std::string &variable) const
View the tridiagonal matrix.
void save_system(std::ostream &output, unsigned int system_size) const
View the tridiagonal system A x = b to an output stream, both A as a full matrix and b as a vector.
std::vector< double > m_work
std::string prefix() const
unsigned int m_max_system_size
TridiagonalSystem(unsigned int max_size, const std::string &prefix)
Allocate a tridiagonal system of maximum size max_system_size.
Definition: ColumnSystem.cc:50
void solve(unsigned int system_size, std::vector< double > &result)
double ddratio(unsigned int system_size) const
Compute diagonal-dominance ratio. If this is less than one then the matrix is strictly diagonally-dom...
double norm1(unsigned int system_size) const
Compute 1-norm, which is max sum of absolute values of columns.
Definition: ColumnSystem.cc:75
static void save_vector(std::ostream &output, const std::vector< double > &v, unsigned int system_size, const std::string &variable)
Utility for simple ascii view of a vector (one-dimensional column) quantity.
std::vector< double > m_D
std::vector< double > m_U
void reset()
Zero all entries.
Definition: ColumnSystem.cc:64
std::vector< double > m_L
void save_system_with_solution(const std::string &filename, unsigned int system_size, const std::vector< double > &solution)
std::vector< double > m_rhs
Virtual base class. Abstracts a tridiagonal system to solve in a column of ice and/or bedrock.
Definition: ColumnSystem.hh:86
double * get_column(int i, int j)
Definition: Array3D.cc:120
A virtual class collecting methods common to ice and bedrock 3D fields.
Definition: Array3D.hh:33
void coarse_to_fine(const array::Array3D &coarse, int i, int j, double *fine) const
const std::vector< double > & z() const
columnSystemCtx(const std::vector< double > &storage_grid, const std::string &prefix, double dx, double dy, double dt, const array::Array3D &u3, const array::Array3D &v3, const array::Array3D &w3)
A column system is a kind of a tridiagonal system.
void reportColumnZeroPivotErrorMFile(unsigned int M)
Write system matrix and right-hand-side into an Python script. The file name contains ZERO_PIVOT_ERRO...
void save_to_file(const std::vector< double > &x)
Write system matrix, right-hand-side, and (provided) solution into Python script. Constructs file nam...
std::vector< double > m_z
levels of the fine vertical grid
unsigned int m_ks
current system size; corresponds to the highest vertical level within the ice
std::vector< double > m_u
u-component of the ice velocity
void init_column(int i, int j, double ice_thickness)
unsigned int ks() const
void init_fine_grid(const std::vector< double > &storage_grid)
int m_i
current column indexes
TridiagonalSystem * m_solver
ColumnInterpolation * m_interp
std::vector< double > m_w
w-component of the ice velocity
void fine_to_coarse(const std::vector< double > &fine, int i, int j, array::Array3D &coarse) const
std::vector< double > m_v
v-component of the ice velocity
#define PISM_ERROR_LOCATION
double max(const array::Scalar &input)
Finds maximum over all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:165
double min(const array::Scalar &input)
Finds minimum over all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:193
std::string printf(const char *format,...)
static const double k
Definition: exactTestP.cc:42