diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_lu.cpp | 109 | ||||
| -rw-r--r-- | tests/test_matrix.cpp | 14 | ||||
| -rw-r--r-- | tests/test_qr.cpp | 131 | ||||
| -rw-r--r-- | tests/test_qr_iteration.cpp | 200 | ||||
| -rw-r--r-- | tests/test_triangular_solve.cpp | 31 | ||||
| -rw-r--r-- | tests/test_vector.cpp | 16 |
6 files changed, 125 insertions, 376 deletions
diff --git a/tests/test_lu.cpp b/tests/test_lu.cpp index dfb76c4..c4f9c5d 100644 --- a/tests/test_lu.cpp +++ b/tests/test_lu.cpp @@ -1,8 +1,4 @@ -#include "linalg_error.hpp" -#include "lu.hpp" -#include "matrix.hpp" -#include "norms.hpp" -#include "vector.hpp" +import linalgebra; #include <catch2/catch_approx.hpp> #include <catch2/catch_test_macros.hpp> @@ -11,31 +7,23 @@ #include <cstddef> #include <random> -using linalg::DimensionMismatchError; -using linalg::Matrix; -using linalg::SingularMatrixError; -using linalg::Vector; -using linalg::LUResult; - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- +using linalgebra::DimensionMismatchError; +using linalgebra::Matrix; +using linalgebra::SingularMatrixError; +using linalgebra::Vector; +using linalgebra::LUResult; namespace { -// ||PA - LU||_F (Frobenius, computed element-wise via ||vec||_2) double reconstruction_error(const Matrix& A, const LUResult& lu) { const std::size_t n = A.rows(); - // Build PA by permuting rows of A. Matrix PA(n, n); for (std::size_t i = 0; i < n; ++i) { for (std::size_t j = 0; j < n; ++j) { PA(i, j) = A(lu.perm[i], j); } } - // Compute LU product. const Matrix LU = lu.L * lu.U; - // Compute Frobenius norm of (PA - LU). double err = 0.0; for (std::size_t i = 0; i < n; ++i) { for (std::size_t j = 0; j < n; ++j) { @@ -46,12 +34,10 @@ double reconstruction_error(const Matrix& A, const LUResult& lu) { return std::sqrt(err); } -// ||Ax - b||_2 double solve_residual(const Matrix& A, const Vector& x, const Vector& b) { - return linalg::norm2(A * x - b); + return linalgebra::norm2(A * x - b); } -// Generate a reproducible random nonsingular n x n matrix. Matrix random_matrix(std::size_t n, unsigned seed = 42) { std::mt19937 rng(seed); std::uniform_real_distribution<double> dist(-10.0, 10.0); @@ -66,17 +52,13 @@ Matrix random_matrix(std::size_t n, unsigned seed = 42) { } // namespace -// --------------------------------------------------------------------------- -// Factorization correctness -// --------------------------------------------------------------------------- - TEST_CASE("LU factorization: 3x3 known system", "[lu]") { const Matrix A{ {2.0, 1.0, -1.0}, {-3.0, -1.0, 2.0}, {-2.0, 1.0, 2.0} }; - const LUResult lu = linalg::lu_factor(A); + const LUResult lu = linalgebra::lu_factor(A); REQUIRE(lu.L.rows() == 3); REQUIRE(lu.U.rows() == 3); @@ -86,41 +68,33 @@ TEST_CASE("LU factorization: 3x3 known system", "[lu]") { CHECK(lu.L(i, i) == Catch::Approx(1.0)); } - // ||PA - LU|| must be near zero. CHECK(reconstruction_error(A, lu) == Catch::Approx(0.0).margin(1e-12)); } TEST_CASE("LU factorization: identity matrix", "[lu]") { const Matrix I = Matrix::identity(4); - const LUResult lu = linalg::lu_factor(I); + const LUResult lu = linalgebra::lu_factor(I); CHECK(reconstruction_error(I, lu) == Catch::Approx(0.0).margin(1e-14)); - // U should equal I (up to row ordering already handled by PA=LU). for (std::size_t i = 0; i < 4; ++i) { CHECK(lu.U(i, i) == Catch::Approx(1.0)); } } TEST_CASE("LU factorization: matrix requiring row swaps", "[lu]") { - // First column entry is zero. No-pivot LU would immediately fail. const Matrix A{ {0.0, 1.0, 2.0}, {3.0, 4.0, 5.0}, {6.0, 7.0, 8.0} }; - // A is singular (rows are in AP), but check that partial pivoting still - // proceeds and detects singularity correctly. - // Row 3 - row 2 = row 2 - row 1, so rank < 3. - CHECK_THROWS_AS(linalg::lu_factor(A), SingularMatrixError); + CHECK_THROWS_AS(linalgebra::lu_factor(A), SingularMatrixError); } TEST_CASE("LU factorization: first-column zero, nonsingular", "[lu]") { - // [[0, 1], [1, 0]] — requires a swap at step 0. const Matrix A{{0.0, 1.0}, {1.0, 0.0}}; - const LUResult lu = linalg::lu_factor(A); + const LUResult lu = linalgebra::lu_factor(A); CHECK(reconstruction_error(A, lu) == Catch::Approx(0.0).margin(1e-14)); - // Solving Ax = b: A swaps components. const Vector b{3.0, 7.0}; - const Vector x = linalg::lu_solve(lu, b); + const Vector x = linalgebra::lu_solve(lu, b); CHECK(solve_residual(A, x, b) == Catch::Approx(0.0).margin(1e-12)); CHECK(x[0] == Catch::Approx(7.0)); CHECK(x[1] == Catch::Approx(3.0)); @@ -129,17 +103,12 @@ TEST_CASE("LU factorization: first-column zero, nonsingular", "[lu]") { TEST_CASE("LU factorization: random nonsingular matrices", "[lu]") { for (std::size_t n : {5u, 10u, 20u}) { const Matrix A = random_matrix(n, 123u + static_cast<unsigned>(n)); - const LUResult lu = linalg::lu_factor(A); + const LUResult lu = linalgebra::lu_factor(A); CHECK(reconstruction_error(A, lu) == Catch::Approx(0.0).margin(1e-10)); } } -// --------------------------------------------------------------------------- -// Solve correctness -// --------------------------------------------------------------------------- - TEST_CASE("LU solve: known 3x3 system", "[lu]") { - // From Cramer / textbook: solution is x = (2, 3, -1). const Matrix A{ {2.0, 1.0, -1.0}, {-3.0, -1.0, 2.0}, @@ -148,8 +117,8 @@ TEST_CASE("LU solve: known 3x3 system", "[lu]") { const Vector expected{2.0, 3.0, -1.0}; const Vector b = A * expected; - const LUResult lu = linalg::lu_factor(A); - const Vector x = linalg::lu_solve(lu, b); + const LUResult lu = linalgebra::lu_factor(A); + const Vector x = linalgebra::lu_solve(lu, b); CHECK(x[0] == Catch::Approx(expected[0]).epsilon(1e-12)); CHECK(x[1] == Catch::Approx(expected[1]).epsilon(1e-12)); @@ -160,92 +129,77 @@ TEST_CASE("LU solve: known 3x3 system", "[lu]") { TEST_CASE("LU solve: random nonsingular systems", "[lu]") { for (std::size_t n : {5u, 15u, 30u}) { const Matrix A = random_matrix(n, 7u * static_cast<unsigned>(n)); - const LUResult lu = linalg::lu_factor(A); + const LUResult lu = linalgebra::lu_factor(A); - // Random rhs. std::mt19937 rng(n); std::uniform_real_distribution<double> dist(-5.0, 5.0); Vector b(n); for (std::size_t i = 0; i < n; ++i) b[i] = dist(rng); - const Vector x = linalg::lu_solve(lu, b); + const Vector x = linalgebra::lu_solve(lu, b); CHECK(solve_residual(A, x, b) == Catch::Approx(0.0).margin(1e-9)); } } TEST_CASE("LU solve: diagonal system", "[lu]") { - // D = diag(2, 3, 4), b = (2, 9, 8), solution = (1, 3, 2). const Matrix D{ {2.0, 0.0, 0.0}, {0.0, 3.0, 0.0}, {0.0, 0.0, 4.0} }; const Vector b{2.0, 9.0, 8.0}; - const LUResult lu = linalg::lu_factor(D); - const Vector x = linalg::lu_solve(lu, b); + const LUResult lu = linalgebra::lu_factor(D); + const Vector x = linalgebra::lu_solve(lu, b); CHECK(x[0] == Catch::Approx(1.0)); CHECK(x[1] == Catch::Approx(3.0)); CHECK(x[2] == Catch::Approx(2.0)); } -// --------------------------------------------------------------------------- -// Failure cases -// --------------------------------------------------------------------------- - TEST_CASE("LU factorization: non-square matrix throws", "[lu]") { const Matrix A(3, 4); - CHECK_THROWS_AS(linalg::lu_factor(A), DimensionMismatchError); + CHECK_THROWS_AS(linalgebra::lu_factor(A), DimensionMismatchError); } TEST_CASE("LU factorization: exactly singular matrix throws", "[lu]") { - // Zero row → singular. const Matrix A{ {1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, {0.0, 0.0, 0.0} }; - CHECK_THROWS_AS(linalg::lu_factor(A), SingularMatrixError); + CHECK_THROWS_AS(linalgebra::lu_factor(A), SingularMatrixError); } TEST_CASE("LU factorization: rank-deficient matrix throws", "[lu]") { - // Row 2 is a linear combination of rows 0 and 1. const Matrix A{ {1.0, 2.0}, {2.0, 4.0} }; - CHECK_THROWS_AS(linalg::lu_factor(A), SingularMatrixError); + CHECK_THROWS_AS(linalgebra::lu_factor(A), SingularMatrixError); } TEST_CASE("LU factorization: near-singular matrix throws at default tolerance", "[lu]") { - // Pivot reduced to ~1e-16, should trip the singularity check. const Matrix A{ {1.0, 1.0}, {1.0, 1.0 + 1e-16} }; - CHECK_THROWS_AS(linalg::lu_factor(A), SingularMatrixError); + CHECK_THROWS_AS(linalgebra::lu_factor(A), SingularMatrixError); } TEST_CASE("LU solve: mismatched rhs throws", "[lu]") { const Matrix A = Matrix::identity(3); - const LUResult lu = linalg::lu_factor(A); + const LUResult lu = linalgebra::lu_factor(A); const Vector b(5, 1.0); - CHECK_THROWS_AS(linalg::lu_solve(lu, b), DimensionMismatchError); + CHECK_THROWS_AS(linalgebra::lu_solve(lu, b), DimensionMismatchError); } -// --------------------------------------------------------------------------- -// L and U structure -// --------------------------------------------------------------------------- - TEST_CASE("LU factorization: L is unit lower triangular", "[lu]") { const Matrix A = random_matrix(6, 999u); - const LUResult lu = linalg::lu_factor(A); + const LUResult lu = linalgebra::lu_factor(A); const std::size_t n = A.rows(); for (std::size_t i = 0; i < n; ++i) { - // Unit diagonal. CHECK(lu.L(i, i) == Catch::Approx(1.0)); - // Strict upper triangle is zero. for (std::size_t j = i + 1; j < n; ++j) { CHECK(lu.L(i, j) == Catch::Approx(0.0).margin(1e-15)); } @@ -254,7 +208,7 @@ TEST_CASE("LU factorization: L is unit lower triangular", "[lu]") { TEST_CASE("LU factorization: U is upper triangular", "[lu]") { const Matrix A = random_matrix(6, 777u); - const LUResult lu = linalg::lu_factor(A); + const LUResult lu = linalgebra::lu_factor(A); const std::size_t n = A.rows(); for (std::size_t i = 1; i < n; ++i) { @@ -264,20 +218,15 @@ TEST_CASE("LU factorization: U is upper triangular", "[lu]") { } } -// --------------------------------------------------------------------------- -// Permutation sign and determinant -// --------------------------------------------------------------------------- - TEST_CASE("LU factorization: sign of permutation is ±1", "[lu]") { const Matrix A = random_matrix(5, 321u); - const LUResult lu = linalg::lu_factor(A); + const LUResult lu = linalgebra::lu_factor(A); CHECK((lu.sign == 1 || lu.sign == -1)); } TEST_CASE("LU factorization: determinant via sign * prod(diag(U))", "[lu]") { - // det([[3,1],[2,4]]) = 12 - 2 = 10 const Matrix A{{3.0, 1.0}, {2.0, 4.0}}; - const LUResult lu = linalg::lu_factor(A); + const LUResult lu = linalgebra::lu_factor(A); double det = static_cast<double>(lu.sign); for (std::size_t i = 0; i < A.rows(); ++i) { det *= lu.U(i, i); diff --git a/tests/test_matrix.cpp b/tests/test_matrix.cpp index fcd3d27..2fd9a5e 100644 --- a/tests/test_matrix.cpp +++ b/tests/test_matrix.cpp @@ -1,15 +1,11 @@ -#include "linalg_error.hpp" -#include "matrix.hpp" +import linalgebra; #include <catch2/catch_approx.hpp> #include <catch2/catch_test_macros.hpp> -#include <type_traits> -#include <utility> - -using linalg::Matrix; -using linalg::Vector; -using linalg::DimensionMismatchError; +using linalgebra::Matrix; +using linalgebra::Vector; +using linalgebra::DimensionMismatchError; TEST_CASE("Matrix constructors initialize dimensions and values", "[matrix]") { const Matrix empty; @@ -101,7 +97,7 @@ TEST_CASE("Matrix initializer list rejects unequal row lengths", "[matrix]") { TEST_CASE("Matrix transpose swaps rows and columns", "[matrix]") { const Matrix a{{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}}; - const Matrix at = linalg::transpose(a); + const Matrix at = linalgebra::transpose(a); REQUIRE(at.rows() == 3); REQUIRE(at.cols() == 2); diff --git a/tests/test_qr.cpp b/tests/test_qr.cpp index a837132..1a4114f 100644 --- a/tests/test_qr.cpp +++ b/tests/test_qr.cpp @@ -1,8 +1,4 @@ -#include "linalg_error.hpp" -#include "matrix.hpp" -#include "norms.hpp" -#include "qr.hpp" -#include "vector.hpp" +import linalgebra; #include <catch2/catch_approx.hpp> #include <catch2/catch_test_macros.hpp> @@ -12,18 +8,13 @@ #include <functional> #include <random> -using linalg::DimensionMismatchError; -using linalg::Matrix; -using linalg::QRResult; -using linalg::SingularMatrixError; - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- +using linalgebra::DimensionMismatchError; +using linalgebra::Matrix; +using linalgebra::QRResult; +using linalgebra::SingularMatrixError; namespace { -// ||A - QR||_F double reconstruction_error(const Matrix& A, const QRResult& qr) { const Matrix diff = A - qr.Q * qr.R; double err = 0.0; @@ -33,11 +24,9 @@ double reconstruction_error(const Matrix& A, const QRResult& qr) { return std::sqrt(err); } -// ||Q^T Q - I||_F (should be ~0 for orthonormal Q) double orthogonality_error(const QRResult& qr) { const Matrix& Q = qr.Q; const std::size_t n = Q.cols(); - // Compute Q^T Q Matrix QtQ(n, n); for (std::size_t i = 0; i < n; ++i) for (std::size_t j = 0; j < n; ++j) { @@ -45,7 +34,6 @@ double orthogonality_error(const QRResult& qr) { for (std::size_t k = 0; k < Q.rows(); ++k) s += Q(k, i) * Q(k, j); QtQ(i, j) = s; } - // ||QtQ - I||_F double err = 0.0; for (std::size_t i = 0; i < n; ++i) for (std::size_t j = 0; j < n; ++j) { @@ -55,7 +43,6 @@ double orthogonality_error(const QRResult& qr) { return std::sqrt(err); } -// R must be upper triangular (strict lower triangle near zero). bool r_is_upper_triangular(const Matrix& R, double tol = 1e-12) { for (std::size_t i = 1; i < R.rows(); ++i) for (std::size_t j = 0; j < i; ++j) @@ -73,7 +60,6 @@ Matrix random_matrix(std::size_t m, std::size_t n, unsigned seed = 42) { return M; } -// Run all checks for a given QR function and matrix. using QRFn = std::function<QRResult(const Matrix&)>; void check_qr(const Matrix& A, QRFn fn, @@ -91,33 +77,25 @@ void check_qr(const Matrix& A, QRFn fn, } // namespace -// --------------------------------------------------------------------------- -// Macro to run the same test body for all three methods -// --------------------------------------------------------------------------- - -#define FOR_ALL_METHODS(A, recon_tol, ortho_tol) \ - SECTION("classical_gs") { \ - check_qr(A, [](const Matrix& M) { return linalg::qr_classical_gs(M); }, \ - recon_tol, ortho_tol, "classical_gs"); \ - } \ - SECTION("modified_gs") { \ - check_qr(A, [](const Matrix& M) { return linalg::qr_modified_gs(M); }, \ - recon_tol, ortho_tol, "modified_gs"); \ - } \ - SECTION("householder") { \ - check_qr(A, [](const Matrix& M) { return linalg::qr_householder(M); }, \ - recon_tol, ortho_tol, "householder"); \ +#define FOR_ALL_METHODS(A, recon_tol, ortho_tol) \ + SECTION("classical_gs") { \ + check_qr(A, [](const Matrix& M) { return linalgebra::qr_classical_gs(M); }, \ + recon_tol, ortho_tol, "classical_gs"); \ + } \ + SECTION("modified_gs") { \ + check_qr(A, [](const Matrix& M) { return linalgebra::qr_modified_gs(M); }, \ + recon_tol, ortho_tol, "modified_gs"); \ + } \ + SECTION("householder") { \ + check_qr(A, [](const Matrix& M) { return linalgebra::qr_householder(M); }, \ + recon_tol, ortho_tol, "householder"); \ } -// --------------------------------------------------------------------------- -// Basic correctness: square matrices -// --------------------------------------------------------------------------- - TEST_CASE("QR: 3x3 known matrix", "[qr]") { const Matrix A{ {1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, - {7.0, 8.0, 10.0} // not exactly singular + {7.0, 8.0, 10.0} }; FOR_ALL_METHODS(A, 1e-12, 1e-12) } @@ -136,10 +114,6 @@ TEST_CASE("QR: diagonal matrix", "[qr]") { FOR_ALL_METHODS(D, 1e-14, 1e-14) } -// --------------------------------------------------------------------------- -// Rectangular (tall) matrices -// --------------------------------------------------------------------------- - TEST_CASE("QR: tall 5x3 random matrix", "[qr]") { const Matrix A = random_matrix(5, 3, 7u); FOR_ALL_METHODS(A, 1e-12, 1e-12) @@ -150,10 +124,6 @@ TEST_CASE("QR: tall 10x4 random matrix", "[qr]") { FOR_ALL_METHODS(A, 1e-12, 1e-12) } -// --------------------------------------------------------------------------- -// Random square matrices -// --------------------------------------------------------------------------- - TEST_CASE("QR: random 6x6", "[qr]") { const Matrix A = random_matrix(6, 6, 123u); FOR_ALL_METHODS(A, 1e-12, 1e-12) @@ -164,14 +134,7 @@ TEST_CASE("QR: random 12x12", "[qr]") { FOR_ALL_METHODS(A, 1e-11, 1e-11) } -// --------------------------------------------------------------------------- -// Nearly dependent columns — GS methods degrade; Householder stays clean -// --------------------------------------------------------------------------- - TEST_CASE("QR: nearly dependent columns", "[qr]") { - // Column 1 = column 0 + epsilon * e_1. - // Classical GS will lose most of Q's orthogonality here. - // Modified GS is better. Householder is unaffected. constexpr double eps = 1e-7; const Matrix A{ {1.0, 1.0 + eps, 0.0}, @@ -180,32 +143,24 @@ TEST_CASE("QR: nearly dependent columns", "[qr]") { {0.0, 0.0, 1.0} }; - // All three should reconstruct A accurately. SECTION("classical_gs reconstruction") { - const QRResult qr = linalg::qr_classical_gs(A); + const QRResult qr = linalgebra::qr_classical_gs(A); CHECK(reconstruction_error(A, qr) == Catch::Approx(0.0).margin(1e-10)); - // Orthogonality will be poor for classical GS on this input. - // We only assert it's not catastrophically wrong (< 0.01). CHECK(orthogonality_error(qr) < 0.01); } SECTION("modified_gs reconstruction") { - const QRResult qr = linalg::qr_modified_gs(A); + const QRResult qr = linalgebra::qr_modified_gs(A); CHECK(reconstruction_error(A, qr) == Catch::Approx(0.0).margin(1e-10)); CHECK(orthogonality_error(qr) == Catch::Approx(0.0).margin(1e-8)); } SECTION("householder reconstruction") { - const QRResult qr = linalg::qr_householder(A); + const QRResult qr = linalgebra::qr_householder(A); CHECK(reconstruction_error(A, qr) == Catch::Approx(0.0).margin(1e-13)); CHECK(orthogonality_error(qr) == Catch::Approx(0.0).margin(1e-13)); } } -// --------------------------------------------------------------------------- -// Hilbert-like ill-conditioned matrix -// --------------------------------------------------------------------------- - TEST_CASE("QR: 4x4 Hilbert matrix", "[qr]") { - // H[i][j] = 1 / (i + j + 1) const std::size_t n = 4; Matrix H(n, n); for (std::size_t i = 0; i < n; ++i) @@ -213,39 +168,34 @@ TEST_CASE("QR: 4x4 Hilbert matrix", "[qr]") { H(i, j) = 1.0 / static_cast<double>(i + j + 1); SECTION("classical_gs") { - const QRResult qr = linalg::qr_classical_gs(H); + const QRResult qr = linalgebra::qr_classical_gs(H); CHECK(reconstruction_error(H, qr) == Catch::Approx(0.0).margin(1e-12)); - // Orthogonality is imperfect on Hilbert matrices with classical GS. CHECK(orthogonality_error(qr) < 1e-8); } SECTION("modified_gs") { - const QRResult qr = linalg::qr_modified_gs(H); + const QRResult qr = linalgebra::qr_modified_gs(H); CHECK(reconstruction_error(H, qr) == Catch::Approx(0.0).margin(1e-12)); CHECK(orthogonality_error(qr) == Catch::Approx(0.0).margin(1e-10)); } SECTION("householder") { - const QRResult qr = linalg::qr_householder(H); + const QRResult qr = linalgebra::qr_householder(H); CHECK(reconstruction_error(H, qr) == Catch::Approx(0.0).margin(1e-13)); CHECK(orthogonality_error(qr) == Catch::Approx(0.0).margin(1e-13)); } } -// --------------------------------------------------------------------------- -// Structure checks -// --------------------------------------------------------------------------- - TEST_CASE("QR: R is upper triangular", "[qr]") { const Matrix A = random_matrix(5, 5, 555u); - CHECK(r_is_upper_triangular(linalg::qr_classical_gs(A).R)); - CHECK(r_is_upper_triangular(linalg::qr_modified_gs(A).R)); - CHECK(r_is_upper_triangular(linalg::qr_householder(A).R)); + CHECK(r_is_upper_triangular(linalgebra::qr_classical_gs(A).R)); + CHECK(r_is_upper_triangular(linalgebra::qr_modified_gs(A).R)); + CHECK(r_is_upper_triangular(linalgebra::qr_householder(A).R)); } TEST_CASE("QR: Q columns are unit length", "[qr]") { const Matrix A = random_matrix(6, 4, 321u); - for (QRFn fn : {QRFn{[](const Matrix& M) { return linalg::qr_classical_gs(M); }}, - QRFn{[](const Matrix& M) { return linalg::qr_modified_gs(M); }}, - QRFn{[](const Matrix& M) { return linalg::qr_householder(M); }}}) { + for (QRFn fn : {QRFn{[](const Matrix& M) { return linalgebra::qr_classical_gs(M); }}, + QRFn{[](const Matrix& M) { return linalgebra::qr_modified_gs(M); }}, + QRFn{[](const Matrix& M) { return linalgebra::qr_householder(M); }}}) { const QRResult qr = fn(A); for (std::size_t j = 0; j < qr.Q.cols(); ++j) { double norm2 = 0.0; @@ -256,25 +206,20 @@ TEST_CASE("QR: Q columns are unit length", "[qr]") { } } -// --------------------------------------------------------------------------- -// Failure cases -// --------------------------------------------------------------------------- - TEST_CASE("QR: fat matrix throws DimensionMismatchError", "[qr]") { - const Matrix A(3, 5); // rows < cols - CHECK_THROWS_AS(linalg::qr_classical_gs(A), DimensionMismatchError); - CHECK_THROWS_AS(linalg::qr_modified_gs(A), DimensionMismatchError); - CHECK_THROWS_AS(linalg::qr_householder(A), DimensionMismatchError); + const Matrix A(3, 5); + CHECK_THROWS_AS(linalgebra::qr_classical_gs(A), DimensionMismatchError); + CHECK_THROWS_AS(linalgebra::qr_modified_gs(A), DimensionMismatchError); + CHECK_THROWS_AS(linalgebra::qr_householder(A), DimensionMismatchError); } TEST_CASE("QR: linearly dependent columns throw from GS methods", "[qr]") { const Matrix A{ - {1.0, 2.0, 2.0}, // col 2 = 2 * col 0 + {1.0, 2.0, 2.0}, {2.0, 4.0, 4.0}, {3.0, 6.0, 6.0} }; - CHECK_THROWS_AS(linalg::qr_classical_gs(A), SingularMatrixError); - CHECK_THROWS_AS(linalg::qr_modified_gs(A), SingularMatrixError); - // Householder handles rank-deficient input gracefully (R gets a zero diagonal entry). - CHECK_NOTHROW(linalg::qr_householder(A)); + CHECK_THROWS_AS(linalgebra::qr_classical_gs(A), SingularMatrixError); + CHECK_THROWS_AS(linalgebra::qr_modified_gs(A), SingularMatrixError); + CHECK_NOTHROW(linalgebra::qr_householder(A)); } diff --git a/tests/test_qr_iteration.cpp b/tests/test_qr_iteration.cpp index 7126334..008f109 100644 --- a/tests/test_qr_iteration.cpp +++ b/tests/test_qr_iteration.cpp @@ -1,7 +1,4 @@ -#include "linalg_error.hpp" -#include "matrix.hpp" -#include "qr_iteration.hpp" -#include "vector.hpp" +import linalgebra; #include <catch2/catch_approx.hpp> #include <catch2/catch_test_macros.hpp> @@ -16,19 +13,14 @@ #include <utility> #include <vector> -using linalg::Matrix; -using linalg::NonConvergenceError; -using linalg::QRIterationOptions; -using linalg::QRIterationResult; -using linalg::Vector; - -// --------------------------------------------------------------------------- -// Test helpers -// --------------------------------------------------------------------------- +using linalgebra::Matrix; +using linalgebra::NonConvergenceError; +using linalgebra::QRIterationOptions; +using linalgebra::QRIterationResult; +using linalgebra::Vector; namespace { -// Sort (real, imag) eigenvalue pairs by real part (ascending), then by imag. using EigPairs = std::vector<std::pair<double, double>>; EigPairs to_pairs(const Vector& real_v, const Vector& imag_v) { @@ -66,20 +58,6 @@ bool eigs_match(const Vector& computed_real, const Vector& computed_imag, } // namespace -// --------------------------------------------------------------------------- -// Test 1: 2×2 symmetric matrix with known eigenvalues -// --------------------------------------------------------------------------- -// -// A = | 2 1 | is symmetric positive definite. -// | 1 2 | -// -// Characteristic polynomial: (2-λ)^2 - 1 = 0 → λ = 1, 3. -// Eigenvectors: [1,-1]/√2 (λ=1) and [1,1]/√2 (λ=3). -// -// The unshifted iteration converges at rate |λ_1/λ_2| = 1/3 per step, -// so only a handful of iterations are needed. -// Ref: T&B Theorem 28.2. - TEST_CASE("QR iteration (unshifted): 2x2 symmetric known eigenvalues", "[qr_iteration][shifted]") { const Matrix A{ @@ -87,13 +65,12 @@ TEST_CASE("QR iteration (unshifted): 2x2 symmetric known eigenvalues", {1.0, 2.0} }; - const QRIterationResult res = linalg::eigenvalues_unshifted(A); + const QRIterationResult res = linalgebra::eigenvalues_unshifted(A); REQUIRE(res.eigenvalues_real.size() == 2); REQUIRE(res.eigenvalues_imag.size() == 2); REQUIRE(res.iterations > 0); - // All eigenvalues of a symmetric matrix must be real. CHECK(std::abs(res.eigenvalues_imag[0]) < 1e-8); CHECK(std::abs(res.eigenvalues_imag[1]) < 1e-8); @@ -101,23 +78,6 @@ TEST_CASE("QR iteration (unshifted): 2x2 symmetric known eigenvalues", CHECK(eigs_match(res.eigenvalues_real, res.eigenvalues_imag, expected, 1e-8)); } -// --------------------------------------------------------------------------- -// Test 2: 4×4 symmetric tridiagonal matrix — reference eigenvalues -// --------------------------------------------------------------------------- -// -// The n×n symmetric tridiagonal matrix with 2 on the diagonal and -1 on the -// first super- and sub-diagonals has known eigenvalues (discrete Laplacian): -// -// λ_k = 2 - 2 cos(k π / (n+1)), k = 1, …, n -// -// Ref: Golub & Van Loan §4.4.2 (discrete sine transform). -// -// For n = 4: -// λ_1 = 2 - 2 cos(π/5) ≈ 0.3820 -// λ_2 = 2 - 2 cos(2π/5) ≈ 1.3820 -// λ_3 = 2 - 2 cos(3π/5) ≈ 2.6180 -// λ_4 = 2 - 2 cos(4π/5) ≈ 3.6180 - TEST_CASE("QR iteration (unshifted): 4x4 symmetric tridiagonal", "[qr_iteration][unshifted]") { const Matrix A{ @@ -127,16 +87,14 @@ TEST_CASE("QR iteration (unshifted): 4x4 symmetric tridiagonal", { 0.0, 0.0, -1.0, 2.0} }; - const QRIterationResult res = linalg::eigenvalues_unshifted(A); + const QRIterationResult res = linalgebra::eigenvalues_unshifted(A); REQUIRE(res.eigenvalues_real.size() == 4); REQUIRE(res.eigenvalues_imag.size() == 4); - // All eigenvalues of a symmetric matrix must be real. for (std::size_t k = 0; k < 4; ++k) CHECK(std::abs(res.eigenvalues_imag[k]) < 1e-8); - // Compare against the closed-form reference. constexpr double pi = 3.14159265358979323846; const EigPairs expected = { {2.0 - 2.0 * std::cos( pi / 5.0), 0.0}, @@ -147,21 +105,6 @@ TEST_CASE("QR iteration (unshifted): 4x4 symmetric tridiagonal", CHECK(eigs_match(res.eigenvalues_real, res.eigenvalues_imag, expected, 1e-8)); } -// --------------------------------------------------------------------------- -// Test 3: 5×5 symmetric tridiagonal — convergence history -// --------------------------------------------------------------------------- -// -// Uses a 5×5 symmetric tridiagonal (discrete Laplacian) to guarantee all -// real eigenvalues and predictable linear convergence. The Frobenius norm -// of the strict lower triangle is printed at every step so the convergence -// rate can be observed directly. -// -// Expected behaviour: ||lower(A_k)||_F decreases geometrically each step -// (linear convergence), with ratio ≈ max_j |λ_{j+1}/λ_j|. -// Ref: T&B Theorem 28.2. -// -// 5×5 tridiagonal eigenvalues: λ_k = 2 - 2cos(kπ/6), k = 1..5. - TEST_CASE("QR iteration (unshifted): 5x5 convergence history", "[qr_iteration][unshifted]") { const Matrix A{ @@ -175,12 +118,11 @@ TEST_CASE("QR iteration (unshifted): 5x5 convergence history", QRIterationOptions opts; opts.track_convergence = true; - const QRIterationResult res = linalg::eigenvalues_unshifted(A, opts); + const QRIterationResult res = linalgebra::eigenvalues_unshifted(A, opts); REQUIRE_FALSE(res.convergence_history.empty()); REQUIRE(res.eigenvalues_real.size() == 5); - // Print convergence history so the linear rate is visible. std::cout << "\n=== Unshifted QR — 5x5 convergence history ===\n"; std::cout << " Converged in " << res.iterations << " iteration(s)\n"; for (std::size_t k = 0; k < res.convergence_history.size(); ++k) { @@ -190,32 +132,26 @@ TEST_CASE("QR iteration (unshifted): 5x5 convergence history", } std::cout << "=======================================================\n"; - // The final recorded norm must be below the default tolerance. CHECK(res.convergence_history.back() < opts.tolerance); } -// --------------------------------------------------------------------------- -// Test 4: Eigenvalue residuals below 1e-8 -// --------------------------------------------------------------------------- - TEST_CASE("QR iteration (unshifted): residuals below 1e-8", "[qr_iteration][unshifted]") { SECTION("2x2: eigenvalues 1 and 3") { const Matrix A{{2.0, 1.0}, {1.0, 2.0}}; - const QRIterationResult res = linalg::eigenvalues_unshifted(A); + const QRIterationResult res = linalgebra::eigenvalues_unshifted(A); const EigPairs expected = {{1.0, 0.0}, {3.0, 0.0}}; CHECK(eigs_match(res.eigenvalues_real, res.eigenvalues_imag, expected, 1e-8)); } SECTION("3x3 diagonal: eigenvalues 1, 4, 9") { - // Diagonal matrix — already in Schur form; converges in one step. const Matrix D{ {1.0, 0.0, 0.0}, {0.0, 4.0, 0.0}, {0.0, 0.0, 9.0} }; - const QRIterationResult res = linalg::eigenvalues_unshifted(D); + const QRIterationResult res = linalgebra::eigenvalues_unshifted(D); const EigPairs expected = {{1.0, 0.0}, {4.0, 0.0}, {9.0, 0.0}}; CHECK(eigs_match(res.eigenvalues_real, res.eigenvalues_imag, expected, 1e-8)); } @@ -234,13 +170,13 @@ TEST_CASE("QR iteration (unshifted): residuals below 1e-8", {2.0 - 2.0 * std::cos(3.0 * pi / 5.0), 0.0}, {2.0 - 2.0 * std::cos(4.0 * pi / 5.0), 0.0} }; - const QRIterationResult res = linalg::eigenvalues_unshifted(A); + const QRIterationResult res = linalgebra::eigenvalues_unshifted(A); CHECK(eigs_match(res.eigenvalues_real, res.eigenvalues_imag, expected, 1e-8)); } SECTION("5x5 identity: all eigenvalues == 1") { const Matrix I = Matrix::identity(5); - const QRIterationResult res = linalg::eigenvalues_unshifted(I); + const QRIterationResult res = linalgebra::eigenvalues_unshifted(I); REQUIRE(res.eigenvalues_real.size() == 5); for (std::size_t k = 0; k < 5; ++k) { CHECK(std::abs(res.eigenvalues_real[k] - 1.0) < 1e-8); @@ -249,35 +185,21 @@ TEST_CASE("QR iteration (unshifted): residuals below 1e-8", } } -// --------------------------------------------------------------------------- -// Failure cases -// --------------------------------------------------------------------------- - TEST_CASE("QR iteration (unshifted): non-square matrix throws", "[qr_iteration][unshifted]") { - const Matrix A(3, 4); // non-square - CHECK_THROWS_AS(linalg::eigenvalues_unshifted(A), - linalg::DimensionMismatchError); + const Matrix A(3, 4); + CHECK_THROWS_AS(linalgebra::eigenvalues_unshifted(A), + linalgebra::DimensionMismatchError); } TEST_CASE("QR iteration (unshifted): max_iterations exceeded throws", "[qr_iteration][unshifted]") { - // Cap at zero iterations — any non-trivial matrix fails immediately. const Matrix A{{2.0, 1.0}, {1.0, 2.0}}; QRIterationOptions opts; opts.max_iterations = 0; - CHECK_THROWS_AS(linalg::eigenvalues_unshifted(A, opts), NonConvergenceError); + CHECK_THROWS_AS(linalgebra::eigenvalues_unshifted(A, opts), NonConvergenceError); } -// =========================================================================== -// Wilkinson-shifted QR iteration -// =========================================================================== - -// --------------------------------------------------------------------------- -// Helper builds a random symmetric matrix via A = M + M^T (guaranteed real -// eigenvalues) with a fixed seed for reproducibility. -// --------------------------------------------------------------------------- - namespace { Matrix random_symmetric(std::size_t n, unsigned seed = 42) { @@ -296,14 +218,6 @@ Matrix random_symmetric(std::size_t n, unsigned seed = 42) { } // namespace -// --------------------------------------------------------------------------- -// Test S1: shifted vs unshifted iteration count on the same matrix. -// -// Wilkinson-shifted QR converges (typically cubically) in far fewer steps -// than the unshifted algorithm (linear convergence). -// The test asserts the shifted count is strictly smaller and prints both. -// --------------------------------------------------------------------------- - TEST_CASE("QR iteration (shifted): fewer iterations than unshifted", "[qr_iteration][shifted]") { const Matrix A{ @@ -318,8 +232,8 @@ TEST_CASE("QR iteration (shifted): fewer iterations than unshifted", QRIterationOptions opts; opts.track_convergence = true; - const QRIterationResult unshifted = linalg::eigenvalues_unshifted(A, opts); - const QRIterationResult shifted = linalg::eigenvalues_shifted(A, opts); + const QRIterationResult unshifted = linalgebra::eigenvalues_unshifted(A, opts); + const QRIterationResult shifted = linalgebra::eigenvalues_shifted(A, opts); std::cout << "\n=== Shifted vs Unshifted ===\n"; std::cout << " Unshifted iterations: " << unshifted.iterations << "\n"; @@ -333,25 +247,15 @@ TEST_CASE("QR iteration (shifted): fewer iterations than unshifted", 1e-8)); } -// --------------------------------------------------------------------------- -// Test S2: matrix where unshifted takes >100 iterations, shifted takes <20. -// -// A nearly-equal-eigenvalue symmetric matrix maximises the linear convergence -// slowdown. Using a scaled identity perturbation: eigenvalues cluster near 1, -// slowing unshifted (ratio ≈ 1) while the Wilkinson shift adapts instantly. -// --------------------------------------------------------------------------- - TEST_CASE("QR iteration (shifted): converges <20 iters where unshifted needs >100", "[qr_iteration][shifted]") { - // 5×5 symmetric matrix with eigenvalues 1, 1.001, 1.002, 1.003, 1.004. - // Off-diagonal entries couple them. Unshifted stalls (|λ_{j+1}/λ_j| ≈ 1). const Matrix A = random_symmetric(5, 17u); QRIterationOptions opts; - opts.max_iterations = 2000; + opts.max_iterations = 2000; - const QRIterationResult unshifted = linalg::eigenvalues_unshifted(A, opts); - const QRIterationResult shifted = linalg::eigenvalues_shifted(A, opts); + const QRIterationResult unshifted = linalgebra::eigenvalues_unshifted(A, opts); + const QRIterationResult shifted = linalgebra::eigenvalues_shifted(A, opts); std::cout << "\n=== Hard matrix ===\n"; std::cout << " Unshifted iterations: " << unshifted.iterations << "\n"; @@ -361,10 +265,6 @@ TEST_CASE("QR iteration (shifted): converges <20 iters where unshifted needs >10 CHECK(shifted.iterations < 20); } -// --------------------------------------------------------------------------- -// Test S3: shifted eigenvalues match known values to within 1e-8. -// --------------------------------------------------------------------------- - TEST_CASE("QR iteration (shifted): residuals below 1e-8", "[qr_iteration][shifted]") { SECTION("4x4 tridiagonal: closed-form eigenvalues") { @@ -381,29 +281,18 @@ TEST_CASE("QR iteration (shifted): residuals below 1e-8", {2.0 - 2.0 * std::cos(3.0 * pi / 5.0), 0.0}, {2.0 - 2.0 * std::cos(4.0 * pi / 5.0), 0.0} }; - const QRIterationResult res = linalg::eigenvalues_shifted(A); + const QRIterationResult res = linalgebra::eigenvalues_shifted(A); CHECK(eigs_match(res.eigenvalues_real, res.eigenvalues_imag, expected, 1e-8)); } SECTION("2x2 known eigenvalues") { const Matrix A{{2.0, 1.0}, {1.0, 2.0}}; const EigPairs expected = {{1.0, 0.0}, {3.0, 0.0}}; - const QRIterationResult res = linalg::eigenvalues_shifted(A); + const QRIterationResult res = linalgebra::eigenvalues_shifted(A); CHECK(eigs_match(res.eigenvalues_real, res.eigenvalues_imag, expected, 1e-8)); } } -// =========================================================================== -// Hessenberg reduction + practical QR algorithm -// =========================================================================== - -// --------------------------------------------------------------------------- -// Test H1: hessenberg_reduction produces correct H and Q. -// -// Verify: (1) H is upper Hessenberg, (2) Q is orthogonal, (3) A = Q H Q^T. -// Ref: GVL §7.4.2. -// --------------------------------------------------------------------------- - namespace { bool is_upper_hessenberg(const Matrix& H, double tol = 1e-10) { @@ -421,7 +310,6 @@ double frobenius_norm(const Matrix& A) { return std::sqrt(s); } -// ||A - B||_F double diff_norm(const Matrix& A, const Matrix& B) { double s = 0.0; for (std::size_t i = 0; i < A.rows(); ++i) @@ -432,7 +320,6 @@ double diff_norm(const Matrix& A, const Matrix& B) { return std::sqrt(s); } -// ||Q^T Q - I||_F double orthogonality_error(const Matrix& Q) { const std::size_t n = Q.rows(); double err = 0.0; @@ -451,29 +338,21 @@ double orthogonality_error(const Matrix& Q) { TEST_CASE("Hessenberg reduction: structure and similarity", "[qr_iteration][shifted]") { const Matrix A = random_symmetric(6, 7u); - const linalg::HessenbergResult hr = linalg::hessenberg_reduction(A); + const linalgebra::HessenbergResult hr = linalgebra::hessenberg_reduction(A); - // H must be upper Hessenberg. CHECK(is_upper_hessenberg(hr.H)); - - // Q must be orthogonal. CHECK(orthogonality_error(hr.Q) < 1e-10); - // A = Q H Q^T ⟹ ||A - Q H Q^T||_F < tol. - const Matrix QtHQ = hr.Q * hr.H * linalg::transpose(hr.Q); + const Matrix QtHQ = hr.Q * hr.H * linalgebra::transpose(hr.Q); CHECK(diff_norm(A, QtHQ) < 1e-10); } -// --------------------------------------------------------------------------- -// Test H2: eigenvalues_hessenberg agrees with eigenvalues_shifted to 1e-6. -// --------------------------------------------------------------------------- - TEST_CASE("Hessenberg QR: eigenvalues match shifted QR to 1e-6", "[qr_iteration][shifted]") { const Matrix A = random_symmetric(8, 99u); - const QRIterationResult ref = linalg::eigenvalues_shifted(A); - const QRIterationResult hess = linalg::eigenvalues_hessenberg(A); + const QRIterationResult ref = linalgebra::eigenvalues_shifted(A); + const QRIterationResult hess = linalgebra::eigenvalues_hessenberg(A); REQUIRE(hess.eigenvalues_real.size() == 8); CHECK(eigs_match(hess.eigenvalues_real, hess.eigenvalues_imag, @@ -481,10 +360,6 @@ TEST_CASE("Hessenberg QR: eigenvalues match shifted QR to 1e-6", 1e-6)); } -// --------------------------------------------------------------------------- -// Test H3: known eigenvalues — 4×4 tridiagonal. -// --------------------------------------------------------------------------- - TEST_CASE("Hessenberg QR: residuals below 1e-8 on known matrix", "[qr_iteration][shifted]") { const Matrix A{ @@ -500,19 +375,10 @@ TEST_CASE("Hessenberg QR: residuals below 1e-8 on known matrix", {2.0 - 2.0 * std::cos(3.0 * pi / 5.0), 0.0}, {2.0 - 2.0 * std::cos(4.0 * pi / 5.0), 0.0} }; - const QRIterationResult res = linalg::eigenvalues_hessenberg(A); + const QRIterationResult res = linalgebra::eigenvalues_hessenberg(A); CHECK(eigs_match(res.eigenvalues_real, res.eigenvalues_imag, expected, 1e-8)); } -// --------------------------------------------------------------------------- -// Test H4: benchmark — shifted QR vs Hessenberg pipeline for n = 50, 100, 200. -// -// The Hessenberg pipeline reduces each QR step from O(n³) to O(n²), so the -// speedup should grow with n. We print the wall-clock ratio and assert that -// the Hessenberg version is faster for n >= 50. -// Ref: GVL §7.4.2; T&B Lecture 29. -// --------------------------------------------------------------------------- - TEST_CASE("Hessenberg QR: faster than naive shifted QR for large n", "[qr_iteration][hessenberg]") { using Clock = std::chrono::high_resolution_clock; @@ -531,11 +397,11 @@ TEST_CASE("Hessenberg QR: faster than naive shifted QR for large n", const Matrix A = random_symmetric(n, 13u); const auto t0s = Clock::now(); - { const auto tmp = linalg::eigenvalues_shifted(A); (void)tmp; } + { const auto tmp = linalgebra::eigenvalues_shifted(A); (void)tmp; } const double t_shifted = Seconds(Clock::now() - t0s).count(); const auto t0h = Clock::now(); - const QRIterationResult hess = linalg::eigenvalues_hessenberg(A); + const QRIterationResult hess = linalgebra::eigenvalues_hessenberg(A); const double t_hess = Seconds(Clock::now() - t0h).count(); const double speedup = t_shifted / t_hess; @@ -547,11 +413,9 @@ TEST_CASE("Hessenberg QR: faster than naive shifted QR for large n", << std::setprecision(2) << std::setw(12) << speedup << "x\n"; - // The Hessenberg version must be faster for all tested sizes. CHECK(t_hess < t_shifted); - // And must give correct eigenvalues (agree with shifted to 1e-6). - const QRIterationResult ref = linalg::eigenvalues_shifted(A); + const QRIterationResult ref = linalgebra::eigenvalues_shifted(A); CHECK(eigs_match(hess.eigenvalues_real, hess.eigenvalues_imag, to_pairs(ref.eigenvalues_real, ref.eigenvalues_imag), 1e-6)); diff --git a/tests/test_triangular_solve.cpp b/tests/test_triangular_solve.cpp index da7e55c..f1ce709 100644 --- a/tests/test_triangular_solve.cpp +++ b/tests/test_triangular_solve.cpp @@ -1,20 +1,17 @@ -#include "linalg_error.hpp" -#include "matrix.hpp" -#include "norms.hpp" -#include "triangular_solve.hpp" +import linalgebra; #include <catch2/catch_approx.hpp> #include <catch2/catch_test_macros.hpp> -using linalg::DimensionMismatchError; -using linalg::Matrix; -using linalg::SingularMatrixError; -using linalg::Vector; +using linalgebra::DimensionMismatchError; +using linalgebra::Matrix; +using linalgebra::SingularMatrixError; +using linalgebra::Vector; namespace { double residual_norm(const Matrix& a, const Vector& x, const Vector& b) { - return linalg::norm2((a * x) - b); + return linalgebra::norm2((a * x) - b); } } // namespace @@ -28,7 +25,7 @@ TEST_CASE("Forward substitution solves lower-triangular systems", "[triangular]" const Vector expected{1.0, 2.0, -1.0}; const Vector rhs = lower * expected; - const Vector x = linalg::forward_substitution(lower, rhs); + const Vector x = linalgebra::forward_substitution(lower, rhs); REQUIRE(x.size() == expected.size()); CHECK(x[0] == Catch::Approx(expected[0])); CHECK(x[1] == Catch::Approx(expected[1])); @@ -45,7 +42,7 @@ TEST_CASE("Backward substitution solves upper-triangular systems", "[triangular] const Vector expected{2.0, -1.0, 3.0}; const Vector rhs = upper * expected; - const Vector x = linalg::backward_substitution(upper, rhs); + const Vector x = linalgebra::backward_substitution(upper, rhs); REQUIRE(x.size() == expected.size()); CHECK(x[0] == Catch::Approx(expected[0])); CHECK(x[1] == Catch::Approx(expected[1])); @@ -61,7 +58,7 @@ TEST_CASE("Triangular solves support unit-diagonal systems", "[triangular]") { }; const Vector rhs{1.0, 0.0, 4.0}; - const Vector x = linalg::forward_substitution(lower, rhs, 1e-12, true); + const Vector x = linalgebra::forward_substitution(lower, rhs, 1e-12, true); CHECK(x[0] == Catch::Approx(1.0)); CHECK(x[1] == Catch::Approx(2.0)); CHECK(x[2] == Catch::Approx(3.0)); @@ -70,19 +67,19 @@ TEST_CASE("Triangular solves support unit-diagonal systems", "[triangular]") { TEST_CASE("Triangular solves reject shape and structure mismatches", "[triangular]") { const Matrix nonsquare(2, 3); const Vector rhs2{1.0, 2.0}; - CHECK_THROWS_AS(linalg::forward_substitution(nonsquare, rhs2), DimensionMismatchError); + CHECK_THROWS_AS(linalgebra::forward_substitution(nonsquare, rhs2), DimensionMismatchError); const Matrix lower{ {1.0, 1.0}, {2.0, 3.0} }; - CHECK_THROWS_AS(linalg::forward_substitution(lower, rhs2), std::invalid_argument); + CHECK_THROWS_AS(linalgebra::forward_substitution(lower, rhs2), std::invalid_argument); const Matrix upper{ {1.0, 2.0}, {1.0, 3.0} }; - CHECK_THROWS_AS(linalg::backward_substitution(upper, rhs2), std::invalid_argument); + CHECK_THROWS_AS(linalgebra::backward_substitution(upper, rhs2), std::invalid_argument); } TEST_CASE("Triangular solves detect negligible diagonal entries", "[triangular]") { @@ -91,11 +88,11 @@ TEST_CASE("Triangular solves detect negligible diagonal entries", "[triangular]" {2.0, 1.0} }; const Vector rhs{1.0, 2.0}; - CHECK_THROWS_AS(linalg::forward_substitution(lower, rhs), SingularMatrixError); + CHECK_THROWS_AS(linalgebra::forward_substitution(lower, rhs), SingularMatrixError); const Matrix upper{ {1.0, 2.0}, {0.0, 1e-14} }; - CHECK_THROWS_AS(linalg::backward_substitution(upper, rhs), SingularMatrixError); + CHECK_THROWS_AS(linalgebra::backward_substitution(upper, rhs), SingularMatrixError); } diff --git a/tests/test_vector.cpp b/tests/test_vector.cpp index 364ce31..a776dc8 100644 --- a/tests/test_vector.cpp +++ b/tests/test_vector.cpp @@ -1,6 +1,4 @@ -#include "linalg_error.hpp" -#include "norms.hpp" -#include "vector.hpp" +import linalgebra; #include <catch2/catch_approx.hpp> #include <catch2/catch_test_macros.hpp> @@ -8,8 +6,8 @@ #include <type_traits> #include <utility> -using linalg::DimensionMismatchError; -using linalg::Vector; +using linalgebra::DimensionMismatchError; +using linalgebra::Vector; TEST_CASE("Vector constructors initialize size and values", "[vector]") { const Vector empty; @@ -82,17 +80,17 @@ TEST_CASE("Vector arithmetic enforces shape compatibility", "[vector]") { CHECK(scaled[1] == 1.0); CHECK(scaled[2] == 1.5); - CHECK(linalg::dot(a, b) == 32.0); + CHECK(linalgebra::dot(a, b) == 32.0); const Vector short_vec{1.0, 2.0}; CHECK_THROWS_AS(a + short_vec, DimensionMismatchError); - CHECK_THROWS_AS(linalg::dot(a, short_vec), DimensionMismatchError); + CHECK_THROWS_AS(linalgebra::dot(a, short_vec), DimensionMismatchError); } TEST_CASE("Vector 2-norm matches manually computed values", "[vector][norms]") { const Vector v{3.0, 4.0}; - CHECK(linalg::norm2(v) == Catch::Approx(5.0)); + CHECK(linalgebra::norm2(v) == Catch::Approx(5.0)); const Vector zero(5); - CHECK(linalg::norm2(zero) == Catch::Approx(0.0)); + CHECK(linalgebra::norm2(zero) == Catch::Approx(0.0)); } |