Integrate Refactor solver sources and dual MSVC CMake builds
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "math/jet_linear_solve.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
@@ -11,129 +11,197 @@
|
||||
namespace fesa::dense_blas_internal {
|
||||
namespace {
|
||||
|
||||
TEST(DenseBlasInternal, PolicyQueryPreservesDefaultPositiveAndNestedTls) {
|
||||
const int previous = mkl_set_num_threads_local(0);
|
||||
struct Restore {
|
||||
int previous;
|
||||
~Restore() { static_cast<void>(mkl_set_num_threads_local(previous)); }
|
||||
} restore{previous};
|
||||
const int global = mkl_get_max_threads();
|
||||
const int domain = mkl_domain_get_max_threads(MKL_DOMAIN_BLAS);
|
||||
const int dynamic = mkl_get_dynamic();
|
||||
for (int repeat = 0; repeat < 2; ++repeat) {
|
||||
const auto policy = ReadScopedLocalDensePolicy();
|
||||
EXPECT_EQ(policy.blas_max_threads, 1);
|
||||
EXPECT_EQ(policy.dynamic, dynamic);
|
||||
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
|
||||
EXPECT_EQ(mkl_get_max_threads(), global);
|
||||
EXPECT_EQ(mkl_domain_get_max_threads(MKL_DOMAIN_BLAS), domain);
|
||||
EXPECT_EQ(mkl_get_dynamic(), dynamic);
|
||||
}
|
||||
static_cast<void>(mkl_set_num_threads_local(2));
|
||||
EXPECT_EQ(ReadScopedLocalDensePolicy().blas_max_threads, 2);
|
||||
EXPECT_EQ(mkl_set_num_threads_local(2), 2);
|
||||
static_cast<void>(mkl_set_num_threads_local(0));
|
||||
try {
|
||||
const ScopedLocalDenseThreads outer{};
|
||||
EXPECT_EQ(ReadScopedLocalDensePolicy().blas_max_threads, 1);
|
||||
EXPECT_EQ(mkl_set_num_threads_local(1), 1);
|
||||
throw std::runtime_error{"policy-query-unwind"};
|
||||
} catch (const std::runtime_error&) {
|
||||
}
|
||||
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
|
||||
EXPECT_EQ(mkl_get_dynamic(), dynamic);
|
||||
// Forms RHS and residuals independently of the production dense adapter.
|
||||
Matrix FactorRhs(const Matrix& a, const Matrix& x, const bool transpose,
|
||||
const bool right) {
|
||||
Matrix b{x.Rows(), x.Columns()};
|
||||
for (std::size_t i = 0; i < b.Rows(); ++i)
|
||||
for (std::size_t j = 0; j < b.Columns(); ++j)
|
||||
for (std::size_t k = 0; k < a.Rows(); ++k)
|
||||
b(i, j) += right ? x(i, k) * (transpose ? a(j, k) : a(k, j))
|
||||
: (transpose ? a(k, i) : a(i, k)) * x(k, j);
|
||||
return b;
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal,
|
||||
OuterProductUsesRectangularStorageAndPreservesRejection) {
|
||||
const std::array<double, 2U> left{2.0, -3.0};
|
||||
const std::array<double, 3U> right{4.0, -5.0, 0.25};
|
||||
std::array<double, 6U> product{};
|
||||
ASSERT_TRUE(OuterProduct(left.data(), left.size(), right.data(), right.size(),
|
||||
product.data()));
|
||||
const std::array<double, 6U> expected{8.0, -10.0, 0.5, -12.0, 15.0, -0.75};
|
||||
EXPECT_EQ(product, expected);
|
||||
const auto accepted = product;
|
||||
const std::array<double, 2U> invalid{
|
||||
1.0, std::numeric_limits<double>::quiet_NaN()};
|
||||
EXPECT_FALSE(OuterProduct(invalid.data(), invalid.size(), right.data(),
|
||||
right.size(), product.data()));
|
||||
EXPECT_EQ(product, accepted);
|
||||
EXPECT_TRUE(OuterProduct(nullptr, 0U, nullptr, 3U, nullptr));
|
||||
EXPECT_TRUE(OuterProduct(nullptr, 2U, nullptr, 0U, nullptr));
|
||||
EXPECT_THROW(OuterProduct(nullptr, 2U, right.data(), 3U, product.data()),
|
||||
std::invalid_argument);
|
||||
const auto too_large =
|
||||
static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)()) + 1U;
|
||||
EXPECT_THROW(OuterProduct(nullptr, too_large, nullptr, 1U, nullptr),
|
||||
std::length_error);
|
||||
const auto too_many_elements =
|
||||
std::numeric_limits<std::size_t>::max() / sizeof(double) + 1U;
|
||||
EXPECT_THROW(OuterProduct(nullptr, too_many_elements, nullptr, 1U, nullptr),
|
||||
std::length_error);
|
||||
void ExpectFactorSolution(const Matrix& a, const Matrix& x, const Matrix& b,
|
||||
const Matrix& expected, const bool transpose,
|
||||
const bool right) {
|
||||
ASSERT_EQ(x.Rows(), expected.Rows());
|
||||
ASSERT_EQ(x.Columns(), expected.Columns());
|
||||
const auto residual = FactorRhs(a, x, transpose, right);
|
||||
for (std::size_t i = 0; i < x.Rows(); ++i)
|
||||
for (std::size_t j = 0; j < x.Columns(); ++j) {
|
||||
EXPECT_DOUBLE_EQ(x(i, j), expected(i, j));
|
||||
EXPECT_DOUBLE_EQ(residual(i, j), b(i, j));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal, LocalDenseScopeRestoresTlsAfterProductsAndUnwind) {
|
||||
const int previous = mkl_set_num_threads_local(0);
|
||||
struct Restore {
|
||||
int previous;
|
||||
~Restore() { static_cast<void>(mkl_set_num_threads_local(previous)); }
|
||||
} restore{previous};
|
||||
const int default_threads = mkl_get_max_threads();
|
||||
const std::array<double, 2U> left{0.0, 2.0};
|
||||
const std::array<double, 2U> right{3.0, 4.0};
|
||||
std::array<double, 4U> product{};
|
||||
product.fill(std::numeric_limits<double>::quiet_NaN());
|
||||
{
|
||||
const ScopedLocalDenseThreads scope{};
|
||||
EXPECT_EQ(mkl_get_max_threads(), 1);
|
||||
EXPECT_TRUE(
|
||||
OuterProduct(left.data(), 2U, right.data(), 2U, product.data()));
|
||||
EXPECT_DOUBLE_EQ(product[0U], 0.0);
|
||||
EXPECT_DOUBLE_EQ(product[3U], 8.0);
|
||||
}
|
||||
EXPECT_EQ(mkl_get_max_threads(), default_threads);
|
||||
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
|
||||
try {
|
||||
const ScopedLocalDenseThreads scope{};
|
||||
OuterProduct(nullptr, 2U, right.data(), 2U, product.data());
|
||||
} catch (const std::invalid_argument&) {
|
||||
}
|
||||
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
|
||||
static_cast<void>(mkl_set_num_threads_local(3));
|
||||
{
|
||||
const ScopedLocalDenseThreads scope{};
|
||||
EXPECT_EQ(mkl_get_max_threads(), 3);
|
||||
}
|
||||
EXPECT_EQ(mkl_get_max_threads(), 3);
|
||||
TEST(DenseFactor, LuSolvesRectangularRhsOnBothSidesAndTransposes) {
|
||||
Matrix a{3U, 3U};
|
||||
const double values[]{0, 2, 1, 1, 1, 0, 2, 0, 1};
|
||||
for (std::size_t i = 0; i < 9U; ++i) a.Data()[i] = values[i];
|
||||
auto factor = DenseFactor::Lu(a);
|
||||
ASSERT_TRUE(factor.HasValue());
|
||||
EXPECT_DOUBLE_EQ(factor.Value().Determinant(), -4.0);
|
||||
for (const bool right : {false, true})
|
||||
for (const bool transpose : {false, true}) {
|
||||
Matrix expected{right ? 2U : 3U, right ? 3U : 2U};
|
||||
for (std::size_t i = 0; i < 6U; ++i)
|
||||
expected.Data()[i] = static_cast<double>(i) - 2.0;
|
||||
const auto b = FactorRhs(a, expected, transpose, right);
|
||||
const auto solved = right ? factor.Value().SolveRight(b, transpose)
|
||||
: factor.Value().Solve(b, transpose);
|
||||
ASSERT_TRUE(solved.HasValue());
|
||||
ExpectFactorSolution(a, solved.Value(), b, expected, transpose, right);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal, OuterProductLeavesFiniteInputOverflowForOwnerGate) {
|
||||
const std::array<double, 1U> left{std::numeric_limits<double>::max()};
|
||||
const std::array<double, 1U> right{2.0};
|
||||
std::array<double, 1U> product{};
|
||||
EXPECT_TRUE(OuterProduct(left.data(), 1U, right.data(), 1U, product.data()));
|
||||
EXPECT_TRUE(std::isinf(product[0U]));
|
||||
TEST(DenseFactor, CholeskySolvesAndWhiteningRetainsScaleAndOrientation) {
|
||||
Matrix a{2U, 2U};
|
||||
a(0, 0) = 4;
|
||||
a(0, 1) = 2;
|
||||
a(1, 0) = 2;
|
||||
a(1, 1) = 5;
|
||||
const auto factor = DenseFactor::Cholesky(a);
|
||||
ASSERT_TRUE(factor.HasValue());
|
||||
EXPECT_DOUBLE_EQ(factor.Value().Determinant(), 16.0);
|
||||
const auto lower = factor.Value().Lower();
|
||||
ASSERT_TRUE(lower.HasValue());
|
||||
EXPECT_DOUBLE_EQ(lower.Value()(0, 0), 2.0);
|
||||
EXPECT_DOUBLE_EQ(lower.Value()(0, 1), 0.0);
|
||||
EXPECT_DOUBLE_EQ(lower.Value()(1, 0), 1.0);
|
||||
EXPECT_DOUBLE_EQ(lower.Value()(1, 1), 2.0);
|
||||
for (const bool right : {false, true})
|
||||
for (const bool transpose : {false, true}) {
|
||||
Matrix expected{right ? 3U : 2U, right ? 2U : 3U};
|
||||
for (std::size_t i = 0; i < 6U; ++i)
|
||||
expected.Data()[i] = static_cast<double>(i) - 2.0;
|
||||
const auto b = FactorRhs(a, expected, transpose, right);
|
||||
const auto solved = right ? factor.Value().SolveRight(b, transpose)
|
||||
: factor.Value().Solve(b, transpose);
|
||||
ASSERT_TRUE(solved.HasValue());
|
||||
ExpectFactorSolution(a, solved.Value(), b, expected, transpose, right);
|
||||
const auto triangular_rhs =
|
||||
FactorRhs(lower.Value(), expected, transpose, right);
|
||||
const auto triangular =
|
||||
right ? factor.Value().SolveLowerRight(triangular_rhs, transpose)
|
||||
: factor.Value().SolveLower(triangular_rhs, transpose);
|
||||
ASSERT_TRUE(triangular.HasValue());
|
||||
ExpectFactorSolution(lower.Value(), triangular.Value(), triangular_rhs,
|
||||
expected, transpose, right);
|
||||
}
|
||||
// W = I * L^-T / sqrt(4); verify W^T (4*A) W = I without a procedure.
|
||||
Matrix basis{3U, 2U};
|
||||
basis(0, 0) = 0.5;
|
||||
basis(1, 1) = 0.5;
|
||||
const auto w = factor.Value().SolveLowerRight(basis, true);
|
||||
ASSERT_TRUE(w.HasValue());
|
||||
for (std::size_t i = 0; i < 2U; ++i)
|
||||
for (std::size_t j = 0; j < 2U; ++j) {
|
||||
double gram = 0;
|
||||
for (std::size_t k = 0; k < 2U; ++k)
|
||||
for (std::size_t l = 0; l < 2U; ++l)
|
||||
gram += w.Value()(k, i) * (4 * a(k, l)) * w.Value()(l, j);
|
||||
EXPECT_DOUBLE_EQ(gram, i == j ? 1.0 : 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal, TransposeRightProductUsesInnerAndRectangularStrides) {
|
||||
const std::array<double, 6U> left{1, 2, 3, 4, 5, 6};
|
||||
const std::array<double, 12U> right{7, 8, 9, 10, 11, 12,
|
||||
13, 14, 15, 16, 17, 18};
|
||||
std::array<double, 8U> product;
|
||||
ASSERT_TRUE(MultiplyTransposeRight(left.data(), 2U, right.data(), 4U, 3U,
|
||||
product.data()));
|
||||
const std::array<double, 8U> expected{50, 68, 86, 104, 122, 167, 212, 257};
|
||||
EXPECT_EQ(product, expected);
|
||||
EXPECT_TRUE(
|
||||
MultiplyTransposeRight(nullptr, 2U, nullptr, 4U, 0U, product.data()));
|
||||
for (const auto value : product) EXPECT_DOUBLE_EQ(value, 0.0);
|
||||
auto nonfinite = right;
|
||||
nonfinite[8U] = std::numeric_limits<double>::infinity();
|
||||
product.fill(31.0);
|
||||
EXPECT_FALSE(MultiplyTransposeRight(left.data(), 2U, nonfinite.data(), 4U, 3U,
|
||||
product.data()));
|
||||
for (const auto value : product) EXPECT_DOUBLE_EQ(value, 31.0);
|
||||
TEST(DenseFactor, OwnsSnapshotAndFailedCandidatesPreserveReusableFactor) {
|
||||
Matrix a{2U, 2U};
|
||||
a(0, 0) = 2;
|
||||
a(1, 1) = 4;
|
||||
auto factor = DenseFactor::Lu(a);
|
||||
ASSERT_TRUE(factor.HasValue());
|
||||
a(0, 0) = 8;
|
||||
const auto changed = DenseFactor::Lu(a);
|
||||
ASSERT_TRUE(changed.HasValue());
|
||||
Matrix rhs{2U, 1U, 8};
|
||||
const auto old_solution = factor.Value().Solve(rhs);
|
||||
ASSERT_TRUE(old_solution.HasValue());
|
||||
EXPECT_DOUBLE_EQ(old_solution.Value()(0, 0), 4);
|
||||
EXPECT_DOUBLE_EQ(changed.Value().Solve(rhs).Value()(0, 0), 1);
|
||||
EXPECT_FALSE(DenseFactor::Lu(Matrix{2U, 2U}).HasValue());
|
||||
EXPECT_FALSE(DenseFactor::Cholesky(Matrix{2U, 2U}).HasValue());
|
||||
a(0, 0) = -1;
|
||||
EXPECT_FALSE(DenseFactor::Cholesky(a).HasValue());
|
||||
EXPECT_FALSE(DenseFactor::Lu(Matrix{2U, 3U}).HasValue());
|
||||
a(0, 0) = (std::numeric_limits<double>::quiet_NaN)();
|
||||
EXPECT_FALSE(DenseFactor::Lu(a).HasValue());
|
||||
EXPECT_FALSE(DenseFactor::Cholesky(a).HasValue());
|
||||
EXPECT_FALSE(factor.Value().Solve(Matrix{3U, 1U}).HasValue());
|
||||
EXPECT_FALSE(factor.Value().SolveRight(Matrix{1U, 3U}).HasValue());
|
||||
EXPECT_FALSE(factor.Value().SolveLower(rhs).HasValue());
|
||||
rhs(0, 0) = (std::numeric_limits<double>::infinity)();
|
||||
EXPECT_FALSE(factor.Value().Solve(rhs).HasValue());
|
||||
EXPECT_DOUBLE_EQ(old_solution.Value()(0, 0), 4);
|
||||
rhs(0, 0) = 8;
|
||||
EXPECT_DOUBLE_EQ(factor.Value().Solve(rhs).Value()(0, 0), 4);
|
||||
Matrix tiny{1U, 1U, (std::numeric_limits<double>::min)()};
|
||||
const auto tiny_factor = DenseFactor::Lu(tiny);
|
||||
ASSERT_TRUE(tiny_factor.HasValue());
|
||||
EXPECT_FALSE(tiny_factor.Value()
|
||||
.Solve(Matrix{1U, 1U, (std::numeric_limits<double>::max)()})
|
||||
.HasValue());
|
||||
EXPECT_DOUBLE_EQ(tiny_factor.Value().Solve(tiny).Value()(0, 0), 1);
|
||||
}
|
||||
|
||||
TEST(DenseFactor, EmptyAndInfinityConditionBoundaryAreExplicit) {
|
||||
for (const bool positive : {false, true}) {
|
||||
const auto empty = positive ? DenseFactor::Cholesky(Matrix{0U, 0U})
|
||||
: DenseFactor::Lu(Matrix{0U, 0U});
|
||||
ASSERT_TRUE(empty.HasValue());
|
||||
const auto left = empty.Value().Solve(Matrix{0U, 3U});
|
||||
ASSERT_TRUE(left.HasValue());
|
||||
EXPECT_EQ(left.Value().Columns(), 3U);
|
||||
const auto right = empty.Value().SolveRight(Matrix{4U, 0U});
|
||||
ASSERT_TRUE(right.HasValue());
|
||||
EXPECT_EQ(right.Value().Rows(), 4U);
|
||||
EXPECT_FALSE(empty.Value().ReciprocalConditionInfinity().HasValue());
|
||||
for (const double diagonal : {0x1p-31, 0x1p-32, 0x1p-33}) {
|
||||
Matrix a{2U, 2U};
|
||||
a(0, 0) = 1;
|
||||
a(1, 1) = diagonal;
|
||||
const auto factor =
|
||||
positive ? DenseFactor::Cholesky(a) : DenseFactor::Lu(a);
|
||||
ASSERT_TRUE(factor.HasValue());
|
||||
const auto rcond = factor.Value().ReciprocalConditionInfinity();
|
||||
ASSERT_TRUE(rcond.HasValue());
|
||||
EXPECT_DOUBLE_EQ(rcond.Value(), diagonal);
|
||||
EXPECT_EQ(rcond.Value() >= 0x1p-32, diagonal >= 0x1p-32);
|
||||
EXPECT_EQ(factor.Value().Solve(Matrix{2U, 0U}).Value().Columns(), 0U);
|
||||
EXPECT_EQ(factor.Value().SolveRight(Matrix{0U, 2U}).Value().Rows(), 0U);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DenseFactor, GeneralDimensionAndNonDiagonalConditionUseFullCoefficient) {
|
||||
Matrix a{8U, 8U};
|
||||
a(0, 0) = 4;
|
||||
for (std::size_t i = 1; i < 8U; ++i) {
|
||||
a(i, i) = 5;
|
||||
a(i, i - 1U) = 2;
|
||||
a(i - 1U, i) = 2;
|
||||
}
|
||||
const auto factor = DenseFactor::Cholesky(a);
|
||||
ASSERT_TRUE(factor.HasValue());
|
||||
Matrix expected{8U, 3U};
|
||||
for (std::size_t i = 0; i < 24U; ++i)
|
||||
expected.Data()[i] = static_cast<double>(i) - 8;
|
||||
const auto rhs = FactorRhs(a, expected, false, false);
|
||||
const auto solved = factor.Value().Solve(rhs);
|
||||
ASSERT_TRUE(solved.HasValue());
|
||||
ExpectFactorSolution(a, solved.Value(), rhs, expected, false, false);
|
||||
Matrix general{2U, 2U};
|
||||
general(0, 0) = 2;
|
||||
general(0, 1) = 1;
|
||||
general(1, 1) = 1;
|
||||
const auto lu = DenseFactor::Lu(general);
|
||||
ASSERT_TRUE(lu.HasValue());
|
||||
EXPECT_DOUBLE_EQ(lu.Value().ReciprocalConditionInfinity().Value(), 1.0 / 3.0);
|
||||
EXPECT_FALSE(lu.Value().Lower().HasValue());
|
||||
Matrix overflow{2U, 2U, (std::numeric_limits<double>::max)()};
|
||||
overflow(1, 0) = -overflow(1, 0);
|
||||
EXPECT_FALSE(DenseFactor::Lu(overflow).HasValue());
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal, CDup003ConvertsZeroNormalAndOverflowLengths) {
|
||||
@@ -162,138 +230,199 @@ TEST(DenseBlasInternal, CDup003CopiesZeroAndNonzeroContiguousValues) {
|
||||
EXPECT_EQ(destination, source);
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal, TransposeProductChecksShapeAndKnownRectangularAction) {
|
||||
|
||||
TEST(DenseBlasInternal, TransposeLeftMatchesIndependentRectangularProduct) {
|
||||
Matrix left{3U, 2U};
|
||||
Matrix right{3U, 1U};
|
||||
Matrix right{3U, 4U};
|
||||
for (std::size_t row = 0; row < 3U; ++row) {
|
||||
left(row, 0U) = static_cast<double>(row + 1U);
|
||||
left(row, 1U) = 2.0;
|
||||
right(row, 0U) = static_cast<double>(row);
|
||||
for (std::size_t column = 0; column < 2U; ++column)
|
||||
left(row, column) = static_cast<double>(1U + row * 2U + column);
|
||||
for (std::size_t column = 0; column < 4U; ++column)
|
||||
right(row, column) = static_cast<double>(row + column) - 2.0;
|
||||
}
|
||||
const auto product = MultiplyTransposeLeft(left, right);
|
||||
const Matrix product = MultiplyTransposeLeft(left, right);
|
||||
ASSERT_EQ(product.Rows(), 2U);
|
||||
ASSERT_EQ(product.Columns(), 1U);
|
||||
EXPECT_DOUBLE_EQ(product(0U, 0U), 8.0);
|
||||
EXPECT_DOUBLE_EQ(product(1U, 0U), 6.0);
|
||||
EXPECT_THROW(static_cast<void>(MultiplyTransposeLeft(left, Matrix{2U, 1U})),
|
||||
ASSERT_EQ(product.Columns(), 4U);
|
||||
for (std::size_t row = 0; row < 2U; ++row)
|
||||
for (std::size_t column = 0; column < 4U; ++column) {
|
||||
double expected = 0.0;
|
||||
for (std::size_t inner = 0; inner < 3U; ++inner)
|
||||
expected += left(inner, row) * right(inner, column);
|
||||
EXPECT_DOUBLE_EQ(product(row, column), expected);
|
||||
}
|
||||
const auto gram = MultiplyTransposeLeft(left, left);
|
||||
EXPECT_DOUBLE_EQ(gram(0U, 0U), 35.0);
|
||||
EXPECT_DOUBLE_EQ(gram(0U, 1U), 44.0);
|
||||
EXPECT_DOUBLE_EQ(gram(1U, 1U), 56.0);
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal, TransposeLeftRetainsEmptyAndFailureContracts) {
|
||||
const auto zeros = MultiplyTransposeLeft(Matrix{0U, 2U}, Matrix{0U, 3U});
|
||||
ASSERT_EQ(zeros.Rows(), 2U);
|
||||
ASSERT_EQ(zeros.Columns(), 3U);
|
||||
EXPECT_DOUBLE_EQ(zeros(1U, 2U), 0.0);
|
||||
const auto empty = MultiplyTransposeLeft(
|
||||
Matrix{(std::numeric_limits<std::size_t>::max)(), 0U},
|
||||
Matrix{(std::numeric_limits<std::size_t>::max)(), 0U});
|
||||
EXPECT_EQ(empty.Rows(), 0U);
|
||||
EXPECT_EQ(empty.Columns(), 0U);
|
||||
EXPECT_THROW(static_cast<void>(MultiplyTransposeLeft(Matrix{1U, 2U},
|
||||
Matrix{2U, 1U})),
|
||||
std::invalid_argument);
|
||||
const auto empty = MultiplyTransposeLeft(Matrix{0U, 2U}, Matrix{0U, 3U});
|
||||
EXPECT_EQ(empty.Rows(), 2U);
|
||||
EXPECT_EQ(empty.Columns(), 3U);
|
||||
EXPECT_DOUBLE_EQ(empty(1U, 2U), 0.0);
|
||||
left(0U, 0U) = std::numeric_limits<double>::quiet_NaN();
|
||||
EXPECT_THROW(static_cast<void>(MultiplyTransposeLeft(left, right)),
|
||||
std::invalid_argument);
|
||||
left(0U, 0U) = 1.0;
|
||||
right(0U, 0U) = std::numeric_limits<double>::infinity();
|
||||
EXPECT_THROW(static_cast<void>(MultiplyTransposeLeft(left, right)),
|
||||
EXPECT_THROW(static_cast<void>(MultiplyTransposeLeft(
|
||||
Matrix{1U, 0U}, Matrix{1U, 1U,
|
||||
(std::numeric_limits<double>::quiet_NaN)()})),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal, DiagonalProductKeepsEntrywiseCancellationAndChecks) {
|
||||
Vector diagonal{3U};
|
||||
diagonal[0U] = 4;
|
||||
diagonal[1U] = 2;
|
||||
diagonal[2U] = -1;
|
||||
Vector input{3U};
|
||||
const double large = std::ldexp(1.0, 56);
|
||||
input[0U] = 1;
|
||||
input[1U] = -large;
|
||||
input[2U] = 2 * large;
|
||||
const auto product = MultiplyDiagonal(diagonal, input);
|
||||
EXPECT_DOUBLE_EQ(product[0U] + (product[1U] - product[2U]), 4);
|
||||
EXPECT_EQ(MultiplyDiagonal(Vector{0U}, Vector{0U}).Size(), 0U);
|
||||
EXPECT_THROW(static_cast<void>(MultiplyDiagonal(diagonal, Vector{2U})),
|
||||
std::invalid_argument);
|
||||
input[0U] = std::numeric_limits<double>::infinity();
|
||||
EXPECT_THROW(static_cast<void>(MultiplyDiagonal(diagonal, input)),
|
||||
std::invalid_argument);
|
||||
input[0U] = 1;
|
||||
diagonal[0U] = std::numeric_limits<double>::quiet_NaN();
|
||||
EXPECT_THROW(static_cast<void>(MultiplyDiagonal(diagonal, input)),
|
||||
std::invalid_argument);
|
||||
TEST(DenseBlasInternal, RawProductsPreserveDestinationOnNonfiniteInput) {
|
||||
const double left[]{1.0, 2.0, -3.0, 4.0};
|
||||
const double right[]{5.0, -6.0, 7.0, 8.0, -9.0, 10.0};
|
||||
std::array<double, 6U> destination{};
|
||||
ASSERT_TRUE(MultiplyTransposeRight(left, 2U, right, 3U, 2U,
|
||||
destination.data()));
|
||||
EXPECT_EQ(destination, (std::array<double, 6U>{-7.0, 23.0, 11.0,
|
||||
-39.0, 11.0, 67.0}));
|
||||
const double invalid[]{(std::numeric_limits<double>::infinity)(), 1.0};
|
||||
destination.fill(42.0);
|
||||
EXPECT_FALSE(MultiplyTransposeRight(invalid, 1U, right, 3U, 2U,
|
||||
destination.data()));
|
||||
EXPECT_FALSE(OuterProduct(left, 2U, invalid, 2U, destination.data()));
|
||||
for (const double value : destination) EXPECT_DOUBLE_EQ(value, 42.0);
|
||||
ASSERT_TRUE(OuterProduct(left, 2U, right, 3U, destination.data()));
|
||||
EXPECT_EQ(destination, (std::array<double, 6U>{5.0, -6.0, 7.0,
|
||||
10.0, -12.0, 14.0}));
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal, CholeskyAndInverseMatchKnownFactorsAndKeepFailedOutput) {
|
||||
Matrix matrix{2U, 2U};
|
||||
matrix(0U, 0U) = 4;
|
||||
matrix(0U, 1U) = 2;
|
||||
matrix(1U, 0U) = 2;
|
||||
matrix(1U, 1U) = 5;
|
||||
Matrix lower{0U, 0U}, inverse{0U, 0U};
|
||||
ASSERT_TRUE(CholeskyLowerAndInverse(matrix, lower, inverse));
|
||||
EXPECT_DOUBLE_EQ(lower(0U, 0U), 2);
|
||||
EXPECT_DOUBLE_EQ(lower(1U, 0U), 1);
|
||||
EXPECT_DOUBLE_EQ(lower(1U, 1U), 2);
|
||||
EXPECT_DOUBLE_EQ(lower(0U, 1U), 0);
|
||||
EXPECT_DOUBLE_EQ(inverse(0U, 0U), .5);
|
||||
EXPECT_DOUBLE_EQ(inverse(1U, 0U), -.25);
|
||||
EXPECT_DOUBLE_EQ(inverse(1U, 1U), .5);
|
||||
matrix(1U, 1U) = 0;
|
||||
EXPECT_FALSE(CholeskyLowerAndInverse(matrix, lower, inverse));
|
||||
EXPECT_DOUBLE_EQ(lower(1U, 1U), 2);
|
||||
EXPECT_DOUBLE_EQ(inverse(1U, 1U), .5);
|
||||
EXPECT_THROW(static_cast<void>(CholeskyLowerAndInverse(
|
||||
Matrix{2U, 1U}, lower, inverse)), std::invalid_argument);
|
||||
matrix(0U, 0U) = std::numeric_limits<double>::quiet_NaN();
|
||||
EXPECT_THROW(static_cast<void>(CholeskyLowerAndInverse(matrix, lower, inverse)),
|
||||
TEST(DenseBlasInternal, RawProductZeroDimensionsAndOverflowAreExplicit) {
|
||||
EXPECT_TRUE(OuterProduct(nullptr, 0U, nullptr, 3U, nullptr));
|
||||
EXPECT_TRUE(MultiplyTransposeRight(nullptr, 2U, nullptr, 0U, 3U, nullptr));
|
||||
std::array<double, 6U> zeros{1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
|
||||
ASSERT_TRUE(MultiplyTransposeRight(nullptr, 2U, nullptr, 3U, 0U,
|
||||
zeros.data()));
|
||||
for (const double value : zeros) EXPECT_DOUBLE_EQ(value, 0.0);
|
||||
EXPECT_THROW(MultiplyTransposeRight(nullptr, 1U, nullptr, 1U, 1U, nullptr),
|
||||
std::invalid_argument);
|
||||
const auto overflow =
|
||||
static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)()) + 1U;
|
||||
EXPECT_THROW(MultiplyTransposeRight(nullptr, overflow, nullptr, 0U, 0U,
|
||||
nullptr), std::length_error);
|
||||
double unchanged = 42.0;
|
||||
EXPECT_THROW(CopyValues(&unchanged, overflow, &unchanged), std::length_error);
|
||||
EXPECT_DOUBLE_EQ(unchanged, 42.0);
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal,
|
||||
CholeskyFailureReasonPreservesCandidatesAndResetsOnReuse) {
|
||||
Matrix matrix{2U, 2U};
|
||||
matrix(0U, 0U) = 4.0;
|
||||
matrix(1U, 1U) = 9.0;
|
||||
Matrix lower{0U, 0U};
|
||||
Matrix inverse{0U, 0U};
|
||||
auto reason = CholeskyFailure::kInverse;
|
||||
ASSERT_TRUE(CholeskyLowerAndInverse(matrix, lower, inverse, &reason));
|
||||
EXPECT_EQ(reason, CholeskyFailure::kNone);
|
||||
matrix(1U, 1U) = -1.0;
|
||||
EXPECT_FALSE(CholeskyLowerAndInverse(matrix, lower, inverse, &reason));
|
||||
EXPECT_EQ(reason, CholeskyFailure::kPivot);
|
||||
EXPECT_DOUBLE_EQ(lower(1U, 1U), 3.0);
|
||||
EXPECT_DOUBLE_EQ(inverse(1U, 1U), 1.0 / 3.0);
|
||||
matrix(1U, 1U) = 9.0;
|
||||
EXPECT_TRUE(CholeskyLowerAndInverse(matrix, lower, inverse, &reason));
|
||||
EXPECT_EQ(reason, CholeskyFailure::kNone);
|
||||
reason = CholeskyFailure::kInverse;
|
||||
EXPECT_THROW(CholeskyLowerAndInverse(Matrix{2U, 1U}, lower, inverse, &reason),
|
||||
std::invalid_argument);
|
||||
EXPECT_EQ(reason, CholeskyFailure::kNone);
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal, SingleEquationScopeRestoresTlsAndRespectsOverrides) {
|
||||
const int previous = mkl_set_num_threads_local(0);
|
||||
struct Restore {
|
||||
int previous;
|
||||
~Restore() { static_cast<void>(mkl_set_num_threads_local(previous)); }
|
||||
} restore{previous};
|
||||
const int default_threads = mkl_get_max_threads();
|
||||
{
|
||||
const ScopedSingleEquationThreads scope{1U};
|
||||
EXPECT_EQ(mkl_get_max_threads(), 1);
|
||||
}
|
||||
EXPECT_EQ(mkl_get_max_threads(), default_threads);
|
||||
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
|
||||
{
|
||||
const ScopedSingleEquationThreads scope{2U};
|
||||
EXPECT_EQ(mkl_get_max_threads(), default_threads);
|
||||
}
|
||||
try {
|
||||
const ScopedSingleEquationThreads scope{1U};
|
||||
throw std::runtime_error{"scope failure"};
|
||||
} catch (const std::runtime_error&) {
|
||||
}
|
||||
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
|
||||
static_cast<void>(mkl_set_num_threads_local(3));
|
||||
{
|
||||
const ScopedSingleEquationThreads scope{1U};
|
||||
EXPECT_EQ(mkl_get_max_threads(), 3);
|
||||
}
|
||||
EXPECT_EQ(mkl_get_max_threads(), 3);
|
||||
TEST(DenseBlasInternal, ContiguousPrimitivesCheckEmptyAndOverflowBeforeUse) {
|
||||
EXPECT_DOUBLE_EQ(DotValues(nullptr, 0U, nullptr), 0.0);
|
||||
EXPECT_DOUBLE_EQ(NormValues(nullptr, 0U), 0.0);
|
||||
EXPECT_NO_THROW(ScaleValues(2.0, nullptr, 0U));
|
||||
EXPECT_NO_THROW(AxpyValues(2.0, nullptr, 0U, nullptr));
|
||||
const auto overflow =
|
||||
static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)()) + 1U;
|
||||
double unchanged = 42.0;
|
||||
EXPECT_THROW(DotValues(nullptr, overflow, nullptr), std::length_error);
|
||||
EXPECT_THROW(NormValues(nullptr, overflow), std::length_error);
|
||||
EXPECT_THROW(ScaleValues(2.0, &unchanged, overflow), std::length_error);
|
||||
EXPECT_THROW(AxpyValues(2.0, nullptr, overflow, &unchanged), std::length_error);
|
||||
EXPECT_DOUBLE_EQ(unchanged, 42.0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace fesa::dense_blas_internal
|
||||
|
||||
namespace fesa::math_internal {
|
||||
namespace {
|
||||
// Taylor coefficients of t^i s^j through total degree three. This independent
|
||||
// polynomial oracle deliberately uses coefficients, not derivative channels.
|
||||
struct Polynomial {
|
||||
std::array<std::array<double, 4>, 4> c{};
|
||||
};
|
||||
Polynomial operator-(Polynomial a, const Polynomial& b) {
|
||||
for (std::size_t i = 0; i <= 3; ++i)
|
||||
for (std::size_t j = 0; i + j <= 3; ++j) a.c[i][j] -= b.c[i][j];
|
||||
return a;
|
||||
}
|
||||
Polynomial operator*(const Polynomial& a, const Polynomial& b) {
|
||||
Polynomial result;
|
||||
for (std::size_t i = 0; i <= 3; ++i)
|
||||
for (std::size_t j = 0; i + j <= 3; ++j)
|
||||
for (std::size_t k = 0; k <= i; ++k)
|
||||
for (std::size_t l = 0; l <= j; ++l)
|
||||
result.c[i][j] += a.c[k][l] * b.c[i-k][j-l];
|
||||
return result;
|
||||
}
|
||||
Polynomial PolynomialConstant(double value) {
|
||||
Polynomial p;
|
||||
p.c[0][0] = value;
|
||||
return p;
|
||||
}
|
||||
double PolynomialRead(const Polynomial& p, std::size_t degree,
|
||||
std::size_t channel) {
|
||||
return p.c[channel][degree-channel];
|
||||
}
|
||||
void PolynomialWrite(Polynomial& p, std::size_t degree, std::size_t channel,
|
||||
double value) {
|
||||
p.c[channel][degree-channel] = value;
|
||||
}
|
||||
TEST(JetLinearSolve, RecoversMixedCubicCoefficientsWithOnePrimalFactor) {
|
||||
std::array<std::array<Polynomial, 3>, 3> a{};
|
||||
const double values[]{0, 2, 1, 1, 1, 0, 2, 0, 1};
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
for (std::size_t j = 0; j < 3; ++j) {
|
||||
a[i][j] = PolynomialConstant(values[3*i+j]);
|
||||
for (std::size_t degree = 1; degree <= 3; ++degree)
|
||||
for (std::size_t c = 0; c <= degree; ++c)
|
||||
a[i][j].c[c][degree-c] = (static_cast<double>(i+2*j+c)-2) / 16;
|
||||
}
|
||||
std::array<Polynomial, 3> expected{}, rhs{};
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
for (std::size_t degree = 0; degree <= 3; ++degree)
|
||||
for (std::size_t c = 0; c <= degree; ++c)
|
||||
expected[i].c[c][degree-c] = (static_cast<double>(i+degree+2*c)-3) / 8;
|
||||
// Form the full product independently using exact dyadic long-double sums.
|
||||
for (std::size_t row = 0; row < 3; ++row)
|
||||
for (std::size_t i = 0; i <= 3; ++i)
|
||||
for (std::size_t j = 0; i+j <= 3; ++j) {
|
||||
long double sum = 0;
|
||||
for (std::size_t column = 0; column < 3; ++column)
|
||||
for (std::size_t k = 0; k <= i; ++k)
|
||||
for (std::size_t l = 0; l <= j; ++l)
|
||||
sum += static_cast<long double>(a[row][column].c[k][l]) *
|
||||
expected[column].c[i-k][j-l];
|
||||
rhs[row].c[i][j] = static_cast<double>(sum);
|
||||
}
|
||||
Matrix primal{3, 3};
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
for (std::size_t j = 0; j < 3; ++j) primal(i,j) = a[i][j].c[0][0];
|
||||
const auto factor = dense_blas_internal::DenseFactor::Lu(primal);
|
||||
ASSERT_TRUE(factor.HasValue());
|
||||
std::array<Polynomial, 3> solved{};
|
||||
ASSERT_TRUE(SolveJetAction(a, rhs, factor.Value(), {1,2,3,4},
|
||||
PolynomialConstant, PolynomialRead,
|
||||
PolynomialWrite, solved));
|
||||
for (std::size_t row = 0; row < 3; ++row)
|
||||
for (std::size_t i = 0; i <= 3; ++i)
|
||||
for (std::size_t j = 0; i+j <= 3; ++j)
|
||||
EXPECT_NEAR(solved[row].c[i][j], expected[row].c[i][j], 2e-14);
|
||||
}
|
||||
TEST(JetLinearSolve, FailedChannelSolvePreservesCandidateAndInputs) {
|
||||
std::array<std::array<Polynomial, 3>, 3> a{};
|
||||
Matrix primal{3, 3};
|
||||
for (std::size_t i = 0; i < 3; ++i) {
|
||||
primal(i,i) = 1;
|
||||
a[i][i] = PolynomialConstant(1);
|
||||
}
|
||||
const auto factor = dense_blas_internal::DenseFactor::Lu(primal);
|
||||
ASSERT_TRUE(factor.HasValue());
|
||||
std::array<Polynomial, 3> rhs{}, solved{};
|
||||
rhs[1].c[1][0] = std::numeric_limits<double>::infinity();
|
||||
solved[0].c[0][0] = 17;
|
||||
EXPECT_FALSE(SolveJetAction(a, rhs, factor.Value(), {1,2,3,4},
|
||||
PolynomialConstant, PolynomialRead,
|
||||
PolynomialWrite, solved));
|
||||
EXPECT_EQ(solved[0].c[0][0], 17);
|
||||
EXPECT_EQ(a[1][1].c[0][0], 1);
|
||||
EXPECT_EQ(rhs[1].c[1][0], std::numeric_limits<double>::infinity());
|
||||
}
|
||||
} // namespace
|
||||
} // namespace fesa::math_internal
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
#include "../../../src/fesa/math/high_low_arithmetic.h"
|
||||
#include "elements/hex8_pair_simd.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "high_low_arithmetic_original.h"
|
||||
|
||||
namespace {
|
||||
using namespace fesa::math_internal;
|
||||
@@ -131,109 +126,81 @@ TEST(HighLowArithmetic, RejectsChangedRoundingAndFlushToZeroEnvironment) {
|
||||
EXPECT_FALSE(ArithmeticEnvironmentSupported());
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::uint64_t Bits(const double value) {
|
||||
std::uint64_t result = 0;
|
||||
std::memcpy(&result, &value, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
double FromBits(const std::uint64_t bits) {
|
||||
double result = 0;
|
||||
std::memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, FiniteClassificationMatchesBinary64Boundaries) {
|
||||
const auto check = [](const double value) {
|
||||
const bool expected = std::isfinite(value);
|
||||
EXPECT_EQ(IsFinite(HighLow{value, 0.0}), expected);
|
||||
EXPECT_EQ(IsFinite(HighLow{1.0, value}), expected);
|
||||
};
|
||||
for (std::uint64_t exponent = 0; exponent < 2048U; ++exponent) {
|
||||
for (const auto fraction : {0ULL, 1ULL, 1ULL << 51U,
|
||||
(1ULL << 52U) - 1ULL}) {
|
||||
for (const auto sign : {0ULL, 1ULL << 63U})
|
||||
check(FromBits(sign | (exponent << 52U) | fraction));
|
||||
}
|
||||
}
|
||||
std::mt19937_64 random{0x5800f17U};
|
||||
for (int sample = 0; sample < 10000; ++sample) check(FromBits(random()));
|
||||
}
|
||||
|
||||
void ExpectSamePair(const HighLow actual,
|
||||
const fesa::high_low_original::HighLow expected) {
|
||||
EXPECT_EQ(Bits(actual.high), Bits(expected.high));
|
||||
EXPECT_EQ(Bits(actual.low), Bits(expected.low));
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, BitExponentMatchesFrozenOracleAcrossEveryExponent) {
|
||||
ASSERT_TRUE(ArithmeticEnvironmentSupported());
|
||||
// Every normal exponent, every possible trailing significand-bit count,
|
||||
// both signs; then every subnormal single-bit and mixed-bit boundary.
|
||||
for (std::uint64_t exponent = 1; exponent < 2047; ++exponent) {
|
||||
for (unsigned trailing = 0; trailing <= 52U; ++trailing) {
|
||||
const auto fraction = trailing == 52U ? 0ULL : 1ULL << trailing;
|
||||
for (const auto sign : {0ULL, 1ULL << 63U}) {
|
||||
const double value = FromBits(sign | (exponent << 52U) | fraction);
|
||||
ASSERT_EQ(LowestBitExponent(value),
|
||||
fesa::high_low_original::LowestBitExponent(value));
|
||||
namespace {
|
||||
TEST(HighLowArithmetic, PacketDispatchRetainsComponentsAndInstructionGates) {
|
||||
using namespace fesa::hex8_internal;
|
||||
const std::uint32_t leaf1 = (1U << 12) | (1U << 26) | (1U << 27) | (1U << 28);
|
||||
const std::uint32_t leaf7 = (1U << 3) | (1U << 5) | (1U << 8);
|
||||
EXPECT_TRUE(SupportsMaterialPacketInstructions(leaf1, 6, leaf7));
|
||||
for (const auto bit : {12,26,27,28})
|
||||
EXPECT_FALSE(SupportsMaterialPacketInstructions(leaf1 & ~(1U << bit),6,leaf7));
|
||||
for (const auto bit : {3,5,8})
|
||||
EXPECT_FALSE(SupportsMaterialPacketInstructions(leaf1,6,leaf7 & ~(1U << bit)));
|
||||
EXPECT_FALSE(SupportsMaterialPacketInstructions(leaf1,2,leaf7));
|
||||
EXPECT_FALSE(SupportsMaterialPacketInstructions(leaf1,4,leaf7));
|
||||
const auto selected = SelectMaterialPacketEvaluator();
|
||||
std::size_t finite_pairs = 0, retained_low_pairs = 0, invalid_pairs = 0;
|
||||
RecordProperty("selected_backend", selected == EvaluateMaterialPacketAvx2 ? "avx2" : "scalar");
|
||||
const auto verify = [selected, &finite_pairs, &retained_low_pairs, &invalid_pairs](
|
||||
const MaterialPacketInput& input) {
|
||||
PairPacket expected{}, actual{};
|
||||
EvaluateMaterialPacketScalar(input, &expected);
|
||||
selected(input, &actual);
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
if (std::isnan(expected.values[lane].high)) {
|
||||
++invalid_pairs;
|
||||
EXPECT_TRUE(std::isnan(actual.values[lane].high));
|
||||
} else {
|
||||
++finite_pairs;
|
||||
if (expected.values[lane].low != 0) ++retained_low_pairs;
|
||||
EXPECT_DOUBLE_EQ(actual.values[lane].high, expected.values[lane].high);
|
||||
EXPECT_DOUBLE_EQ(actual.values[lane].low, expected.values[lane].low);
|
||||
EXPECT_EQ(std::signbit(actual.values[lane].low),
|
||||
std::signbit(expected.values[lane].low));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (unsigned bit = 0; bit < 52U; ++bit) {
|
||||
for (const auto significand : {1ULL << bit, (1ULL << 52U) - (1ULL << bit)}) {
|
||||
const double value = FromBits(significand);
|
||||
EXPECT_EQ(LowestBitExponent(value),
|
||||
fesa::high_low_original::LowestBitExponent(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, OptimizedArithmeticMatchesFrozenOriginalBits) {
|
||||
ASSERT_TRUE(ArithmeticEnvironmentSupported());
|
||||
std::vector<double> values{0.0, -0.0,
|
||||
std::numeric_limits<double>::denorm_min(),
|
||||
-std::numeric_limits<double>::denorm_min(),
|
||||
std::numeric_limits<double>::min(),
|
||||
std::nextafter(std::numeric_limits<double>::min(), 0.0),
|
||||
std::numeric_limits<double>::max(),
|
||||
-std::numeric_limits<double>::max(), 1.0, -1.0,
|
||||
std::nextafter(1.0, 2.0), std::ldexp(1.0, -53),
|
||||
std::ldexp(1.0, -54), std::numeric_limits<double>::infinity(),
|
||||
-std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::quiet_NaN()};
|
||||
std::mt19937_64 random{0x58a0fU};
|
||||
for (int sample = 0; sample < 4000; ++sample) {
|
||||
values.push_back(FromBits(random()));
|
||||
}
|
||||
for (std::size_t index = 0; index < values.size(); ++index) {
|
||||
const double high = values[index];
|
||||
const double low = values[(index * 13U + 7U) % values.size()];
|
||||
const fesa::high_low_original::HighLow original{high, low};
|
||||
EXPECT_EQ(IsNormalized({high, low}),
|
||||
fesa::high_low_original::IsNormalized(original));
|
||||
// Normalize a raw pair with the frozen operation, never the new predicate.
|
||||
const auto a = fesa::high_low_original::TwoSum(high, low);
|
||||
const auto b = fesa::high_low_original::TwoSum(
|
||||
values[(index + 1U) % values.size()],
|
||||
values[(index * 3U + 2U) % values.size()]);
|
||||
ExpectSamePair(Add({a.high, a.low}, {b.high, b.low}),
|
||||
fesa::high_low_original::Add(a, b));
|
||||
ExpectSamePair(Multiply({a.high, a.low}, {b.high, b.low}),
|
||||
fesa::high_low_original::Multiply(a, b));
|
||||
ExpectSamePair(Divide({a.high, a.low}, {b.high, b.low}),
|
||||
fesa::high_low_original::Divide(a, b));
|
||||
ExpectSamePair(TwoProduct(high, low),
|
||||
fesa::high_low_original::TwoProduct(high, low));
|
||||
}
|
||||
for (double high : {1.0, std::nextafter(1.0, 2.0), -1.0,
|
||||
std::numeric_limits<double>::max()}) {
|
||||
for (double low : {0.0, -0.0, std::ldexp(1.0, -53),
|
||||
-std::ldexp(1.0, -53)}) {
|
||||
EXPECT_EQ(IsNormalized({high, low}),
|
||||
fesa::high_low_original::IsNormalized({high, low}));
|
||||
};
|
||||
for (const bool full_low : {false,true})
|
||||
for (const int exponent : {-970,-100,0,100,400}) {
|
||||
MaterialPacketInput input{};
|
||||
input.weight = {0.5, 0};
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
input.deviatoric_left.values[lane] =
|
||||
{std::ldexp(1.0, exponent), full_low ? std::ldexp(1.0,exponent-54) : 0};
|
||||
input.deviatoric_right.values[lane] = {1.25, std::ldexp(1.0,-60)};
|
||||
input.shear_left.values[lane] = {0, 0};
|
||||
input.shear_right.values[lane] = {1,0};
|
||||
input.tensor_left.values[lane] = {-1, std::ldexp(1.0,-55)};
|
||||
input.gradient_dot.values[lane] = {std::ldexp(1.0,exponent),0};
|
||||
input.accumulated.values[lane] = {std::ldexp(1.0,exponent),0};
|
||||
}
|
||||
verify(input);
|
||||
// Mixed-low packets explicitly exercise the scalar bridge.
|
||||
input.deviatoric_left.values[1].low = 0;
|
||||
verify(input);
|
||||
input.shear_left.values[3] = {-0.0,0}; // raw signed-zero is invalid by contract
|
||||
input.deviatoric_left.values[0] = {std::numeric_limits<double>::max(),0};
|
||||
input.deviatoric_right.values[0] = {2,0};
|
||||
input.deviatoric_left.values[1] = {1,1};
|
||||
input.deviatoric_left.values[2] = {std::numeric_limits<double>::denorm_min(),0};
|
||||
input.deviatoric_right.values[2] = {0.5,0};
|
||||
verify(input);
|
||||
}
|
||||
EXPECT_GT(finite_pairs, 0U);
|
||||
EXPECT_GT(retained_low_pairs, 0U);
|
||||
EXPECT_GT(invalid_pairs, 0U);
|
||||
RecordProperty("finite_pairs", static_cast<int>(finite_pairs));
|
||||
RecordProperty("retained_low_pairs", static_cast<int>(retained_low_pairs));
|
||||
RecordProperty("invalid_pairs", static_cast<int>(invalid_pairs));
|
||||
// Selection is thread-environment dependent rather than cached after first use.
|
||||
#if defined(_M_X64) || defined(__SSE2__)
|
||||
const unsigned saved = _mm_getcsr();
|
||||
_mm_setcsr(saved | 0x8000U);
|
||||
EXPECT_EQ(SelectMaterialPacketEvaluator(), EvaluateMaterialPacketScalar);
|
||||
_mm_setcsr(saved);
|
||||
EXPECT_EQ(SelectMaterialPacketEvaluator(), selected);
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
@@ -109,22 +110,60 @@ TEST(DenseMath, RowMajorMatrixMatchesKnownGemvGemm) {
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(DenseMath, MatrixProductsRejectNonfiniteOperandsAndPreserveValidReuse) {
|
||||
Matrix left{2U, 2U, 1.0};
|
||||
Matrix right{2U, 1U, 2.0};
|
||||
Vector rhs{2U, 2.0};
|
||||
rhs[1U] = std::numeric_limits<double>::infinity();
|
||||
EXPECT_THROW(static_cast<void>(left.Multiply(rhs)), std::invalid_argument);
|
||||
rhs[1U] = 2.0;
|
||||
left(0U, 1U) = std::numeric_limits<double>::quiet_NaN();
|
||||
EXPECT_THROW(static_cast<void>(left.Multiply(rhs)), std::invalid_argument);
|
||||
EXPECT_THROW(static_cast<void>(left.Multiply(right)), std::invalid_argument);
|
||||
left(0U, 1U) = 1.0;
|
||||
right(1U, 0U) = std::numeric_limits<double>::infinity();
|
||||
EXPECT_THROW(static_cast<void>(left.Multiply(right)), std::invalid_argument);
|
||||
right(1U, 0U) = 2.0;
|
||||
EXPECT_DOUBLE_EQ(left.Multiply(rhs)[0U], 4.0);
|
||||
EXPECT_DOUBLE_EQ(left.Multiply(right)(1U, 0U), 4.0);
|
||||
|
||||
TEST(DenseMath, MatrixContiguousScaleAndRobustNorm) {
|
||||
Matrix matrix{2U, 3U};
|
||||
const double entries[]{1.0, -2.0, 3.0, -4.0, 5.0, -6.0};
|
||||
for (std::size_t i = 0; i < 6U; ++i) matrix.Data()[i] = entries[i];
|
||||
const Matrix& immutable = matrix;
|
||||
EXPECT_EQ(immutable.Data() + 3, &immutable(1U, 0U));
|
||||
EXPECT_NEAR(matrix.Norm(), std::sqrt(91.0), 2.0e-15);
|
||||
matrix.Scale(-2.0);
|
||||
for (std::size_t i = 0; i < 6U; ++i)
|
||||
EXPECT_DOUBLE_EQ(matrix.Data()[i], -2.0 * entries[i]);
|
||||
EXPECT_DOUBLE_EQ(immutable(1U, 2U), 12.0);
|
||||
Matrix huge{1U, 2U, 1.0e308};
|
||||
EXPECT_NEAR(huge.Norm() / 1.0e308, std::sqrt(2.0), 4.0e-16);
|
||||
Matrix tiny{2U, 1U, 1.0e-300};
|
||||
EXPECT_NEAR(tiny.Norm() / 1.0e-300, std::sqrt(2.0), 4.0e-16);
|
||||
Matrix empty{0U, (std::numeric_limits<std::size_t>::max)()};
|
||||
EXPECT_DOUBLE_EQ(empty.Norm(), 0.0);
|
||||
EXPECT_NO_THROW(empty.Scale(3.0));
|
||||
}
|
||||
|
||||
TEST(DenseMath, MatrixProductKeepsShapeAndFiniteValidationOrder) {
|
||||
const double nan = (std::numeric_limits<double>::quiet_NaN)();
|
||||
Matrix matrix{1U, 2U, nan};
|
||||
try {
|
||||
static_cast<void>(matrix.Multiply(Vector{1U}));
|
||||
FAIL() << "Shape mismatch was accepted";
|
||||
} catch (const std::invalid_argument& failure) {
|
||||
EXPECT_STREQ(failure.what(),
|
||||
"Matrix-vector multiplication has incompatible dimensions.");
|
||||
}
|
||||
EXPECT_THROW(static_cast<void>(matrix.Multiply(Vector{2U})),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(static_cast<void>(matrix.Multiply(Matrix{2U, 0U})),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(static_cast<void>(Matrix{0U, 2U}.Multiply(Vector{2U, nan})),
|
||||
std::invalid_argument);
|
||||
const Matrix finite{2U, 2U, 3.0};
|
||||
const auto square = finite.Multiply(finite);
|
||||
EXPECT_DOUBLE_EQ(square(0U, 0U), 18.0);
|
||||
EXPECT_DOUBLE_EQ(finite(0U, 0U), 3.0);
|
||||
}
|
||||
|
||||
TEST(DenseMath, MatrixSelfAssignmentPreservesShapeAndStorage) {
|
||||
Matrix matrix{2U, 1U, 7.0};
|
||||
const Matrix* same = &matrix;
|
||||
matrix = *same;
|
||||
EXPECT_EQ(matrix.Rows(), 2U);
|
||||
EXPECT_EQ(matrix.Columns(), 1U);
|
||||
EXPECT_DOUBLE_EQ(matrix(1U, 0U), 7.0);
|
||||
Matrix* mutable_same = &matrix;
|
||||
matrix = std::move(*mutable_same);
|
||||
EXPECT_EQ(matrix.Rows(), 2U);
|
||||
EXPECT_DOUBLE_EQ(matrix(1U, 0U), 7.0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <future>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -133,104 +131,4 @@ TEST(SparseAssembly, PreservesExpectedStructuralZeros) {
|
||||
4);
|
||||
}
|
||||
|
||||
TEST(SparseAssembly, MultipliesRectangularCopyMoveAndConcurrentConstCalls) {
|
||||
auto assembled = SparseMatrix::FromCoo(
|
||||
3U, 5U, {{0U, 4U, 3.0, 0U, 0U}, {2U, 1U, -2.0, 1U, 0U}},
|
||||
{{0U, 1U, 1U, 2U}, {4U, 1U}});
|
||||
ASSERT_TRUE(assembled.HasValue());
|
||||
const auto original = assembled.Value();
|
||||
auto copied = original;
|
||||
auto moved = std::move(copied);
|
||||
Vector rhs{5U, 2.0};
|
||||
const auto evaluate = [&rhs](const SparseMatrix& matrix) {
|
||||
for (int repeat = 0; repeat < 20; ++repeat) {
|
||||
const auto product = matrix.Multiply(rhs);
|
||||
if (product.Size() != 3U || product[0U] != 6.0 ||
|
||||
product[1U] != 0.0 || product[2U] != -4.0) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto first = std::async(std::launch::async, [&] { return evaluate(moved); });
|
||||
auto second = std::async(std::launch::async, [&] { return evaluate(moved); });
|
||||
EXPECT_TRUE(first.get());
|
||||
EXPECT_TRUE(second.get());
|
||||
EXPECT_TRUE(evaluate(original));
|
||||
auto replacement = SparseMatrix::FromCoo(
|
||||
1U, 1U, {{0U, 0U, 7.0, 0U, 0U}}, {{0U, 1U}, {0U}});
|
||||
ASSERT_TRUE(replacement.HasValue());
|
||||
moved = replacement.Value();
|
||||
EXPECT_DOUBLE_EQ(moved.Multiply(Vector{1U, 3.0})[0U], 21.0);
|
||||
EXPECT_TRUE(evaluate(original));
|
||||
}
|
||||
|
||||
TEST(SparseAssembly, PreservesZeroShapesAndEmptyRows) {
|
||||
auto no_rows = SparseMatrix::FromCoo(0U, 3U, {}, {{0U}, {}});
|
||||
auto no_columns = SparseMatrix::FromCoo(2U, 0U, {}, {{0U, 0U, 0U}, {}});
|
||||
auto no_entries = SparseMatrix::FromCoo(2U, 3U, {}, {{0U, 0U, 0U}, {}});
|
||||
ASSERT_TRUE(no_rows.HasValue());
|
||||
ASSERT_TRUE(no_columns.HasValue());
|
||||
ASSERT_TRUE(no_entries.HasValue());
|
||||
EXPECT_EQ(no_rows.Value().Multiply(Vector{3U}).Size(), 0U);
|
||||
const auto empty_columns = no_columns.Value().Multiply(Vector{0U});
|
||||
const auto empty_entries = no_entries.Value().Multiply(Vector{3U});
|
||||
ASSERT_EQ(empty_columns.Size(), 2U);
|
||||
ASSERT_EQ(empty_entries.Size(), 2U);
|
||||
for (std::size_t row = 0; row < 2U; ++row) {
|
||||
EXPECT_DOUBLE_EQ(empty_columns[row], 0.0);
|
||||
EXPECT_DOUBLE_EQ(empty_entries[row], 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SparseAssembly, RejectsNonfiniteRhsAndRecoversAfterFiniteOverflow) {
|
||||
auto matrix = SparseMatrix::FromCoo(
|
||||
2U, 2U, {{0U, 0U, 2.0, 0U, 0U}}, {{0U, 1U, 2U}, {0U, 1U}});
|
||||
ASSERT_TRUE(matrix.HasValue());
|
||||
Vector rhs{2U, (std::numeric_limits<double>::infinity)()};
|
||||
EXPECT_THROW(static_cast<void>(matrix.Value().Multiply(rhs)),
|
||||
std::invalid_argument);
|
||||
rhs[1U] = 4.0;
|
||||
rhs[0U] = (std::numeric_limits<double>::max)();
|
||||
EXPECT_TRUE(std::isinf(matrix.Value().Multiply(rhs)[0U]));
|
||||
rhs[0U] = (std::numeric_limits<double>::quiet_NaN)();
|
||||
EXPECT_THROW(static_cast<void>(matrix.Value().Multiply(rhs)),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(static_cast<void>(matrix.Value().Multiply(Vector{1U})),
|
||||
std::invalid_argument);
|
||||
rhs[0U] = 3.0;
|
||||
rhs[1U] = 4.0;
|
||||
const auto finite = matrix.Value().Multiply(rhs);
|
||||
EXPECT_DOUBLE_EQ(finite[0U], 6.0);
|
||||
EXPECT_DOUBLE_EQ(finite[1U], 0.0);
|
||||
}
|
||||
|
||||
TEST(SparseAssembly, MatchesIndependentLongDoubleResidualOnUnevenRows) {
|
||||
const std::vector<double> entries{1.25, -0.375, 4.5, -3.0, 0.125, 2.0};
|
||||
const SparsePattern pattern{{0U, 3U, 3U, 5U, 6U}, {0U, 2U, 4U, 1U, 3U, 2U}};
|
||||
std::vector<CooContribution> contributions;
|
||||
for (std::size_t row = 0; row < 4U; ++row) {
|
||||
for (auto position = pattern.row_offsets[row];
|
||||
position < pattern.row_offsets[row + 1U]; ++position) {
|
||||
contributions.push_back(
|
||||
{row, pattern.column_indices[position], entries[position], row,
|
||||
position});
|
||||
}
|
||||
}
|
||||
auto matrix = SparseMatrix::FromCoo(4U, 5U, contributions, pattern);
|
||||
ASSERT_TRUE(matrix.HasValue());
|
||||
Vector rhs{5U};
|
||||
for (std::size_t column = 0; column < 5U; ++column) {
|
||||
rhs[column] = 0.3 + static_cast<double>(column) * 0.7;
|
||||
}
|
||||
const auto product = matrix.Value().Multiply(rhs);
|
||||
for (std::size_t row = 0; row < 4U; ++row) {
|
||||
long double expected = 0;
|
||||
for (auto position = pattern.row_offsets[row];
|
||||
position < pattern.row_offsets[row + 1U]; ++position) {
|
||||
expected += static_cast<long double>(entries[position]) *
|
||||
rhs[pattern.column_indices[position]];
|
||||
}
|
||||
EXPECT_NEAR(product[row], static_cast<double>(expected), 2.0e-14);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -112,5 +112,19 @@ TEST(Vector3, RejectsZeroAndNonfiniteNormalization) {
|
||||
EXPECT_FALSE(Vector3(0.0, not_a_number, 0.0).Normalized().has_value());
|
||||
}
|
||||
|
||||
|
||||
TEST(Vector3, RightHandedFrameMatchesIndependentCoordinateTransform) {
|
||||
const Vector3 x_axis{0.0, 1.0, 0.0};
|
||||
const Vector3 y_axis{0.0, 0.0, 1.0};
|
||||
const Vector3 z_axis = x_axis.Cross(y_axis);
|
||||
const Vector3 global{2.0, -3.0, 4.0};
|
||||
EXPECT_DOUBLE_EQ(x_axis.Dot(global), -3.0);
|
||||
EXPECT_DOUBLE_EQ(y_axis.Dot(global), 4.0);
|
||||
EXPECT_DOUBLE_EQ(z_axis.Dot(global), 2.0);
|
||||
const Vector3 recovered = x_axis * -3.0 + y_axis * 4.0 + z_axis * 2.0;
|
||||
EXPECT_TRUE(recovered == global);
|
||||
EXPECT_DOUBLE_EQ(x_axis.Dot(y_axis.Cross(z_axis)), 1.0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace fesa
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
@@ -83,5 +84,31 @@ TEST(DenseMath, VectorOwnsAndChecksContiguousStorage) {
|
||||
EXPECT_THROW(original.Axpy(1.0, Vector{2}), std::invalid_argument);
|
||||
}
|
||||
|
||||
|
||||
TEST(DenseMath, VectorRobustNormSelfAliasAndNonfinitePropagation) {
|
||||
Vector large{2U, 1.0e308};
|
||||
EXPECT_NEAR(large.Norm() / 1.0e308, std::sqrt(2.0), 4.0e-16);
|
||||
Vector tiny{2U, 1.0e-300};
|
||||
EXPECT_NEAR(tiny.Norm() / 1.0e-300, std::sqrt(2.0), 4.0e-16);
|
||||
Vector values{2U};
|
||||
values[0] = 2.0;
|
||||
values[1] = -3.0;
|
||||
values.Axpy(-2.0, values);
|
||||
EXPECT_DOUBLE_EQ(values[0], -2.0);
|
||||
EXPECT_DOUBLE_EQ(values[1], 3.0);
|
||||
const Vector* same = &values;
|
||||
values = *same;
|
||||
Vector* mutable_same = &values;
|
||||
values = std::move(*mutable_same);
|
||||
EXPECT_DOUBLE_EQ(values.Dot(values), 13.0);
|
||||
Vector nonfinite{1U, (std::numeric_limits<double>::quiet_NaN)()};
|
||||
EXPECT_TRUE(std::isnan(nonfinite.Dot(Vector{1U, 1.0})));
|
||||
EXPECT_TRUE(std::isnan(nonfinite.Norm()));
|
||||
EXPECT_NO_THROW(nonfinite.Scale(2.0));
|
||||
EXPECT_NO_THROW(nonfinite.Axpy(1.0, Vector{1U, 1.0}));
|
||||
EXPECT_TRUE(std::isnan(nonfinite[0]));
|
||||
EXPECT_DOUBLE_EQ(Vector{0U}.Dot(Vector{0U}), 0.0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace fesa
|
||||
|
||||
Reference in New Issue
Block a user