#include "fesa/solvers/linear/mkl_pardiso_solver.hpp" #include "fesa/fem/dof_manager.hpp" #include "fesa/math/sparse_matrix.hpp" #include #include #include #include #include #include #include namespace { fesa::SparseMatrix makeDenseCsr( const std::size_t size, const std::vector& values) { EXPECT_EQ(values.size(), size * size); fesa::SparsePattern pattern; std::vector 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()); } fesa::Vector makeVector(const std::initializer_list 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::infinity)(); } if (denominator == 0.0) { return numerator == 0.0 ? 0.0 : (std::numeric_limits::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::infinity)(); } if (denominator == 0.0) { return numerator == 0.0 ? 0.0 : (std::numeric_limits::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()); } } // 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); fesa::MklPardisoSolver concreteSolver; fesa::LinearSolver& solver = concreteSolver; 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); } 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); 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()); 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); } 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}); 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); 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); } TEST(MklPardisoSolver, ClassifiesSingularIndefiniteAndNonfiniteFailures) { 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 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::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::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 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 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 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); } }