feat: complete first MITC4 FEAST eigen implementation validation
This commit is contained in:
@@ -17,23 +17,23 @@
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "solvers/eigen/mkl_feast_eigen_solver_test_seam.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
namespace internal {
|
||||
namespace {
|
||||
|
||||
constexpr std::uint64_t kSeed = 0x4D495443344D4645ULL;
|
||||
constexpr std::size_t kMaximumCountCalls = 512U;
|
||||
|
||||
Status Failure(const std::string& code, const std::string& identity,
|
||||
const std::string& message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kSolver,
|
||||
{{Severity::kError, code, {{}, 0U}, "FEAST", identity, message}});
|
||||
thread_local FeastBackendTestHook* g_feast_backend_test_hook = nullptr;
|
||||
thread_local FeastWorkspaceTestHook* g_feast_workspace_test_hook = nullptr;
|
||||
}
|
||||
|
||||
bool FitsMkl(const std::size_t value) {
|
||||
return value <=
|
||||
static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)());
|
||||
bool CanCallFeast(const std::size_t used) {
|
||||
return used < kMaximumCountCalls;
|
||||
}
|
||||
|
||||
bool CanStartUpperCandidate(const std::size_t used) {
|
||||
return used <= 384U - 21U;
|
||||
}
|
||||
|
||||
bool ProductFits(const std::size_t left, const std::size_t right) {
|
||||
@@ -48,6 +48,78 @@ bool FitsDoubleStorage(const std::size_t count) {
|
||||
sizeof(double);
|
||||
}
|
||||
|
||||
ScopedFeastBackendTestHook::ScopedFeastBackendTestHook(
|
||||
FeastBackendTestHook hook)
|
||||
: hook_(std::move(hook)), previous_(g_feast_backend_test_hook) {
|
||||
g_feast_backend_test_hook = &hook_;
|
||||
}
|
||||
|
||||
ScopedFeastBackendTestHook::~ScopedFeastBackendTestHook() {
|
||||
g_feast_backend_test_hook = previous_;
|
||||
}
|
||||
|
||||
ScopedFeastWorkspaceTestHook::ScopedFeastWorkspaceTestHook(
|
||||
FeastWorkspaceTestHook hook)
|
||||
: hook_(std::move(hook)), previous_(g_feast_workspace_test_hook) {
|
||||
g_feast_workspace_test_hook = &hook_;
|
||||
}
|
||||
|
||||
ScopedFeastWorkspaceTestHook::~ScopedFeastWorkspaceTestHook() {
|
||||
g_feast_workspace_test_hook = previous_;
|
||||
}
|
||||
|
||||
void ApplyFeastWorkspaceTestHook(const bool count_workspace) {
|
||||
if (g_feast_workspace_test_hook != nullptr)
|
||||
(*g_feast_workspace_test_hook)(count_workspace);
|
||||
}
|
||||
|
||||
void ApplyFeastBackendTestHook(const FeastBackendTestCall& call, MKL_INT* m0,
|
||||
MKL_INT* returned, MKL_INT* loop,
|
||||
double* epsout, MKL_INT* info,
|
||||
double* eigenvalues,
|
||||
const std::size_t eigenvalue_count,
|
||||
double* vectors,
|
||||
const std::size_t vector_count,
|
||||
double* residuals,
|
||||
const std::size_t residual_count) {
|
||||
if (g_feast_backend_test_hook == nullptr) return;
|
||||
FeastBackendTestOutputs outputs{
|
||||
static_cast<int>(*info), static_cast<std::ptrdiff_t>(*m0),
|
||||
static_cast<std::ptrdiff_t>(*returned), static_cast<int>(*loop), *epsout,
|
||||
std::vector<double>(eigenvalues, eigenvalues + eigenvalue_count),
|
||||
std::vector<double>(vectors, vectors + vector_count),
|
||||
std::vector<double>(residuals, residuals + residual_count)};
|
||||
(*g_feast_backend_test_hook)(call, outputs);
|
||||
*info = static_cast<MKL_INT>(outputs.info);
|
||||
*m0 = static_cast<MKL_INT>(outputs.m0);
|
||||
*returned = static_cast<MKL_INT>(outputs.returned);
|
||||
*loop = static_cast<MKL_INT>(outputs.loop);
|
||||
*epsout = outputs.epsout;
|
||||
if (outputs.eigenvalues.size() == eigenvalue_count)
|
||||
std::copy(outputs.eigenvalues.begin(), outputs.eigenvalues.end(), eigenvalues);
|
||||
if (outputs.vectors.size() == vector_count)
|
||||
std::copy(outputs.vectors.begin(), outputs.vectors.end(), vectors);
|
||||
if (outputs.residuals.size() == residual_count)
|
||||
std::copy(outputs.residuals.begin(), outputs.residuals.end(), residuals);
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::uint64_t kSeed = 0x4D495443344D4645ULL;
|
||||
|
||||
Status Failure(const std::string& code, const std::string& identity,
|
||||
const std::string& message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kSolver,
|
||||
{{Severity::kError, code, {{}, 0U}, "FEAST", identity, message}});
|
||||
}
|
||||
|
||||
bool FitsMkl(const std::size_t value) {
|
||||
return value <=
|
||||
static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)());
|
||||
}
|
||||
|
||||
double NextUp(const double value) {
|
||||
return std::nextafter(value, (std::numeric_limits<double>::infinity)());
|
||||
}
|
||||
@@ -307,12 +379,12 @@ Result<FeastEigenResult> MklFeastEigenSolver::Solve(
|
||||
"feast-invalid-dimension", "matrix-or-request",
|
||||
"FEAST requires 1 <= requested modes <= square matrix dimension."));
|
||||
}
|
||||
if (!FitsMkl(n) || !ProductFits(n, n)) {
|
||||
if (!FitsMkl(n) || !internal::ProductFits(n, n)) {
|
||||
return Result<FeastEigenResult>::Failure(Failure(
|
||||
"feast-integer-overflow", "matrix-shape",
|
||||
"FEAST workspace dimensions exceed the private backend range."));
|
||||
}
|
||||
if (!FitsDoubleStorage(n) || !FitsDoubleStorage(n * n)) {
|
||||
if (!internal::FitsDoubleStorage(n) || !internal::FitsDoubleStorage(n * n)) {
|
||||
return Result<FeastEigenResult>::Failure(Failure(
|
||||
"eigen-allocation-failed", "count-workspace",
|
||||
"The required count workspace exceeds addressable double storage."));
|
||||
@@ -405,6 +477,7 @@ Result<FeastEigenResult> MklFeastEigenSolver::Solve(
|
||||
std::vector<double> count_res;
|
||||
std::vector<double> count_x;
|
||||
try {
|
||||
internal::ApplyFeastWorkspaceTestHook(true);
|
||||
count_e.assign(n, 0.0);
|
||||
count_res.assign(n, 0.0);
|
||||
count_x.assign(n * n, 0.0);
|
||||
@@ -416,7 +489,7 @@ Result<FeastEigenResult> MklFeastEigenSolver::Solve(
|
||||
|
||||
auto Count = [&](const double call_lower, const double call_upper,
|
||||
const std::string& purpose, std::size_t* count) -> Status {
|
||||
if (calls.size() >= kMaximumCountCalls)
|
||||
if (!internal::CanCallFeast(calls.size()))
|
||||
return Failure("eigen-search-budget-exhausted", "count-calls",
|
||||
"The bounded FEAST count-call budget was exhausted.");
|
||||
std::array<MKL_INT, 128> fpm{};
|
||||
@@ -438,6 +511,10 @@ Result<FeastEigenResult> MklFeastEigenSolver::Solve(
|
||||
b.Value().columns.data(), fpm.data(), &epsout, &loop,
|
||||
&call_lower, &call_upper, &m0, count_e.data(), count_x.data(),
|
||||
&returned, count_res.data(), &info);
|
||||
internal::ApplyFeastBackendTestHook(
|
||||
{true, calls.size(), call_lower, call_upper, n}, &m0, &returned,
|
||||
&loop, &epsout, &info, count_e.data(), count_e.size(), count_x.data(),
|
||||
count_x.size(), count_res.data(), count_res.size());
|
||||
const bool count_available =
|
||||
info == 0 && m0 >= 0 && static_cast<std::size_t>(m0) <= n;
|
||||
FeastBackendCall event{calls.size(),
|
||||
@@ -538,7 +615,7 @@ Result<FeastEigenResult> MklFeastEigenSolver::Solve(
|
||||
for (std::size_t midpoint_attempt = 0U;
|
||||
upper_search_termination != "count-target" && midpoint_attempt < 32U;
|
||||
++midpoint_attempt) {
|
||||
if (calls.size() + 21U > 384U) {
|
||||
if (!internal::CanStartUpperCandidate(calls.size())) {
|
||||
upper_search_termination = "optimization-budget";
|
||||
break;
|
||||
}
|
||||
@@ -646,11 +723,12 @@ Result<FeastEigenResult> MklFeastEigenSolver::Solve(
|
||||
const std::size_t padded_count =
|
||||
(std::max)(count + 8U, (3U * count + 1U) / 2U);
|
||||
std::size_t capacity = count == n ? n : (std::min)(n, padded_count);
|
||||
if (!ProductFits(n, capacity))
|
||||
if (!internal::ProductFits(n, capacity))
|
||||
return Result<FeastEigenResult>::Failure(
|
||||
Failure("feast-workspace-overflow", "solve-workspace",
|
||||
"The FEAST solve workspace size overflows."));
|
||||
if (!FitsDoubleStorage(n * capacity) || !FitsDoubleStorage(capacity)) {
|
||||
if (!internal::FitsDoubleStorage(n * capacity) ||
|
||||
!internal::FitsDoubleStorage(capacity)) {
|
||||
return Result<FeastEigenResult>::Failure(Failure(
|
||||
"eigen-allocation-failed", "solve-workspace",
|
||||
"The required solve workspace exceeds addressable double storage."));
|
||||
@@ -663,11 +741,12 @@ Result<FeastEigenResult> MklFeastEigenSolver::Solve(
|
||||
bool solve_accepted = false;
|
||||
std::size_t solve_capacity = capacity;
|
||||
for (std::size_t attempt = 0U; attempt < 3U; ++attempt) {
|
||||
if (calls.size() >= kMaximumCountCalls)
|
||||
if (!internal::CanCallFeast(calls.size()))
|
||||
return Result<FeastEigenResult>::Failure(
|
||||
Failure("eigen-search-budget-exhausted", "solve-call",
|
||||
"The total backend call budget is exhausted before solve."));
|
||||
try {
|
||||
internal::ApplyFeastWorkspaceTestHook(false);
|
||||
eigenvalues.assign(solve_capacity, 0.0);
|
||||
residuals.assign(solve_capacity, 0.0);
|
||||
vectors.assign(n * solve_capacity, 0.0);
|
||||
@@ -695,6 +774,11 @@ Result<FeastEigenResult> MklFeastEigenSolver::Solve(
|
||||
b.Value().columns.data(), fpm.data(), &epsout, &loop,
|
||||
&fixed_lower, &solve_upper, &m0, eigenvalues.data(),
|
||||
vectors.data(), &returned, residuals.data(), &info);
|
||||
internal::ApplyFeastBackendTestHook(
|
||||
{false, calls.size(), fixed_lower, solve_upper, solve_capacity}, &m0,
|
||||
&returned, &loop, &epsout, &info, eigenvalues.data(),
|
||||
eigenvalues.size(), vectors.data(), vectors.size(), residuals.data(),
|
||||
residuals.size());
|
||||
const bool solve_outputs_available =
|
||||
info == 0 && loop >= 0 && std::isfinite(epsout) && returned >= 0 &&
|
||||
static_cast<std::size_t>(returned) <= solve_capacity;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef FESA_SOLVERS_EIGEN_MKL_FEAST_EIGEN_SOLVER_TEST_SEAM_H_
|
||||
#define FESA_SOLVERS_EIGEN_MKL_FEAST_EIGEN_SOLVER_TEST_SEAM_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::internal {
|
||||
|
||||
bool CanCallFeast(std::size_t used);
|
||||
bool CanStartUpperCandidate(std::size_t used);
|
||||
bool ProductFits(std::size_t left, std::size_t right);
|
||||
bool FitsDoubleStorage(std::size_t count);
|
||||
|
||||
// Private test seam. It is applied only after the production oneMKL call has
|
||||
// returned and is thread-local so production solver state remains unchanged.
|
||||
struct FeastBackendTestCall {
|
||||
bool count;
|
||||
std::size_t ordinal;
|
||||
double lower;
|
||||
double upper;
|
||||
std::size_t capacity;
|
||||
};
|
||||
|
||||
struct FeastBackendTestOutputs {
|
||||
int info;
|
||||
std::ptrdiff_t m0;
|
||||
std::ptrdiff_t returned;
|
||||
int loop;
|
||||
double epsout;
|
||||
std::vector<double> eigenvalues;
|
||||
std::vector<double> vectors;
|
||||
std::vector<double> residuals;
|
||||
};
|
||||
|
||||
using FeastBackendTestHook =
|
||||
std::function<void(const FeastBackendTestCall&, FeastBackendTestOutputs&)>;
|
||||
using FeastWorkspaceTestHook = std::function<void(bool count_workspace)>;
|
||||
|
||||
class ScopedFeastBackendTestHook final {
|
||||
public:
|
||||
explicit ScopedFeastBackendTestHook(FeastBackendTestHook hook);
|
||||
~ScopedFeastBackendTestHook();
|
||||
|
||||
ScopedFeastBackendTestHook(const ScopedFeastBackendTestHook&) = delete;
|
||||
ScopedFeastBackendTestHook& operator=(const ScopedFeastBackendTestHook&) =
|
||||
delete;
|
||||
|
||||
private:
|
||||
FeastBackendTestHook hook_;
|
||||
FeastBackendTestHook* previous_;
|
||||
};
|
||||
|
||||
class ScopedFeastWorkspaceTestHook final {
|
||||
public:
|
||||
explicit ScopedFeastWorkspaceTestHook(FeastWorkspaceTestHook hook);
|
||||
~ScopedFeastWorkspaceTestHook();
|
||||
|
||||
ScopedFeastWorkspaceTestHook(const ScopedFeastWorkspaceTestHook&) = delete;
|
||||
ScopedFeastWorkspaceTestHook& operator=(
|
||||
const ScopedFeastWorkspaceTestHook&) = delete;
|
||||
|
||||
private:
|
||||
FeastWorkspaceTestHook hook_;
|
||||
FeastWorkspaceTestHook* previous_;
|
||||
};
|
||||
|
||||
} // namespace fesa::internal
|
||||
|
||||
#endif // FESA_SOLVERS_EIGEN_MKL_FEAST_EIGEN_SOLVER_TEST_SEAM_H_
|
||||
Reference in New Issue
Block a user