Working on breaking up the steps into manageable chunks
This commit is contained in:
@@ -1025,3 +1025,384 @@ TEST_CASE("SVD Phase 4: AssembleUAndVt", "[Matrix][SVD]") {
|
||||
REQUIRE_THAT(Vt.Get(1, 1), Catch::Matchers::WithinRel(-0.6f, 1e-6f));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TEST 13: Bidiagonalize (Phase 1) - Square matrix
|
||||
// ===========================================================================
|
||||
TEST_CASE("SVD Phase 1: Bidiagonalize square matrix", "[Matrix][SVD]") {
|
||||
// Test case 1: 3×3 matrix
|
||||
// C++ verified reference:
|
||||
// W[0] = [-4.123106, -5.335784, 6.548462]
|
||||
// W[1] = [ 0.000000, 7.037714, -8.107580]
|
||||
// W[2] = [ 0.000000, 0.000000, 0.620321]
|
||||
// Note: W[0][2]=6.548462 is NOT zeroed because right HH at k=0 has only 1 element
|
||||
{
|
||||
Matrix<5, 5> W{1.0f, 2.0f, 3.0f, 0.0f, 0.0f,
|
||||
4.0f, 5.0f, 6.0f, 0.0f, 0.0f,
|
||||
0.0f, 7.0f, 8.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 3, 3, 3, QL, QR);
|
||||
|
||||
// Subdiagonal elements should be zero: W[1][0], W[2][0], W[2][1]
|
||||
REQUIRE_THAT(W.Get(1, 0), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
REQUIRE_THAT(W.Get(2, 0), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
REQUIRE_THAT(W.Get(2, 1), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
|
||||
// Verify orthogonality
|
||||
REQUIRE(isOrthogonal5(QL));
|
||||
REQUIRE(isOrthogonal5(QR));
|
||||
}
|
||||
|
||||
// Test case 2: 2×2 matrix (simplest non-trivial case)
|
||||
// C++ verified reference:
|
||||
// W[0] = [-3.162278, -4.427189]
|
||||
// W[1] = [-0.000000, 0.632456]
|
||||
{
|
||||
Matrix<5, 5> W{3.0f, 4.0f, 0.0f, 0.0f, 0.0f,
|
||||
1.0f, 2.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 2, 2, 2, QL, QR);
|
||||
|
||||
// For 2×2, bidiagonal form has no elements to zero out
|
||||
REQUIRE(isOrthogonal5(QL));
|
||||
REQUIRE(isOrthogonal5(QR));
|
||||
}
|
||||
|
||||
// Test case 3: Diagonal matrix (no transformations needed)
|
||||
// C++ verified reference: unchanged
|
||||
{
|
||||
Matrix<5, 5> W{10.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 5.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 2.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> W_orig{10.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 5.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 2.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 3, 3, 3, QL, QR);
|
||||
|
||||
// Diagonal matrix may have sign flips but absolute values preserved
|
||||
float err = 0.0f;
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
float diff = fabsf(W.Get(i, i)) - fabsf(W_orig.Get(i, i));
|
||||
err += diff * diff;
|
||||
}
|
||||
REQUIRE_THAT(sqrtf(err), Catch::Matchers::WithinAbs(0.0f, 1e-6f));
|
||||
|
||||
// QL and QR may have sign flips but should remain orthogonal
|
||||
// Check that |QL[i][j]| and |QR[i][j]| match identity pattern
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
for (uint8_t j = 0; j < 5; j++) {
|
||||
float expected = (i == j) ? 1.0f : 0.0f;
|
||||
REQUIRE_THAT(fabsf(QL.Get(i, j)), Catch::Matchers::WithinAbs(expected, 1e-6f));
|
||||
REQUIRE_THAT(fabsf(QR.Get(i, j)), Catch::Matchers::WithinAbs(expected, 1e-6f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TEST 14: Bidiagonalize (Phase 1) — Tall matrix (m > n)
|
||||
// ===========================================================================
|
||||
TEST_CASE("SVD Phase 1: Bidiagonalize tall matrix", "[Matrix][SVD]") {
|
||||
// Test case 1: 4×3 matrix
|
||||
// C++ verified reference (partial - subdiagonal zeros):
|
||||
// W[0] = [-4.123106, -5.335784, 6.548462]
|
||||
// W[1] = [-0.000000, 12.228222, 12.026182]
|
||||
// W[2] = [ 0.000000, 0.000000, -1.577527]
|
||||
// W[3] = [ 0.000000, 0.000000, 0.000000]
|
||||
{
|
||||
Matrix<5, 5> W{1.0f, 2.0f, 3.0f, 0.0f, 0.0f,
|
||||
4.0f, 5.0f, 6.0f, 0.0f, 0.0f,
|
||||
0.0f, 7.0f, 8.0f, 0.0f, 0.0f,
|
||||
0.0f, 10.0f, 9.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 4, 3, 3, QL, QR);
|
||||
|
||||
// Zero below subdiagonal: W[2][0], W[3][0], W[3][1]
|
||||
REQUIRE_THAT(W.Get(2, 0), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
REQUIRE_THAT(W.Get(3, 0), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
REQUIRE_THAT(W.Get(3, 1), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
|
||||
// Verify orthogonality
|
||||
REQUIRE(isOrthogonal5(QL));
|
||||
REQUIRE(isOrthogonal5(QR));
|
||||
}
|
||||
|
||||
// Test case 2: 3×2 matrix
|
||||
// C++ verified reference:
|
||||
// W[0] = [-5.916080, -7.437357]
|
||||
// W[1] = [-0.000001, 0.828077]
|
||||
// W[2] = [-0.000000, -0.000000]
|
||||
{
|
||||
Matrix<5, 5> W{1.0f, 2.0f, 0.0f, 0.0f, 0.0f,
|
||||
3.0f, 4.0f, 0.0f, 0.0f, 0.0f,
|
||||
5.0f, 6.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 3, 2, 2, QL, QR);
|
||||
|
||||
// Zero below subdiagonal: W[2][0] ≈ 0
|
||||
REQUIRE_THAT(W.Get(2, 0), Catch::Matchers::WithinAbs(0.0f, 1e-3f));
|
||||
|
||||
// Verify orthogonality
|
||||
REQUIRE(isOrthogonal5(QL));
|
||||
REQUIRE(isOrthogonal5(QR));
|
||||
}
|
||||
|
||||
// Test case 3: 5×3 matrix (full 5-row tall)
|
||||
{
|
||||
Matrix<5, 5> W{1.0f, 2.0f, 3.0f, 0.0f, 0.0f,
|
||||
4.0f, 5.0f, 6.0f, 0.0f, 0.0f,
|
||||
0.0f, 7.0f, 8.0f, 0.0f, 0.0f,
|
||||
0.0f, 10.0f, 9.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 5, 3, 3, QL, QR);
|
||||
|
||||
// Zero below subdiagonal: W[2][0], W[3][0], W[4][0], W[3][1], W[4][1]
|
||||
REQUIRE_THAT(W.Get(2, 0), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
REQUIRE_THAT(W.Get(3, 0), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
REQUIRE_THAT(W.Get(4, 0), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
REQUIRE_THAT(W.Get(3, 1), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
REQUIRE_THAT(W.Get(4, 1), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
|
||||
// Verify orthogonality
|
||||
REQUIRE(isOrthogonal5(QL));
|
||||
REQUIRE(isOrthogonal5(QR));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TEST 15: Bidiagonalize (Phase 1) — Wide matrix (m < n)
|
||||
// ===========================================================================
|
||||
TEST_CASE("SVD Phase 1: Bidiagonalize wide matrix", "[Matrix][SVD]") {
|
||||
// Test case 1: 2×4 matrix
|
||||
// C++ verified reference:
|
||||
// W[0] = [-5.099020, -6.275717, 11.401754, 0.000000]
|
||||
// W[1] = [ 0.000000, -0.784465, 2.806586, -0.350823]
|
||||
// Note: W[1][2]=2.806586 and W[1][3]=-0.350823 are NOT zeroed because
|
||||
// right HH at k=0 has only 2 elements (cols 2,3), so it zeros col 3 but preserves col 2
|
||||
{
|
||||
Matrix<5, 5> W{1.0f, 2.0f, 3.0f, 4.0f, 0.0f,
|
||||
5.0f, 6.0f, 7.0f, 8.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 2, 4, 2, QL, QR);
|
||||
|
||||
// For 2×4: right HH at k=0 has 2 elements (cols 2,3)
|
||||
// It zeros col 3 but preserves col 2 as the superdiagonal element for row 1
|
||||
// W[0][3] should be zeroed (above superdiagonal in row 0)
|
||||
REQUIRE_THAT(W.Get(0, 3), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
// W[1][2] and W[1][3] are part of the bidiagonal structure for row 1
|
||||
// (superdiagonal at col 2, and right HH preserves first element)
|
||||
REQUIRE_THAT(W.Get(1, 2), !Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
|
||||
// Verify orthogonality
|
||||
REQUIRE(isOrthogonal5(QL));
|
||||
REQUIRE(isOrthogonal5(QR));
|
||||
}
|
||||
|
||||
// Test case 2: 3×5 matrix
|
||||
{
|
||||
Matrix<5, 5> W{1.0f, 2.0f, 3.0f, 4.0f, 5.0f,
|
||||
6.0f, 7.0f, 8.0f, 9.0f, 10.0f,
|
||||
0.0f, 11.0f, 12.0f, 13.0f, 14.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 3, 5, 3, QL, QR);
|
||||
|
||||
// For 3×5: check that subdiagonal elements are zero
|
||||
REQUIRE_THAT(W.Get(2, 0), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
REQUIRE_THAT(W.Get(2, 1), Catch::Matchers::WithinAbs(0.0f, 1e-4f));
|
||||
|
||||
// Verify orthogonality
|
||||
REQUIRE(isOrthogonal5(QL));
|
||||
REQUIRE(isOrthogonal5(QR));
|
||||
}
|
||||
|
||||
// Test case 3: 1×3 matrix (row vector)
|
||||
// C++ verified reference: W[0] = [1.0, 2.0, 3.0] (no transformations needed)
|
||||
{
|
||||
Matrix<5, 5> W{1.0f, 2.0f, 3.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 1, 3, 1, QL, QR);
|
||||
|
||||
// For 1×3, no transformations needed
|
||||
REQUIRE_THAT(W.Get(0, 0), Catch::Matchers::WithinAbs(1.0f, 1e-4f));
|
||||
REQUIRE_THAT(W.Get(0, 1), Catch::Matchers::WithinAbs(2.0f, 1e-4f));
|
||||
REQUIRE_THAT(W.Get(0, 2), Catch::Matchers::WithinAbs(3.0f, 1e-4f));
|
||||
|
||||
// Verify orthogonality
|
||||
REQUIRE(isOrthogonal5(QL));
|
||||
REQUIRE(isOrthogonal5(QR));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TEST 16: Bidiagonalize — Reconstruction property
|
||||
// ===========================================================================
|
||||
TEST_CASE("SVD Phase 1: Bidiagonalize reconstruction property", "[Matrix][SVD]") {
|
||||
// Test: QLᵀ · W_original · QR = B (bidiagonal)
|
||||
// This verifies that the accumulated transformations correctly represent
|
||||
// the bidiagonalization.
|
||||
{
|
||||
Matrix<5, 5> W_orig{1.0f, 2.0f, 3.0f, 0.0f, 0.0f,
|
||||
4.0f, 5.0f, 6.0f, 0.0f, 0.0f,
|
||||
0.0f, 7.0f, 8.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> W = W_orig;
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 3, 3, 3, QL, QR);
|
||||
|
||||
// Compute QLᵀ · W_orig · QR and verify it equals W (the bidiagonal result)
|
||||
Matrix<5, 5> Qt = QL.Transpose();
|
||||
Matrix<5, 5> QtW_orig{0};
|
||||
Qt.Mult(W_orig, QtW_orig);
|
||||
|
||||
Matrix<5, 5> QtW_origQR{0};
|
||||
QtW_orig.Mult(QR, QtW_origQR);
|
||||
|
||||
// The reconstruction should match the bidiagonal result
|
||||
float err = frobeniusNorm5(W - QtW_origQR);
|
||||
REQUIRE_THAT(err, Catch::Matchers::WithinAbs(1e-3f, 1e-3f));
|
||||
}
|
||||
|
||||
// Test: Tall matrix reconstruction (4×3)
|
||||
{
|
||||
Matrix<5, 5> W_orig{1.0f, 2.0f, 3.0f, 0.0f, 0.0f,
|
||||
4.0f, 5.0f, 6.0f, 0.0f, 0.0f,
|
||||
0.0f, 7.0f, 8.0f, 0.0f, 0.0f,
|
||||
0.0f, 10.0f, 9.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> W = W_orig;
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 4, 3, 3, QL, QR);
|
||||
|
||||
Matrix<5, 5> Qt = QL.Transpose();
|
||||
Matrix<5, 5> QtW_orig{0};
|
||||
Qt.Mult(W_orig, QtW_orig);
|
||||
|
||||
Matrix<5, 5> QtW_origQR{0};
|
||||
QtW_orig.Mult(QR, QtW_origQR);
|
||||
|
||||
float err = frobeniusNorm5(W - QtW_origQR);
|
||||
REQUIRE_THAT(err, Catch::Matchers::WithinAbs(1e-3f, 1e-3f));
|
||||
}
|
||||
|
||||
// Test: Wide matrix reconstruction (2×4)
|
||||
{
|
||||
Matrix<5, 5> W_orig{1.0f, 2.0f, 3.0f, 4.0f, 0.0f,
|
||||
5.0f, 6.0f, 7.0f, 8.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
Matrix<5, 5> W = W_orig;
|
||||
|
||||
Matrix<5, 5> QL{0}, QR{0};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
QL[i][i] = 1.0f;
|
||||
QR[i][i] = 1.0f;
|
||||
}
|
||||
|
||||
SVD::Bidiagonalize(W, 2, 4, 2, QL, QR);
|
||||
|
||||
Matrix<5, 5> Qt = QL.Transpose();
|
||||
Matrix<5, 5> QtW_orig{0};
|
||||
Qt.Mult(W_orig, QtW_orig);
|
||||
|
||||
Matrix<5, 5> QtW_origQR{0};
|
||||
QtW_orig.Mult(QR, QtW_origQR);
|
||||
|
||||
float err = frobeniusNorm5(W - QtW_origQR);
|
||||
REQUIRE_THAT(err, Catch::Matchers::WithinAbs(1e-3f, 1e-3f));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user