Add QR eigen library: implicit Wilkinson-shifted QR for symmetric matrices
Merge-Checker / build_and_test (pull_request) Failing after 28m10s
Merge-Checker / build_and_test (pull_request) Failing after 28m10s
- src/QR.hpp / src/QR.cpp: fully templated QR::EigenQR (N >= 2), no heap allocation (3*N^2 float working buffers on stack). Givens tridiagonalization (bottom-up) + implicit Wilkinson-shifted QR with bulge chasing, relative deflation, exact-zero peeling, closed-form 2x2 termination. - Matrix::EigenQR now delegates to QR::EigenQR (old unshifted body removed); eigenvalues sorted descending, eigenvectors in columns of the output. - unit-tests/qr-build-blocks-tests.cpp: 8 building-block test cases (215 assertions) with scipy/numpy references. - unit-tests/qr-reference-values.py: numpy/scipy reference generator mirroring every building block and the full pipeline (eigh, n=3..8). - CMake: new 'qr' static library; Matrix links against it; qr-build-blocks-tests target enabled.
This commit is contained in:
+19
-31
@@ -5,6 +5,21 @@
|
||||
#include "Matrix.hpp"
|
||||
#endif
|
||||
|
||||
// Forward-declare QR::EigenQR so the Matrix::EigenQR implementation below can
|
||||
// call it even when Matrix.cpp is pulled in through QR.hpp's own include chain
|
||||
// (QR.cpp -> QR.hpp -> Matrix.hpp -> Matrix.cpp), where the QR namespace has
|
||||
// not been declared yet at this point. If we are not already inside that
|
||||
// chain, pull in the full QR library so its template definition is available.
|
||||
namespace QR {
|
||||
template <uint8_t N>
|
||||
void EigenQR(Matrix<N, N> &matrixToDecompose, Matrix<N, N> &eigenVectors,
|
||||
Matrix<N, 1> &eigenValues, uint32_t maxIterations,
|
||||
float tolerance);
|
||||
}
|
||||
#ifndef QR_H_
|
||||
#include "QR.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef MATRIX_H_ // since the .cpp file has to be included by the .hpp file this
|
||||
// will evaluate to true
|
||||
#include "Matrix.hpp"
|
||||
@@ -571,37 +586,10 @@ void Matrix<rows, columns>::EigenQR(Matrix<rows, rows> &eigenVectors,
|
||||
static_assert(rows > 1, "Matrix size must be > 1 for QR iteration");
|
||||
static_assert(rows == columns, "Matrix size must be square for QR iteration");
|
||||
|
||||
Matrix<rows, rows> Ak = *this; // Copy original matrix
|
||||
Matrix<rows, rows> QQ{Matrix<rows, rows>::Identity()};
|
||||
Matrix<rows, rows> shift{0};
|
||||
|
||||
for (uint32_t iter = 0; iter < maxIterations; ++iter) {
|
||||
Matrix<rows, rows> Q, R;
|
||||
|
||||
// // QR shift lets us "attack" the first diagonal to speed up the algorithm
|
||||
// shift = Matrix<rows, rows>::Identity() * Ak[rows - 1][rows - 1];
|
||||
(Ak - shift).QRDecomposition(Q, R);
|
||||
Ak = R * Q + shift;
|
||||
QQ = QQ * Q;
|
||||
|
||||
// Check convergence: off-diagonal norm
|
||||
float offDiagSum = 0.0f;
|
||||
for (uint32_t row = 1; row < rows; row++) {
|
||||
for (uint32_t column = 0; column < row; column++) {
|
||||
offDiagSum += fabs(Ak[row][column]);
|
||||
}
|
||||
}
|
||||
|
||||
if (offDiagSum < tolerance) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Diagonal elements are the eigenvalues
|
||||
for (uint8_t i = 0; i < rows; i++) {
|
||||
eigenValues[i][0] = Ak[i][i];
|
||||
}
|
||||
eigenVectors = QQ;
|
||||
// Delegate to the QR library: implicit shifted QR iteration with
|
||||
// Wilkinson shift (see src/QR.hpp for the algorithm and conventions).
|
||||
Matrix<rows, rows> A = *this; // QR::EigenQR does not modify its input
|
||||
QR::EigenQR(A, eigenVectors, eigenValues, maxIterations, tolerance);
|
||||
}
|
||||
|
||||
#endif // MATRIX_H_
|
||||
Reference in New Issue
Block a user