Integrate Refactor solver sources and dual MSVC CMake builds
This commit is contained in:
@@ -355,11 +355,12 @@ bool FillInitialBasis(std::vector<double>& vectors, const std::size_t n,
|
||||
}
|
||||
for (double& value : reflector) value /= reflector_norm;
|
||||
for (std::size_t target = column; target < columns; ++target) {
|
||||
const auto length = static_cast<MKL_INT>(n - column);
|
||||
const std::size_t length = n - column;
|
||||
auto* target_column = vectors.data() + column + target * n;
|
||||
const double dot = cblas_ddot(length, reflector.data(), 1,
|
||||
target_column, 1);
|
||||
cblas_daxpy(length, -2.0 * dot, reflector.data(), 1, target_column, 1);
|
||||
const double dot = dense_blas_internal::DotValues(
|
||||
reflector.data(), length, target_column);
|
||||
dense_blas_internal::AxpyValues(-2.0 * dot, reflector.data(), length,
|
||||
target_column);
|
||||
}
|
||||
reflectors.push_back(std::move(reflector));
|
||||
}
|
||||
@@ -370,11 +371,12 @@ bool FillInitialBasis(std::vector<double>& vectors, const std::size_t n,
|
||||
const std::size_t column = index - 1U;
|
||||
const auto& reflector = reflectors[column];
|
||||
for (std::size_t target = 0U; target < columns; ++target) {
|
||||
const auto length = static_cast<MKL_INT>(n - column);
|
||||
const std::size_t length = n - column;
|
||||
auto* target_column = vectors.data() + column + target * n;
|
||||
const double dot = cblas_ddot(length, reflector.data(), 1,
|
||||
target_column, 1);
|
||||
cblas_daxpy(length, -2.0 * dot, reflector.data(), 1, target_column, 1);
|
||||
const double dot = dense_blas_internal::DotValues(
|
||||
reflector.data(), length, target_column);
|
||||
dense_blas_internal::AxpyValues(-2.0 * dot, reflector.data(), length,
|
||||
target_column);
|
||||
}
|
||||
}
|
||||
for (std::size_t column = 0U; column < columns; ++column)
|
||||
@@ -383,9 +385,8 @@ bool FillInitialBasis(std::vector<double>& vectors, const std::size_t n,
|
||||
double gram_error = 0.0;
|
||||
for (std::size_t left = 0U; left < columns; ++left) {
|
||||
for (std::size_t right = 0U; right < columns; ++right) {
|
||||
const double dot = cblas_ddot(static_cast<MKL_INT>(n),
|
||||
vectors.data() + left * n, 1,
|
||||
vectors.data() + right * n, 1);
|
||||
const double dot = dense_blas_internal::DotValues(
|
||||
vectors.data() + left * n, n, vectors.data() + right * n);
|
||||
gram_error = std::hypot(gram_error, dot - (left == right ? 1.0 : 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,339 +1,15 @@
|
||||
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
||||
|
||||
#include <mkl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "fesa/math/vector.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "solvers/linear/pardiso_session.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
/// @brief Builds a structured linear-solver failure.
|
||||
Status SolverFailure(const std::string& code, const std::string& identity,
|
||||
const std::string& message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kSolver,
|
||||
{{Severity::kError, code, {{}, 0U}, "PARDISO", identity, message}});
|
||||
}
|
||||
|
||||
/// @brief Translates a PARDISO phase error into the stable solver taxonomy.
|
||||
Status PardisoFailure(const MKL_INT phase, const MKL_INT error) {
|
||||
std::string code;
|
||||
std::string reason;
|
||||
switch (error) {
|
||||
case -4:
|
||||
code = "pardiso-zero-or-negative-pivot";
|
||||
reason = "zero or negative pivot";
|
||||
break;
|
||||
case -7:
|
||||
code = "pardiso-singular-diagonal";
|
||||
reason = "singular diagonal";
|
||||
break;
|
||||
case -8:
|
||||
code = "pardiso-integer-overflow";
|
||||
reason = "32-bit backend integer overflow";
|
||||
break;
|
||||
case 21:
|
||||
case 22:
|
||||
case 23:
|
||||
case 24:
|
||||
code = "pardiso-invalid-csr";
|
||||
reason = "matrix checker rejected the CSR indices";
|
||||
break;
|
||||
default:
|
||||
code = phase == 11 ? "pardiso-analysis-failed"
|
||||
: phase == 22 ? "pardiso-factorization-failed"
|
||||
: phase == 33 ? "pardiso-solve-failed"
|
||||
: "pardiso-release-failed";
|
||||
reason = "backend error";
|
||||
break;
|
||||
}
|
||||
|
||||
const std::string phase_text = std::to_string(phase);
|
||||
const std::string error_text = std::to_string(error);
|
||||
return SolverFailure(code, "phase=" + phase_text + ",error=" + error_text,
|
||||
"oneMKL PARDISO phase " + phase_text +
|
||||
" failed with error " + error_text + " (" + reason +
|
||||
").");
|
||||
}
|
||||
|
||||
/// @brief Reports whether a public size fits the private MKL integer type.
|
||||
bool ConvertsToMklInt(const std::size_t value) {
|
||||
return value <=
|
||||
static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class MklPardisoSolver::Impl {
|
||||
public:
|
||||
Impl() = default;
|
||||
|
||||
~Impl() { static_cast<void>(Release()); }
|
||||
|
||||
/// @brief Validates and factorizes one public CSR matrix.
|
||||
Status Factorize(const SparseMatrix& matrix) {
|
||||
const MKL_INT release_error = Release();
|
||||
if (release_error != 0) {
|
||||
return PardisoFailure(-1, release_error);
|
||||
}
|
||||
|
||||
const Status csr_status = matrix.Validate();
|
||||
if (!csr_status.IsOk()) {
|
||||
return SolverFailure("solver-invalid-csr", "public-csr",
|
||||
"The public sparse matrix failed CSR validation.");
|
||||
}
|
||||
if (matrix.Rows() != matrix.Columns()) {
|
||||
return SolverFailure("solver-matrix-not-square", "matrix-shape",
|
||||
"PARDISO factorization requires a square matrix.");
|
||||
}
|
||||
if (matrix.Rows() == 0U) {
|
||||
// A fully constrained model has no free equations. Preserve the
|
||||
// observable factorize/solve lifecycle without creating backend
|
||||
// state or calling PARDISO with its invalid n=0 input.
|
||||
factorized_ = true;
|
||||
return Status::Ok();
|
||||
}
|
||||
if (!ConvertsToMklInt(matrix.Rows()) ||
|
||||
!ConvertsToMklInt(matrix.Values().size())) {
|
||||
return SolverFailure(
|
||||
"solver-dimension-overflow", "matrix-shape",
|
||||
"Sparse matrix dimensions exceed the oneMKL integer range.");
|
||||
}
|
||||
|
||||
const Status copy_status = CopyValidatedUpperTriangle(matrix);
|
||||
if (!copy_status.IsOk()) {
|
||||
ClearOwnedArrays();
|
||||
return copy_status;
|
||||
}
|
||||
|
||||
// PARDISO owns internal memory behind pt after phase 11. Initialize
|
||||
// once per factorization and retain it until refactorization/destruction.
|
||||
pt_.fill(nullptr);
|
||||
iparm_.fill(0);
|
||||
pardisoinit(pt_.data(), &mtype_, iparm_.data());
|
||||
iparm_[26] = 1; // Validate sorted CSR integer arrays.
|
||||
iparm_[34] = 1; // Consume the project's native zero-based CSR.
|
||||
permutation_.assign(static_cast<std::size_t>(equation_count_), 0);
|
||||
owns_pardiso_state_ = true;
|
||||
|
||||
MKL_INT phase = 11;
|
||||
MKL_INT error = 0;
|
||||
CallPardiso(phase, nullptr, nullptr, error);
|
||||
if (error != 0) {
|
||||
const Status failure = PardisoFailure(phase, error);
|
||||
static_cast<void>(Release());
|
||||
return failure;
|
||||
}
|
||||
|
||||
phase = 22;
|
||||
error = 0;
|
||||
CallPardiso(phase, nullptr, nullptr, error);
|
||||
if (error != 0) {
|
||||
const Status failure = PardisoFailure(phase, error);
|
||||
static_cast<void>(Release());
|
||||
return failure;
|
||||
}
|
||||
|
||||
factorized_ = true;
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
/// @brief Substitutes one RHS while preserving solution on failure.
|
||||
Status Solve(const Vector& rhs, Vector& solution) {
|
||||
if (!factorized_) {
|
||||
return SolverFailure(
|
||||
"solver-not-factorized", "factorization-state",
|
||||
"Substitution requires a successful retained factorization.");
|
||||
}
|
||||
const std::size_t size = static_cast<std::size_t>(equation_count_);
|
||||
if (rhs.Size() != size || solution.Size() != size) {
|
||||
return SolverFailure(
|
||||
"solver-vector-dimension-mismatch", "rhs-or-solution",
|
||||
"RHS and solution dimensions must match the factorized matrix.");
|
||||
}
|
||||
for (std::size_t index = 0U; index < rhs.Size(); ++index) {
|
||||
if (!std::isfinite(rhs[index])) {
|
||||
return SolverFailure("nonfinite-solver-rhs", std::to_string(index),
|
||||
"PARDISO RHS values must be finite.");
|
||||
}
|
||||
}
|
||||
if (size == 0U) {
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
std::vector<double> rhs_copy(rhs.Data(), rhs.Data() + rhs.Size());
|
||||
Vector candidate{size};
|
||||
MKL_INT phase = 33;
|
||||
MKL_INT error = 0;
|
||||
CallPardiso(phase, rhs_copy.data(), candidate.Data(), error);
|
||||
if (error != 0) {
|
||||
return PardisoFailure(phase, error);
|
||||
}
|
||||
for (std::size_t index = 0U; index < candidate.Size(); ++index) {
|
||||
if (!std::isfinite(candidate[index])) {
|
||||
return SolverFailure(
|
||||
"nonfinite-solver-solution", std::to_string(index),
|
||||
"PARDISO substitution produced a nonfinite solution.");
|
||||
}
|
||||
}
|
||||
|
||||
solution = std::move(candidate);
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief Validates symmetry and copies the upper triangle for PARDISO.
|
||||
Status CopyValidatedUpperTriangle(const SparseMatrix& matrix) {
|
||||
const auto& public_offsets = matrix.RowOffsets();
|
||||
const auto& public_columns = matrix.ColumnIndices();
|
||||
const auto& public_values = matrix.Values();
|
||||
|
||||
double matrix_scale = 0.0;
|
||||
for (const double value : public_values) {
|
||||
matrix_scale = (std::max)(matrix_scale, std::abs(value));
|
||||
}
|
||||
|
||||
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
|
||||
for (std::size_t position = public_offsets[row];
|
||||
position < public_offsets[row + 1U]; ++position) {
|
||||
const std::size_t column = public_columns[position];
|
||||
const auto reverse_begin =
|
||||
public_columns.begin() +
|
||||
static_cast<std::ptrdiff_t>(public_offsets[column]);
|
||||
const auto reverse_end =
|
||||
public_columns.begin() +
|
||||
static_cast<std::ptrdiff_t>(public_offsets[column + 1U]);
|
||||
const auto reverse = std::lower_bound(reverse_begin, reverse_end, row);
|
||||
if (reverse == reverse_end || *reverse != row) {
|
||||
return SolverFailure(
|
||||
"solver-matrix-not-symmetric",
|
||||
std::to_string(row) + ":" + std::to_string(column),
|
||||
"The full public CSR must contain both symmetric entries.");
|
||||
}
|
||||
|
||||
const std::size_t reverse_position = static_cast<std::size_t>(
|
||||
std::distance(public_columns.begin(), reverse));
|
||||
const double left = public_values[position];
|
||||
const double right = public_values[reverse_position];
|
||||
const double difference = std::abs(left - right);
|
||||
// The approved symmetry test is normalized by the matrix's
|
||||
// actual nonzero scale, without an absolute unit-size floor.
|
||||
const bool is_symmetric = matrix_scale == 0.0
|
||||
? difference == 0.0
|
||||
: difference <= 1.0e-12 * matrix_scale;
|
||||
if (!is_symmetric) {
|
||||
return SolverFailure(
|
||||
"solver-matrix-not-symmetric",
|
||||
std::to_string(row) + ":" + std::to_string(column),
|
||||
"The full public CSR values violate the approved symmetry "
|
||||
"tolerance.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
equation_count_ = static_cast<MKL_INT>(matrix.Rows());
|
||||
row_offsets_.clear();
|
||||
column_indices_.clear();
|
||||
values_.clear();
|
||||
row_offsets_.reserve(matrix.Rows() + 1U);
|
||||
row_offsets_.push_back(0);
|
||||
|
||||
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
|
||||
bool has_diagonal = false;
|
||||
for (std::size_t position = public_offsets[row];
|
||||
position < public_offsets[row + 1U]; ++position) {
|
||||
const std::size_t column = public_columns[position];
|
||||
if (column < row) {
|
||||
continue;
|
||||
}
|
||||
if (!ConvertsToMklInt(column) ||
|
||||
!ConvertsToMklInt(column_indices_.size())) {
|
||||
return SolverFailure(
|
||||
"solver-dimension-overflow",
|
||||
std::to_string(row) + ":" + std::to_string(column),
|
||||
"CSR indices exceed the oneMKL integer range.");
|
||||
}
|
||||
has_diagonal = has_diagonal || column == row;
|
||||
column_indices_.push_back(static_cast<MKL_INT>(column));
|
||||
values_.push_back(public_values[position]);
|
||||
}
|
||||
if (!has_diagonal) {
|
||||
return SolverFailure(
|
||||
"solver-missing-diagonal", std::to_string(row),
|
||||
"Every PARDISO SPD row must retain its diagonal slot.");
|
||||
}
|
||||
if (!ConvertsToMklInt(column_indices_.size())) {
|
||||
return SolverFailure(
|
||||
"solver-dimension-overflow", std::to_string(row),
|
||||
"CSR row offsets exceed the oneMKL integer range.");
|
||||
}
|
||||
row_offsets_.push_back(static_cast<MKL_INT>(column_indices_.size()));
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
/// @brief Calls PARDISO with retained private arrays and phase state.
|
||||
void CallPardiso(const MKL_INT phase, double* rhs, double* solution,
|
||||
MKL_INT& error) {
|
||||
const dense_blas_internal::ScopedSingleEquationThreads thread_scope{
|
||||
static_cast<std::size_t>(equation_count_)};
|
||||
pardiso(pt_.data(), &max_factorizations_, &matrix_number_, &mtype_, &phase,
|
||||
&equation_count_, values_.data(), row_offsets_.data(),
|
||||
column_indices_.data(), permutation_.data(), &rhs_count_,
|
||||
iparm_.data(), &message_level_, rhs, solution, &error);
|
||||
}
|
||||
|
||||
/// @brief Releases backend state and resets every retained array.
|
||||
MKL_INT Release() noexcept {
|
||||
MKL_INT error = 0;
|
||||
if (owns_pardiso_state_) {
|
||||
const MKL_INT phase = -1;
|
||||
double placeholder = 0.0;
|
||||
CallPardiso(phase, &placeholder, &placeholder, error);
|
||||
}
|
||||
owns_pardiso_state_ = false;
|
||||
factorized_ = false;
|
||||
pt_.fill(nullptr);
|
||||
iparm_.fill(0);
|
||||
permutation_.clear();
|
||||
ClearOwnedArrays();
|
||||
return error;
|
||||
}
|
||||
|
||||
/// @brief Clears FESA-owned CSR arrays without touching backend state.
|
||||
void ClearOwnedArrays() noexcept {
|
||||
equation_count_ = 0;
|
||||
row_offsets_.clear();
|
||||
column_indices_.clear();
|
||||
values_.clear();
|
||||
}
|
||||
|
||||
std::array<void*, 64U> pt_{};
|
||||
std::array<MKL_INT, 64U> iparm_{};
|
||||
std::vector<MKL_INT> row_offsets_;
|
||||
std::vector<MKL_INT> column_indices_;
|
||||
std::vector<MKL_INT> permutation_;
|
||||
std::vector<double> values_;
|
||||
MKL_INT equation_count_{0};
|
||||
MKL_INT max_factorizations_{1};
|
||||
MKL_INT matrix_number_{1};
|
||||
MKL_INT mtype_{2};
|
||||
MKL_INT rhs_count_{1};
|
||||
MKL_INT message_level_{0};
|
||||
bool owns_pardiso_state_{false};
|
||||
bool factorized_{false};
|
||||
pardiso_internal::PardisoSession session_{
|
||||
pardiso_internal::PardisoMatrixPolicy::kSpdUpper};
|
||||
};
|
||||
|
||||
MklPardisoSolver::MklPardisoSolver() : impl_{std::make_unique<Impl>()} {}
|
||||
@@ -341,11 +17,11 @@ MklPardisoSolver::MklPardisoSolver() : impl_{std::make_unique<Impl>()} {}
|
||||
MklPardisoSolver::~MklPardisoSolver() = default;
|
||||
|
||||
Status MklPardisoSolver::Factorize(const SparseMatrix& matrix) {
|
||||
return impl_->Factorize(matrix);
|
||||
return impl_->session_.Factorize(matrix);
|
||||
}
|
||||
|
||||
Status MklPardisoSolver::Solve(const Vector& rhs, Vector& solution) const {
|
||||
return impl_->Solve(rhs, solution);
|
||||
return impl_->session_.Solve(rhs, solution);
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
@@ -1,297 +1,27 @@
|
||||
#include "fesa/solvers/linear/mkl_pardiso_tangent_solver.h"
|
||||
|
||||
#include <mkl.h>
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "fesa/math/vector.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "solvers/linear/pardiso_session.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
/// @brief Builds a structured nonlinear tangent-solver failure.
|
||||
Status SolverFailure(const std::string& code, const std::string& identity,
|
||||
const std::string& message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kSolver,
|
||||
{{Severity::kError, code, {{}, 0U}, "PARDISO", identity, message}});
|
||||
}
|
||||
|
||||
/// @brief Translates a PARDISO phase error into the stable solver taxonomy.
|
||||
Status PardisoFailure(const MKL_INT phase, const MKL_INT error) {
|
||||
std::string code;
|
||||
std::string reason;
|
||||
switch (error) {
|
||||
case -4:
|
||||
code = "pardiso-zero-or-negative-pivot";
|
||||
reason = "zero or negative pivot";
|
||||
break;
|
||||
case -7:
|
||||
code = "pardiso-singular-diagonal";
|
||||
reason = "singular diagonal";
|
||||
break;
|
||||
case -8:
|
||||
code = "pardiso-integer-overflow";
|
||||
reason = "32-bit backend integer overflow";
|
||||
break;
|
||||
case 21:
|
||||
case 22:
|
||||
case 23:
|
||||
case 24:
|
||||
code = "pardiso-invalid-csr";
|
||||
reason = "matrix checker rejected the CSR indices";
|
||||
break;
|
||||
default:
|
||||
code = phase == 11 ? "pardiso-analysis-failed"
|
||||
: phase == 22 ? "pardiso-factorization-failed"
|
||||
: phase == 33 ? "pardiso-solve-failed"
|
||||
: "pardiso-release-failed";
|
||||
reason = "backend error";
|
||||
break;
|
||||
}
|
||||
|
||||
const std::string phase_text = std::to_string(phase);
|
||||
const std::string error_text = std::to_string(error);
|
||||
return SolverFailure(code, "phase=" + phase_text + ",error=" + error_text,
|
||||
"oneMKL PARDISO phase " + phase_text +
|
||||
" failed with error " + error_text + " (" + reason +
|
||||
").");
|
||||
}
|
||||
|
||||
/// @brief Reports whether a public size fits the private MKL integer type.
|
||||
bool ConvertsToMklInt(const std::size_t value) {
|
||||
return value <=
|
||||
static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class MklPardisoTangentSolver::Impl {
|
||||
public:
|
||||
Impl() = default;
|
||||
|
||||
~Impl() { static_cast<void>(Release()); }
|
||||
|
||||
/// @brief Replaces any retained state with one validated full-CSR tangent.
|
||||
Status FactorizeFresh(const SparseMatrix& matrix) {
|
||||
const MKL_INT release_error = Release();
|
||||
if (release_error != 0) {
|
||||
return PardisoFailure(-1, release_error);
|
||||
}
|
||||
|
||||
const Status csr_status = matrix.Validate();
|
||||
if (!csr_status.IsOk()) {
|
||||
return SolverFailure("solver-invalid-csr", "public-csr",
|
||||
"The public sparse matrix failed CSR validation.");
|
||||
}
|
||||
if (matrix.Rows() != matrix.Columns()) {
|
||||
return SolverFailure("solver-matrix-not-square", "matrix-shape",
|
||||
"PARDISO factorization requires a square matrix.");
|
||||
}
|
||||
if (matrix.Rows() == 0U) {
|
||||
// PARDISO does not accept n=0, but a fully constrained nonlinear
|
||||
// correction remains a valid factorize/solve lifecycle.
|
||||
factorized_ = true;
|
||||
return Status::Ok();
|
||||
}
|
||||
if (!ConvertsToMklInt(matrix.Rows()) ||
|
||||
!ConvertsToMklInt(matrix.Values().size())) {
|
||||
return SolverFailure(
|
||||
"solver-dimension-overflow", "matrix-shape",
|
||||
"Sparse matrix dimensions exceed the oneMKL integer range.");
|
||||
}
|
||||
|
||||
const Status copy_status = CopyValidatedFullCsr(matrix);
|
||||
if (!copy_status.IsOk()) {
|
||||
ClearOwnedArrays();
|
||||
return copy_status;
|
||||
}
|
||||
|
||||
// The nonlinear tangent is generally nonsymmetric, so PARDISO consumes
|
||||
// the complete public CSR with mtype=11 instead of an SPD triangle.
|
||||
pt_.fill(nullptr);
|
||||
iparm_.fill(0);
|
||||
pardisoinit(pt_.data(), &mtype_, iparm_.data());
|
||||
iparm_[26] = 1; // Validate sorted CSR integer arrays.
|
||||
iparm_[34] = 1; // Consume the project's native zero-based CSR.
|
||||
permutation_.assign(static_cast<std::size_t>(equation_count_), 0);
|
||||
owns_pardiso_state_ = true;
|
||||
|
||||
MKL_INT phase = 11;
|
||||
MKL_INT error = 0;
|
||||
CallPardiso(phase, nullptr, nullptr, error);
|
||||
if (error != 0) {
|
||||
const Status failure = PardisoFailure(phase, error);
|
||||
static_cast<void>(Release());
|
||||
return failure;
|
||||
}
|
||||
|
||||
phase = 22;
|
||||
error = 0;
|
||||
CallPardiso(phase, nullptr, nullptr, error);
|
||||
if (error != 0) {
|
||||
const Status failure = PardisoFailure(phase, error);
|
||||
static_cast<void>(Release());
|
||||
return failure;
|
||||
}
|
||||
if (iparm_[13] != 0) {
|
||||
const Status failure = SolverFailure(
|
||||
"pardiso-perturbed-pivot", "phase=22",
|
||||
"oneMKL PARDISO perturbed one or more tangent pivots.");
|
||||
static_cast<void>(Release());
|
||||
return failure;
|
||||
}
|
||||
|
||||
factorized_ = true;
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
/// @brief Solves one correction while preserving caller output on failure.
|
||||
Status SolveCorrection(const Vector& rhs, Vector& correction) {
|
||||
if (!factorized_) {
|
||||
return SolverFailure(
|
||||
"solver-not-factorized", "factorization-state",
|
||||
"Substitution requires a successful retained factorization.");
|
||||
}
|
||||
const std::size_t size = static_cast<std::size_t>(equation_count_);
|
||||
if (rhs.Size() != size || correction.Size() != size) {
|
||||
return SolverFailure(
|
||||
"solver-vector-dimension-mismatch", "rhs-or-correction",
|
||||
"RHS and correction dimensions must match the factorized matrix.");
|
||||
}
|
||||
for (std::size_t index = 0U; index < rhs.Size(); ++index) {
|
||||
if (!std::isfinite(rhs[index])) {
|
||||
return SolverFailure("nonfinite-solver-rhs", std::to_string(index),
|
||||
"PARDISO RHS values must be finite.");
|
||||
}
|
||||
}
|
||||
if (size == 0U) {
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
std::vector<double> rhs_copy(rhs.Data(), rhs.Data() + rhs.Size());
|
||||
Vector candidate{size};
|
||||
MKL_INT phase = 33;
|
||||
MKL_INT error = 0;
|
||||
CallPardiso(phase, rhs_copy.data(), candidate.Data(), error);
|
||||
if (error != 0) {
|
||||
return PardisoFailure(phase, error);
|
||||
}
|
||||
for (std::size_t index = 0U; index < candidate.Size(); ++index) {
|
||||
if (!std::isfinite(candidate[index])) {
|
||||
return SolverFailure(
|
||||
"nonfinite-solver-solution", std::to_string(index),
|
||||
"PARDISO substitution produced a nonfinite solution.");
|
||||
}
|
||||
}
|
||||
|
||||
correction = std::move(candidate);
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief Copies validated public CSR without symmetry or diagonal filtering.
|
||||
Status CopyValidatedFullCsr(const SparseMatrix& matrix) {
|
||||
equation_count_ = static_cast<MKL_INT>(matrix.Rows());
|
||||
row_offsets_.clear();
|
||||
column_indices_.clear();
|
||||
values_.clear();
|
||||
row_offsets_.reserve(matrix.RowOffsets().size());
|
||||
column_indices_.reserve(matrix.ColumnIndices().size());
|
||||
values_.reserve(matrix.Values().size());
|
||||
|
||||
for (const std::size_t offset : matrix.RowOffsets()) {
|
||||
if (!ConvertsToMklInt(offset)) {
|
||||
return SolverFailure("solver-dimension-overflow", "row-offsets",
|
||||
"CSR row offsets exceed the oneMKL integer "
|
||||
"range.");
|
||||
}
|
||||
row_offsets_.push_back(static_cast<MKL_INT>(offset));
|
||||
}
|
||||
for (const std::size_t column : matrix.ColumnIndices()) {
|
||||
if (!ConvertsToMklInt(column)) {
|
||||
return SolverFailure("solver-dimension-overflow", "column-indices",
|
||||
"CSR indices exceed the oneMKL integer range.");
|
||||
}
|
||||
column_indices_.push_back(static_cast<MKL_INT>(column));
|
||||
}
|
||||
values_ = matrix.Values();
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
/// @brief Calls PARDISO with retained private arrays and phase state.
|
||||
void CallPardiso(const MKL_INT phase, double* rhs, double* correction,
|
||||
MKL_INT& error) {
|
||||
const dense_blas_internal::ScopedSingleEquationThreads thread_scope{
|
||||
static_cast<std::size_t>(equation_count_)};
|
||||
pardiso(pt_.data(), &max_factorizations_, &matrix_number_, &mtype_, &phase,
|
||||
&equation_count_, values_.data(), row_offsets_.data(),
|
||||
column_indices_.data(), permutation_.data(), &rhs_count_,
|
||||
iparm_.data(), &message_level_, rhs, correction, &error);
|
||||
}
|
||||
|
||||
/// @brief Releases backend state and resets every retained array.
|
||||
MKL_INT Release() noexcept {
|
||||
MKL_INT error = 0;
|
||||
if (owns_pardiso_state_) {
|
||||
const MKL_INT phase = -1;
|
||||
double placeholder = 0.0;
|
||||
CallPardiso(phase, &placeholder, &placeholder, error);
|
||||
}
|
||||
owns_pardiso_state_ = false;
|
||||
factorized_ = false;
|
||||
pt_.fill(nullptr);
|
||||
iparm_.fill(0);
|
||||
permutation_.clear();
|
||||
ClearOwnedArrays();
|
||||
return error;
|
||||
}
|
||||
|
||||
/// @brief Clears FESA-owned CSR arrays without touching backend state.
|
||||
void ClearOwnedArrays() noexcept {
|
||||
equation_count_ = 0;
|
||||
row_offsets_.clear();
|
||||
column_indices_.clear();
|
||||
values_.clear();
|
||||
}
|
||||
|
||||
std::array<void*, 64U> pt_{};
|
||||
std::array<MKL_INT, 64U> iparm_{};
|
||||
std::vector<MKL_INT> row_offsets_;
|
||||
std::vector<MKL_INT> column_indices_;
|
||||
std::vector<MKL_INT> permutation_;
|
||||
std::vector<double> values_;
|
||||
MKL_INT equation_count_{0};
|
||||
MKL_INT max_factorizations_{1};
|
||||
MKL_INT matrix_number_{1};
|
||||
MKL_INT mtype_{11};
|
||||
MKL_INT rhs_count_{1};
|
||||
MKL_INT message_level_{0};
|
||||
bool owns_pardiso_state_{false};
|
||||
bool factorized_{false};
|
||||
pardiso_internal::PardisoSession session_{
|
||||
pardiso_internal::PardisoMatrixPolicy::kUnsymmetricFull};
|
||||
};
|
||||
|
||||
MklPardisoTangentSolver::MklPardisoTangentSolver()
|
||||
: impl_{std::make_unique<Impl>()} {}
|
||||
MklPardisoTangentSolver::MklPardisoTangentSolver() : impl_{std::make_unique<Impl>()} {}
|
||||
|
||||
MklPardisoTangentSolver::~MklPardisoTangentSolver() = default;
|
||||
|
||||
Status MklPardisoTangentSolver::FactorizeFresh(const SparseMatrix& matrix) {
|
||||
return impl_->FactorizeFresh(matrix);
|
||||
return impl_->session_.Factorize(matrix);
|
||||
}
|
||||
|
||||
Status MklPardisoTangentSolver::SolveCorrection(const Vector& rhs,
|
||||
Vector& correction) const {
|
||||
return impl_->SolveCorrection(rhs, correction);
|
||||
Status MklPardisoTangentSolver::SolveCorrection(const Vector& rhs, Vector& solution) const {
|
||||
return impl_->session_.Solve(rhs, solution);
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
#include "solvers/linear/pardiso_session.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "fesa/math/vector.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "solvers/linear/pardiso_session_test_seam.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
thread_local internal::PardisoBackendTestHook* g_pardiso_backend_test_hook =
|
||||
nullptr;
|
||||
} // namespace
|
||||
|
||||
namespace internal {
|
||||
ScopedPardisoBackendTestHook::ScopedPardisoBackendTestHook(
|
||||
PardisoBackendTestHook hook)
|
||||
: hook_(std::move(hook)), previous_(g_pardiso_backend_test_hook) {
|
||||
g_pardiso_backend_test_hook = &hook_;
|
||||
}
|
||||
|
||||
ScopedPardisoBackendTestHook::~ScopedPardisoBackendTestHook() {
|
||||
g_pardiso_backend_test_hook = previous_;
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
namespace pardiso_internal {
|
||||
namespace {
|
||||
/// @brief Builds a structured linear-solver failure.
|
||||
Status SolverFailure(const std::string& code, const std::string& identity,
|
||||
const std::string& message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kSolver,
|
||||
{{Severity::kError, code, {{}, 0U}, "PARDISO", identity, message}});
|
||||
}
|
||||
|
||||
/// @brief Translates a PARDISO phase error into the stable solver taxonomy.
|
||||
Status PardisoFailure(const MKL_INT phase, const MKL_INT error) {
|
||||
std::string code;
|
||||
std::string reason;
|
||||
switch (error) {
|
||||
case -4:
|
||||
code = "pardiso-zero-or-negative-pivot";
|
||||
reason = "zero or negative pivot";
|
||||
break;
|
||||
case -7:
|
||||
code = "pardiso-singular-diagonal";
|
||||
reason = "singular diagonal";
|
||||
break;
|
||||
case -8:
|
||||
code = "pardiso-integer-overflow";
|
||||
reason = "32-bit backend integer overflow";
|
||||
break;
|
||||
case 21:
|
||||
case 22:
|
||||
case 23:
|
||||
case 24:
|
||||
code = "pardiso-invalid-csr";
|
||||
reason = "matrix checker rejected the CSR indices";
|
||||
break;
|
||||
default:
|
||||
code = phase == 11 ? "pardiso-analysis-failed"
|
||||
: phase == 22 ? "pardiso-factorization-failed"
|
||||
: phase == 33 ? "pardiso-solve-failed"
|
||||
: "pardiso-release-failed";
|
||||
reason = "backend error";
|
||||
break;
|
||||
}
|
||||
|
||||
const std::string phase_text = std::to_string(phase);
|
||||
const std::string error_text = std::to_string(error);
|
||||
return SolverFailure(code, "phase=" + phase_text + ",error=" + error_text,
|
||||
"oneMKL PARDISO phase " + phase_text +
|
||||
" failed with error " + error_text + " (" + reason +
|
||||
").");
|
||||
}
|
||||
|
||||
/// @brief Reports whether a public size fits the private MKL integer type.
|
||||
bool ConvertsToMklInt(const std::size_t value) {
|
||||
return dense_blas_internal::ToMklSize(value).HasValue();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PardisoSession::PardisoSession(const PardisoMatrixPolicy policy)
|
||||
: policy_(policy),
|
||||
mtype_(policy == PardisoMatrixPolicy::kSpdUpper ? 2 : 11) {}
|
||||
|
||||
PardisoSession::~PardisoSession() { static_cast<void>(Release()); }
|
||||
|
||||
Status PardisoSession::Factorize(const SparseMatrix& matrix) {
|
||||
const MKL_INT release_error = Release();
|
||||
if (release_error != 0) {
|
||||
return PardisoFailure(-1, release_error);
|
||||
}
|
||||
|
||||
const Status csr_status = matrix.Validate();
|
||||
if (!csr_status.IsOk()) {
|
||||
return SolverFailure("solver-invalid-csr", "public-csr",
|
||||
"The public sparse matrix failed CSR validation.");
|
||||
}
|
||||
if (matrix.Rows() != matrix.Columns()) {
|
||||
return SolverFailure("solver-matrix-not-square", "matrix-shape",
|
||||
"PARDISO factorization requires a square matrix.");
|
||||
}
|
||||
if (matrix.Rows() == 0U) {
|
||||
// A fully constrained model has no free equations. Preserve the
|
||||
// observable factorize/solve lifecycle without creating backend
|
||||
// state or calling PARDISO with its invalid n=0 input.
|
||||
factorized_ = true;
|
||||
return Status::Ok();
|
||||
}
|
||||
if (!ConvertsToMklInt(matrix.Rows()) ||
|
||||
!ConvertsToMklInt(matrix.Values().size())) {
|
||||
return SolverFailure(
|
||||
"solver-dimension-overflow", "matrix-shape",
|
||||
"Sparse matrix dimensions exceed the oneMKL integer range.");
|
||||
}
|
||||
|
||||
const Status copy_status =
|
||||
policy_ == PardisoMatrixPolicy::kSpdUpper
|
||||
? CopyValidatedUpperTriangle(matrix)
|
||||
: CopyValidatedFullCsr(matrix);
|
||||
if (!copy_status.IsOk()) {
|
||||
ClearOwnedArrays();
|
||||
return copy_status;
|
||||
}
|
||||
|
||||
// PARDISO owns internal memory behind pt after phase 11. Initialize
|
||||
// once per factorization and retain it until refactorization/destruction.
|
||||
pt_.fill(nullptr);
|
||||
iparm_.fill(0);
|
||||
pardisoinit(pt_.data(), &mtype_, iparm_.data());
|
||||
iparm_[26] = 1; // Validate sorted CSR integer arrays.
|
||||
iparm_[34] = 1; // Consume the project's native zero-based CSR.
|
||||
permutation_.assign(static_cast<std::size_t>(equation_count_), 0);
|
||||
owns_pardiso_state_ = true;
|
||||
|
||||
try {
|
||||
MKL_INT phase = 11;
|
||||
MKL_INT error = 0;
|
||||
CallPardiso(phase, nullptr, nullptr, error);
|
||||
if (error != 0) {
|
||||
const Status failure = PardisoFailure(phase, error);
|
||||
static_cast<void>(Release());
|
||||
return failure;
|
||||
}
|
||||
|
||||
phase = 22;
|
||||
error = 0;
|
||||
CallPardiso(phase, nullptr, nullptr, error);
|
||||
if (error != 0) {
|
||||
const Status failure = PardisoFailure(phase, error);
|
||||
static_cast<void>(Release());
|
||||
return failure;
|
||||
}
|
||||
|
||||
if (policy_ == PardisoMatrixPolicy::kUnsymmetricFull && iparm_[13] != 0) {
|
||||
const Status failure = SolverFailure(
|
||||
"pardiso-perturbed-pivot", "phase=22",
|
||||
"oneMKL PARDISO perturbed one or more tangent pivots.");
|
||||
static_cast<void>(Release());
|
||||
return failure;
|
||||
}
|
||||
factorized_ = true;
|
||||
return Status::Ok();
|
||||
} catch (...) {
|
||||
static_cast<void>(Release());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Status PardisoSession::Solve(const Vector& rhs, Vector& solution) {
|
||||
if (!factorized_) {
|
||||
return SolverFailure(
|
||||
"solver-not-factorized", "factorization-state",
|
||||
"Substitution requires a successful retained factorization.");
|
||||
}
|
||||
const std::size_t size = static_cast<std::size_t>(equation_count_);
|
||||
if (rhs.Size() != size || solution.Size() != size) {
|
||||
return SolverFailure(
|
||||
"solver-vector-dimension-mismatch",
|
||||
policy_ == PardisoMatrixPolicy::kSpdUpper ? "rhs-or-solution"
|
||||
: "rhs-or-correction",
|
||||
policy_ == PardisoMatrixPolicy::kSpdUpper
|
||||
? "RHS and solution dimensions must match the factorized matrix."
|
||||
: "RHS and correction dimensions must match the factorized matrix.");
|
||||
}
|
||||
for (std::size_t index = 0U; index < rhs.Size(); ++index) {
|
||||
if (!std::isfinite(rhs[index])) {
|
||||
return SolverFailure("nonfinite-solver-rhs", std::to_string(index),
|
||||
"PARDISO RHS values must be finite.");
|
||||
}
|
||||
}
|
||||
if (size == 0U) {
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
std::vector<double> rhs_copy(rhs.Size());
|
||||
dense_blas_internal::CopyValues(rhs.Data(), rhs.Size(), rhs_copy.data());
|
||||
Vector candidate{size};
|
||||
MKL_INT phase = 33;
|
||||
MKL_INT error = 0;
|
||||
CallPardiso(phase, rhs_copy.data(), candidate.Data(), error);
|
||||
if (error != 0) {
|
||||
return PardisoFailure(phase, error);
|
||||
}
|
||||
for (std::size_t index = 0U; index < candidate.Size(); ++index) {
|
||||
if (!std::isfinite(candidate[index])) {
|
||||
return SolverFailure(
|
||||
"nonfinite-solver-solution", std::to_string(index),
|
||||
"PARDISO substitution produced a nonfinite solution.");
|
||||
}
|
||||
}
|
||||
|
||||
solution = std::move(candidate);
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status PardisoSession::CopyValidatedUpperTriangle(const SparseMatrix& matrix) {
|
||||
const auto& public_offsets = matrix.RowOffsets();
|
||||
const auto& public_columns = matrix.ColumnIndices();
|
||||
const auto& public_values = matrix.Values();
|
||||
|
||||
double matrix_scale = 0.0;
|
||||
for (const double value : public_values) {
|
||||
matrix_scale = (std::max)(matrix_scale, std::abs(value));
|
||||
}
|
||||
|
||||
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
|
||||
for (std::size_t position = public_offsets[row];
|
||||
position < public_offsets[row + 1U]; ++position) {
|
||||
const std::size_t column = public_columns[position];
|
||||
const auto reverse_begin =
|
||||
public_columns.begin() +
|
||||
static_cast<std::ptrdiff_t>(public_offsets[column]);
|
||||
const auto reverse_end =
|
||||
public_columns.begin() +
|
||||
static_cast<std::ptrdiff_t>(public_offsets[column + 1U]);
|
||||
const auto reverse = std::lower_bound(reverse_begin, reverse_end, row);
|
||||
if (reverse == reverse_end || *reverse != row) {
|
||||
return SolverFailure(
|
||||
"solver-matrix-not-symmetric",
|
||||
std::to_string(row) + ":" + std::to_string(column),
|
||||
"The full public CSR must contain both symmetric entries.");
|
||||
}
|
||||
|
||||
const std::size_t reverse_position = static_cast<std::size_t>(
|
||||
std::distance(public_columns.begin(), reverse));
|
||||
const double left = public_values[position];
|
||||
const double right = public_values[reverse_position];
|
||||
const double difference = std::abs(left - right);
|
||||
// The approved symmetry test is normalized by the matrix's
|
||||
// actual nonzero scale, without an absolute unit-size floor.
|
||||
const bool is_symmetric = matrix_scale == 0.0
|
||||
? difference == 0.0
|
||||
: difference <= 1.0e-12 * matrix_scale;
|
||||
if (!is_symmetric) {
|
||||
return SolverFailure(
|
||||
"solver-matrix-not-symmetric",
|
||||
std::to_string(row) + ":" + std::to_string(column),
|
||||
"The full public CSR values violate the approved symmetry "
|
||||
"tolerance.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
equation_count_ = static_cast<MKL_INT>(matrix.Rows());
|
||||
row_offsets_.clear();
|
||||
column_indices_.clear();
|
||||
values_.clear();
|
||||
row_offsets_.reserve(matrix.Rows() + 1U);
|
||||
row_offsets_.push_back(0);
|
||||
|
||||
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
|
||||
bool has_diagonal = false;
|
||||
for (std::size_t position = public_offsets[row];
|
||||
position < public_offsets[row + 1U]; ++position) {
|
||||
const std::size_t column = public_columns[position];
|
||||
if (column < row) {
|
||||
continue;
|
||||
}
|
||||
if (!ConvertsToMklInt(column) ||
|
||||
!ConvertsToMklInt(column_indices_.size())) {
|
||||
return SolverFailure(
|
||||
"solver-dimension-overflow",
|
||||
std::to_string(row) + ":" + std::to_string(column),
|
||||
"CSR indices exceed the oneMKL integer range.");
|
||||
}
|
||||
has_diagonal = has_diagonal || column == row;
|
||||
column_indices_.push_back(static_cast<MKL_INT>(column));
|
||||
values_.push_back(public_values[position]);
|
||||
}
|
||||
if (!has_diagonal) {
|
||||
return SolverFailure(
|
||||
"solver-missing-diagonal", std::to_string(row),
|
||||
"Every PARDISO SPD row must retain its diagonal slot.");
|
||||
}
|
||||
if (!ConvertsToMklInt(column_indices_.size())) {
|
||||
return SolverFailure(
|
||||
"solver-dimension-overflow", std::to_string(row),
|
||||
"CSR row offsets exceed the oneMKL integer range.");
|
||||
}
|
||||
row_offsets_.push_back(static_cast<MKL_INT>(column_indices_.size()));
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status PardisoSession::CopyValidatedFullCsr(const SparseMatrix& matrix) {
|
||||
equation_count_ = static_cast<MKL_INT>(matrix.Rows());
|
||||
row_offsets_.clear();
|
||||
column_indices_.clear();
|
||||
values_.clear();
|
||||
row_offsets_.reserve(matrix.RowOffsets().size());
|
||||
column_indices_.reserve(matrix.ColumnIndices().size());
|
||||
values_.reserve(matrix.Values().size());
|
||||
|
||||
for (const std::size_t offset : matrix.RowOffsets()) {
|
||||
if (!ConvertsToMklInt(offset)) {
|
||||
return SolverFailure("solver-dimension-overflow", "row-offsets",
|
||||
"CSR row offsets exceed the oneMKL integer "
|
||||
"range.");
|
||||
}
|
||||
row_offsets_.push_back(static_cast<MKL_INT>(offset));
|
||||
}
|
||||
for (const std::size_t column : matrix.ColumnIndices()) {
|
||||
if (!ConvertsToMklInt(column)) {
|
||||
return SolverFailure("solver-dimension-overflow", "column-indices",
|
||||
"CSR indices exceed the oneMKL integer range.");
|
||||
}
|
||||
column_indices_.push_back(static_cast<MKL_INT>(column));
|
||||
}
|
||||
values_.resize(matrix.Values().size());
|
||||
dense_blas_internal::CopyValues(matrix.Values().data(), values_.size(),
|
||||
values_.data());
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
void PardisoSession::CallPardiso(const MKL_INT phase, double* rhs,
|
||||
double* solution, MKL_INT& error) {
|
||||
const dense_blas_internal::ScopedSingleEquationThreads thread_scope{
|
||||
static_cast<std::size_t>(equation_count_)};
|
||||
pardiso(pt_.data(), &max_factorizations_, &matrix_number_, &mtype_, &phase,
|
||||
&equation_count_, values_.data(), row_offsets_.data(),
|
||||
column_indices_.data(), permutation_.data(), &rhs_count_,
|
||||
iparm_.data(), &message_level_, rhs, solution, &error);
|
||||
if (g_pardiso_backend_test_hook != nullptr) {
|
||||
const internal::PardisoBackendTestCall call{
|
||||
phase, mtype_, equation_count_, row_offsets_.data(),
|
||||
column_indices_.data(), values_.data(), solution};
|
||||
(*g_pardiso_backend_test_hook)(call, error, iparm_[13]);
|
||||
}
|
||||
}
|
||||
|
||||
MKL_INT PardisoSession::Release() noexcept {
|
||||
MKL_INT error = 0;
|
||||
if (owns_pardiso_state_) {
|
||||
const MKL_INT phase = -1;
|
||||
double placeholder = 0.0;
|
||||
CallPardiso(phase, &placeholder, &placeholder, error);
|
||||
}
|
||||
owns_pardiso_state_ = false;
|
||||
factorized_ = false;
|
||||
pt_.fill(nullptr);
|
||||
iparm_.fill(0);
|
||||
permutation_.clear();
|
||||
ClearOwnedArrays();
|
||||
return error;
|
||||
}
|
||||
|
||||
void PardisoSession::ClearOwnedArrays() noexcept {
|
||||
equation_count_ = 0;
|
||||
row_offsets_.clear();
|
||||
column_indices_.clear();
|
||||
values_.clear();
|
||||
}
|
||||
|
||||
} // namespace pardiso_internal
|
||||
} // namespace fesa
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifndef FESA_SRC_FESA_SOLVERS_LINEAR_PARDISO_SESSION_H_
|
||||
#define FESA_SRC_FESA_SOLVERS_LINEAR_PARDISO_SESSION_H_
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include <mkl.h>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
|
||||
namespace fesa {
|
||||
class SparseMatrix;
|
||||
class Vector;
|
||||
|
||||
namespace pardiso_internal {
|
||||
|
||||
/// @brief Retains the two existing CSR and pivot policies.
|
||||
enum class PardisoMatrixPolicy { kSpdUpper, kUnsymmetricFull };
|
||||
|
||||
/// @brief Owns one backend factorization, CSR storage and phase lifecycle.
|
||||
/// @note No symbolic reuse is performed across Factorize calls.
|
||||
class PardisoSession final {
|
||||
public:
|
||||
explicit PardisoSession(PardisoMatrixPolicy policy);
|
||||
~PardisoSession();
|
||||
PardisoSession(const PardisoSession&) = delete;
|
||||
PardisoSession& operator=(const PardisoSession&) = delete;
|
||||
|
||||
Status Factorize(const SparseMatrix& matrix);
|
||||
Status Solve(const Vector& rhs, Vector& solution);
|
||||
|
||||
private:
|
||||
Status CopyValidatedUpperTriangle(const SparseMatrix& matrix);
|
||||
Status CopyValidatedFullCsr(const SparseMatrix& matrix);
|
||||
void CallPardiso(MKL_INT phase, double* rhs, double* solution,
|
||||
MKL_INT& error);
|
||||
MKL_INT Release() noexcept;
|
||||
void ClearOwnedArrays() noexcept;
|
||||
|
||||
std::array<void*, 64U> pt_{};
|
||||
std::array<MKL_INT, 64U> iparm_{};
|
||||
std::vector<MKL_INT> row_offsets_;
|
||||
std::vector<MKL_INT> column_indices_;
|
||||
std::vector<MKL_INT> permutation_;
|
||||
std::vector<double> values_;
|
||||
MKL_INT equation_count_{0};
|
||||
MKL_INT max_factorizations_{1};
|
||||
MKL_INT matrix_number_{1};
|
||||
const PardisoMatrixPolicy policy_;
|
||||
MKL_INT mtype_;
|
||||
MKL_INT rhs_count_{1};
|
||||
MKL_INT message_level_{0};
|
||||
bool owns_pardiso_state_{false};
|
||||
bool factorized_{false};
|
||||
};
|
||||
|
||||
} // namespace pardiso_internal
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_SRC_FESA_SOLVERS_LINEAR_PARDISO_SESSION_H_
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef FESA_SRC_FESA_SOLVERS_LINEAR_PARDISO_SESSION_TEST_SEAM_H_
|
||||
#define FESA_SRC_FESA_SOLVERS_LINEAR_PARDISO_SESSION_TEST_SEAM_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <mkl.h>
|
||||
|
||||
namespace fesa::internal {
|
||||
|
||||
/// @brief Borrows call storage only while a backend observation hook runs.
|
||||
struct PardisoBackendTestCall {
|
||||
MKL_INT phase;
|
||||
MKL_INT matrix_type;
|
||||
MKL_INT equation_count;
|
||||
const MKL_INT* row_offsets;
|
||||
const MKL_INT* column_indices;
|
||||
const double* values;
|
||||
double* solution;
|
||||
};
|
||||
|
||||
// Private fault seam runs after the real call, including real resource release.
|
||||
// It is thread-local and never replaces a backend allocation with fake state.
|
||||
using PardisoBackendTestHook = std::function<void(
|
||||
const PardisoBackendTestCall&, MKL_INT& error, MKL_INT& perturbed_pivots)>;
|
||||
|
||||
class ScopedPardisoBackendTestHook final {
|
||||
public:
|
||||
explicit ScopedPardisoBackendTestHook(PardisoBackendTestHook hook);
|
||||
~ScopedPardisoBackendTestHook();
|
||||
ScopedPardisoBackendTestHook(const ScopedPardisoBackendTestHook&) = delete;
|
||||
ScopedPardisoBackendTestHook& operator=(const ScopedPardisoBackendTestHook&) =
|
||||
delete;
|
||||
|
||||
private:
|
||||
PardisoBackendTestHook hook_;
|
||||
PardisoBackendTestHook* previous_;
|
||||
};
|
||||
|
||||
} // namespace fesa::internal
|
||||
|
||||
#endif // FESA_SRC_FESA_SOLVERS_LINEAR_PARDISO_SESSION_TEST_SEAM_H_
|
||||
Reference in New Issue
Block a user