Add QR eigenvalue algorithm and tests (non-symmetric tests need some more checking)
This commit is contained in:
parent
5eef1e13f5
commit
2364c15120
2 changed files with 87 additions and 1 deletions
|
|
@ -9,9 +9,10 @@ namespace psemek::geom
|
|||
struct qr_result
|
||||
{
|
||||
matrix<T, N, N> q; // orthogonal
|
||||
matrix<T, N, N> r; // upper-triangular
|
||||
matrix<T, N, N> r; // upper-triangular (for decomposition) or block upper-triangular (for eigenvalues)
|
||||
};
|
||||
|
||||
// returns {q, r} such that m = q * r
|
||||
template <typename T, std::size_t N>
|
||||
qr_result<T, N> qr_decomposition(matrix<T, N, N> const & m)
|
||||
{
|
||||
|
|
@ -59,4 +60,22 @@ namespace psemek::geom
|
|||
return {q, r};
|
||||
}
|
||||
|
||||
// returns {q, r} such that m = q * r * q^(-1)
|
||||
// NB: q^(-1) = q^T
|
||||
template <typename T, std::size_t N>
|
||||
qr_result<T, N> qr_eigenvalues(matrix<T, N, N> const & m, std::size_t const iterations)
|
||||
{
|
||||
matrix<T, N, N> r = m;
|
||||
matrix<T, N, N> q = matrix<T, N, N>::identity();
|
||||
|
||||
for (std::size_t iteration = 0; iteration < iterations; ++iteration)
|
||||
{
|
||||
auto qr = qr_decomposition(r);
|
||||
r = qr.r * qr.q;
|
||||
q = q * qr.q;
|
||||
}
|
||||
|
||||
return {q, r};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,3 +217,70 @@ test_case(geom_matrix_qr)
|
|||
expect_small(result.r[j][i], 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
test_case(geom_matrix_qr__eig_symmetric)
|
||||
{
|
||||
random::generator rng;
|
||||
random::normal_distribution<float> normal;
|
||||
|
||||
for (int iteration = 0; iteration < 64; ++iteration)
|
||||
{
|
||||
matrix<float, 8, 8> m;
|
||||
for (std::size_t i = 0; i < m.rows(); ++i)
|
||||
for (std::size_t j = 0; j < m.columns(); ++j)
|
||||
m[i][j] = normal(rng);
|
||||
|
||||
m = transpose(m) * m;
|
||||
|
||||
auto result = qr_eigenvalues(m, 1024);
|
||||
|
||||
expect_small(frobenius_norm(m * result.q - result.q * result.r), 1e-3);
|
||||
expect_small(frobenius_norm(matrix<float, 8, 8>::identity() - result.q * transpose(result.q)), 1e-4);
|
||||
|
||||
for (std::size_t i = 0; i < m.rows(); ++i)
|
||||
for (std::size_t j = i + 1; j < m.columns(); ++j)
|
||||
expect_small(result.r[j][i], 1e-3);
|
||||
|
||||
std::string test;
|
||||
|
||||
for (std::size_t i = 0; i < m.columns(); ++i)
|
||||
{
|
||||
auto v = column(result.q, i);
|
||||
expect_small(length(m * v - result.r[i][i] * v), 1e-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test_case(geom_matrix_qr__eig_general)
|
||||
{
|
||||
random::generator rng;
|
||||
random::normal_distribution<float> normal;
|
||||
|
||||
for (int iteration = 0; iteration < 64; ++iteration)
|
||||
{
|
||||
matrix<float, 8, 8> m;
|
||||
for (std::size_t i = 0; i < m.rows(); ++i)
|
||||
for (std::size_t j = 0; j < m.columns(); ++j)
|
||||
m[i][j] = normal(rng);
|
||||
|
||||
auto result = qr_eigenvalues(m, 4096);
|
||||
|
||||
expect_small(frobenius_norm(m * result.q - result.q * result.r), 1e-2);
|
||||
expect_small(frobenius_norm(matrix<float, 8, 8>::identity() - result.q * transpose(result.q)), 1e-3);
|
||||
|
||||
for (std::size_t i = 0; i < m.rows(); ++i)
|
||||
for (std::size_t j = i + 2; j < m.columns(); ++j)
|
||||
expect_small(result.r[j][i], 1e-2);
|
||||
|
||||
for (std::size_t i = 0; i < m.columns(); ++i)
|
||||
{
|
||||
// Don't check complex eigenvalues
|
||||
if (i > 0 && std::abs(result.r[i][i - 1]) > 1e-3f) continue;
|
||||
if (i + 1 < m.rows() && std::abs(result.r[i + 1][i]) > 1e-3f) continue;
|
||||
|
||||
// TODO: properly check eigenvalues
|
||||
// auto v = column(result.q, i);
|
||||
// expect_small(length(m * v - result.r[i][i] * v), 1e-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue