Files
Flux/test/test_inverse.cpp
T

126 lines
3.6 KiB
C++

#include "test_common.h"
#include "./utils/utils.h"
#include "./numerics/inverse.h"
#include "./numerics/matmul.h"
TEST_CASE(Inverse_2x2_WellConditioned) {
using T = double;
// A = [[4,7],[2,6]] inverse = (1/10) * [[6,-7],[-2,4]]
utils::Matrix<T> A(2,2, T{0});
A(0,0)=4; A(0,1)=7;
A(1,0)=2; A(1,1)=6;
auto Ainv = numerics::inverse<T>(A); // out-of-place
// Check A * Ainv ≈ I and Ainv * A ≈ I
auto Ileft = numerics::matmul(A, Ainv);
auto Iright = numerics::matmul(Ainv, A);
utils::Md Iref(2,2, T{0});
for (uint64_t i=0;i<Iref.rows();++i) Iref(i,i)=T{1};
//auto Iref = eye<T>(2);
CHECK((Ileft.nearly_equal(Iref, 1e-12)), "A * inverse(A) ≠ I");
CHECK((Iright.nearly_equal(Iref, 1e-12)), "inverse(A) * A ≠ I");
}
TEST_CASE(Inverse_InPlace_Equals_OutOfPlace) {
using T = double;
utils::Matrix<T> A(3,3, T{0});
// A = [[3, 0, 2],
// [2, 0, -2],
// [0, 1, 1]]
A(0,0)=3; A(0,1)=0; A(0,2)= 2;
A(1,0)=2; A(1,1)=0; A(1,2)=-2;
A(2,0)=0; A(2,1)=1; A(2,2)= 1;
auto Ainv_ref = numerics::inverse<T>(A); // copy path
auto A_inp = A;
numerics::inplace_inverse<T>(A_inp); // in-place path
CHECK((A_inp.nearly_equal(Ainv_ref, 1e-12)), "in-place inverse differs from out-of-place");
}
TEST_CASE(Inverse_Singular_Throws) {
using T = double;
utils::Matrix<T> S(2,2, T{0});
// Singular: rows are multiples → det = 0
S(0,0)=1; S(0,1)=2;
S(1,0)=2; S(1,1)=4;
bool threw=false;
try {
auto _ = numerics::inverse<T>(S);
(void)_;
} catch (const std::runtime_error&) { threw = true; }
CHECK(threw, "inverse should throw on singular matrix");
threw=false;
try {
numerics::inplace_inverse<T>(S);
} catch (const std::runtime_error&) { threw = true; }
CHECK(threw, "inplace_inverse should throw on singular matrix");
}
TEST_CASE(Inverse_RoundTrip_DiagonallyDominant_5x5) {
// Build a well-conditioned 5x5: diagonally dominant
utils::Md A(5,5,0.0);
for (uint64_t i=0;i<5;++i) {
double rowsum = 0.0;
for (uint64_t j=0;j<5;++j) {
if (i==j) continue;
A(i,j) = 0.01 * double(1 + ((i+1)*(j+3)) % 7);
rowsum += std::fabs(A(i,j));
}
A(i,i) = rowsum + 1.0; // strictly diagonally dominant
}
utils::Md A_copy = A; // ensure wrapper doesn't mutate input
utils::Md Ainv = numerics::inverse<double>(A);
// Input must be unchanged by the non-inplace wrapper
CHECK(A.nearly_equal(A_copy, 0.0), "inverse wrapper modified input");
utils::Md I(5,5, 0);
for (uint64_t i=0;i<I.rows();++i) I(i,i)=1;
auto L = numerics::matmul<double>(A, Ainv);
auto R = numerics::matmul<double>(Ainv, A);
CHECK(L.nearly_equal(I, 1e-10), "A * Ainv not close to I");
CHECK(R.nearly_equal(I, 1e-10), "Ainv * A not close to I");
}
TEST_CASE(Inverse_NonSquare_Throws) {
// Non-square: 2x3 — algorithm expects square; should throw
utils::Md A(2,3,0.0);
bool threw = false;
try {
numerics::inplace_inverse<double>(A);
} catch (const std::runtime_error&) {
threw = true;
} catch (...) {
threw = true; // any failure is fine; must not silently succeed
}
CHECK(threw, "inplace_inverse should throw on non-square matrix");
}
TEST_CASE(Inverse_Unknown_Method_Throws) {
utils::Md A(3,3, 0);
for (uint64_t i=0;i<A.rows();++i) A(i,i)=1;
bool threw = false;
try {
numerics::inplace_inverse<double>(A, "NotARealMethod");
} catch (const std::runtime_error&) {
threw = true;
}
CHECK(threw, "should throw for unknown inverse method");
}