openGPMP
Open Source Mathematics Package
eigen.cpp
Go to the documentation of this file.
1 /*************************************************************************
2  *
3  * Project
4  * _____ _____ __ __ _____
5  * / ____| __ \| \/ | __ \
6  * ___ _ __ ___ _ __ | | __| |__) | \ / | |__) |
7  * / _ \| '_ \ / _ \ '_ \| | |_ | ___/| |\/| | ___/
8  *| (_) | |_) | __/ | | | |__| | | | | | | |
9  * \___/| .__/ \___|_| |_|\_____|_| |_| |_|_|
10  * | |
11  * |_|
12  *
13  * Copyright (C) Akiel Aries, <akiel@akiel.org>, et al.
14  *
15  * This software is licensed as described in the file LICENSE, which
16  * you should have received as part of this distribution. The terms
17  * among other details are referenced in the official documentation
18  * seen here : https://akielaries.github.io/openGPMP/ along with
19  * important files seen in this project.
20  *
21  * You may opt to use, copy, modify, merge, publish, distribute
22  * and/or sell copies of the Software, and permit persons to whom
23  * the Software is furnished to do so, under the terms of the
24  * LICENSE file. As this is an Open Source effort, all implementations
25  * must be of the same methodology.
26  *
27  *
28  *
29  * This software is distributed on an AS IS basis, WITHOUT
30  * WARRANTY OF ANY KIND, either express or implied.
31  *
32  ************************************************************************/
33 #include <cmath>
34 #include <iostream>
35 #include <numeric>
37 #include <vector>
38 
39 gpmp::linalg::Eigen::Eigen(const std::vector<std::vector<double>> &mat)
40  : matrix(mat), size(mat.size()) {
41  // Check if the matrix is square
42  /*if (size != mat[0].size()) {
43  throw std::invalid_argument(
44  "Error: Eigenvalue computation requires a square matrix.");
45  }*/
46 }
47 
48 double gpmp::linalg::Eigen::power_iter(int max_iters, double tolerance) const {
49  // initial guess
50  std::vector<double> x(size, 1.0);
51  std::vector<double> y(size);
52 
53  for (int iter = 0; iter < max_iters; ++iter) {
54  // perform matrix-vector multiplication: y = A * x
55  for (int i = 0; i < size; ++i) {
56  y[i] = 0.0;
57  for (int j = 0; j < size; ++j) {
58  y[i] += matrix[i][j] * x[j];
59  }
60  }
61 
62  // find the maximum element in y
63  double max_element = 0.0;
64  for (int i = 0; i < size; ++i) {
65  max_element = std::max(max_element, std::abs(y[i]));
66  }
67 
68  // normalize y
69  for (int i = 0; i < size; ++i) {
70  y[i] /= max_element;
71  }
72 
73  // check for convergence
74  double error = 0.0;
75  for (int i = 0; i < size; ++i) {
76  error += std::abs(y[i] - x[i]);
77  }
78 
79  if (error < tolerance) {
80  // eigenvalue found
81  return max_element;
82  }
83 
84  x = y;
85  }
86 
87  std::cerr << "Warning: Power iteration did not converge within the"
88  "specified iterations\n";
89  // not converged
90  return 0.0;
91 }
92 
93 std::vector<double> gpmp::linalg::Eigen::qr_algorithm(int max_iters,
94  double tolerance) const {
95  // initialize a copy of the matrix to avoid modifying the original
96  std::vector<std::vector<double>> hessenberg_mtx = matrix;
97 
98  // QR Iteration
99  for (int iter = 0; iter < max_iters; ++iter) {
100  // QR decomposition of the Hessenberg matrix
101  for (int i = 0; i < size - 1; ++i) {
102  // perform Givens rotation to introduce zeros below the subdiagonal
103  double a = hessenberg_mtx[i][i];
104  double b = hessenberg_mtx[i + 1][i];
105  double r = std::hypot(a, b);
106  double c = a / r;
107  double s = b / r;
108 
109  // apply Givens rotation to the submatrix
110  for (int j = 0; j < size; ++j) {
111  double temp1 =
112  c * hessenberg_mtx[i][j] + s * hessenberg_mtx[i + 1][j];
113  double temp2 =
114  -s * hessenberg_mtx[i][j] + c * hessenberg_mtx[i + 1][j];
115  hessenberg_mtx[i][j] = temp1;
116  hessenberg_mtx[i + 1][j] = temp2;
117  }
118  }
119 
120  // check for convergence (off-diagonal elements close to zero)
121  double offDiagonalSum = 0.0;
122  for (int i = 2; i < size; ++i) {
123  offDiagonalSum += std::abs(hessenberg_mtx[i][i - 1]);
124  }
125 
126  if (offDiagonalSum < tolerance) {
127  // extract eigenvalues from the diagonal of the Hessenberg matrix
128  std::vector<double> eigenvalues;
129  for (int i = 0; i < size; ++i) {
130  eigenvalues.push_back(hessenberg_mtx[i][i]);
131  }
132 
133  return eigenvalues;
134  }
135  }
136 
137  std::cerr << "Warning: QR iteration did not converge within the "
138  "specified iterations\n";
139  // empty vector if not converged
140  return std::vector<double>();
141 }
142 
144  // compute the determinant as the product of eigenvalues
145  std::vector<double> eigenvalues = qr_algorithm();
146  double determinant = std::accumulate(eigenvalues.begin(),
147  eigenvalues.end(),
148  1.0,
149  std::multiplies<double>());
150  return determinant;
151 }
152 
153 std::vector<double>
154 gpmp::linalg::Eigen::sensitivity(double perturbation) const {
155  std::vector<double> originalEigenvalues = qr_algorithm();
156 
157  std::vector<double> perturbedEigenvalues;
158  for (int i = 0; i < size; ++i) {
159  // perturb the matrix
160  std::vector<std::vector<double>> perturbedMatrix = matrix;
161  perturbedMatrix[i][i] += perturbation;
162 
163  // calculate eigenvalues of perturbed matrix
164  std::vector<double> perturbedVals =
165  Eigen(perturbedMatrix).qr_algorithm();
166  perturbedEigenvalues.push_back(perturbedVals[i]);
167  }
168 
169  // calculate sensitivity: (perturbed eigenvalue - original eigenvalue) /
170  // perturbation
171  std::vector<double> sensitivity;
172  for (int i = 0; i < size; ++i) {
173  double sens =
174  (perturbedEigenvalues[i] - originalEigenvalues[i]) / perturbation;
175  sensitivity.push_back(sens);
176  }
177 
178  return sensitivity;
179 }
180 
182  std::vector<std::vector<double>> &schurMatrix,
183  double tolerance) const {
184  // Hessenberg matrix is already calculated in QR iteration
185  schurMatrix = matrix;
186 
187  for (int i = size - 1; i > 0; --i) {
188  // check for small subdiagonal elements to introduce a zero
189  if (std::abs(schurMatrix[i][i - 1]) < tolerance) {
190  schurMatrix[i][i - 1] = 0.0;
191  } else {
192  // Apply Givens rotation to introduce a zero below the subdiagonal
193  double a = schurMatrix[i - 1][i - 1];
194  double b = schurMatrix[i][i - 1];
195  double r = std::hypot(a, b);
196  double c = a / r;
197  double s = b / r;
198 
199  for (int j = 0; j < size; ++j) {
200  double temp1 =
201  c * schurMatrix[i - 1][j] + s * schurMatrix[i][j];
202  double temp2 =
203  -s * schurMatrix[i - 1][j] + c * schurMatrix[i][j];
204  schurMatrix[i - 1][j] = temp1;
205  schurMatrix[i][j] = temp2;
206  }
207  }
208  }
209 }
210 
211 std::vector<std::vector<double>>
213  std::vector<std::vector<double>> jordanMatrix(
214  size,
215  std::vector<double>(size, 0.0));
216  std::vector<double> eigenvalues = qr_algorithm();
217 
218  for (int i = 0; i < size; ++i) {
219  jordanMatrix[i][i] = eigenvalues[i];
220 
221  // find the size of the Jordan block
222  int jordanBlockSize = 1;
223  while (i + jordanBlockSize < size &&
224  std::abs(matrix[i + jordanBlockSize][i]) < tolerance) {
225  ++jordanBlockSize;
226  }
227 
228  // fill the Jordan block
229  for (int j = 1; j < jordanBlockSize; ++j) {
230  jordanMatrix[i + j][i + j - 1] = 1.0;
231  }
232  }
233 
234  return jordanMatrix;
235 }
236 
237 double gpmp::linalg::Eigen::rayleigh_iter(int maxIterations,
238  double tolerance) const {
239  // initial guess
240  std::vector<double> x(size, 1.0);
241  double lambdaPrev = 0.0;
242 
243  for (int iter = 0; iter < maxIterations; ++iter) {
244  // Rayleigh quotient iteration
245  std::vector<double> Ax(size, 0.0);
246 
247  // perform matrix-vector multiplication: Ax = A * x
248  for (int i = 0; i < size; ++i) {
249  for (int j = 0; j < size; ++j) {
250  Ax[i] += matrix[i][j] * x[j];
251  }
252  }
253 
254  // compute the Rayleigh quotient
255  double lambda =
256  std::inner_product(x.begin(), x.end(), Ax.begin(), 0.0) /
257  std::inner_product(x.begin(), x.end(), x.begin(), 0.0);
258 
259  // check for convergence
260  if (std::abs(lambda - lambdaPrev) < tolerance) {
261  return lambda;
262  }
263 
264  // update the guess vector
265  double normAx = std::sqrt(
266  std::inner_product(Ax.begin(), Ax.end(), Ax.begin(), 0.0));
267  for (int i = 0; i < size; ++i) {
268  // normalize each element of x by dividing by its magnitude
269  x[i] = Ax[i] / normAx;
270  }
271 
272  lambdaPrev = lambda;
273  }
274 
275  std::cerr << "Warning: Rayleigh quotient iteration did not converge within "
276  "the specified iterations."
277  << std::endl;
278  // if not converged
279  return 0.0;
280 }
Class for computing eigenvalues of a matrix.
Definition: eigen.hpp:45
void schur_decomp(std::vector< std::vector< double >> &schurMatrix, double tolerance=1e-10) const
Computes the Schur decomposition of a matrix.
Definition: eigen.cpp:181
std::vector< double > qr_algorithm(int max_iters=100, double tolerance=1e-10) const
Calculate all eigenvalues of the matrix using the QR algorithm.
Definition: eigen.cpp:93
double power_iter(int max_iters=100, double tolerance=1e-10) const
Perform the power iteration method to find the dominant eigenvalue.
Definition: eigen.cpp:48
double rayleigh_iter(int maxIterations, double tolerance) const
Perform Rayleigh Quotient Iteration to approximate the dominant eigenvalue.
Definition: eigen.cpp:237
std::vector< double > sensitivity(double perturbation) const
Calculates the sensitivity of the eigenvalues of a matrix to small perturbations.
Definition: eigen.cpp:154
std::vector< std::vector< double > > jordan_normal_form(double tolerance=1e-10) const
Computes the Jordan normal form of a matrix.
Definition: eigen.cpp:212
double determinant() const
Definition: eigen.cpp:143
Eigen(const std::vector< std::vector< double >> &mat)
Constructor for the Eigen class.
Definition: eigen.cpp:39