Ifpack2 Templated Preconditioning Package  Version 1.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
Ifpack2_PowerMethod.hpp
Go to the documentation of this file.
1 // @HEADER
2 // *****************************************************************************
3 // Ifpack2: Templated Object-Oriented Algebraic Preconditioner Package
4 //
5 // Copyright 2009 NTESS and the Ifpack2 contributors.
6 // SPDX-License-Identifier: BSD-3-Clause
7 // *****************************************************************************
8 // @HEADER
9 
10 #ifndef IFPACK2_POWERMETHOD_HPP
11 #define IFPACK2_POWERMETHOD_HPP
12 
19 
20 #include "Kokkos_ArithTraits.hpp"
21 #include "Teuchos_FancyOStream.hpp"
22 #include "Teuchos_oblackholestream.hpp"
23 #include "Tpetra_Details_residual.hpp"
24 #include <cmath>
25 #include <iostream>
26 
27 namespace Ifpack2 {
28 namespace PowerMethod {
29 
30 namespace { // (anonymous)
31 
32 // Functor for making sure the real parts of all entries of a vector
33 // are nonnegative. We use this in computeInitialGuessForPowerMethod
34 // below.
35 template <class OneDViewType,
36  class LocalOrdinal = typename OneDViewType::size_type>
37 class PositivizeVector {
38  static_assert(Kokkos::is_view<OneDViewType>::value,
39  "OneDViewType must be a 1-D Kokkos::View.");
40  static_assert(static_cast<int>(OneDViewType::rank) == 1,
41  "This functor only works with a 1-D View.");
42  static_assert(std::is_integral<LocalOrdinal>::value,
43  "The second template parameter, LocalOrdinal, "
44  "must be an integer type.");
45 
46  public:
47  PositivizeVector(const OneDViewType& x)
48  : x_(x) {}
49 
50  KOKKOS_INLINE_FUNCTION void
51  operator()(const LocalOrdinal& i) const {
52  typedef typename OneDViewType::non_const_value_type IST;
53  typedef Kokkos::ArithTraits<IST> STS;
54  typedef Kokkos::ArithTraits<typename STS::mag_type> STM;
55 
56  if (STS::real(x_(i)) < STM::zero()) {
57  x_(i) = -x_(i);
58  }
59  }
60 
61  private:
62  OneDViewType x_;
63 };
64 
65 } // namespace
66 
86 template <class OperatorType, class V>
87 typename V::scalar_type
88 powerMethodWithInitGuess(const OperatorType& A,
89  const V& D_inv,
90  const int numIters,
94  const int eigNormalizationFreq = 1,
95  Teuchos::RCP<Teuchos::FancyOStream> out = Teuchos::null,
96  const bool computeSpectralRadius = true) {
97  typedef typename V::scalar_type ST;
100 
101  bool verbose = (out != Teuchos::null);
102 
103  using std::endl;
104  if (verbose) {
105  *out << " powerMethodWithInitGuess:" << endl;
106  }
107 
108  const ST zero = static_cast<ST>(0.0);
109  const ST one = static_cast<ST>(1.0);
110  ST lambdaMax = zero;
111  ST lambdaMaxOld = one;
112  ST norm;
113 
114  norm = x->norm2();
115  TEUCHOS_TEST_FOR_EXCEPTION(norm == zero, std::runtime_error,
116  "Ifpack2::PowerMethod::powerMethodWithInitGuess: The initial guess "
117  "has zero norm. This could be either because Tpetra::Vector::"
118  "randomize filled the vector with zeros (if that was used to "
119  "compute the initial guess), or because the norm2 method has a "
120  "bug. The first is not impossible, but unlikely.");
121 
122  if (verbose) {
123  *out << " Original norm1(x): " << x->norm1()
124  << ", norm2(x): " << norm << endl;
125  }
126 
127  x->scale(one / norm);
128 
129  if (verbose) {
130  *out << " norm1(x.scale(one/norm)): " << x->norm1() << endl;
131  }
132 
133  if (y.is_null())
134  y = Teuchos::rcp(new V(A.getRangeMap()));
135 
136  // GH 04 Nov 2022: The previous implementation of the power method was inconsistent with itself.
137  // It computed x->norm2() inside the loop, but returned x^T*Ax.
138  // This returned horribly incorrect estimates for Chebyshev spectral radius in certain
139  // cases, such as the Cheby_belos test, which has two dominant eigenvalues of 12.5839, -10.6639.
140  // In such case, the power method iteration history would appear to converge to something
141  // between 10 and 12, but the resulting spectral radius estimate was sometimes negative.
142  // These have now been split into two routes which behave consistently with themselves.
143  if (computeSpectralRadius) {
144  // In this route, the update is as follows:
145  // y=A*x, x = Dinv*y, lambda = norm(x), x = x/lambda
146  if (verbose) {
147  *out << " PowerMethod using spectral radius route" << endl;
148  }
149  for (int iter = 0; iter < numIters - 1; ++iter) {
150  if (verbose) {
151  *out << " Iteration " << iter << endl;
152  }
153  A.apply(*x, *y);
154  x->elementWiseMultiply(STS::one(), D_inv, *y, STS::zero());
155 
156  if (((iter + 1) % eigNormalizationFreq == 0) && (iter < numIters - 2)) {
157  norm = x->norm2();
158  if (norm == zero) { // Return something reasonable.
159  if (verbose) {
160  *out << " norm is zero; returning zero" << endl;
161  *out << " Power method terminated after " << iter << " iterations." << endl;
162  }
163  return zero;
164  } else {
165  lambdaMaxOld = lambdaMax;
166  lambdaMax = pow(norm, Teuchos::ScalarTraits<MT>::one() / eigNormalizationFreq);
167  if (Teuchos::ScalarTraits<ST>::magnitude(lambdaMax - lambdaMaxOld) < tolerance * Teuchos::ScalarTraits<ST>::magnitude(lambdaMax)) {
168  if (verbose) {
169  *out << " lambdaMax: " << lambdaMax << endl;
170  *out << " Power method terminated after " << iter << " iterations." << endl;
171  }
172  return lambdaMax;
173  } else if (verbose) {
174  *out << " lambdaMaxOld: " << lambdaMaxOld << endl;
175  *out << " lambdaMax: " << lambdaMax << endl;
176  *out << " |lambdaMax-lambdaMaxOld|/|lambdaMax|: " << Teuchos::ScalarTraits<ST>::magnitude(lambdaMax - lambdaMaxOld) / Teuchos::ScalarTraits<ST>::magnitude(lambdaMax) << endl;
177  }
178  }
179  x->scale(one / norm);
180  }
181  }
182  if (verbose) {
183  *out << " lambdaMax: " << lambdaMax << endl;
184  }
185 
186  norm = x->norm2();
187  if (norm == zero) { // Return something reasonable.
188  if (verbose) {
189  *out << " norm is zero; returning zero" << endl;
190  *out << " Power method terminated after " << numIters << " iterations." << endl;
191  }
192  return zero;
193  }
194  x->scale(one / norm);
195  A.apply(*x, *y);
196  y->elementWiseMultiply(STS::one(), D_inv, *y, STS::zero());
197  lambdaMax = y->norm2();
198  } else {
199  // In this route, the update is as follows:
200  // y=A*x, y = Dinv*y, lambda = x->dot(y), x = y/lambda
201  ST xDinvAx = norm;
202  if (verbose) {
203  *out << " PowerMethod using largest eigenvalue route" << endl;
204  }
205  for (int iter = 0; iter < numIters - 1; ++iter) {
206  if (verbose) {
207  *out << " Iteration " << iter << endl;
208  }
209  A.apply(*x, *y);
210  y->elementWiseMultiply(STS::one(), D_inv, *y, STS::zero());
211 
212  if (((iter + 1) % eigNormalizationFreq == 0) && (iter < numIters - 2)) {
213  xDinvAx = x->dot(*y);
214  if (xDinvAx == zero) { // Return something reasonable.
215  if (verbose) {
216  *out << " xDinvAx is zero; returning zero" << endl;
217  *out << " Power method terminated after " << iter << " iterations." << endl;
218  }
219  return zero;
220  } else {
221  lambdaMaxOld = lambdaMax;
222  lambdaMax = pow(xDinvAx, Teuchos::ScalarTraits<MT>::one() / eigNormalizationFreq);
223  if (Teuchos::ScalarTraits<ST>::magnitude(lambdaMax - lambdaMaxOld) < tolerance * Teuchos::ScalarTraits<ST>::magnitude(lambdaMax)) {
224  if (verbose) {
225  *out << " lambdaMax: " << lambdaMax << endl;
226  *out << " Power method terminated after " << iter << " iterations." << endl;
227  }
228  return lambdaMax;
229  } else if (verbose) {
230  *out << " lambdaMaxOld: " << lambdaMaxOld << endl;
231  *out << " lambdaMax: " << lambdaMax << endl;
232  *out << " |lambdaMax-lambdaMaxOld|/|lambdaMax|: " << Teuchos::ScalarTraits<ST>::magnitude(lambdaMax - lambdaMaxOld) / Teuchos::ScalarTraits<ST>::magnitude(lambdaMax) << endl;
233  }
234  }
235  x->swap(*y);
236  norm = x->norm2();
237  x->scale(one / norm);
238  }
239  }
240  if (verbose) {
241  *out << " lambdaMax: " << lambdaMax << endl;
242  }
243 
244  norm = x->norm2();
245  if (norm == zero) { // Return something reasonable.
246  if (verbose) {
247  *out << " norm is zero; returning zero" << endl;
248  *out << " Power method terminated after " << numIters << " iterations." << endl;
249  }
250  return zero;
251  }
252  x->scale(one / norm);
253  A.apply(*x, *y);
254  y->elementWiseMultiply(STS::one(), D_inv, *y, STS::zero());
255  lambdaMax = y->dot(*x);
256  }
257 
258  if (verbose) {
259  *out << " lambdaMax: " << lambdaMax << endl;
260  *out << " Power method terminated after " << numIters << " iterations." << endl;
261  }
262 
263  return lambdaMax;
264 }
265 
277 template <class V>
278 void computeInitialGuessForPowerMethod(V& x, const bool nonnegativeRealParts) {
279  typedef typename V::device_type::execution_space dev_execution_space;
280  typedef typename V::local_ordinal_type LO;
281 
282  x.randomize();
283 
284  if (nonnegativeRealParts) {
285  auto x_lcl = x.getLocalViewDevice(Tpetra::Access::ReadWrite);
286  auto x_lcl_1d = Kokkos::subview(x_lcl, Kokkos::ALL(), 0);
287 
288  const LO lclNumRows = static_cast<LO>(x.getLocalLength());
289  Kokkos::RangePolicy<dev_execution_space, LO> range(0, lclNumRows);
290  PositivizeVector<decltype(x_lcl_1d), LO> functor(x_lcl_1d);
291  Kokkos::parallel_for(range, functor);
292  }
293 }
294 
311 template <class OperatorType, class V>
312 typename V::scalar_type
313 powerMethod(const OperatorType& A,
314  const V& D_inv,
315  const int numIters,
316  Teuchos::RCP<V> y,
318  const int eigNormalizationFreq = 1,
319  Teuchos::RCP<Teuchos::FancyOStream> out = Teuchos::null,
320  const bool computeSpectralRadius = true) {
321  typedef typename V::scalar_type ST;
323 
324  bool verbose = (out != Teuchos::null);
325 
326  if (verbose) {
327  *out << "powerMethod:" << std::endl;
328  }
329 
330  const ST zero = static_cast<ST>(0.0);
331 
332  Teuchos::RCP<V> x = Teuchos::rcp(new V(A.getDomainMap()));
333  // For the first pass, just let the pseudorandom number generator
334  // fill x with whatever values it wants; don't try to make its
335  // entries nonnegative.
336  computeInitialGuessForPowerMethod(*x, false);
337 
338  ST lambdaMax = powerMethodWithInitGuess(A, D_inv, numIters, x, y, tolerance, eigNormalizationFreq, out, computeSpectralRadius);
339 
340  // mfh 07 Jan 2015: Taking the real part here is only a concession
341  // to the compiler, so that this can build with ScalarType =
342  // std::complex<T>. Our Chebyshev implementation only works with
343  // real, symmetric positive definite matrices. The right thing to
344  // do would be what Belos does, which is provide a partial
345  // specialization for ScalarType = std::complex<T> with a stub
346  // implementation (that builds, but whose constructor throws).
347  if (STS::real(lambdaMax) < STS::real(zero)) {
348  if (verbose) {
349  *out << "real(lambdaMax) = " << STS::real(lambdaMax) << " < 0; "
350  "try again with a different random initial guess"
351  << std::endl;
352  }
353  // Max eigenvalue estimate was negative. Perhaps we got unlucky
354  // with the random initial guess. Try again with a different (but
355  // still random) initial guess. Only try again once, so that the
356  // run time is bounded.
357 
358  // For the second pass, make all the entries of the initial guess
359  // vector have nonnegative real parts.
360  computeInitialGuessForPowerMethod(*x, true);
361  lambdaMax = powerMethodWithInitGuess(A, D_inv, numIters, x, y, tolerance, eigNormalizationFreq, out, computeSpectralRadius);
362  }
363  return lambdaMax;
364 }
365 
366 } // namespace PowerMethod
367 } // namespace Ifpack2
368 
369 #endif // IFPACK2_POWERMETHOD_HPP
#define TEUCHOS_TEST_FOR_EXCEPTION(throw_exception_test, Exception, msg)
void swap(RCP< T > &r_ptr)
TEUCHOS_DEPRECATED RCP< T > rcp(T *p, Dealloc_T dealloc, bool owns_mem)
static magnitudeType magnitude(T a)
bool is_null() const