#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 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(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(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 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(A); // copy path auto A_inp = A; numerics::inplace_inverse(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 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(S); (void)_; } catch (const std::runtime_error&) { threw = true; } CHECK(threw, "inverse should throw on singular matrix"); threw=false; try { numerics::inplace_inverse(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(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(A, Ainv); auto R = numerics::matmul(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(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, "NotARealMethod"); } catch (const std::runtime_error&) { threw = true; } CHECK(threw, "should throw for unknown inverse method"); }