Ready for fvm steady case
This commit is contained in:
+118
-92
@@ -1,115 +1,141 @@
|
||||
#include "test_common.h"
|
||||
#include "./utils/utils.h"
|
||||
|
||||
#include "./utils/matrix.h"
|
||||
#include "./utils/vector.h"
|
||||
#include "./numerics/inverse.h"
|
||||
#include "./numerics/matmul.h"
|
||||
|
||||
|
||||
TEST_CASE(Inverse_GJ_Basic_3x3) {
|
||||
using T = double;
|
||||
utils::Matrix<T> A(3,3, T{0});
|
||||
// Well-conditioned 3x3
|
||||
A(0,0)=3; A(0,1)=0.2; A(0,2)=-0.1;
|
||||
A(1,0)=0.1; A(1,1)=2.5; A(1,2)=0.3;
|
||||
A(2,0)=-0.2;A(2,1)=0.4; A(2,2)=2.0;
|
||||
|
||||
auto Ainv = numerics::inverse<T>(A, "Gauss-Jordan");
|
||||
utils::Matrix<T> I;
|
||||
I.eye(3);
|
||||
auto prod = numerics::matmul<T>(A, Ainv);
|
||||
|
||||
CHECK(prod.nearly_equal(I, (T)1e-10), "inverse(GJ): A*A^{-1} ≈ I");
|
||||
}
|
||||
|
||||
TEST_CASE(Inverse_LU_Basic_3x3) {
|
||||
using T = double;
|
||||
utils::Matrix<T> A(3,3, T{0});
|
||||
A(0,0)=3; A(0,1)=0.2; A(0,2)=-0.1;
|
||||
A(1,0)=0.1; A(1,1)=2.5; A(1,2)=0.3;
|
||||
A(2,0)=-0.2;A(2,1)=0.4; A(2,2)=2.0;
|
||||
|
||||
auto Ainv = numerics::inverse<T>(A, "LU");
|
||||
utils::Matrix<T> I;
|
||||
I.eye(3);
|
||||
auto prod = numerics::matmul<T>(A, Ainv);
|
||||
|
||||
CHECK(prod.nearly_equal(I, (T)1e-10), "inverse(LU): A*A^{-1} ≈ I");
|
||||
}
|
||||
|
||||
TEST_CASE(Inverse_GJ_vs_LU_Consistency) {
|
||||
using T = double;
|
||||
utils::Matrix<T> A(3,3, T{0});
|
||||
A(0,0)=4; A(0,1)=1; A(0,2)=2;
|
||||
A(1,0)=0; A(1,1)=3; A(1,2)=-1;
|
||||
A(2,0)=0; A(2,1)=0; A(2,2)=2;
|
||||
|
||||
auto GJ = numerics::inverse<T>(A, "Gauss-Jordan");
|
||||
auto LU = numerics::inverse<T>(A, "LU");
|
||||
|
||||
CHECK(GJ.nearly_equal(LU, (T)1e-12), "inverse: GJ and LU produce nearly the same result");
|
||||
// ---------- helpers ----------
|
||||
template <typename T>
|
||||
static utils::Matrix<T> identity(std::uint64_t n) {
|
||||
utils::Matrix<T> I(n,n,T(0));
|
||||
for (std::uint64_t i=0;i<n;++i) I(i,i) = T(1);
|
||||
return I;
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE(Inplace_Inverse_LU) {
|
||||
using T = double;
|
||||
utils::Matrix<T> A(3,3, T{0});
|
||||
A(0,0)=3; A(0,1)=0.2; A(0,2)=-0.1;
|
||||
A(1,0)=0.1; A(1,1)=2.5; A(1,2)=0.3;
|
||||
A(2,0)=-0.2;A(2,1)=0.4; A(2,2)=2.0;
|
||||
|
||||
auto Ainv_ref = numerics::inverse<T>(A, "LU"); // out-of-place
|
||||
auto A_copy = A;
|
||||
numerics::inplace_inverse<T>(A_copy, "LU"); // in-place
|
||||
|
||||
CHECK(A_copy.nearly_equal(Ainv_ref, (T)1e-12), "inplace_inverse(LU) equals out-of-place");
|
||||
template <typename T>
|
||||
static bool mats_equal_tol(const utils::Matrix<T>& X,
|
||||
const utils::Matrix<T>& Y,
|
||||
double tol = 1e-12) {
|
||||
if (X.rows()!=Y.rows() || X.cols()!=Y.cols()) return false;
|
||||
for (std::uint64_t i=0;i<X.rows();++i)
|
||||
for (std::uint64_t j=0;j<X.cols();++j)
|
||||
if (std::fabs(double(X(i,j) - Y(i,j))) > tol) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CASE(Inplace_Inverse_GJ) {
|
||||
using T = double;
|
||||
utils::Matrix<T> A(2,2, T{0});
|
||||
A(0,0)=2; A(0,1)=1;
|
||||
A(1,0)=1; A(1,1)=3;
|
||||
|
||||
auto Ainv_ref = numerics::inverse<T>(A, "Gauss-Jordan");
|
||||
auto A_copy = A;
|
||||
numerics::inplace_inverse<T>(A_copy, "Gauss-Jordan");
|
||||
|
||||
CHECK(A_copy.nearly_equal(Ainv_ref, (T)1e-12), "inplace_inverse(GJ) equals out-of-place");
|
||||
// A small well-conditioned SPD 3x3
|
||||
static utils::Matrix<double> make_A3() {
|
||||
utils::Matrix<double> A(3,3,0.0);
|
||||
// [ 4 3 0
|
||||
// 3 4 -1
|
||||
// 0 -1 4 ]
|
||||
A(0,0)=4; A(0,1)=3; A(0,2)=0;
|
||||
A(1,0)=3; A(1,1)=4; A(1,2)=-1;
|
||||
A(2,0)=0; A(2,1)=-1; A(2,2)=4;
|
||||
return A;
|
||||
}
|
||||
|
||||
TEST_CASE(Inverse_Identity) {
|
||||
using T = double;
|
||||
utils::Matrix<T> I;
|
||||
I.eye(3);
|
||||
auto invI = numerics::inverse<T>(I, "LU");
|
||||
CHECK(invI.nearly_equal(I, (T)0), "inverse(I) == I");
|
||||
// A random-ish SPD 5x5 (constructed deterministically)
|
||||
static utils::Matrix<double> make_A5_spd() {
|
||||
utils::Matrix<double> R(5,5,0.0);
|
||||
// Fill R with a simple pattern
|
||||
double v=1.0;
|
||||
for (std::uint64_t i=0;i<5;++i)
|
||||
for (std::uint64_t j=0;j<5;++j, v+=0.37)
|
||||
R(i,j) = std::fmod(v, 3.0) - 1.0; // values in [-1,2)
|
||||
// A = R^T R + 5 I → SPD and well-conditioned
|
||||
utils::Matrix<double> Rt(5,5,0.0);
|
||||
for (std::uint64_t i=0;i<5;++i)
|
||||
for (std::uint64_t j=0;j<5;++j)
|
||||
Rt(i,j) = R(j,i);
|
||||
auto RtR = numerics::matmul(Rt, R);
|
||||
for (std::uint64_t i=0;i<5;++i) RtR(i,i) += 5.0;
|
||||
return RtR;
|
||||
}
|
||||
|
||||
// ---------- tests ----------
|
||||
|
||||
TEST_CASE(Inverse_GJ_3x3) {
|
||||
auto A = make_A3();
|
||||
auto A_copy = A;
|
||||
|
||||
auto Inv = numerics::inverse(A, "Gauss-Jordan"); // non-inplace
|
||||
auto I = identity<double>(3);
|
||||
|
||||
auto AInv = numerics::matmul(A, Inv);
|
||||
auto InvA = numerics::matmul(Inv, A);
|
||||
|
||||
CHECK(mats_equal_tol(AInv, I, 1e-11), "A*Inv != I (Gauss-Jordan)");
|
||||
CHECK(mats_equal_tol(InvA, I, 1e-11), "Inv*A != I (Gauss-Jordan)");
|
||||
|
||||
// A should be unchanged by numerics::inverse (it copies internally)
|
||||
CHECK(mats_equal_tol(A, A_copy, 1e-15), "inverse() modified input A");
|
||||
}
|
||||
|
||||
TEST_CASE(Inverse_LU_3x3) {
|
||||
auto A = make_A3();
|
||||
|
||||
auto Inv = numerics::inverse(A, "LU");
|
||||
auto I = identity<double>(3);
|
||||
|
||||
auto AInv = numerics::matmul(A, Inv);
|
||||
auto InvA = numerics::matmul(Inv, A);
|
||||
|
||||
CHECK(mats_equal_tol(AInv, I, 1e-11), "A*Inv != I (LU)");
|
||||
CHECK(mats_equal_tol(InvA, I, 1e-11), "Inv*A != I (LU)");
|
||||
}
|
||||
|
||||
TEST_CASE(Inplace_Inverse_Both_Methods_Agree_5x5) {
|
||||
auto A = make_A5_spd();
|
||||
|
||||
auto GJ = A; numerics::inplace_inverse(GJ, "Gauss-Jordan");
|
||||
auto LU = A; numerics::inplace_inverse(LU, "LU");
|
||||
|
||||
// Both should be valid inverses
|
||||
auto I = identity<double>(5);
|
||||
CHECK(mats_equal_tol(numerics::matmul(A, GJ), I, 1e-10), "A*GJ != I");
|
||||
CHECK(mats_equal_tol(numerics::matmul(GJ, A), I, 1e-10), "GJ*A != I");
|
||||
CHECK(mats_equal_tol(numerics::matmul(A, LU), I, 1e-10), "A*LU != I");
|
||||
CHECK(mats_equal_tol(numerics::matmul(LU, A), I, 1e-10), "LU*A != I");
|
||||
|
||||
// And they should be very close to each other
|
||||
CHECK(mats_equal_tol(GJ, LU, 1e-10), "Gauss-Jordan inverse != LU inverse");
|
||||
}
|
||||
|
||||
TEST_CASE(Inverse_NonSquare_Throws) {
|
||||
using T = double;
|
||||
utils::Matrix<T> A(2,3, T{0}); // non-square
|
||||
bool threw1=false, threw2=false;
|
||||
try { auto X = numerics::inverse<T>(A, "LU"); (void)X; } catch(...) { threw1=true; }
|
||||
try { numerics::inplace_inverse<T>(A, "Gauss-Jordan"); } catch(...) { threw2=true; }
|
||||
CHECK(threw1 && threw2, "inverse throws on non-square for both methods");
|
||||
utils::Matrix<double> A(2,3,1.0);
|
||||
bool threw=false;
|
||||
try { auto B = numerics::inverse(A, "Gauss-Jordan"); (void)B; } catch(const std::runtime_error&) { threw=true; }
|
||||
CHECK(threw, "inverse should throw on non-square (Gauss-Jordan)");
|
||||
|
||||
threw=false;
|
||||
try { numerics::inplace_inverse(A, "LU"); } catch(const std::runtime_error&) { threw=true; }
|
||||
CHECK(threw, "inplace_inverse should throw on non-square (LU)");
|
||||
}
|
||||
|
||||
TEST_CASE(Inverse_Singular_Throws) {
|
||||
using T = double;
|
||||
utils::Matrix<T> S(3,3, T{0});
|
||||
S(0,0)=1; S(0,1)=2; S(0,2)=3;
|
||||
S(1,0)=1; S(1,1)=2; S(1,2)=3; // duplicate row -> singular
|
||||
S(2,0)=0; S(2,1)=1; S(2,2)=0;
|
||||
utils::Matrix<double> A(3,3,0.0);
|
||||
// Two identical rows → singular
|
||||
A(0,0)=1; A(0,1)=2; A(0,2)=3;
|
||||
A(1,0)=1; A(1,1)=2; A(1,2)=3;
|
||||
A(2,0)=0; A(2,1)=1; A(2,2)=4;
|
||||
|
||||
bool threw_gj=false, threw_lu=false;
|
||||
try { auto X = numerics::inverse<T>(S, "Gauss-Jordan"); (void)X; } catch(...) { threw_gj=true; }
|
||||
try { auto X = numerics::inverse<T>(S, "LU"); (void)X; } catch(...) { threw_lu=true; }
|
||||
CHECK(threw_gj && threw_lu, "inverse throws on singular for both methods");
|
||||
bool threw=false;
|
||||
try { auto B = numerics::inverse(A, "Gauss-Jordan"); (void)B; } catch(const std::runtime_error&) { threw=true; }
|
||||
CHECK(threw, "inverse(GJ) should throw on singular");
|
||||
|
||||
threw=false;
|
||||
try { auto B = numerics::inverse(A, "LU"); (void)B; } catch(const std::runtime_error&) { threw=true; }
|
||||
CHECK(threw, "inverse(LU) should throw on singular");
|
||||
}
|
||||
|
||||
TEST_CASE(Inverse_Unknown_Method_Throws) {
|
||||
using T = double;
|
||||
utils::Matrix<T> A(2,2, T{0});
|
||||
A(0,0)=1; A(1,1)=1;
|
||||
TEST_CASE(Inverse_Invalid_Method_Throws) {
|
||||
auto A = make_A3();
|
||||
bool threw=false;
|
||||
try { auto X = numerics::inverse<T>(A, "Foobar"); (void)X; } catch(...) { threw=true; }
|
||||
CHECK(threw, "inverse unknown method throws");
|
||||
try { auto B = numerics::inverse(A, "NotAThing"); (void)B; } catch(const std::runtime_error&) { threw=true; }
|
||||
CHECK(threw, "inverse should throw on unknown method");
|
||||
}
|
||||
Reference in New Issue
Block a user