Arnoldi.h
Go to the documentation of this file.
1 // Copyright (C) 2018-2025 Yixuan Qiu <yixuan.qiu@cos.name>
2 //
3 // This Source Code Form is subject to the terms of the Mozilla
4 // Public License v. 2.0. If a copy of the MPL was not distributed
5 // with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 
7 #ifndef SPECTRA_ARNOLDI_H
8 #define SPECTRA_ARNOLDI_H
9 
10 #include <Eigen/Core>
11 #include <cmath> // std::sqrt
12 #include <utility> // std::move
13 #include <stdexcept> // std::invalid_argument
14 
15 #include "../MatOp/internal/ArnoldiOp.h"
16 #include "../Util/TypeTraits.h"
17 #include "../Util/SimpleRandom.h"
18 #include "UpperHessenbergQR.h"
19 #include "DoubleShiftQR.h"
20 
21 namespace Spectra {
22 
23 // Arnoldi factorization A * V = V * H + f * e'
24 // A: n x n
25 // V: n x k
26 // H: k x k
27 // f: n x 1
28 // e: [0, ..., 0, 1]
29 // V and H are allocated of dimension m, so the maximum value of k is m
30 template <typename Scalar, typename ArnoldiOpType>
31 class Arnoldi
32 {
33 private:
34  // The real part type of the matrix element
42 
43 protected:
44  // A very small value, but 1.0 / m_near_0 does not overflow
45  // ~= 1e-307 for the "double" type
47  // The machine precision, ~= 1e-16 for the "double" type
49 
50  ArnoldiOpType m_op; // Operators for the Arnoldi factorization
51  const Index m_n; // dimension of A
52  const Index m_m; // maximum dimension of subspace V
53  Index m_k; // current dimension of subspace V
54  Matrix m_fac_V; // V matrix in the Arnoldi factorization
55  Matrix m_fac_H; // H matrix in the Arnoldi factorization
56  Vector m_fac_f; // residual in the Arnoldi factorization
57  RealScalar m_beta; // ||f||, B-norm of f
58 
59  // Given orthonormal basis V (w.r.t. B), find a nonzero vector f such that (V^H)Bf = 0
60  // With rounding errors, we hope ||(V^H)Bf|| < eps * ||f||
61  // Assume that f has been properly allocated
62  void expand_basis(MapConstMat& V, const Index seed, Vector& f, RealScalar& fnorm, Index& op_counter)
63  {
64  using std::sqrt;
65 
66  Vector v(m_n), Vf(V.cols());
67  for (Index iter = 0; iter < 5; iter++)
68  {
69  // Randomly generate a new vector and orthogonalize it against V
70  SimpleRandom<Scalar> rng(seed + 123 * iter);
71  // The first try forces f to be in the range of A
72  if (iter == 0)
73  {
74  rng.random_vec(v);
75  m_op.perform_op(v.data(), f.data());
76  op_counter++;
77  }
78  else
79  {
80  rng.random_vec(f);
81  }
82  // f <- f - V * (V^H)Bf, so that f is orthogonal to V in B-norm
83  m_op.adjoint_product(V, f, Vf);
84  f.noalias() -= V * Vf;
85  // fnorm <- ||f||
86  fnorm = m_op.norm(f);
87 
88  // Compute (V^H)Bf again
89  m_op.adjoint_product(V, f, Vf);
90  // Test whether ||(V^H)Bf|| < eps * ||f||
91  RealScalar ortho_err = Vf.cwiseAbs().maxCoeff();
92  // If not, iteratively correct the residual
93  int count = 0;
94  while (count < 3 && ortho_err >= m_eps * fnorm)
95  {
96  // f <- f - V * Vf
97  f.noalias() -= V * Vf;
98  // beta <- ||f||
99  fnorm = m_op.norm(f);
100 
101  m_op.adjoint_product(V, f, Vf);
102  ortho_err = Vf.cwiseAbs().maxCoeff();
103  count++;
104  }
105 
106  // If the condition is satisfied, simply return
107  // Otherwise, go to the next iteration and try a new random vector
108  if (ortho_err < m_eps * fnorm)
109  return;
110  }
111  }
112 
113 public:
114  // Copy an ArnoldiOp
115  Arnoldi(const ArnoldiOpType& op, Index m) :
116  m_op(op), m_n(op.rows()), m_m(m), m_k(0)
117  {}
118 
119  // Move an ArnoldiOp
120  Arnoldi(ArnoldiOpType&& op, Index m) :
121  m_op(std::move(op)), m_n(op.rows()), m_m(m), m_k(0)
122  {}
123 
124  // Const-reference to internal structures
125  const Matrix& matrix_V() const { return m_fac_V; }
126  const Matrix& matrix_H() const { return m_fac_H; }
127  const Vector& vector_f() const { return m_fac_f; }
128  RealScalar f_norm() const { return m_beta; }
129  Index subspace_dim() const { return m_k; }
130 
131  // Initialize with an operator and an initial vector
132  void init(MapConstVec& v0, Index& op_counter)
133  {
134  using std::abs;
135 
136  m_fac_V.resize(m_n, m_m);
137  m_fac_H.resize(m_m, m_m);
138  m_fac_f.resize(m_n);
139  m_fac_H.setZero();
140 
141  // Verify the initial vector
142  const RealScalar v0norm = m_op.norm(v0);
143  if (v0norm < m_near_0)
144  throw std::invalid_argument("initial residual vector cannot be zero");
145 
146  // Points to the first column of V
147  MapVec v(m_fac_V.data(), m_n);
148  // Force v to be in the range of A, i.e., v = A * v0
149  m_op.perform_op(v0.data(), v.data());
150  op_counter++;
151 
152  // Normalize
153  const RealScalar vnorm = m_op.norm(v);
154  v /= vnorm;
155 
156  // Compute H and f
157  Vector w(m_n);
158  m_op.perform_op(v.data(), w.data());
159  op_counter++;
160 
161  m_fac_H(0, 0) = m_op.inner_product(v, w);
162  m_fac_f.noalias() = w - v * m_fac_H(0, 0);
163 
164  // In some cases, H[1,1] is already an eigenvalue of A,
165  // so f would be zero in exact arithmetics. But due to rounding errors,
166  // it may contain tiny fluctuations. When this happens, we force f to be zero,
167  // so that it can be restarted in the subsequent Arnoldi factorization
168  if (m_fac_f.cwiseAbs().maxCoeff() < m_eps * abs(m_fac_H(0, 0)))
169  {
170  m_fac_f.setZero();
171  m_beta = RealScalar(0);
172  }
173  else
174  {
175  m_beta = m_op.norm(m_fac_f);
176  }
177 
178  // Indicate that this is a step-1 factorization
179  m_k = 1;
180  }
181 
182  // Arnoldi factorization starting from step-k
183  virtual void factorize_from(Index from_k, Index to_m, Index& op_counter)
184  {
185  using std::sqrt;
186 
187  if (to_m <= from_k)
188  return;
189 
190  if (from_k > m_k)
191  {
192  std::string msg = "Arnoldi: from_k (= " + std::to_string(from_k) +
193  ") is larger than the current subspace dimension (= " + std::to_string(m_k) + ")";
194  throw std::invalid_argument(msg);
195  }
196 
197  const RealScalar beta_thresh = m_eps * sqrt(RealScalar(m_n));
198 
199  // Pre-allocate vectors
200  Vector Vf(to_m);
201  Vector w(m_n);
202 
203  // Keep the upperleft k x k submatrix of H and set other elements to 0
204  m_fac_H.rightCols(m_m - from_k).setZero();
205  m_fac_H.block(from_k, 0, m_m - from_k, from_k).setZero();
206 
207  for (Index i = from_k; i <= to_m - 1; i++)
208  {
209  bool restart = false;
210  // If beta = 0, then the next V is not full rank
211  // We need to generate a new residual vector that is orthogonal
212  // to the current V, which we call a restart
213  if (m_beta < m_near_0)
214  {
215  MapConstMat V(m_fac_V.data(), m_n, i); // The first i columns
216  expand_basis(V, 2 * i, m_fac_f, m_beta, op_counter);
217  restart = true;
218  }
219 
220  // v <- f / ||f||
221  m_fac_V.col(i).noalias() = m_fac_f / m_beta; // The (i+1)-th column
222 
223  // Note that H[i+1, i] equals to the unrestarted beta
224  m_fac_H(i, i - 1) = restart ? Scalar(0) : Scalar(m_beta);
225 
226  // w <- A * v, v = m_fac_V.col(i)
227  m_op.perform_op(&m_fac_V(0, i), w.data());
228  op_counter++;
229 
230  const Index i1 = i + 1;
231  // First i+1 columns of V
232  MapConstMat Vs(m_fac_V.data(), m_n, i1);
233  // h = m_fac_H(0:i, i)
234  MapVec h(&m_fac_H(0, i), i1);
235  // h <- (V^H)Bw
236  m_op.adjoint_product(Vs, w, h);
237 
238  // f <- w - V * h
239  m_fac_f.noalias() = w - Vs * h;
240  m_beta = m_op.norm(m_fac_f);
241 
242  if (m_beta > RealScalar(0.717) * m_op.norm(h))
243  continue;
244 
245  // f/||f|| is going to be the next column of V, so we need to test
246  // whether (V^H)B(f/||f||) ~= 0
247  m_op.adjoint_product(Vs, m_fac_f, Vf.head(i1));
248  RealScalar ortho_err = Vf.head(i1).cwiseAbs().maxCoeff();
249  // If not, iteratively correct the residual
250  int count = 0;
251  while (count < 5 && ortho_err > m_eps * m_beta)
252  {
253  // There is an edge case: when beta=||f|| is close to zero, f mostly consists
254  // of noises of rounding errors, so the test [ortho_err < eps * beta] is very
255  // likely to fail. In particular, if beta=0, then the test is ensured to fail.
256  // Hence when this happens, we force f to be zero, and then restart in the
257  // next iteration.
258  if (m_beta < beta_thresh)
259  {
260  m_fac_f.setZero();
261  m_beta = RealScalar(0);
262  break;
263  }
264 
265  // f <- f - V * Vf
266  m_fac_f.noalias() -= Vs * Vf.head(i1);
267  // h <- h + Vf
268  h.noalias() += Vf.head(i1);
269  // beta <- ||f||
270  m_beta = m_op.norm(m_fac_f);
271 
272  m_op.adjoint_product(Vs, m_fac_f, Vf.head(i1));
273  ortho_err = Vf.head(i1).cwiseAbs().maxCoeff();
274  count++;
275  }
276  }
277 
278  // Indicate that this is a step-m factorization
279  m_k = to_m;
280  }
281 
282  // Apply H -> Q'HQ, where Q is from a double shift QR decomposition
283  void compress_H(const DoubleShiftQR<Scalar>& decomp)
284  {
285  decomp.matrix_QtHQ(m_fac_H);
286  m_k -= 2;
287  }
288 
289  // Apply H -> Q'HQ, where Q is from an upper Hessenberg QR decomposition
291  {
292  decomp.matrix_QtHQ(m_fac_H);
293  m_k--;
294  }
295 
296  // Apply V -> VQ and compute the new f.
297  // Should be called after compress_H(), since m_k is updated there.
298  // Only need to update the first k+1 columns of V
299  // The first (m - k + i) elements of the i-th column of Q are non-zero,
300  // and the rest are zero
301  //
302  // When V has a complex type, Q can be either real or complex
303  // Hense we use a generic implementation
304  template <typename Derived>
306  {
307  using QScalar = typename Derived::Scalar;
309  using QMapConstVec = Eigen::Map<const QVector>;
310 
311  Matrix Vs(m_n, m_k + 1);
312  for (Index i = 0; i < m_k; i++)
313  {
314  const Index nnz = m_m - m_k + i + 1;
315  QMapConstVec q(&Q(0, i), nnz);
316  Vs.col(i).noalias() = m_fac_V.leftCols(nnz) * q;
317  }
318  Vs.col(m_k).noalias() = m_fac_V * Q.col(m_k);
319  m_fac_V.leftCols(m_k + 1).noalias() = Vs;
320 
321  Vector fk = m_fac_f * Q(m_m - 1, m_k - 1) + m_fac_V.col(m_k) * m_fac_H(m_k, m_k - 1);
322  m_fac_f.swap(fk);
323  m_beta = m_op.norm(m_fac_f);
324  }
325 };
326 
327 } // namespace Spectra
328 
329 #endif // SPECTRA_ARNOLDI_H
w
RowVector3d w
Definition: Matrix_resize_int.cpp:3
Spectra::Arnoldi
Definition: Arnoldi.h:31
Spectra::Arnoldi::f_norm
RealScalar f_norm() const
Definition: Arnoldi.h:128
Spectra::Arnoldi::m_fac_f
Vector m_fac_f
Definition: Arnoldi.h:56
rng
static std::mt19937 rng
Definition: timeFactorOverhead.cpp:31
Spectra::Arnoldi::subspace_dim
Index subspace_dim() const
Definition: Arnoldi.h:129
Spectra::DoubleShiftQR
Definition: DoubleShiftQR.h:22
v0
static const double v0
Definition: testCal3DFisheye.cpp:31
Spectra::Arnoldi::init
void init(MapConstVec &v0, Index &op_counter)
Definition: Arnoldi.h:132
Spectra::Arnoldi::matrix_H
const Matrix & matrix_H() const
Definition: Arnoldi.h:126
Spectra::Arnoldi::m_near_0
const RealScalar m_near_0
Definition: Arnoldi.h:46
Eigen::PlainObjectBase::swap
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void swap(DenseBase< OtherDerived > &other)
Definition: PlainObjectBase.h:953
Spectra::Arnoldi::matrix_V
const Matrix & matrix_V() const
Definition: Arnoldi.h:125
Spectra::Arnoldi::RealScalar
typename Eigen::NumTraits< Scalar >::Real RealScalar
Definition: Arnoldi.h:35
Spectra::Arnoldi::m_beta
RealScalar m_beta
Definition: Arnoldi.h:57
UpperHessenbergQR.h
Spectra::Arnoldi::m_n
const Index m_n
Definition: Arnoldi.h:51
h
const double h
Definition: testSimpleHelicopter.cpp:19
Eigen::PlainObjectBase::resize
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void resize(Index rows, Index cols)
Definition: PlainObjectBase.h:271
Q
Quaternion Q
Definition: testQuaternion.cpp:27
rows
int rows
Definition: Tutorial_commainit_02.cpp:1
Spectra::Arnoldi::vector_f
const Vector & vector_f() const
Definition: Arnoldi.h:127
Spectra::Arnoldi::Arnoldi
Arnoldi(ArnoldiOpType &&op, Index m)
Definition: Arnoldi.h:120
epsilon
static double epsilon
Definition: testRot3.cpp:37
Spectra::Arnoldi::m_op
ArnoldiOpType m_op
Definition: Arnoldi.h:50
Spectra::DoubleShiftQR::matrix_QtHQ
void matrix_QtHQ(Matrix &dest) const
Definition: DoubleShiftQR.h:401
Spectra::Arnoldi::Index
Eigen::Index Index
Definition: Arnoldi.h:36
Eigen::numext::q
EIGEN_DEVICE_FUNC const Scalar & q
Definition: SpecialFunctionsImpl.h:1984
DoubleShiftQR.h
Spectra::Arnoldi::m_k
Index m_k
Definition: Arnoldi.h:53
Spectra::Arnoldi::Arnoldi
Arnoldi(const ArnoldiOpType &op, Index m)
Definition: Arnoldi.h:115
m
Matrix3f m
Definition: AngleAxis_mimic_euler.cpp:1
Spectra::Arnoldi::expand_basis
void expand_basis(MapConstMat &V, const Index seed, Vector &f, RealScalar &fnorm, Index &op_counter)
Definition: Arnoldi.h:62
Spectra::Arnoldi::compress_V
void compress_V(const Eigen::MatrixBase< Derived > &Q)
Definition: Arnoldi.h:305
Eigen::Map
A matrix or vector expression mapping an existing array of data.
Definition: Map.h:94
Spectra::Arnoldi::compress_H
void compress_H(const DoubleShiftQR< Scalar > &decomp)
Definition: Arnoldi.h:283
tree::f
Point2(* f)(const Point3 &, OptionalJacobian< 2, 3 >)
Definition: testExpression.cpp:218
Spectra::Arnoldi::m_m
const Index m_m
Definition: Arnoldi.h:52
Eigen::Quaternion
The quaternion class used to represent 3D orientations and rotations.
Definition: ForwardDeclarations.h:293
Spectra::Arnoldi::m_fac_H
Matrix m_fac_H
Definition: Arnoldi.h:55
move
detail::enable_if_t<!detail::move_never< T >::value, T > move(object &&obj)
Definition: cast.h:1243
i1
double i1(double x)
Definition: i1.c:150
Spectra::Arnoldi::compress_H
void compress_H(const UpperHessenbergQR< Scalar > &decomp)
Definition: Arnoldi.h:290
iter
iterator iter(handle obj)
Definition: pytypes.h:2475
Spectra::UpperHessenbergQR
Definition: UpperHessenbergQR.h:45
std
Definition: BFloat16.h:88
Spectra::Arnoldi::m_fac_V
Matrix m_fac_V
Definition: Arnoldi.h:54
Spectra
Definition: LOBPCGSolver.h:19
v
Array< int, Dynamic, 1 > v
Definition: Array_initializer_list_vector_cxx11.cpp:1
Spectra::Arnoldi::factorize_from
virtual void factorize_from(Index from_k, Index to_m, Index &op_counter)
Definition: Arnoldi.h:183
Eigen::PlainObjectBase::data
EIGEN_DEVICE_FUNC const EIGEN_STRONG_INLINE Scalar * data() const
Definition: PlainObjectBase.h:247
min
#define min(a, b)
Definition: datatypes.h:19
V
MatrixXcd V
Definition: EigenSolver_EigenSolver_MatrixType.cpp:15
Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic >
abs
#define abs(x)
Definition: datatypes.h:17
Eigen::PlainObjectBase::setZero
EIGEN_DEVICE_FUNC Derived & setZero(Index size)
Definition: CwiseNullaryOp.h:562
Eigen::MatrixBase
Base class for all dense matrices, vectors, and expressions.
Definition: MatrixBase.h:48
Spectra::Arnoldi::m_eps
const RealScalar m_eps
Definition: Arnoldi.h:48
Spectra::UpperHessenbergQR::matrix_QtHQ
virtual void matrix_QtHQ(Matrix &dest) const
Definition: UpperHessenbergQR.h:305
ceres::sqrt
Jet< T, N > sqrt(const Jet< T, N > &f)
Definition: jet.h:418
i
int i
Definition: BiCGSTAB_step_by_step.cpp:9
Eigen::GenericNumTraits::Real
T Real
Definition: NumTraits.h:164
Scalar
SCALAR Scalar
Definition: bench_gemm.cpp:46
Eigen::Index
EIGEN_DEFAULT_DENSE_INDEX_TYPE Index
The Index type as used for the API.
Definition: Meta.h:74
pybind11.msg
msg
Definition: wrap/pybind11/pybind11/__init__.py:6


gtsam
Author(s):
autogenerated on Sun Feb 16 2025 04:00:56