Working on an SVD implimentation
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
// include the module you're going to test next
|
||||
#include "Matrix.hpp"
|
||||
#include "SVD.hpp"
|
||||
|
||||
// any other libraries
|
||||
#include <array>
|
||||
@@ -637,4 +638,399 @@ TEST_CASE("Eigenvalues and Vectors", "Matrix") {
|
||||
REQUIRE_THAT(values[1][0], Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
REQUIRE_THAT(values[2][0], Catch::Matchers::WithinRel(16.1168f, 1e-4f));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SVD Tests — Reference values computed via scipy.linalg.svd (Python)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Helper: compute Frobenius norm of a matrix.
|
||||
*/
|
||||
template <uint8_t rows, uint8_t columns>
|
||||
static float frobeniusNorm(const Matrix<rows, columns> &M) {
|
||||
float sum = 0;
|
||||
for (uint8_t i = 0; i < rows; i++) {
|
||||
for (uint8_t j = 0; j < columns; j++) {
|
||||
float v = M.Get(i, j);
|
||||
sum += v * v;
|
||||
}
|
||||
}
|
||||
return sqrtf(sum);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper: compute reconstruction error ||A - UΣVᵀ||_F.
|
||||
*
|
||||
* Verifies the fundamental SVD identity A = U × diag(σ) × Vᵀ.
|
||||
* For non-square matrices, only the first min(rows,cols) singular values
|
||||
* contribute to the reconstruction.
|
||||
*/
|
||||
template <uint8_t rows, uint8_t columns>
|
||||
static float svdReconstructionError(const Matrix<rows, columns> &A,
|
||||
const Matrix<rows, columns> &U,
|
||||
const Matrix<columns, 1> &sigma,
|
||||
const Matrix<columns, columns> &Vt) {
|
||||
// Compute U × diag(σ): only first min(rows,cols) columns of U are used
|
||||
constexpr uint8_t k = (rows < columns) ? rows : columns;
|
||||
Matrix<rows, columns> USigma{0};
|
||||
for (uint8_t i = 0; i < rows; i++) {
|
||||
for (uint8_t j = 0; j < k; j++) {
|
||||
USigma[i][j] = U.Get(i, j) * sigma.Get(j, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute (UΣ) × Vᵀ: only first k rows of Vt are used
|
||||
Matrix<rows, columns> UVt{0};
|
||||
for (uint8_t i = 0; i < rows; i++) {
|
||||
for (uint8_t j = 0; j < columns; j++) {
|
||||
float sum = 0;
|
||||
for (uint8_t p = 0; p < k; p++) {
|
||||
sum += USigma[i][p] * Vt.Get(p, j);
|
||||
}
|
||||
UVt[i][j] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute ||A - UVᵀ||_F
|
||||
Matrix<rows, columns> diff{0};
|
||||
A.Sub(UVt, diff);
|
||||
return frobeniusNorm(diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper: check orthogonality of the first k columns of M.
|
||||
* Verifies M[:,0:k]ᵀ × M[:,0:k] ≈ I_k.
|
||||
*/
|
||||
template <uint8_t rows, uint8_t columns>
|
||||
static float orthogonalityError(const Matrix<rows, columns> &M) {
|
||||
constexpr uint8_t k = (rows < columns) ? rows : columns;
|
||||
|
||||
// Compute Mᵀ × M (should be I_k in top-left)
|
||||
Matrix<columns, rows> Mt = M.Transpose();
|
||||
Matrix<columns, columns> MtM{0};
|
||||
Mt.Mult(M, MtM);
|
||||
|
||||
float err = 0;
|
||||
for (uint8_t i = 0; i < k; i++) {
|
||||
for (uint8_t j = 0; j < k; j++) {
|
||||
float expected = (i == j) ? 1.0f : 0.0f;
|
||||
err += (MtM.Get(i, j) - expected) * (MtM.Get(i, j) - expected);
|
||||
}
|
||||
}
|
||||
return sqrtf(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper: check that singular values are sorted in descending order.
|
||||
*/
|
||||
template <uint8_t maxCols>
|
||||
static bool isSortedDescending(const Matrix<maxCols, 1> &sigma, uint8_t count) {
|
||||
for (uint8_t i = 0; i < count - 1; i++) {
|
||||
if (sigma.Get(i + 1, 0) > sigma.Get(i, 0) + 1e-6f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Simple 2x2 Matrix", "Matrix") {
|
||||
// Reference: scipy.linalg.svd([[1,2],[3,4]])
|
||||
// σ = [5.4649857042, 0.3659661906]
|
||||
Matrix<2, 2> A{1.0f, 2.0f, 3.0f, 4.0f};
|
||||
Matrix<2, 2> U{}, Vt{};
|
||||
Matrix<2, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
// Verify singular values (verified with Python scipy.linalg.svd)
|
||||
REQUIRE_THAT(sigma.Get(0, 0),
|
||||
Catch::Matchers::WithinRel(5.4649857042f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0),
|
||||
Catch::Matchers::WithinRel(0.3659661906f, 1e-4f));
|
||||
|
||||
// Verify descending order
|
||||
REQUIRE(isSortedDescending(sigma, 2));
|
||||
|
||||
// Verify U is orthogonal: UᵀU ≈ I
|
||||
REQUIRE_THAT(orthogonalityError(U), Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
|
||||
// Verify Vt is orthogonal: VtVᵀ ≈ I
|
||||
REQUIRE_THAT(orthogonalityError(Vt), Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
|
||||
// Verify reconstruction: A ≈ U Σ Vᵀ
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Symmetric Positive Definite 2x2", "Matrix") {
|
||||
// Reference: scipy.linalg.svd([[5,3],[3,5]])
|
||||
// σ = [8.0, 2.0] (eigenvalues since symmetric PD)
|
||||
Matrix<2, 2> A{5.0f, 3.0f, 3.0f, 5.0f};
|
||||
Matrix<2, 2> U{}, Vt{};
|
||||
Matrix<2, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
REQUIRE_THAT(sigma.Get(0, 0), Catch::Matchers::WithinRel(8.0f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0), Catch::Matchers::WithinRel(2.0f, 1e-4f));
|
||||
|
||||
// For symmetric PD matrices, U ≈ V (up to sign)
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Full-Rank 3x3 Matrix", "Matrix") {
|
||||
// Reference: scipy.linalg.svd([[1,2,3],[4,5,6],[7,8,10]])
|
||||
// σ = [17.4125051668, 0.8751613501, 0.1968665211]
|
||||
Matrix<3, 3> A{1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f};
|
||||
Matrix<3, 3> U{}, Vt{};
|
||||
Matrix<3, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
REQUIRE_THAT(sigma.Get(0, 0),
|
||||
Catch::Matchers::WithinRel(17.4125051668f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0),
|
||||
Catch::Matchers::WithinRel(0.8751613501f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(2, 0),
|
||||
Catch::Matchers::WithinRel(0.1968665211f, 1e-4f));
|
||||
|
||||
REQUIRE(isSortedDescending(sigma, 3));
|
||||
REQUIRE_THAT(orthogonalityError(U), Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
REQUIRE_THAT(orthogonalityError(Vt), Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-3f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Rank-Deficient 3x3 Matrix", "Matrix") {
|
||||
// Reference: scipy.linalg.svd([[1,2,3],[4,5,6],[7,8,9]])
|
||||
// σ = [16.8481033526, 1.0683695146, ~0] (rank 2)
|
||||
Matrix<3, 3> A{1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f};
|
||||
Matrix<3, 3> U{}, Vt{};
|
||||
Matrix<3, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
REQUIRE_THAT(sigma.Get(0, 0),
|
||||
Catch::Matchers::WithinRel(16.8481033526f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0),
|
||||
Catch::Matchers::WithinRel(1.0683695146f, 1e-4f));
|
||||
// Third singular value should be ~0 (rank deficiency)
|
||||
REQUIRE(sigma.Get(2, 0) < 1e-3f);
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-3f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Diagonal 3x3 Matrix", "Matrix") {
|
||||
// For a diagonal matrix, σ = diagonal entries, U = V = I
|
||||
Matrix<3, 3> A{10.0f, 0.0f, 0.0f, 5.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f};
|
||||
Matrix<3, 3> U{}, Vt{};
|
||||
Matrix<3, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
REQUIRE_THAT(sigma.Get(0, 0), Catch::Matchers::WithinRel(10.0f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0), Catch::Matchers::WithinRel(5.0f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(2, 0), Catch::Matchers::WithinRel(2.0f, 1e-4f));
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Tall Matrix (4×3)", "Matrix") {
|
||||
// Reference: scipy.linalg.svd with full_matrices=False
|
||||
// σ = [25.4624074360, 1.2906616758, ~0] (rank 2)
|
||||
Matrix<4, 3> A{1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f,
|
||||
7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f};
|
||||
Matrix<4, 3> U{};
|
||||
Matrix<3, 3> Vt{}; // Vt is always n×n
|
||||
Matrix<3, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
REQUIRE_THAT(sigma.Get(0, 0),
|
||||
Catch::Matchers::WithinRel(25.4624074360f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0),
|
||||
Catch::Matchers::WithinRel(1.2906616758f, 1e-4f));
|
||||
REQUIRE(sigma.Get(2, 0) < 1e-3f);
|
||||
|
||||
// U should be 4×3 with orthonormal columns
|
||||
REQUIRE_THAT(orthogonalityError(U), Catch::Matchers::WithinRel(0.0f, 1e-3f));
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-3f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Wide Matrix (3×5)", "Matrix") {
|
||||
// Reference: scipy.linalg.svd with full_matrices=False
|
||||
// σ = [35.1272233336, 2.4653966969, ~0] (rank 2)
|
||||
Matrix<3, 5> A{1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f,
|
||||
9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f};
|
||||
Matrix<3, 5> U{};
|
||||
Matrix<5, 5> Vt{}; // Vt is always n×n
|
||||
Matrix<5, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
REQUIRE_THAT(sigma.Get(0, 0),
|
||||
Catch::Matchers::WithinRel(35.1272233336f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0),
|
||||
Catch::Matchers::WithinRel(2.4653966969f, 1e-4f));
|
||||
REQUIRE(sigma.Get(2, 0) < 1e-3f);
|
||||
|
||||
// Vt should be 5×5 with orthonormal rows (first k)
|
||||
REQUIRE_THAT(orthogonalityError(Vt), Catch::Matchers::WithinRel(0.0f, 1e-3f));
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-3f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: 5×5 Symmetric Tridiagonal", "Matrix") {
|
||||
// Reference: scipy.linalg.svd for discrete Laplacian-like matrix
|
||||
// σ = [3.7320508076, 3.0, 2.0, 1.0, 0.2679491924]
|
||||
Matrix<5, 5> A{2.0f, -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 2.0f, -1.0f, 0.0f,
|
||||
0.0f, 0.0f, -1.0f, 2.0f, -1.0f, 0.0f, 0.0f, 0.0f, -1.0f,
|
||||
2.0f, -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 2.0f};
|
||||
Matrix<5, 5> U{}, Vt{};
|
||||
Matrix<5, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
REQUIRE_THAT(sigma.Get(0, 0),
|
||||
Catch::Matchers::WithinRel(3.7320508076f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0), Catch::Matchers::WithinRel(3.0f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(2, 0), Catch::Matchers::WithinRel(2.0f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(3, 0), Catch::Matchers::WithinRel(1.0f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(4, 0),
|
||||
Catch::Matchers::WithinRel(0.2679491924f, 1e-4f));
|
||||
|
||||
REQUIRE(isSortedDescending(sigma, 5));
|
||||
REQUIRE_THAT(orthogonalityError(U), Catch::Matchers::WithinRel(0.0f, 1e-3f));
|
||||
REQUIRE_THAT(orthogonalityError(Vt), Catch::Matchers::WithinRel(0.0f, 1e-3f));
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-3f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Non-Square with Negative Values (2×3)", "Matrix") {
|
||||
// Reference: scipy.linalg.svd([[0.5,-0.3,0.8],[-0.2,0.7,0.1]])
|
||||
// σ = [1.0384009867, 0.6646227432]
|
||||
Matrix<2, 3> A{0.5f, -0.3f, 0.8f, -0.2f, 0.7f, 0.1f};
|
||||
Matrix<2, 3> U{};
|
||||
Matrix<3, 3> Vt{}; // Vt is always n×n
|
||||
Matrix<3, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
REQUIRE_THAT(sigma.Get(0, 0),
|
||||
Catch::Matchers::WithinRel(1.0384009867f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0),
|
||||
Catch::Matchers::WithinRel(0.6646227432f, 1e-4f));
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Near-Singular 2×2 Matrix", "Matrix") {
|
||||
// Condition number ≈ 1e6 — tests numerical stability
|
||||
// Reference: scipy.linalg.svd([[1,0],[0,1e-6]])
|
||||
// σ = [1.0, 1e-6]
|
||||
Matrix<2, 2> A{1.0f, 0.0f, 0.0f, 1e-6f};
|
||||
Matrix<2, 2> U{}, Vt{};
|
||||
Matrix<2, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
REQUIRE_THAT(sigma.Get(0, 0), Catch::Matchers::WithinRel(1.0f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0), Catch::Matchers::WithinRel(1e-6f, 1e-2f));
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-6f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Orthogonal Matrix (3×3)", "Matrix") {
|
||||
// For an orthogonal matrix, all singular values should be 1.
|
||||
// Rotation matrix about z-axis by 45°
|
||||
float c = sqrtf(0.5f); // cos(45°)
|
||||
float s = sqrtf(0.5f); // sin(45°)
|
||||
Matrix<3, 3> A{c, -s, 0.0f, s, c, 0.0f, 0.0f, 0.0f, 1.0f};
|
||||
Matrix<3, 3> U{}, Vt{};
|
||||
Matrix<3, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
// All singular values should be 1 for an orthogonal matrix
|
||||
REQUIRE_THAT(sigma.Get(0, 0), Catch::Matchers::WithinRel(1.0f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0), Catch::Matchers::WithinRel(1.0f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(2, 0), Catch::Matchers::WithinRel(1.0f, 1e-4f));
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Identity Matrix", "Matrix") {
|
||||
// For I, σ = [1, 1, 1], U = V = I
|
||||
Matrix<3, 3> A{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f};
|
||||
Matrix<3, 3> U{}, Vt{};
|
||||
Matrix<3, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
REQUIRE_THAT(sigma.Get(0, 0), Catch::Matchers::WithinRel(1.0f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(1, 0), Catch::Matchers::WithinRel(1.0f, 1e-4f));
|
||||
REQUIRE_THAT(sigma.Get(2, 0), Catch::Matchers::WithinRel(1.0f, 1e-4f));
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-6f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: Zero Matrix", "Matrix") {
|
||||
// All singular values should be zero
|
||||
Matrix<3, 3> A{0.0f};
|
||||
Matrix<3, 3> U{}, Vt{};
|
||||
Matrix<3, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
REQUIRE(sigma.Get(0, 0) < 1e-6f);
|
||||
REQUIRE(sigma.Get(1, 0) < 1e-6f);
|
||||
REQUIRE(sigma.Get(2, 0) < 1e-6f);
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-6f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: 2×1 Column Vector", "Matrix") {
|
||||
// For a column vector v, σ = ||v||, U = v/||v|| (with padding)
|
||||
Matrix<2, 1> A{3.0f, 4.0f};
|
||||
Matrix<2, 1> U{};
|
||||
Matrix<1, 1> Vt{}; // Vt is always n×n
|
||||
Matrix<1, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
// σ should be the Euclidean norm: ||[3,4]|| = 5
|
||||
REQUIRE_THAT(sigma.Get(0, 0), Catch::Matchers::WithinRel(5.0f, 1e-4f));
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
}
|
||||
|
||||
TEST_CASE("SVD: 1×2 Row Vector", "Matrix") {
|
||||
// For a row vector vᵀ, σ = ||v||, Vt = v/||v|| (with padding)
|
||||
Matrix<1, 2> A{3.0f, 4.0f};
|
||||
Matrix<1, 2> U{};
|
||||
Matrix<2, 2> Vt{}; // Vt is always n×n
|
||||
Matrix<2, 1> sigma{};
|
||||
|
||||
SVD::SVD(A, U, sigma, Vt);
|
||||
|
||||
// σ should be the Euclidean norm: ||[3,4]|| = 5
|
||||
REQUIRE_THAT(sigma.Get(0, 0), Catch::Matchers::WithinRel(5.0f, 1e-4f));
|
||||
|
||||
float reconErr = svdReconstructionError(A, U, sigma, Vt);
|
||||
REQUIRE_THAT(reconErr, Catch::Matchers::WithinRel(0.0f, 1e-4f));
|
||||
}
|
||||
Reference in New Issue
Block a user