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 public:
46  PositivizeVector (const OneDViewType& x) : x_ (x) {}
47 
48  KOKKOS_INLINE_FUNCTION void
49  operator () (const LocalOrdinal& i) const
50  {
51  typedef typename OneDViewType::non_const_value_type IST;
52  typedef Kokkos::ArithTraits<IST> STS;
53  typedef Kokkos::ArithTraits<typename STS::mag_type> STM;
54 
55  if(STS::real (x_(i)) < STM::zero ()) {
56  x_(i) = -x_(i);
57  }
58  }
59 
60 private:
61  OneDViewType x_;
62 };
63 
64 } // namespace (anonymous)
65 
66 
67 
87 template<class OperatorType, class V>
88 typename V::scalar_type
89 powerMethodWithInitGuess(const OperatorType& A,
90  const V& D_inv,
91  const int numIters,
95  const int eigNormalizationFreq = 1,
96  Teuchos::RCP<Teuchos::FancyOStream> out = Teuchos::null,
97  const bool computeSpectralRadius = true)
98 {
99  typedef typename V::scalar_type ST;
102 
103  bool verbose = (out != Teuchos::null);
104 
105  using std::endl;
106  if(verbose) {
107  *out << " powerMethodWithInitGuess:" << endl;
108  }
109 
110  const ST zero = static_cast<ST> (0.0);
111  const ST one = static_cast<ST> (1.0);
112  ST lambdaMax = zero;
113  ST lambdaMaxOld = one;
114  ST norm;
115 
116  norm = x->norm2();
118  (norm == zero, std::runtime_error,
119  "Ifpack2::PowerMethod::powerMethodWithInitGuess: The initial guess "
120  "has zero norm. This could be either because Tpetra::Vector::"
121  "randomize filled the vector with zeros (if that was used to "
122  "compute the initial guess), or because the norm2 method has a "
123  "bug. The first is not impossible, but unlikely.");
124 
125  if(verbose) {
126  *out << " Original norm1(x): " << x->norm1()
127  << ", norm2(x): " << norm << endl;
128  }
129 
130  x->scale(one / norm);
131 
132  if(verbose) {
133  *out << " norm1(x.scale(one/norm)): " << x->norm1() << endl;
134  }
135 
136  if(y.is_null())
137  y = Teuchos::rcp(new V(A.getRangeMap()));
138 
139  // GH 04 Nov 2022: The previous implementation of the power method was inconsistent with itself.
140  // It computed x->norm2() inside the loop, but returned x^T*Ax.
141  // This returned horribly incorrect estimates for Chebyshev spectral radius in certain
142  // cases, such as the Cheby_belos test, which has two dominant eigenvalues of 12.5839, -10.6639.
143  // In such case, the power method iteration history would appear to converge to something
144  // between 10 and 12, but the resulting spectral radius estimate was sometimes negative.
145  // These have now been split into two routes which behave consistently with themselves.
146  if(computeSpectralRadius) {
147  // In this route, the update is as follows:
148  // y=A*x, x = Dinv*y, lambda = norm(x), x = x/lambda
149  if(verbose) {
150  *out << " PowerMethod using spectral radius route" << endl;
151  }
152  for(int iter = 0; iter < numIters-1; ++iter) {
153  if(verbose) {
154  *out << " Iteration " << iter << endl;
155  }
156  A.apply(*x, *y);
157  x->elementWiseMultiply(STS::one(), D_inv, *y, STS::zero());
158 
159  if(((iter+1) % eigNormalizationFreq == 0) && (iter < numIters-2)) {
160  norm = x->norm2();
161  if(norm == zero) { // Return something reasonable.
162  if(verbose) {
163  *out << " norm is zero; returning zero" << endl;
164  *out << " Power method terminated after "<< iter << " iterations." << endl;
165  }
166  return zero;
167  } else {
168  lambdaMaxOld = lambdaMax;
169  lambdaMax = pow(norm, Teuchos::ScalarTraits<MT>::one() / eigNormalizationFreq);
170  if(Teuchos::ScalarTraits<ST>::magnitude(lambdaMax-lambdaMaxOld) < tolerance * Teuchos::ScalarTraits<ST>::magnitude(lambdaMax)) {
171  if(verbose) {
172  *out << " lambdaMax: " << lambdaMax << endl;
173  *out << " Power method terminated after "<< iter << " iterations." << endl;
174  }
175  return lambdaMax;
176  } else if(verbose) {
177  *out << " lambdaMaxOld: " << lambdaMaxOld << endl;
178  *out << " lambdaMax: " << lambdaMax << endl;
179  *out << " |lambdaMax-lambdaMaxOld|/|lambdaMax|: " << Teuchos::ScalarTraits<ST>::magnitude(lambdaMax-lambdaMaxOld)/Teuchos::ScalarTraits<ST>::magnitude(lambdaMax) << endl;
180  }
181  }
182  x->scale(one / norm);
183  }
184  }
185  if(verbose) {
186  *out << " lambdaMax: " << lambdaMax << endl;
187  }
188 
189  norm = x->norm2();
190  if(norm == zero) { // Return something reasonable.
191  if(verbose) {
192  *out << " norm is zero; returning zero" << endl;
193  *out << " Power method terminated after "<< numIters << " iterations." << endl;
194  }
195  return zero;
196  }
197  x->scale(one / norm);
198  A.apply(*x, *y);
199  y->elementWiseMultiply(STS::one(), D_inv, *y, STS::zero());
200  lambdaMax = y->norm2();
201  } else {
202  // In this route, the update is as follows:
203  // y=A*x, y = Dinv*y, lambda = x->dot(y), x = y/lambda
204  ST xDinvAx = norm;
205  if(verbose) {
206  *out << " PowerMethod using largest eigenvalue route" << endl;
207  }
208  for (int iter = 0; iter < numIters-1; ++iter) {
209  if(verbose) {
210  *out << " Iteration " << iter << endl;
211  }
212  A.apply(*x, *y);
213  y->elementWiseMultiply(STS::one(), D_inv, *y, STS::zero());
214 
215  if(((iter+1) % eigNormalizationFreq == 0) && (iter < numIters-2)) {
216  xDinvAx = x->dot(*y);
217  if(xDinvAx == zero) { // Return something reasonable.
218  if(verbose) {
219  *out << " xDinvAx is zero; returning zero" << endl;
220  *out << " Power method terminated after "<< iter << " iterations." << endl;
221  }
222  return zero;
223  } else {
224  lambdaMaxOld = lambdaMax;
225  lambdaMax = pow(xDinvAx, Teuchos::ScalarTraits<MT>::one() / eigNormalizationFreq);
226  if(Teuchos::ScalarTraits<ST>::magnitude(lambdaMax-lambdaMaxOld) < tolerance * Teuchos::ScalarTraits<ST>::magnitude(lambdaMax)) {
227  if(verbose) {
228  *out << " lambdaMax: " << lambdaMax << endl;
229  *out << " Power method terminated after "<< iter << " iterations." << endl;
230  }
231  return lambdaMax;
232  } else if(verbose) {
233  *out << " lambdaMaxOld: " << lambdaMaxOld << endl;
234  *out << " lambdaMax: " << lambdaMax << endl;
235  *out << " |lambdaMax-lambdaMaxOld|/|lambdaMax|: " << Teuchos::ScalarTraits<ST>::magnitude(lambdaMax-lambdaMaxOld)/Teuchos::ScalarTraits<ST>::magnitude(lambdaMax) << endl;
236  }
237  }
238  x->swap(*y);
239  norm = x->norm2();
240  x->scale(one / norm);
241  }
242  }
243  if(verbose) {
244  *out << " lambdaMax: " << lambdaMax << endl;
245  }
246 
247  norm = x->norm2();
248  if(norm == zero) { // Return something reasonable.
249  if(verbose) {
250  *out << " norm is zero; returning zero" << endl;
251  *out << " Power method terminated after "<< numIters << " iterations." << endl;
252  }
253  return zero;
254  }
255  x->scale(one / norm);
256  A.apply(*x, *y);
257  y->elementWiseMultiply(STS::one(), D_inv, *y, STS::zero());
258  lambdaMax = y->dot(*x);
259  }
260 
261  if(verbose) {
262  *out << " lambdaMax: " << lambdaMax << endl;
263  *out << " Power method terminated after "<< numIters << " iterations." << endl;
264  }
265 
266  return lambdaMax;
267 }
268 
269 
281 template<class V>
282 void
283 computeInitialGuessForPowerMethod (V& x, const bool nonnegativeRealParts)
284 {
285  typedef typename V::device_type::execution_space dev_execution_space;
286  typedef typename V::local_ordinal_type LO;
287 
288  x.randomize ();
289 
290  if(nonnegativeRealParts) {
291  auto x_lcl = x.getLocalViewDevice (Tpetra::Access::ReadWrite);
292  auto x_lcl_1d = Kokkos::subview (x_lcl, Kokkos::ALL (), 0);
293 
294  const LO lclNumRows = static_cast<LO> (x.getLocalLength ());
295  Kokkos::RangePolicy<dev_execution_space, LO> range (0, lclNumRows);
296  PositivizeVector<decltype (x_lcl_1d), LO> functor (x_lcl_1d);
297  Kokkos::parallel_for (range, functor);
298  }
299 }
300 
301 
318 template<class OperatorType, class V>
319 typename V::scalar_type
320 powerMethod(const OperatorType& A,
321  const V& D_inv,
322  const int numIters,
323  Teuchos::RCP<V> y,
325  const int eigNormalizationFreq = 1,
326  Teuchos::RCP<Teuchos::FancyOStream> out = Teuchos::null,
327  const bool computeSpectralRadius = true)
328 {
329  typedef typename V::scalar_type ST;
331 
332  bool verbose = (out != Teuchos::null);
333 
334  if(verbose) {
335  *out << "powerMethod:" << std::endl;
336  }
337 
338  const ST zero = static_cast<ST> (0.0);
339 
340  Teuchos::RCP<V> x = Teuchos::rcp(new V(A.getDomainMap ()));
341  // For the first pass, just let the pseudorandom number generator
342  // fill x with whatever values it wants; don't try to make its
343  // entries nonnegative.
344  computeInitialGuessForPowerMethod (*x, false);
345 
346  ST lambdaMax = powerMethodWithInitGuess (A, D_inv, numIters, x, y, tolerance, eigNormalizationFreq, out, computeSpectralRadius);
347 
348  // mfh 07 Jan 2015: Taking the real part here is only a concession
349  // to the compiler, so that this can build with ScalarType =
350  // std::complex<T>. Our Chebyshev implementation only works with
351  // real, symmetric positive definite matrices. The right thing to
352  // do would be what Belos does, which is provide a partial
353  // specialization for ScalarType = std::complex<T> with a stub
354  // implementation (that builds, but whose constructor throws).
355  if(STS::real (lambdaMax) < STS::real (zero)) {
356  if(verbose) {
357  *out << "real(lambdaMax) = " << STS::real (lambdaMax) << " < 0; "
358  "try again with a different random initial guess" << std::endl;
359  }
360  // Max eigenvalue estimate was negative. Perhaps we got unlucky
361  // with the random initial guess. Try again with a different (but
362  // still random) initial guess. Only try again once, so that the
363  // run time is bounded.
364 
365  // For the second pass, make all the entries of the initial guess
366  // vector have nonnegative real parts.
367  computeInitialGuessForPowerMethod (*x, true);
368  lambdaMax = powerMethodWithInitGuess (A, D_inv, numIters, x, y, tolerance, eigNormalizationFreq, out, computeSpectralRadius);
369  }
370  return lambdaMax;
371 }
372 
373 } // namespace PowerMethod
374 } // namespace Ifpack2
375 
376 #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