feat(cpp-object-oriented-modular-refactoring): step 3 - foundation-google-style
This commit is contained in:
@@ -1,8 +1,4 @@
|
||||
#include "fesa/solvers/linear/linear_solver.hpp"
|
||||
#include "fesa/solvers/linear/mkl_pardiso_solver.hpp"
|
||||
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/math/sparse_matrix.hpp"
|
||||
#include "fesa/solvers/linear/linear_solver.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -10,137 +6,122 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
||||
|
||||
namespace {
|
||||
|
||||
fesa::SparseMatrix makeDenseCsr(
|
||||
const std::size_t rows,
|
||||
const std::size_t columns,
|
||||
const std::vector<double>& values) {
|
||||
EXPECT_EQ(values.size(), rows * columns);
|
||||
fesa::SparseMatrix MakeDenseCsr(const std::size_t rows,
|
||||
const std::size_t columns,
|
||||
const std::vector<double>& values) {
|
||||
EXPECT_EQ(values.size(), rows * columns);
|
||||
|
||||
fesa::SparsePattern pattern;
|
||||
std::vector<fesa::CooContribution> contributions;
|
||||
pattern.rowOffsets.reserve(rows + 1U);
|
||||
pattern.rowOffsets.push_back(0U);
|
||||
for (std::size_t row = 0U; row < rows; ++row) {
|
||||
for (std::size_t column = 0U; column < columns; ++column) {
|
||||
pattern.columnIndices.push_back(column);
|
||||
contributions.push_back({
|
||||
row,
|
||||
column,
|
||||
values[row * columns + column],
|
||||
row,
|
||||
column});
|
||||
}
|
||||
pattern.rowOffsets.push_back(pattern.columnIndices.size());
|
||||
fesa::SparsePattern pattern;
|
||||
std::vector<fesa::CooContribution> contributions;
|
||||
pattern.rowOffsets.reserve(rows + 1U);
|
||||
pattern.rowOffsets.push_back(0U);
|
||||
for (std::size_t row = 0U; row < rows; ++row) {
|
||||
for (std::size_t column = 0U; column < columns; ++column) {
|
||||
pattern.columnIndices.push_back(column);
|
||||
contributions.push_back(
|
||||
{row, column, values[row * columns + column], row, column});
|
||||
}
|
||||
pattern.rowOffsets.push_back(pattern.columnIndices.size());
|
||||
}
|
||||
|
||||
auto matrix = fesa::SparseMatrix::fromCoo(
|
||||
rows, columns, std::move(contributions), pattern);
|
||||
EXPECT_TRUE(matrix.hasValue());
|
||||
return std::move(matrix.value());
|
||||
auto matrix = fesa::SparseMatrix::FromCoo(rows, columns,
|
||||
std::move(contributions), pattern);
|
||||
EXPECT_TRUE(matrix.HasValue());
|
||||
return std::move(matrix.Value());
|
||||
}
|
||||
|
||||
void expectSolverFailure(const fesa::Status& status) {
|
||||
EXPECT_FALSE(status.isOk());
|
||||
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::solver);
|
||||
ASSERT_FALSE(status.diagnostics().empty());
|
||||
EXPECT_EQ(status.diagnostics().front().severity, fesa::Severity::error);
|
||||
void ExpectSolverFailure(const fesa::Status& status) {
|
||||
EXPECT_FALSE(status.IsOk());
|
||||
EXPECT_EQ(status.Category(), fesa::FailureCategory::kSolver);
|
||||
ASSERT_FALSE(status.Diagnostics().empty());
|
||||
EXPECT_EQ(status.Diagnostics().front().severity, fesa::Severity::kError);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
TEST(MklPardisoSolver, RejectsInvalidCsrStateAndDimensions) {
|
||||
static_assert(std::is_base_of_v<fesa::LinearSolver, fesa::MklPardisoSolver>);
|
||||
static_assert(std::has_virtual_destructor_v<fesa::LinearSolver>);
|
||||
static_assert(std::is_base_of_v<fesa::LinearSolver, fesa::MklPardisoSolver>);
|
||||
static_assert(std::has_virtual_destructor_v<fesa::LinearSolver>);
|
||||
|
||||
fesa::MklPardisoSolver solver;
|
||||
fesa::Vector untouched{2U};
|
||||
untouched[0U] = 17.0;
|
||||
untouched[1U] = -4.0;
|
||||
const auto beforeFactorize =
|
||||
solver.solve(fesa::Vector{2U, 1.0}, untouched);
|
||||
expectSolverFailure(beforeFactorize);
|
||||
EXPECT_EQ(
|
||||
beforeFactorize.diagnostics().front().code,
|
||||
"solver-not-factorized");
|
||||
EXPECT_DOUBLE_EQ(untouched[0U], 17.0);
|
||||
EXPECT_DOUBLE_EQ(untouched[1U], -4.0);
|
||||
fesa::MklPardisoSolver solver;
|
||||
fesa::Vector untouched{2U};
|
||||
untouched[0U] = 17.0;
|
||||
untouched[1U] = -4.0;
|
||||
const auto before_factorize = solver.Solve(fesa::Vector{2U, 1.0}, untouched);
|
||||
ExpectSolverFailure(before_factorize);
|
||||
EXPECT_EQ(before_factorize.Diagnostics().front().code,
|
||||
"solver-not-factorized");
|
||||
EXPECT_DOUBLE_EQ(untouched[0U], 17.0);
|
||||
EXPECT_DOUBLE_EQ(untouched[1U], -4.0);
|
||||
|
||||
// A fully constrained model has a valid 0x0 Kff. It still observes the
|
||||
// factorize-then-solve lifecycle without invoking a numerical backend.
|
||||
const auto empty = makeDenseCsr(0U, 0U, {});
|
||||
ASSERT_TRUE(solver.factorize(empty).isOk());
|
||||
fesa::Vector emptySolution{0U};
|
||||
EXPECT_TRUE(solver.solve(fesa::Vector{0U}, emptySolution).isOk());
|
||||
EXPECT_EQ(emptySolution.size(), 0U);
|
||||
// A fully constrained model has a valid 0x0 Kff. It still observes the
|
||||
// factorize-then-solve lifecycle without invoking a numerical backend.
|
||||
const auto empty = MakeDenseCsr(0U, 0U, {});
|
||||
ASSERT_TRUE(solver.Factorize(empty).IsOk());
|
||||
fesa::Vector empty_solution{0U};
|
||||
EXPECT_TRUE(solver.Solve(fesa::Vector{0U}, empty_solution).IsOk());
|
||||
EXPECT_EQ(empty_solution.Size(), 0U);
|
||||
|
||||
// Refactorization from the trivial state must establish ordinary PARDISO
|
||||
// state rather than retaining a zero-equation shortcut.
|
||||
const auto spd = makeDenseCsr(2U, 2U, {4.0, 1.0, 1.0, 3.0});
|
||||
ASSERT_TRUE(solver.factorize(spd).isOk());
|
||||
fesa::Vector solution{2U};
|
||||
ASSERT_TRUE(solver.solve(fesa::Vector{2U, 1.0}, solution).isOk());
|
||||
EXPECT_NEAR(solution[0U], 2.0 / 11.0, 1.0e-14);
|
||||
EXPECT_NEAR(solution[1U], 3.0 / 11.0, 1.0e-14);
|
||||
// Refactorization from the trivial state must establish ordinary PARDISO
|
||||
// state rather than retaining a zero-equation shortcut.
|
||||
const auto spd = MakeDenseCsr(2U, 2U, {4.0, 1.0, 1.0, 3.0});
|
||||
ASSERT_TRUE(solver.Factorize(spd).IsOk());
|
||||
fesa::Vector solution{2U};
|
||||
ASSERT_TRUE(solver.Solve(fesa::Vector{2U, 1.0}, solution).IsOk());
|
||||
EXPECT_NEAR(solution[0U], 2.0 / 11.0, 1.0e-14);
|
||||
EXPECT_NEAR(solution[1U], 3.0 / 11.0, 1.0e-14);
|
||||
|
||||
const double solvedFirst = solution[0U];
|
||||
const double solvedSecond = solution[1U];
|
||||
expectSolverFailure(solver.solve(fesa::Vector{1U, 1.0}, solution));
|
||||
EXPECT_DOUBLE_EQ(solution[0U], solvedFirst);
|
||||
EXPECT_DOUBLE_EQ(solution[1U], solvedSecond);
|
||||
const double solved_first = solution[0U];
|
||||
const double solved_second = solution[1U];
|
||||
ExpectSolverFailure(solver.Solve(fesa::Vector{1U, 1.0}, solution));
|
||||
EXPECT_DOUBLE_EQ(solution[0U], solved_first);
|
||||
EXPECT_DOUBLE_EQ(solution[1U], solved_second);
|
||||
|
||||
fesa::Vector wrongSolution{1U};
|
||||
wrongSolution[0U] = 41.0;
|
||||
expectSolverFailure(solver.solve(fesa::Vector{2U, 1.0}, wrongSolution));
|
||||
EXPECT_DOUBLE_EQ(wrongSolution[0U], 41.0);
|
||||
fesa::Vector wrong_solution{1U};
|
||||
wrong_solution[0U] = 41.0;
|
||||
ExpectSolverFailure(solver.Solve(fesa::Vector{2U, 1.0}, wrong_solution));
|
||||
EXPECT_DOUBLE_EQ(wrong_solution[0U], 41.0);
|
||||
|
||||
const auto rectangular = makeDenseCsr(
|
||||
2U, 3U, {2.0, 0.0, 0.0, 0.0, 3.0, 0.0});
|
||||
const auto rectangularStatus = solver.factorize(rectangular);
|
||||
expectSolverFailure(rectangularStatus);
|
||||
EXPECT_EQ(
|
||||
rectangularStatus.diagnostics().front().code,
|
||||
"solver-matrix-not-square");
|
||||
const auto rectangular = MakeDenseCsr(2U, 3U, {2.0, 0.0, 0.0, 0.0, 3.0, 0.0});
|
||||
const auto rectangular_status = solver.Factorize(rectangular);
|
||||
ExpectSolverFailure(rectangular_status);
|
||||
EXPECT_EQ(rectangular_status.Diagnostics().front().code,
|
||||
"solver-matrix-not-square");
|
||||
|
||||
fesa::SparsePattern invalidPattern{{0U, 2U}, {0U}};
|
||||
auto invalidCsr = fesa::SparseMatrix::fromCoo(
|
||||
1U,
|
||||
1U,
|
||||
{{0U, 0U, 1.0, 0U, 0U}},
|
||||
invalidPattern);
|
||||
EXPECT_FALSE(invalidCsr.hasValue());
|
||||
fesa::SparsePattern invalid_pattern{{0U, 2U}, {0U}};
|
||||
auto invalid_csr = fesa::SparseMatrix::FromCoo(
|
||||
1U, 1U, {{0U, 0U, 1.0, 0U, 0U}}, invalid_pattern);
|
||||
EXPECT_FALSE(invalid_csr.HasValue());
|
||||
|
||||
const auto nonsymmetric = makeDenseCsr(2U, 2U, {2.0, 1.0, 0.0, 3.0});
|
||||
const auto nonsymmetricStatus = solver.factorize(nonsymmetric);
|
||||
expectSolverFailure(nonsymmetricStatus);
|
||||
EXPECT_EQ(
|
||||
nonsymmetricStatus.diagnostics().front().code,
|
||||
"solver-matrix-not-symmetric");
|
||||
const auto nonsymmetric = MakeDenseCsr(2U, 2U, {2.0, 1.0, 0.0, 3.0});
|
||||
const auto nonsymmetric_status = solver.Factorize(nonsymmetric);
|
||||
ExpectSolverFailure(nonsymmetric_status);
|
||||
EXPECT_EQ(nonsymmetric_status.Diagnostics().front().code,
|
||||
"solver-matrix-not-symmetric");
|
||||
|
||||
const auto scaledNonsymmetric = makeDenseCsr(
|
||||
2U, 2U, {2.0e-20, 1.0e-20, 1.1e-20, 3.0e-20});
|
||||
const auto scaledNonsymmetricStatus =
|
||||
solver.factorize(scaledNonsymmetric);
|
||||
// Stop this case before inspecting diagnostics when the production code
|
||||
// incorrectly accepts the matrix; this keeps the RED failure deterministic.
|
||||
ASSERT_FALSE(scaledNonsymmetricStatus.isOk());
|
||||
expectSolverFailure(scaledNonsymmetricStatus);
|
||||
EXPECT_EQ(
|
||||
scaledNonsymmetricStatus.diagnostics().front().code,
|
||||
"solver-matrix-not-symmetric");
|
||||
|
||||
fesa::SparsePattern noDiagonalPattern{{0U, 1U, 2U}, {1U, 0U}};
|
||||
auto noDiagonal = fesa::SparseMatrix::fromCoo(
|
||||
2U,
|
||||
2U,
|
||||
{{0U, 1U, 1.0, 0U, 0U}, {1U, 0U, 1.0, 1U, 0U}},
|
||||
noDiagonalPattern);
|
||||
ASSERT_TRUE(noDiagonal.hasValue());
|
||||
const auto noDiagonalStatus = solver.factorize(noDiagonal.value());
|
||||
expectSolverFailure(noDiagonalStatus);
|
||||
EXPECT_EQ(
|
||||
noDiagonalStatus.diagnostics().front().code,
|
||||
"solver-missing-diagonal");
|
||||
const auto scaled_nonsymmetric =
|
||||
MakeDenseCsr(2U, 2U, {2.0e-20, 1.0e-20, 1.1e-20, 3.0e-20});
|
||||
const auto scaled_nonsymmetric_status = solver.Factorize(scaled_nonsymmetric);
|
||||
// Stop this case before inspecting diagnostics when the production code
|
||||
// incorrectly accepts the matrix; this keeps the RED failure deterministic.
|
||||
ASSERT_FALSE(scaled_nonsymmetric_status.IsOk());
|
||||
ExpectSolverFailure(scaled_nonsymmetric_status);
|
||||
EXPECT_EQ(scaled_nonsymmetric_status.Diagnostics().front().code,
|
||||
"solver-matrix-not-symmetric");
|
||||
|
||||
fesa::SparsePattern no_diagonal_pattern{{0U, 1U, 2U}, {1U, 0U}};
|
||||
auto no_diagonal = fesa::SparseMatrix::FromCoo(
|
||||
2U, 2U, {{0U, 1U, 1.0, 0U, 0U}, {1U, 0U, 1.0, 1U, 0U}},
|
||||
no_diagonal_pattern);
|
||||
ASSERT_TRUE(no_diagonal.HasValue());
|
||||
const auto no_diagonal_status = solver.Factorize(no_diagonal.Value());
|
||||
ExpectSolverFailure(no_diagonal_status);
|
||||
EXPECT_EQ(no_diagonal_status.Diagnostics().front().code,
|
||||
"solver-missing-diagonal");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
#include "fesa/solvers/linear/mkl_pardiso_solver.hpp"
|
||||
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/math/sparse_matrix.hpp"
|
||||
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -12,261 +9,242 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
|
||||
namespace {
|
||||
|
||||
fesa::SparseMatrix makeDenseCsr(
|
||||
const std::size_t size,
|
||||
const std::vector<double>& values) {
|
||||
EXPECT_EQ(values.size(), size * size);
|
||||
fesa::SparseMatrix MakeDenseCsr(const std::size_t size,
|
||||
const std::vector<double>& values) {
|
||||
EXPECT_EQ(values.size(), size * size);
|
||||
|
||||
fesa::SparsePattern pattern;
|
||||
std::vector<fesa::CooContribution> contributions;
|
||||
pattern.rowOffsets.reserve(size + 1U);
|
||||
pattern.rowOffsets.push_back(0U);
|
||||
for (std::size_t row = 0U; row < size; ++row) {
|
||||
for (std::size_t column = 0U; column < size; ++column) {
|
||||
pattern.columnIndices.push_back(column);
|
||||
contributions.push_back({
|
||||
row,
|
||||
column,
|
||||
values[row * size + column],
|
||||
row,
|
||||
column});
|
||||
}
|
||||
pattern.rowOffsets.push_back(pattern.columnIndices.size());
|
||||
fesa::SparsePattern pattern;
|
||||
std::vector<fesa::CooContribution> contributions;
|
||||
pattern.rowOffsets.reserve(size + 1U);
|
||||
pattern.rowOffsets.push_back(0U);
|
||||
for (std::size_t row = 0U; row < size; ++row) {
|
||||
for (std::size_t column = 0U; column < size; ++column) {
|
||||
pattern.columnIndices.push_back(column);
|
||||
contributions.push_back(
|
||||
{row, column, values[row * size + column], row, column});
|
||||
}
|
||||
pattern.rowOffsets.push_back(pattern.columnIndices.size());
|
||||
}
|
||||
|
||||
auto matrix = fesa::SparseMatrix::fromCoo(
|
||||
size, size, std::move(contributions), pattern);
|
||||
EXPECT_TRUE(matrix.hasValue());
|
||||
return std::move(matrix.value());
|
||||
auto matrix = fesa::SparseMatrix::FromCoo(size, size,
|
||||
std::move(contributions), pattern);
|
||||
EXPECT_TRUE(matrix.HasValue());
|
||||
return std::move(matrix.Value());
|
||||
}
|
||||
|
||||
fesa::Vector makeVector(const std::initializer_list<double> values) {
|
||||
fesa::Vector result{values.size()};
|
||||
std::size_t index = 0U;
|
||||
for (const double value : values) {
|
||||
result[index++] = value;
|
||||
}
|
||||
return result;
|
||||
fesa::Vector MakeVector(const std::initializer_list<double> values) {
|
||||
fesa::Vector result{values.size()};
|
||||
std::size_t index = 0U;
|
||||
for (const double value : values) {
|
||||
result[index++] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double normalizedResidual(
|
||||
const fesa::SparseMatrix& matrix,
|
||||
const fesa::Vector& solution,
|
||||
const fesa::Vector& rhs) {
|
||||
auto residual = matrix.multiply(solution);
|
||||
residual.axpy(-1.0, rhs);
|
||||
const double numerator = residual.norm();
|
||||
const double denominator = rhs.norm();
|
||||
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
|
||||
return (std::numeric_limits<double>::infinity)();
|
||||
}
|
||||
if (denominator == 0.0) {
|
||||
return numerator == 0.0 ? 0.0 :
|
||||
(std::numeric_limits<double>::infinity)();
|
||||
}
|
||||
return numerator / denominator;
|
||||
double NormalizedResidual(const fesa::SparseMatrix& matrix,
|
||||
const fesa::Vector& solution,
|
||||
const fesa::Vector& rhs) {
|
||||
auto residual = matrix.Multiply(solution);
|
||||
residual.Axpy(-1.0, rhs);
|
||||
const double numerator = residual.Norm();
|
||||
const double denominator = rhs.Norm();
|
||||
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
|
||||
return (std::numeric_limits<double>::infinity)();
|
||||
}
|
||||
if (denominator == 0.0) {
|
||||
return numerator == 0.0 ? 0.0 : (std::numeric_limits<double>::infinity)();
|
||||
}
|
||||
return numerator / denominator;
|
||||
}
|
||||
|
||||
double relativeError(
|
||||
const fesa::Vector& actual,
|
||||
const fesa::Vector& expected) {
|
||||
auto difference = actual;
|
||||
difference.axpy(-1.0, expected);
|
||||
const double numerator = difference.norm();
|
||||
const double denominator = expected.norm();
|
||||
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
|
||||
return (std::numeric_limits<double>::infinity)();
|
||||
}
|
||||
if (denominator == 0.0) {
|
||||
return numerator == 0.0 ? 0.0 :
|
||||
(std::numeric_limits<double>::infinity)();
|
||||
}
|
||||
return numerator / denominator;
|
||||
double RelativeError(const fesa::Vector& actual, const fesa::Vector& expected) {
|
||||
auto difference = actual;
|
||||
difference.Axpy(-1.0, expected);
|
||||
const double numerator = difference.Norm();
|
||||
const double denominator = expected.Norm();
|
||||
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
|
||||
return (std::numeric_limits<double>::infinity)();
|
||||
}
|
||||
if (denominator == 0.0) {
|
||||
return numerator == 0.0 ? 0.0 : (std::numeric_limits<double>::infinity)();
|
||||
}
|
||||
return numerator / denominator;
|
||||
}
|
||||
|
||||
void expectStructuredSolverFailure(const fesa::Status& status) {
|
||||
EXPECT_FALSE(status.isOk());
|
||||
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::solver);
|
||||
ASSERT_EQ(status.diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.diagnostics()[0U].severity, fesa::Severity::error);
|
||||
EXPECT_FALSE(status.diagnostics()[0U].code.empty());
|
||||
EXPECT_FALSE(status.diagnostics()[0U].message.empty());
|
||||
void ExpectStructuredSolverFailure(const fesa::Status& status) {
|
||||
EXPECT_FALSE(status.IsOk());
|
||||
EXPECT_EQ(status.Category(), fesa::FailureCategory::kSolver);
|
||||
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.Diagnostics()[0U].severity, fesa::Severity::kError);
|
||||
EXPECT_FALSE(status.Diagnostics()[0U].code.empty());
|
||||
EXPECT_FALSE(status.Diagnostics()[0U].message.empty());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
TEST(MklPardisoSolver, SolvesKnownSpdWithNormalizedResidual) {
|
||||
const auto matrix = makeDenseCsr(3U, {
|
||||
6.0, 2.0, 1.0,
|
||||
2.0, 5.0, 2.0,
|
||||
1.0, 2.0, 4.0});
|
||||
const auto expected = makeVector({1.0, -2.0, 3.0});
|
||||
const auto rhs = matrix.multiply(expected);
|
||||
const auto matrix =
|
||||
MakeDenseCsr(3U, {6.0, 2.0, 1.0, 2.0, 5.0, 2.0, 1.0, 2.0, 4.0});
|
||||
const auto expected = MakeVector({1.0, -2.0, 3.0});
|
||||
const auto rhs = matrix.Multiply(expected);
|
||||
|
||||
fesa::MklPardisoSolver concreteSolver;
|
||||
fesa::LinearSolver& solver = concreteSolver;
|
||||
ASSERT_TRUE(solver.factorize(matrix).isOk());
|
||||
fesa::MklPardisoSolver concrete_solver;
|
||||
fesa::LinearSolver& solver = concrete_solver;
|
||||
ASSERT_TRUE(solver.Factorize(matrix).IsOk());
|
||||
|
||||
fesa::Vector solution{3U};
|
||||
ASSERT_TRUE(solver.solve(rhs, solution).isOk());
|
||||
EXPECT_LE(normalizedResidual(matrix, solution, rhs), 1.0e-10);
|
||||
EXPECT_LE(relativeError(solution, expected), 1.0e-9);
|
||||
fesa::Vector solution{3U};
|
||||
ASSERT_TRUE(solver.Solve(rhs, solution).IsOk());
|
||||
EXPECT_LE(NormalizedResidual(matrix, solution, rhs), 1.0e-10);
|
||||
EXPECT_LE(RelativeError(solution, expected), 1.0e-9);
|
||||
}
|
||||
|
||||
TEST(MklPardisoSolver, ReusesOneFactorizationForRepeatedRhs) {
|
||||
const auto matrix = makeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
|
||||
const auto expectedFirst = makeVector({1.0, 2.0});
|
||||
const auto expectedSecond = makeVector({-2.0, 0.5});
|
||||
const auto rhsFirst = matrix.multiply(expectedFirst);
|
||||
const auto rhsSecond = matrix.multiply(expectedSecond);
|
||||
const auto matrix = MakeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
|
||||
const auto expected_first = MakeVector({1.0, 2.0});
|
||||
const auto expected_second = MakeVector({-2.0, 0.5});
|
||||
const auto rhs_first = matrix.Multiply(expected_first);
|
||||
const auto rhs_second = matrix.Multiply(expected_second);
|
||||
|
||||
fesa::MklPardisoSolver solver;
|
||||
ASSERT_TRUE(solver.factorize(matrix).isOk());
|
||||
fesa::Vector first{2U};
|
||||
fesa::Vector second{2U};
|
||||
ASSERT_TRUE(solver.solve(rhsFirst, first).isOk());
|
||||
ASSERT_TRUE(solver.solve(rhsSecond, second).isOk());
|
||||
fesa::MklPardisoSolver solver;
|
||||
ASSERT_TRUE(solver.Factorize(matrix).IsOk());
|
||||
fesa::Vector first{2U};
|
||||
fesa::Vector second{2U};
|
||||
ASSERT_TRUE(solver.Solve(rhs_first, first).IsOk());
|
||||
ASSERT_TRUE(solver.Solve(rhs_second, second).IsOk());
|
||||
|
||||
EXPECT_LE(relativeError(first, expectedFirst), 1.0e-9);
|
||||
EXPECT_LE(relativeError(second, expectedSecond), 1.0e-9);
|
||||
EXPECT_LE(normalizedResidual(matrix, first, rhsFirst), 1.0e-10);
|
||||
EXPECT_LE(normalizedResidual(matrix, second, rhsSecond), 1.0e-10);
|
||||
EXPECT_LE(RelativeError(first, expected_first), 1.0e-9);
|
||||
EXPECT_LE(RelativeError(second, expected_second), 1.0e-9);
|
||||
EXPECT_LE(NormalizedResidual(matrix, first, rhs_first), 1.0e-10);
|
||||
EXPECT_LE(NormalizedResidual(matrix, second, rhs_second), 1.0e-10);
|
||||
}
|
||||
|
||||
TEST(MklPardisoSolver, RefactorizesWithoutLeakingState) {
|
||||
const auto firstMatrix = makeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
|
||||
const auto secondMatrix = makeDenseCsr(2U, {2.0, 0.0, 0.0, 5.0});
|
||||
const auto firstExpected = makeVector({1.0, 2.0});
|
||||
const auto secondExpected = makeVector({-3.0, 4.0});
|
||||
const auto first_matrix = MakeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
|
||||
const auto second_matrix = MakeDenseCsr(2U, {2.0, 0.0, 0.0, 5.0});
|
||||
const auto first_expected = MakeVector({1.0, 2.0});
|
||||
const auto second_expected = MakeVector({-3.0, 4.0});
|
||||
|
||||
fesa::MklPardisoSolver solver;
|
||||
ASSERT_TRUE(solver.factorize(firstMatrix).isOk());
|
||||
fesa::Vector firstSolution{2U};
|
||||
ASSERT_TRUE(
|
||||
solver.solve(firstMatrix.multiply(firstExpected), firstSolution).isOk());
|
||||
EXPECT_LE(relativeError(firstSolution, firstExpected), 1.0e-9);
|
||||
fesa::MklPardisoSolver solver;
|
||||
ASSERT_TRUE(solver.Factorize(first_matrix).IsOk());
|
||||
fesa::Vector first_solution{2U};
|
||||
ASSERT_TRUE(
|
||||
solver.Solve(first_matrix.Multiply(first_expected), first_solution)
|
||||
.IsOk());
|
||||
EXPECT_LE(RelativeError(first_solution, first_expected), 1.0e-9);
|
||||
|
||||
ASSERT_TRUE(solver.factorize(secondMatrix).isOk());
|
||||
fesa::Vector secondSolution{2U};
|
||||
const auto secondRhs = secondMatrix.multiply(secondExpected);
|
||||
ASSERT_TRUE(solver.solve(secondRhs, secondSolution).isOk());
|
||||
EXPECT_LE(relativeError(secondSolution, secondExpected), 1.0e-9);
|
||||
EXPECT_LE(
|
||||
normalizedResidual(secondMatrix, secondSolution, secondRhs), 1.0e-10);
|
||||
ASSERT_TRUE(solver.Factorize(second_matrix).IsOk());
|
||||
fesa::Vector second_solution{2U};
|
||||
const auto second_rhs = second_matrix.Multiply(second_expected);
|
||||
ASSERT_TRUE(solver.Solve(second_rhs, second_solution).IsOk());
|
||||
EXPECT_LE(RelativeError(second_solution, second_expected), 1.0e-9);
|
||||
EXPECT_LE(NormalizedResidual(second_matrix, second_solution, second_rhs),
|
||||
1.0e-10);
|
||||
}
|
||||
|
||||
TEST(MklPardisoSolver, ClassifiesSingularIndefiniteAndNonfiniteFailures) {
|
||||
fesa::MklPardisoSolver solver;
|
||||
|
||||
const auto singular = MakeDenseCsr(2U, {1.0, 1.0, 1.0, 1.0});
|
||||
const auto singular_status = solver.Factorize(singular);
|
||||
ExpectStructuredSolverFailure(singular_status);
|
||||
EXPECT_TRUE(singular_status.Diagnostics()[0U].code ==
|
||||
"pardiso-zero-or-negative-pivot" ||
|
||||
singular_status.Diagnostics()[0U].code ==
|
||||
"pardiso-singular-diagonal");
|
||||
EXPECT_NE(
|
||||
singular_status.Diagnostics()[0U].entity_identity.find("phase=22,error="),
|
||||
std::string::npos);
|
||||
|
||||
const auto indefinite = MakeDenseCsr(2U, {1.0, 2.0, 2.0, 1.0});
|
||||
const auto indefinite_status = solver.Factorize(indefinite);
|
||||
ExpectStructuredSolverFailure(indefinite_status);
|
||||
EXPECT_TRUE(indefinite_status.Diagnostics()[0U].code ==
|
||||
"pardiso-zero-or-negative-pivot" ||
|
||||
indefinite_status.Diagnostics()[0U].code ==
|
||||
"pardiso-singular-diagonal");
|
||||
EXPECT_NE(indefinite_status.Diagnostics()[0U].entity_identity.find(
|
||||
"phase=22,error="),
|
||||
std::string::npos);
|
||||
|
||||
const auto spd = MakeDenseCsr(2U, {3.0, 1.0, 1.0, 2.0});
|
||||
ASSERT_TRUE(solver.Factorize(spd).IsOk());
|
||||
auto rhs = MakeVector({1.0, 2.0});
|
||||
rhs[1U] = (std::numeric_limits<double>::infinity)();
|
||||
fesa::Vector solution{2U};
|
||||
solution[0U] = 23.0;
|
||||
solution[1U] = -9.0;
|
||||
const auto rhs_status = solver.Solve(rhs, solution);
|
||||
ExpectStructuredSolverFailure(rhs_status);
|
||||
EXPECT_EQ(rhs_status.Diagnostics()[0U].code, "nonfinite-solver-rhs");
|
||||
EXPECT_DOUBLE_EQ(solution[0U], 23.0);
|
||||
EXPECT_DOUBLE_EQ(solution[1U], -9.0);
|
||||
|
||||
fesa::SparsePattern pattern{{0U, 1U}, {0U}};
|
||||
auto nonfinite_matrix = fesa::SparseMatrix::FromCoo(
|
||||
1U, 1U, {{0U, 0U, (std::numeric_limits<double>::quiet_NaN)(), 0U, 0U}},
|
||||
pattern);
|
||||
EXPECT_FALSE(nonfinite_matrix.HasValue());
|
||||
EXPECT_EQ(nonfinite_matrix.GetStatus().Diagnostics()[0U].code,
|
||||
"nonfinite-sparse-value");
|
||||
}
|
||||
|
||||
TEST(MklPardisoSolver,
|
||||
ConditioningSweepPassesResolvedCasesAndFailsUnresolvedCasesExplicitly) {
|
||||
const std::vector<double> common_scales{1.0e-12, 1.0, 1.0e12};
|
||||
for (const double scale : common_scales) {
|
||||
const auto matrix =
|
||||
MakeDenseCsr(2U, {4.0 * scale, 1.0 * scale, 1.0 * scale, 3.0 * scale});
|
||||
const auto expected = MakeVector({1.25, -0.75});
|
||||
const auto rhs = matrix.Multiply(expected);
|
||||
|
||||
fesa::MklPardisoSolver solver;
|
||||
ASSERT_TRUE(solver.Factorize(matrix).IsOk()) << "scale=" << scale;
|
||||
fesa::Vector solution{2U};
|
||||
ASSERT_TRUE(solver.Solve(rhs, solution).IsOk()) << "scale=" << scale;
|
||||
EXPECT_LE(NormalizedResidual(matrix, solution, rhs), 1.0e-10);
|
||||
EXPECT_LE(RelativeError(solution, expected), 1.0e-9);
|
||||
}
|
||||
|
||||
const double resolved_ratio = 1.0e-8;
|
||||
const auto resolved_matrix =
|
||||
MakeDenseCsr(2U, {1.0, 0.0, 0.0, resolved_ratio});
|
||||
const auto resolved_expected = MakeVector({0.5, -2.0});
|
||||
const auto resolved_rhs = resolved_matrix.Multiply(resolved_expected);
|
||||
fesa::MklPardisoSolver resolved_solver;
|
||||
ASSERT_TRUE(resolved_solver.Factorize(resolved_matrix).IsOk());
|
||||
fesa::Vector resolved_solution{2U};
|
||||
ASSERT_TRUE(resolved_solver.Solve(resolved_rhs, resolved_solution).IsOk());
|
||||
EXPECT_LE(
|
||||
NormalizedResidual(resolved_matrix, resolved_solution, resolved_rhs),
|
||||
1.0e-10);
|
||||
EXPECT_LE(RelativeError(resolved_solution, resolved_expected), 1.0e-9);
|
||||
|
||||
const std::vector<double> unresolved_candidates{1.0e-16, 1.0e-300};
|
||||
for (const double ratio : unresolved_candidates) {
|
||||
const auto matrix = MakeDenseCsr(2U, {1.0, 0.0, 0.0, ratio});
|
||||
const auto expected = MakeVector({0.5, -2.0});
|
||||
const auto rhs = matrix.Multiply(expected);
|
||||
fesa::MklPardisoSolver solver;
|
||||
|
||||
const auto singular = makeDenseCsr(2U, {1.0, 1.0, 1.0, 1.0});
|
||||
const auto singularStatus = solver.factorize(singular);
|
||||
expectStructuredSolverFailure(singularStatus);
|
||||
EXPECT_TRUE(
|
||||
singularStatus.diagnostics()[0U].code ==
|
||||
"pardiso-zero-or-negative-pivot" ||
|
||||
singularStatus.diagnostics()[0U].code ==
|
||||
"pardiso-singular-diagonal");
|
||||
EXPECT_NE(
|
||||
singularStatus.diagnostics()[0U].entityIdentity.find(
|
||||
"phase=22,error="),
|
||||
std::string::npos);
|
||||
const auto factor_status = solver.Factorize(matrix);
|
||||
if (!factor_status.IsOk()) {
|
||||
ExpectStructuredSolverFailure(factor_status);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto indefinite = makeDenseCsr(2U, {1.0, 2.0, 2.0, 1.0});
|
||||
const auto indefiniteStatus = solver.factorize(indefinite);
|
||||
expectStructuredSolverFailure(indefiniteStatus);
|
||||
EXPECT_TRUE(
|
||||
indefiniteStatus.diagnostics()[0U].code ==
|
||||
"pardiso-zero-or-negative-pivot" ||
|
||||
indefiniteStatus.diagnostics()[0U].code ==
|
||||
"pardiso-singular-diagonal");
|
||||
EXPECT_NE(
|
||||
indefiniteStatus.diagnostics()[0U].entityIdentity.find(
|
||||
"phase=22,error="),
|
||||
std::string::npos);
|
||||
|
||||
const auto spd = makeDenseCsr(2U, {3.0, 1.0, 1.0, 2.0});
|
||||
ASSERT_TRUE(solver.factorize(spd).isOk());
|
||||
auto rhs = makeVector({1.0, 2.0});
|
||||
rhs[1U] = (std::numeric_limits<double>::infinity)();
|
||||
fesa::Vector solution{2U};
|
||||
solution[0U] = 23.0;
|
||||
solution[1U] = -9.0;
|
||||
const auto rhsStatus = solver.solve(rhs, solution);
|
||||
expectStructuredSolverFailure(rhsStatus);
|
||||
EXPECT_EQ(rhsStatus.diagnostics()[0U].code, "nonfinite-solver-rhs");
|
||||
EXPECT_DOUBLE_EQ(solution[0U], 23.0);
|
||||
EXPECT_DOUBLE_EQ(solution[1U], -9.0);
|
||||
|
||||
fesa::SparsePattern pattern{{0U, 1U}, {0U}};
|
||||
auto nonfiniteMatrix = fesa::SparseMatrix::fromCoo(
|
||||
1U,
|
||||
1U,
|
||||
{{0U,
|
||||
0U,
|
||||
(std::numeric_limits<double>::quiet_NaN)(),
|
||||
0U,
|
||||
0U}},
|
||||
pattern);
|
||||
EXPECT_FALSE(nonfiniteMatrix.hasValue());
|
||||
EXPECT_EQ(
|
||||
nonfiniteMatrix.status().diagnostics()[0U].code,
|
||||
"nonfinite-sparse-value");
|
||||
}
|
||||
|
||||
TEST(MklPardisoSolver, ConditioningSweepPassesResolvedCasesAndFailsUnresolvedCasesExplicitly) {
|
||||
const std::vector<double> commonScales{1.0e-12, 1.0, 1.0e12};
|
||||
for (const double scale : commonScales) {
|
||||
const auto matrix = makeDenseCsr(2U, {
|
||||
4.0 * scale, 1.0 * scale,
|
||||
1.0 * scale, 3.0 * scale});
|
||||
const auto expected = makeVector({1.25, -0.75});
|
||||
const auto rhs = matrix.multiply(expected);
|
||||
|
||||
fesa::MklPardisoSolver solver;
|
||||
ASSERT_TRUE(solver.factorize(matrix).isOk()) << "scale=" << scale;
|
||||
fesa::Vector solution{2U};
|
||||
ASSERT_TRUE(solver.solve(rhs, solution).isOk()) << "scale=" << scale;
|
||||
EXPECT_LE(normalizedResidual(matrix, solution, rhs), 1.0e-10);
|
||||
EXPECT_LE(relativeError(solution, expected), 1.0e-9);
|
||||
const auto solve_status = solver.Solve(rhs, solution);
|
||||
if (!solve_status.IsOk()) {
|
||||
ExpectStructuredSolverFailure(solve_status);
|
||||
continue;
|
||||
}
|
||||
|
||||
const double resolvedRatio = 1.0e-8;
|
||||
const auto resolvedMatrix = makeDenseCsr(
|
||||
2U, {1.0, 0.0, 0.0, resolvedRatio});
|
||||
const auto resolvedExpected = makeVector({0.5, -2.0});
|
||||
const auto resolvedRhs = resolvedMatrix.multiply(resolvedExpected);
|
||||
fesa::MklPardisoSolver resolvedSolver;
|
||||
ASSERT_TRUE(resolvedSolver.factorize(resolvedMatrix).isOk());
|
||||
fesa::Vector resolvedSolution{2U};
|
||||
ASSERT_TRUE(resolvedSolver.solve(resolvedRhs, resolvedSolution).isOk());
|
||||
EXPECT_LE(
|
||||
normalizedResidual(resolvedMatrix, resolvedSolution, resolvedRhs),
|
||||
1.0e-10);
|
||||
EXPECT_LE(relativeError(resolvedSolution, resolvedExpected), 1.0e-9);
|
||||
|
||||
const std::vector<double> unresolvedCandidates{1.0e-16, 1.0e-300};
|
||||
for (const double ratio : unresolvedCandidates) {
|
||||
const auto matrix = makeDenseCsr(2U, {1.0, 0.0, 0.0, ratio});
|
||||
const auto expected = makeVector({0.5, -2.0});
|
||||
const auto rhs = matrix.multiply(expected);
|
||||
fesa::MklPardisoSolver solver;
|
||||
|
||||
const auto factorStatus = solver.factorize(matrix);
|
||||
if (!factorStatus.isOk()) {
|
||||
expectStructuredSolverFailure(factorStatus);
|
||||
continue;
|
||||
}
|
||||
|
||||
fesa::Vector solution{2U};
|
||||
const auto solveStatus = solver.solve(rhs, solution);
|
||||
if (!solveStatus.isOk()) {
|
||||
expectStructuredSolverFailure(solveStatus);
|
||||
continue;
|
||||
}
|
||||
|
||||
EXPECT_LE(normalizedResidual(matrix, solution, rhs), 1.0e-10);
|
||||
EXPECT_LE(relativeError(solution, expected), 1.0e-9);
|
||||
}
|
||||
EXPECT_LE(NormalizedResidual(matrix, solution, rhs), 1.0e-10);
|
||||
EXPECT_LE(RelativeError(solution, expected), 1.0e-9);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user