Integrate Refactor solver sources and dual MSVC CMake builds
This commit is contained in:
@@ -2,13 +2,22 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/linear_frequency_procedure.h"
|
||||
#include "fesa/analysis/solid_frequency_procedure.h"
|
||||
#include "fesa/solvers/eigen/eigen_solver.h"
|
||||
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
||||
#include "fesa/solvers/linear/mkl_pardiso_tangent_solver.h"
|
||||
#include "solvers/linear/pardiso_session_test_seam.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -125,3 +134,277 @@ TEST(MklPardisoSolver, RejectsInvalidCsrStateAndDimensions) {
|
||||
EXPECT_EQ(no_diagonal_status.Diagnostics().front().code,
|
||||
"solver-missing-diagonal");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class RecordingEigenSolver final : public fesa::EigenSolver {
|
||||
public:
|
||||
explicit RecordingEigenSolver(bool& destroyed) : destroyed_(destroyed) {}
|
||||
~RecordingEigenSolver() override { destroyed_ = true; }
|
||||
|
||||
fesa::Result<fesa::FeastEigenResult> Solve(
|
||||
const fesa::SparseMatrix& matrix, std::size_t modes,
|
||||
double scale) const override {
|
||||
EXPECT_EQ(matrix.Rows(), 0U);
|
||||
EXPECT_EQ(modes, 3U);
|
||||
EXPECT_DOUBLE_EQ(scale, 7.0);
|
||||
++standard_calls;
|
||||
return fesa::Result<fesa::FeastEigenResult>::Failure(
|
||||
fesa::Status::Failure(fesa::FailureCategory::kSolver, {}));
|
||||
}
|
||||
|
||||
fesa::Result<fesa::FeastEigenResult> SolveGeneralized(
|
||||
const fesa::SparseMatrix& stiffness, const fesa::SparseMatrix& mass,
|
||||
std::size_t modes, double lower_bound,
|
||||
double scale) const override {
|
||||
EXPECT_EQ(stiffness.Rows(), 0U);
|
||||
EXPECT_EQ(mass.Rows(), 0U);
|
||||
EXPECT_EQ(modes, 4U);
|
||||
EXPECT_DOUBLE_EQ(lower_bound, 2.0);
|
||||
EXPECT_DOUBLE_EQ(scale, 11.0);
|
||||
++generalized_calls;
|
||||
return fesa::Result<fesa::FeastEigenResult>::Failure(
|
||||
fesa::Status::Failure(fesa::FailureCategory::kSolver, {}));
|
||||
}
|
||||
|
||||
mutable int standard_calls{0};
|
||||
mutable int generalized_calls{0};
|
||||
|
||||
private:
|
||||
bool& destroyed_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(EigenSolverContract, DispatchesBothPencilsAndDestroysThroughBase) {
|
||||
static_assert(std::is_abstract_v<fesa::EigenSolver>);
|
||||
static_assert(std::has_virtual_destructor_v<fesa::EigenSolver>);
|
||||
static_assert(std::is_base_of_v<fesa::EigenSolver,
|
||||
fesa::MklFeastEigenSolver>);
|
||||
static_assert(std::is_constructible_v<fesa::LinearFrequencyProcedureExecutor,
|
||||
const fesa::EigenSolver&>);
|
||||
static_assert(std::is_constructible_v<fesa::SolidFrequencyProcedureExecutor,
|
||||
const fesa::EigenSolver&>);
|
||||
bool destroyed = false;
|
||||
{
|
||||
auto recording = std::make_unique<RecordingEigenSolver>(destroyed);
|
||||
const auto* observations = recording.get();
|
||||
std::unique_ptr<fesa::EigenSolver> solver = std::move(recording);
|
||||
const fesa::EigenSolver& contract = *solver;
|
||||
const auto empty = MakeDenseCsr(0U, 0U, {});
|
||||
EXPECT_FALSE(contract.Solve(empty, 3U, 7.0).HasValue());
|
||||
EXPECT_FALSE(contract.SolveGeneralized(empty, empty, 4U, 2.0, 11.0)
|
||||
.HasValue());
|
||||
EXPECT_EQ(observations->standard_calls, 1);
|
||||
EXPECT_EQ(observations->generalized_calls, 1);
|
||||
EXPECT_FALSE(destroyed);
|
||||
}
|
||||
EXPECT_TRUE(destroyed);
|
||||
}
|
||||
|
||||
TEST(PardisoSession, RetainsDistinctStoragePoliciesAndFreshPhaseOrder) {
|
||||
std::vector<int> phases;
|
||||
fesa::internal::ScopedPardisoBackendTestHook hook{
|
||||
[&](const fesa::internal::PardisoBackendTestCall& call,
|
||||
MKL_INT&, MKL_INT&) {
|
||||
phases.push_back(static_cast<int>(call.phase));
|
||||
if (call.phase == 11) {
|
||||
EXPECT_EQ(call.equation_count, 2);
|
||||
if (call.matrix_type == 2) {
|
||||
EXPECT_EQ(call.row_offsets[2], 3);
|
||||
EXPECT_EQ(call.column_indices[0], 0);
|
||||
EXPECT_EQ(call.column_indices[1], 1);
|
||||
EXPECT_EQ(call.column_indices[2], 1);
|
||||
} else {
|
||||
EXPECT_EQ(call.matrix_type, 11);
|
||||
EXPECT_EQ(call.row_offsets[2], 4);
|
||||
EXPECT_DOUBLE_EQ(call.values[2], 0.0);
|
||||
}
|
||||
}
|
||||
}};
|
||||
{
|
||||
fesa::MklPardisoSolver solver;
|
||||
const auto matrix = MakeDenseCsr(2U, 2U, {4.0, 1.0, 1.0, 3.0});
|
||||
ASSERT_TRUE(solver.Factorize(matrix).IsOk());
|
||||
fesa::Vector solution{2U};
|
||||
ASSERT_TRUE(solver.Solve(fesa::Vector{2U, 1.0}, solution).IsOk());
|
||||
EXPECT_NEAR(solution[0], 2.0 / 11.0, 1e-14);
|
||||
ASSERT_TRUE(solver.Solve(fesa::Vector{2U, 2.0}, solution).IsOk());
|
||||
EXPECT_NEAR(solution[1], 6.0 / 11.0, 1e-14);
|
||||
ASSERT_TRUE(solver.Factorize(matrix).IsOk());
|
||||
}
|
||||
EXPECT_EQ(phases, (std::vector<int>{11, 22, 33, 33, -1, 11, 22, -1}));
|
||||
phases.clear();
|
||||
{
|
||||
fesa::MklPardisoTangentSolver solver;
|
||||
const auto matrix = MakeDenseCsr(2U, 2U, {2.0, 1.0, 0.0, 3.0});
|
||||
ASSERT_TRUE(solver.FactorizeFresh(matrix).IsOk());
|
||||
fesa::Vector correction{2U};
|
||||
ASSERT_TRUE(solver.SolveCorrection(fesa::Vector{2U, 6.0}, correction).IsOk());
|
||||
EXPECT_NEAR(correction[0], 2.0, 1e-14);
|
||||
EXPECT_NEAR(correction[1], 2.0, 1e-14);
|
||||
ASSERT_TRUE(solver.FactorizeFresh(matrix).IsOk());
|
||||
}
|
||||
EXPECT_EQ(phases, (std::vector<int>{11, 22, 33, -1, 11, 22, -1}));
|
||||
}
|
||||
|
||||
TEST(PardisoSession, FailedSolvePreservesOutputAndRetainedFactorization) {
|
||||
int solve_calls = 0;
|
||||
fesa::internal::ScopedPardisoBackendTestHook hook{
|
||||
[&](const fesa::internal::PardisoBackendTestCall& call,
|
||||
MKL_INT& error, MKL_INT&) {
|
||||
if (call.phase != 33) return;
|
||||
++solve_calls;
|
||||
if (solve_calls == 1) error = -3;
|
||||
if (solve_calls == 2)
|
||||
call.solution[0] = (std::numeric_limits<double>::infinity)();
|
||||
}};
|
||||
fesa::MklPardisoSolver solver;
|
||||
ASSERT_TRUE(solver.Factorize(MakeDenseCsr(1U, 1U, {2.0})).IsOk());
|
||||
fesa::Vector solution{1U, 17.0};
|
||||
const auto failed = solver.Solve(fesa::Vector{1U, 6.0}, solution);
|
||||
ASSERT_FALSE(failed.IsOk());
|
||||
EXPECT_EQ(failed.Diagnostics().front().code, "pardiso-solve-failed");
|
||||
EXPECT_DOUBLE_EQ(solution[0], 17.0);
|
||||
const auto nonfinite = solver.Solve(fesa::Vector{1U, 6.0}, solution);
|
||||
ASSERT_FALSE(nonfinite.IsOk());
|
||||
EXPECT_EQ(nonfinite.Diagnostics().front().code, "nonfinite-solver-solution");
|
||||
EXPECT_DOUBLE_EQ(solution[0], 17.0);
|
||||
ASSERT_TRUE(solver.Solve(fesa::Vector{1U, 6.0}, solution).IsOk());
|
||||
EXPECT_DOUBLE_EQ(solution[0], 3.0);
|
||||
}
|
||||
|
||||
TEST(PardisoSession, ReleaseFailurePrecedesNewValidationAndInvalidatesState) {
|
||||
int releases = 0;
|
||||
fesa::internal::ScopedPardisoBackendTestHook hook{
|
||||
[&](const fesa::internal::PardisoBackendTestCall& call,
|
||||
MKL_INT& error, MKL_INT&) {
|
||||
if (call.phase == -1) { ++releases; error = -3; }
|
||||
}};
|
||||
fesa::MklPardisoSolver solver;
|
||||
ASSERT_TRUE(solver.Factorize(MakeDenseCsr(1U, 1U, {2.0})).IsOk());
|
||||
const auto status = solver.Factorize(MakeDenseCsr(1U, 2U, {1.0, 2.0}));
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
EXPECT_EQ(status.Diagnostics().front().code, "pardiso-release-failed");
|
||||
fesa::Vector output{1U, 19.0};
|
||||
const auto unavailable = solver.Solve(fesa::Vector{1U, 1.0}, output);
|
||||
ASSERT_FALSE(unavailable.IsOk());
|
||||
EXPECT_EQ(unavailable.Diagnostics().front().code, "solver-not-factorized");
|
||||
EXPECT_DOUBLE_EQ(output[0], 19.0);
|
||||
EXPECT_EQ(releases, 1);
|
||||
}
|
||||
|
||||
TEST(PardisoSession, FactorizationFailuresAndExceptionsReleaseExactlyOnce) {
|
||||
const auto matrix = MakeDenseCsr(1U, 1U, {2.0});
|
||||
for (const int failing_phase : {11, 22}) {
|
||||
std::vector<int> phases;
|
||||
fesa::internal::ScopedPardisoBackendTestHook hook{
|
||||
[&](const fesa::internal::PardisoBackendTestCall& call,
|
||||
MKL_INT& error, MKL_INT&) {
|
||||
phases.push_back(static_cast<int>(call.phase));
|
||||
if (call.phase == failing_phase) error = -8;
|
||||
}};
|
||||
{
|
||||
fesa::MklPardisoSolver solver;
|
||||
const auto status = solver.Factorize(matrix);
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
EXPECT_EQ(status.Diagnostics().front().code, "pardiso-integer-overflow");
|
||||
fesa::Vector output{1U, 29.0};
|
||||
EXPECT_FALSE(solver.Solve(fesa::Vector{1U, 1.0}, output).IsOk());
|
||||
EXPECT_DOUBLE_EQ(output[0], 29.0);
|
||||
}
|
||||
EXPECT_EQ(phases, failing_phase == 11 ? (std::vector<int>{11, -1})
|
||||
: (std::vector<int>{11, 22, -1}));
|
||||
}
|
||||
std::vector<int> phases;
|
||||
fesa::internal::ScopedPardisoBackendTestHook hook{
|
||||
[&](const fesa::internal::PardisoBackendTestCall& call,
|
||||
MKL_INT&, MKL_INT&) {
|
||||
phases.push_back(static_cast<int>(call.phase));
|
||||
if (call.phase == 11) throw std::runtime_error("injected unwind");
|
||||
}};
|
||||
{
|
||||
fesa::MklPardisoTangentSolver solver;
|
||||
EXPECT_THROW(solver.FactorizeFresh(matrix), std::runtime_error);
|
||||
EXPECT_EQ(phases, (std::vector<int>{11, -1}));
|
||||
}
|
||||
EXPECT_EQ(phases, (std::vector<int>{11, -1}));
|
||||
}
|
||||
|
||||
TEST(PardisoSession, FailedTangentSolveRetainsStateButFailedFreshInvalidatesIt) {
|
||||
bool fail_solve = true;
|
||||
fesa::internal::ScopedPardisoBackendTestHook hook{
|
||||
[&](const fesa::internal::PardisoBackendTestCall& call,
|
||||
MKL_INT& error, MKL_INT&) {
|
||||
if (call.phase == 33 && fail_solve) error = -3;
|
||||
}};
|
||||
fesa::MklPardisoTangentSolver solver;
|
||||
ASSERT_TRUE(solver.FactorizeFresh(
|
||||
MakeDenseCsr(2U, 2U, {2.0, 1.0, 0.0, 3.0})).IsOk());
|
||||
fesa::Vector correction{2U, 31.0};
|
||||
auto status = solver.SolveCorrection(fesa::Vector{2U, 6.0}, correction);
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
EXPECT_EQ(status.Diagnostics().front().code, "pardiso-solve-failed");
|
||||
EXPECT_DOUBLE_EQ(correction[0], 31.0);
|
||||
EXPECT_DOUBLE_EQ(correction[1], 31.0);
|
||||
fail_solve = false;
|
||||
ASSERT_TRUE(solver.SolveCorrection(fesa::Vector{2U, 6.0}, correction).IsOk());
|
||||
EXPECT_NEAR(correction[0], 2.0, 1e-14);
|
||||
EXPECT_NEAR(correction[1], 2.0, 1e-14);
|
||||
status = solver.FactorizeFresh(MakeDenseCsr(1U, 2U, {1.0, 2.0}));
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
EXPECT_EQ(status.Diagnostics().front().code, "solver-matrix-not-square");
|
||||
status = solver.SolveCorrection(fesa::Vector{2U, 6.0}, correction);
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
EXPECT_EQ(status.Diagnostics().front().code, "solver-not-factorized");
|
||||
EXPECT_NEAR(correction[0], 2.0, 1e-14);
|
||||
EXPECT_NEAR(correction[1], 2.0, 1e-14);
|
||||
}
|
||||
|
||||
TEST(PardisoSession, EmptyLifecycleNeverInvokesBackend) {
|
||||
int calls = 0;
|
||||
fesa::internal::ScopedPardisoBackendTestHook hook{
|
||||
[&](const fesa::internal::PardisoBackendTestCall&, MKL_INT&, MKL_INT&) {
|
||||
++calls;
|
||||
}};
|
||||
{
|
||||
fesa::MklPardisoSolver solver;
|
||||
fesa::MklPardisoTangentSolver tangent;
|
||||
const auto empty = MakeDenseCsr(0U, 0U, {});
|
||||
fesa::Vector solution{0U};
|
||||
ASSERT_TRUE(solver.Factorize(empty).IsOk());
|
||||
ASSERT_TRUE(solver.Solve(fesa::Vector{0U}, solution).IsOk());
|
||||
ASSERT_TRUE(solver.Factorize(empty).IsOk());
|
||||
ASSERT_TRUE(tangent.FactorizeFresh(empty).IsOk());
|
||||
ASSERT_TRUE(tangent.SolveCorrection(fesa::Vector{0U}, solution).IsOk());
|
||||
ASSERT_TRUE(tangent.FactorizeFresh(empty).IsOk());
|
||||
}
|
||||
EXPECT_EQ(calls, 0);
|
||||
}
|
||||
|
||||
TEST(PardisoSession, RestoresDefaultThreadPolicyAndPreservesCallerOverride) {
|
||||
const int previous = mkl_set_num_threads_local(0);
|
||||
struct RestoreThreads {
|
||||
int previous;
|
||||
~RestoreThreads() { mkl_set_num_threads_local(previous); }
|
||||
} restore{previous};
|
||||
const auto matrix = MakeDenseCsr(1U, 1U, {2.0});
|
||||
for (const int caller_threads : {0, 2}) {
|
||||
mkl_set_num_threads_local(caller_threads);
|
||||
int release_calls = 0;
|
||||
fesa::internal::ScopedPardisoBackendTestHook hook{
|
||||
[&](const fesa::internal::PardisoBackendTestCall& call,
|
||||
MKL_INT& error, MKL_INT&) {
|
||||
EXPECT_EQ(mkl_get_max_threads(), caller_threads == 0 ? 1 : 2);
|
||||
if (call.phase == 22) error = -8;
|
||||
if (call.phase == -1) ++release_calls;
|
||||
}};
|
||||
{
|
||||
fesa::MklPardisoSolver solver;
|
||||
EXPECT_FALSE(solver.Factorize(matrix).IsOk());
|
||||
EXPECT_EQ(mkl_set_num_threads_local(caller_threads), caller_threads);
|
||||
}
|
||||
EXPECT_EQ(release_calls, 1);
|
||||
EXPECT_EQ(mkl_set_num_threads_local(caller_threads), caller_threads);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <future>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
@@ -12,7 +11,6 @@
|
||||
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -125,42 +123,6 @@ 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;
|
||||
@@ -193,32 +155,6 @@ 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,7 +3,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <future>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
@@ -14,7 +13,6 @@
|
||||
#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 {
|
||||
|
||||
@@ -131,104 +129,4 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user