aboutsummaryrefslogtreecommitdiff
path: root/tests/test_svd.cpp
diff options
context:
space:
mode:
authory-jan137 <yousefjan24000@gmail.com>2026-05-16 12:10:05 +0300
committery-jan137 <yousefjan24000@gmail.com>2026-05-16 12:10:05 +0300
commit750f276a1403c5defd58b06f13c703b5e3d59245 (patch)
tree5218b52ca98f6df3fa4e202ff999b81f35c4e28b /tests/test_svd.cpp
parent92220ea5a483d6ece73bb6472af0773bc4106d73 (diff)
Finish TODOs
Diffstat (limited to 'tests/test_svd.cpp')
-rw-r--r--tests/test_svd.cpp140
1 files changed, 140 insertions, 0 deletions
diff --git a/tests/test_svd.cpp b/tests/test_svd.cpp
new file mode 100644
index 0000000..d2121d6
--- /dev/null
+++ b/tests/test_svd.cpp
@@ -0,0 +1,140 @@
+import linalgebra;
+
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <cmath>
+#include <cstddef>
+#include <random>
+
+using linalgebra::DimensionMismatchError;
+using linalgebra::Matrix;
+using linalgebra::SVDResult;
+
+namespace {
+
+double frobenius_diff(const Matrix& A, const Matrix& B) {
+ double s = 0.0;
+ for (std::size_t i = 0; i < A.rows(); ++i)
+ for (std::size_t j = 0; j < A.cols(); ++j) {
+ const double d = A(i, j) - B(i, j);
+ s += d * d;
+ }
+ return std::sqrt(s);
+}
+
+// Reconstruct A = U * diag(sigma) * Vt and measure error
+double reconstruction_error(const Matrix& A, const SVDResult& svd) {
+ const std::size_t m = A.rows();
+ const std::size_t n = A.cols();
+ const std::size_t p = svd.sigma.size();
+
+ // Build U_thin (m x p) — first p columns of U.
+ Matrix U_thin(m, p);
+ for (std::size_t i = 0; i < m; ++i)
+ for (std::size_t j = 0; j < p; ++j)
+ U_thin(i, j) = svd.U(i, j);
+
+ // Build Sigma_diag (p x p).
+ Matrix S(p, p, 0.0);
+ for (std::size_t i = 0; i < p; ++i) S(i, i) = svd.sigma[i];
+
+ // Build Vt_thin (p x n) — first p rows of Vt.
+ Matrix Vt_thin(p, n);
+ for (std::size_t i = 0; i < p; ++i)
+ for (std::size_t j = 0; j < n; ++j)
+ Vt_thin(i, j) = svd.Vt(i, j);
+
+ const Matrix recon = U_thin * S * Vt_thin;
+ return frobenius_diff(A, recon);
+}
+
+// Check orthogonality of the first k columns of M
+double col_ortho_error(const Matrix& M, std::size_t k) {
+ const std::size_t m = M.rows();
+ double s = 0.0;
+ for (std::size_t i = 0; i < k; ++i) {
+ for (std::size_t j = 0; j < k; ++j) {
+ double dot = 0.0;
+ for (std::size_t r = 0; r < m; ++r) dot += M(r, i) * M(r, j);
+ const double expected = (i == j) ? 1.0 : 0.0;
+ const double d = dot - expected;
+ s += d * d;
+ }
+ }
+ return std::sqrt(s);
+}
+
+} // namespace
+
+TEST_CASE("svd: 2x2 diagonal", "[svd]") {
+ Matrix A{{3.0, 0.0}, {0.0, -2.0}};
+ auto res = linalgebra::svd(A);
+
+ REQUIRE(res.sigma[0] == Catch::Approx(3.0).epsilon(1e-10));
+ REQUIRE(res.sigma[1] == Catch::Approx(2.0).epsilon(1e-10));
+ REQUIRE(reconstruction_error(A, res) < 1e-10);
+}
+
+TEST_CASE("svd: 3x2 tall matrix", "[svd]") {
+ Matrix A{{1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0}};
+ auto res = linalgebra::svd(A);
+
+ REQUIRE(reconstruction_error(A, res) < 1e-10);
+
+ REQUIRE(res.sigma[0] >= res.sigma[1]);
+ REQUIRE(res.sigma[1] >= 0.0);
+
+ REQUIRE(col_ortho_error(res.U, 2) < 1e-10);
+
+ REQUIRE(col_ortho_error(linalgebra::transpose(res.Vt), 2) < 1e-10);
+}
+
+TEST_CASE("svd: rank-1 matrix", "[svd]") {
+ // A = u * v^T for u = [1,2,3], v = [2,1].
+ Matrix A{{2.0, 1.0}, {4.0, 2.0}, {6.0, 3.0}};
+ auto res = linalgebra::svd(A);
+
+ REQUIRE(reconstruction_error(A, res) < 1e-10);
+ // Second singular value should be near zero.
+ REQUIRE(res.sigma[0] > 1e-10);
+ REQUIRE(res.sigma[1] < 1e-8);
+}
+
+TEST_CASE("svd: identity 4x4", "[svd]") {
+ auto I = Matrix::identity(4);
+ auto res = linalgebra::svd(I);
+
+ for (std::size_t i = 0; i < 4; ++i)
+ REQUIRE(res.sigma[i] == Catch::Approx(1.0).epsilon(1e-10));
+ REQUIRE(reconstruction_error(I, res) < 1e-10);
+}
+
+TEST_CASE("svd: 4x4 random", "[svd]") {
+ std::mt19937 rng(42);
+ std::uniform_real_distribution<double> dist(-2.0, 2.0);
+ Matrix A(4, 4);
+ for (std::size_t i = 0; i < 4; ++i)
+ for (std::size_t j = 0; j < 4; ++j)
+ A(i, j) = dist(rng);
+
+ auto res = linalgebra::svd(A);
+ REQUIRE(reconstruction_error(A, res) < 1e-9);
+ REQUIRE(col_ortho_error(res.U, 4) < 1e-10);
+ REQUIRE(col_ortho_error(linalgebra::transpose(res.Vt), 4) < 1e-10);
+}
+
+TEST_CASE("svd: Hilbert 4x4", "[svd]") {
+ Matrix H(4, 4);
+ for (std::size_t i = 0; i < 4; ++i)
+ for (std::size_t j = 0; j < 4; ++j)
+ H(i, j) = 1.0 / static_cast<double>(i + j + 1);
+ auto res = linalgebra::svd(H);
+ REQUIRE(reconstruction_error(H, res) < 1e-10);
+ for (std::size_t i = 0; i < 4; ++i) REQUIRE(res.sigma[i] >= 0.0);
+}
+
+TEST_CASE("svd: wide matrix throws", "[svd]") {
+ Matrix A(2, 4);
+ REQUIRE_THROWS_AS(linalgebra::svd(A), DimensionMismatchError);
+}