Skip to content

Commit

Permalink
save
Browse files Browse the repository at this point in the history
barne856 committed Nov 1, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 7cd555d commit 2c567dd
Showing 2 changed files with 161 additions and 0 deletions.
78 changes: 78 additions & 0 deletions include/squint/tensor/tensor_math.hpp
Original file line number Diff line number Diff line change
@@ -134,6 +134,83 @@ template <host_tensor T1, host_tensor T2> auto solve_general(T1 &A, T2 &B) {
return info;
}

#ifdef SQUINT_BLAS_BACKEND_NONE
/**
* @brief Computes the inverse of a square matrix.
* @param A The matrix to invert.
* @return The inverted matrix.
* @throws std::runtime_error if the matrix is singular or an error occurs during inversion.
*/
template <host_tensor T> auto inv(const T &A) {
inversion_compatible(A);
static_assert(dimensionless_scalar<typename T::value_type>);
using blas_type = blas_type_t<std::remove_const_t<typename T::value_type>>;
using result_type =
tensor<std::remove_const_t<typename T::value_type>, typename T::shape_type, typename T::strides_type,
T::error_checking(), ownership_type::owner, memory_space::host>;

// Create copy of input matrix that will become our result
result_type result = A;
const int n = A.shape()[0];

// Create identity matrix of same shape and type
auto identity = result_type::eye();
// Fill with identity pattern
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
identity(i, j) = (i == j) ? typename T::value_type{1} : typename T::value_type{0};
}
}

// Perform Gauss-Jordan elimination with partial pivoting
for (int i = 0; i < n; i++) {
// Find pivot (largest element in current column)
auto max_element = std::abs(result(i, i));
int max_row = i;
for (int k = i + 1; k < n; k++) {
if (std::abs(result(k, i)) > max_element) {
max_element = std::abs(result(k, i));
max_row = k;
}
}

// Check if matrix is singular
if (approx_equal(max_element, typename T::value_type{0})) {
throw std::runtime_error("Matrix is singular");
}

// Swap maximum row with current row if needed
if (max_row != i) {
for (int k = 0; k < n; k++) {
std::swap(result(i, k), result(max_row, k));
std::swap(identity(i, k), identity(max_row, k));
}
}

// Scale current row to make the diagonal element 1
auto pivot = result(i, i);
for (int k = 0; k < n; k++) {
result(i, k) /= pivot;
identity(i, k) /= pivot;
}

// Eliminate current column elements in other rows
for (int j = 0; j < n; j++) {
if (j != i) {
auto factor = result(j, i);
for (int k = 0; k < n; k++) {
result(j, k) -= factor * result(i, k);
identity(j, k) -= factor * identity(i, k);
}
}
}
}

// At this point, result should be the identity matrix and
// identity should contain the inverse
return identity;
}
#else
/**
* @brief Computes the inverse of a square matrix.
* @param A The matrix to invert.
@@ -201,6 +278,7 @@ template <host_tensor T> auto inv(const T &A) {

return result;
}
#endif

/**
* @brief Computes the Moore-Penrose pseudoinverse of a matrix.
83 changes: 83 additions & 0 deletions tests/tensor_math_tests.cpp
Original file line number Diff line number Diff line change
@@ -248,6 +248,89 @@ TEST_CASE("inv()") {
CHECK(A_T_inv(1, 0) == doctest::Approx(1.0));
CHECK(A_T_inv(1, 1) == doctest::Approx(-0.5));
}

SUBCASE("1000 random 4x4 matrices") {
// Use a fixed seed for deterministic random number generation
std::mt19937 gen(42);
std::uniform_real_distribution<double> dis(-10.0, 10.0);

// Create test matrices
using mat4d = tensor<double, shape<4, 4>>;

for (int test = 0; test < 1000; ++test) {
// Generate a random matrix with deterministic values
mat4d A;
bool singular;
do {
singular = false;
// Fill matrix with random values
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
A(i, j) = dis(gen);
}
}

// Check if matrix is singular by computing determinant
// For 4x4, we can use the direct formula instead of LU decomposition
double det =
A(0,0) * (
A(1,1) * (A(2,2) * A(3,3) - A(2,3) * A(3,2)) -
A(1,2) * (A(2,1) * A(3,3) - A(2,3) * A(3,1)) +
A(1,3) * (A(2,1) * A(3,2) - A(2,2) * A(3,1))
) -
A(0,1) * (
A(1,0) * (A(2,2) * A(3,3) - A(2,3) * A(3,2)) -
A(1,2) * (A(2,0) * A(3,3) - A(2,3) * A(3,0)) +
A(1,3) * (A(2,0) * A(3,2) - A(2,2) * A(3,0))
) +
A(0,2) * (
A(1,0) * (A(2,1) * A(3,3) - A(2,3) * A(3,1)) -
A(1,1) * (A(2,0) * A(3,3) - A(2,3) * A(3,0)) +
A(1,3) * (A(2,0) * A(3,1) - A(2,1) * A(3,0))
) -
A(0,3) * (
A(1,0) * (A(2,1) * A(3,2) - A(2,2) * A(3,1)) -
A(1,1) * (A(2,0) * A(3,2) - A(2,2) * A(3,0)) +
A(1,2) * (A(2,0) * A(3,1) - A(2,1) * A(3,0))
);

singular = std::abs(det) < 1e-10;
} while (singular);

// Compute inverse
auto A_inv = inv(A);

// Multiply A * A_inv, should get identity matrix
auto result = A * A_inv;
auto I = mat4::eye();

// Check that result is identity matrix
const double tolerance = 1e-10;
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
if (i == j) {
// Diagonal elements should be 1
CHECK(result(i, j) == doctest::Approx(1.0).epsilon(tolerance));
} else {
// Off-diagonal elements should be 0
CHECK(result(i, j) == doctest::Approx(0.0).epsilon(tolerance));
}
}
}

// Also check A_inv * A = I
auto result2 = A_inv * A;
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
if (i == j) {
CHECK(result2(i, j) == doctest::Approx(1.0).epsilon(tolerance));
} else {
CHECK(result2(i, j) == doctest::Approx(0.0).epsilon(tolerance));
}
}
}
}
}
}

TEST_CASE("pinv()") {

0 comments on commit 2c567dd

Please sign in to comment.