feat: complete first MITC4 FEAST eigen implementation validation

This commit is contained in:
KOKO\Mimi
2026-09-10 02:24:04 +09:00
parent 7ebdce373c
commit d5e08f868b
26 changed files with 1452 additions and 82 deletions
+102 -18
View File
@@ -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;