test: preserve Dev verification contracts through Refactor APIs

This commit is contained in:
KOKO\Mimi
2026-10-18 02:25:59 +09:00
parent 26009169bf
commit 74a2ce87ee
19 changed files with 6602 additions and 3 deletions
@@ -3,6 +3,7 @@
#include <gtest/gtest.h>
#include <cmath>
#include <future>
#include <initializer_list>
#include <limits>
#include <string>
@@ -11,6 +12,7 @@
#include "fesa/fem/dof_manager.h"
#include "fesa/math/sparse_matrix.h"
#include "math/dense_blas_internal.h"
namespace {
@@ -123,6 +125,42 @@ TEST(MklPardisoSolver, ReusesOneFactorizationForRepeatedRhs) {
EXPECT_LE(NormalizedResidual(matrix, second, rhs_second), 1.0e-10);
}
TEST(MklPardisoSolver, SingleEquationPolicyPreservesLifecycleAndConcurrentTls) {
const auto worker = [] {
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();
fesa::MklPardisoSolver solver;
for (int repeat = 0; repeat < 10; ++repeat) {
if (!solver.Factorize(MakeDenseCsr(1U, {2.0})).IsOk()) return false;
auto solution = MakeVector({9.0});
if (!solver.Solve(MakeVector({4.0}), solution).IsOk() ||
solution[0U] != 2.0) return false;
if (!solver.Factorize(MakeDenseCsr(1U, {4.0})).IsOk()) return false;
if (!solver.Solve(MakeVector({4.0}), solution).IsOk() ||
solution[0U] != 1.0) return false;
auto invalid_rhs = MakeVector({std::numeric_limits<double>::quiet_NaN()});
if (solver.Solve(invalid_rhs, solution).IsOk() || solution[0U] != 1.0)
return false;
if (!solver.Solve(MakeVector({8.0}), solution).IsOk() ||
solution[0U] != 2.0) return false;
if (solver.Factorize(MakeDenseCsr(1U, {-1.0})).IsOk()) return false;
if (solver.Solve(MakeVector({8.0}), solution).IsOk() ||
solution[0U] != 2.0) return false;
if (mkl_get_max_threads() != default_threads ||
mkl_set_num_threads_local(0) != 0) return false;
}
return true;
};
auto first = std::async(std::launch::async, worker);
auto second = std::async(std::launch::async, worker);
EXPECT_TRUE(first.get());
EXPECT_TRUE(second.get());
}
TEST(MklPardisoSolver, StillRejectsNonsymmetricPublicCsr) {
const auto nonsymmetric = MakeDenseCsr(2U, {2.0, 1.0, 0.0, 3.0});
fesa::MklPardisoSolver solver;
@@ -155,6 +193,32 @@ TEST(MklPardisoSolver, RefactorizesWithoutLeakingState) {
1.0e-10);
}
TEST(MklPardisoSolver, SingleEquationScopeTransitionsBetweenRetainedSizes) {
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();
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.Factorize(MakeDenseCsr(1U, {2.0})).IsOk());
auto single = MakeVector({0.0});
ASSERT_TRUE(solver.Solve(MakeVector({4.0}), single).IsOk());
EXPECT_DOUBLE_EQ(single[0U], 2.0);
ASSERT_TRUE(solver.Factorize(MakeDenseCsr(2U, {2, 0, 0, 3})).IsOk());
fesa::Vector pair{2U};
ASSERT_TRUE(solver.Solve(MakeVector({4, 6}), pair).IsOk());
EXPECT_NEAR(pair[0U], 2, 1e-14);
EXPECT_NEAR(pair[1U], 2, 1e-14);
EXPECT_EQ(mkl_get_max_threads(), default_threads);
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
ASSERT_TRUE(solver.Factorize(MakeDenseCsr(1U, {4.0})).IsOk());
ASSERT_TRUE(solver.Solve(MakeVector({8.0}), single).IsOk());
EXPECT_DOUBLE_EQ(single[0U], 2.0);
EXPECT_EQ(mkl_get_max_threads(), default_threads);
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
}
TEST(MklPardisoSolver, ClassifiesSingularIndefiniteAndNonfiniteFailures) {
fesa::MklPardisoSolver solver;
@@ -3,6 +3,7 @@
#include <gtest/gtest.h>
#include <cstddef>
#include <future>
#include <initializer_list>
#include <limits>
#include <type_traits>
@@ -13,6 +14,7 @@
#include "fesa/math/sparse_matrix.h"
#include "fesa/math/vector.h"
#include "fesa/solvers/linear/tangent_solver.h"
#include "math/dense_blas_internal.h"
namespace {
@@ -129,4 +131,104 @@ TEST(TangentSolver, RejectsSolveBeforeSuccessfulFactorization) {
EXPECT_DOUBLE_EQ(correction[1U], -9.0);
}
TEST(TangentSolver, ReusesRhsButFreshValuesAndFailureCannotReuseStaleState) {
fesa::MklPardisoTangentSolver solver;
ASSERT_TRUE(solver.FactorizeFresh(MakeDenseCsr(2U, {2.0, 1.0, 0.0, 3.0}))
.IsOk());
fesa::Vector correction{2U};
for (double scale : {1.0, -2.0, 0.5}) {
ASSERT_TRUE(solver.SolveCorrection(MakeVector({4.0 * scale, 6.0 * scale}),
correction).IsOk());
EXPECT_NEAR(correction[0U], scale, 1.0e-14);
EXPECT_NEAR(correction[1U], 2.0 * scale, 1.0e-14);
}
const auto bad_rhs = MakeVector({std::numeric_limits<double>::quiet_NaN(), 0});
correction = MakeVector({17, -4});
EXPECT_FALSE(solver.SolveCorrection(bad_rhs, correction).IsOk());
EXPECT_DOUBLE_EQ(correction[0U], 17);
EXPECT_DOUBLE_EQ(correction[1U], -4);
ASSERT_TRUE(solver.SolveCorrection(MakeVector({4, 6}), correction).IsOk());
ASSERT_TRUE(solver.FactorizeFresh(MakeDenseCsr(2U, {4, 0, 1, 2})).IsOk());
ASSERT_TRUE(solver.SolveCorrection(MakeVector({4, 5}), correction).IsOk());
EXPECT_NEAR(correction[0U], 1, 1.0e-14);
EXPECT_NEAR(correction[1U], 2, 1.0e-14);
auto diagonal = fesa::SparseMatrix::FromCoo(
2U, 2U, {{0U, 0U, 5, 0U, 0U}, {1U, 1U, 7, 1U, 0U}},
{{0U, 1U, 2U}, {0U, 1U}});
ASSERT_TRUE(diagonal.HasValue());
ASSERT_TRUE(solver.FactorizeFresh(diagonal.Value()).IsOk());
ASSERT_TRUE(solver.SolveCorrection(MakeVector({10, -7}), correction).IsOk());
EXPECT_NEAR(correction[0U], 2, 1.0e-14);
EXPECT_NEAR(correction[1U], -1, 1.0e-14);
EXPECT_FALSE(solver.FactorizeFresh(MakeDenseCsr(2U, {0, 0, 0, 0})).IsOk());
correction = MakeVector({19, -8});
const auto stale = solver.SolveCorrection(MakeVector({10, -7}), correction);
ASSERT_FALSE(stale.IsOk());
EXPECT_EQ(stale.Diagnostics().front().code, "solver-not-factorized");
EXPECT_DOUBLE_EQ(correction[0U], 19);
EXPECT_DOUBLE_EQ(correction[1U], -8);
ASSERT_TRUE(solver.FactorizeFresh(diagonal.Value()).IsOk());
EXPECT_TRUE(solver.SolveCorrection(MakeVector({10, -7}), correction).IsOk());
}
TEST(TangentSolver, SingleEquationScopeRestoresTlsAcrossFreshFailureAndReuse) {
const auto worker = [] {
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();
fesa::MklPardisoTangentSolver solver;
for (int repeat = 0; repeat < 10; ++repeat) {
auto correction = MakeVector({17.0});
if (!solver.FactorizeFresh(MakeDenseCsr(1U, {2.0})).IsOk()) return false;
if (!solver.SolveCorrection(MakeVector({4.0}), correction).IsOk() ||
correction[0U] != 2.0) return false;
if (!solver.FactorizeFresh(MakeDenseCsr(1U, {4.0})).IsOk()) return false;
if (!solver.SolveCorrection(MakeVector({4.0}), correction).IsOk() ||
correction[0U] != 1.0) return false;
if (solver.SolveCorrection(MakeVector({
std::numeric_limits<double>::infinity()}), correction).IsOk() ||
correction[0U] != 1.0) return false;
if (solver.FactorizeFresh(MakeDenseCsr(1U, {0.0})).IsOk()) return false;
if (solver.SolveCorrection(MakeVector({4.0}), correction).IsOk() ||
correction[0U] != 1.0) return false;
if (mkl_get_max_threads() != default_threads ||
mkl_set_num_threads_local(0) != 0) return false;
}
return true;
};
auto first = std::async(std::launch::async, worker);
auto second = std::async(std::launch::async, worker);
EXPECT_TRUE(first.get());
EXPECT_TRUE(second.get());
}
TEST(TangentSolver, SingleEquationScopeTransitionsBetweenRetainedSizes) {
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();
fesa::MklPardisoTangentSolver solver;
ASSERT_TRUE(solver.FactorizeFresh(MakeDenseCsr(1U, {2.0})).IsOk());
auto single = MakeVector({0.0});
ASSERT_TRUE(solver.SolveCorrection(MakeVector({4.0}), single).IsOk());
EXPECT_DOUBLE_EQ(single[0U], 2.0);
ASSERT_TRUE(solver.FactorizeFresh(MakeDenseCsr(2U, {2, 1, 0, 3})).IsOk());
fesa::Vector pair{2U};
ASSERT_TRUE(solver.SolveCorrection(MakeVector({6, 6}), pair).IsOk());
EXPECT_NEAR(pair[0U], 2, 1e-14);
EXPECT_NEAR(pair[1U], 2, 1e-14);
EXPECT_EQ(mkl_get_max_threads(), default_threads);
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
ASSERT_TRUE(solver.FactorizeFresh(MakeDenseCsr(1U, {4.0})).IsOk());
ASSERT_TRUE(solver.SolveCorrection(MakeVector({8.0}), single).IsOk());
EXPECT_DOUBLE_EQ(single[0U], 2.0);
EXPECT_EQ(mkl_get_max_threads(), default_threads);
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
}
} // namespace