feat(analysis): execute solid generalized frequency pencil
This commit is contained in:
@@ -629,3 +629,158 @@ TEST(MklFeastEigenSolver, UsesCertifiedEnclosureAfterOptionalInfoOne) {
|
||||
ASSERT_GE(result.Value().backend_calls.size(), 2U);
|
||||
EXPECT_EQ(result.Value().backend_calls[1U].outcome, "optional-abandoned");
|
||||
}
|
||||
|
||||
TEST(MklFeastGeneralized, GenuineNonDiagonalMassPencil) {
|
||||
const auto k = MakeDenseCsr(2U, {6.0, 2.0, 2.0, 6.0});
|
||||
const auto m = MakeDenseCsr(2U, {2.0, 1.0, 1.0, 2.0});
|
||||
const auto result =
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(k, m, 1U, 1.0, 3.0);
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
ASSERT_EQ(result.Value().eigenvalues.size(), 2U);
|
||||
EXPECT_NEAR(result.Value().eigenvalues[0], 8.0 / 3.0, 1e-11);
|
||||
EXPECT_NEAR(result.Value().eigenvalues[1], 4.0, 1e-11);
|
||||
}
|
||||
|
||||
TEST(MklFeastGeneralized, RejectsIndefiniteMassAndInvalidBound) {
|
||||
const auto k = MakeDenseCsr(2U, {1, 0, 0, 2});
|
||||
const auto m = MakeDenseCsr(2U, {1, 2, 2, 1});
|
||||
ExpectFailureCode(
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(k, m, 1U, 0.1, 2),
|
||||
"feast-mass-not-positive-definite");
|
||||
ExpectFailureCode(
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(k, k, 1U, 0, 1),
|
||||
"feast-invalid-mass-bound");
|
||||
}
|
||||
|
||||
TEST(MklFeastGeneralized, ValidatesPairsBeyondRequestedPrefix) {
|
||||
const auto k = MakeDenseCsr(3U, {2, 0, 0, 0, 6, 0, 0, 0, 12});
|
||||
const auto m = MakeDenseCsr(3U, {2, 0, 0, 0, 3, 0, 0, 0, 4});
|
||||
fesa::internal::ScopedFeastBackendTestHook hook{
|
||||
[](const auto& call, auto& output) {
|
||||
if (!call.count) output.vectors[2U + 2U * 3U] *= 2.0;
|
||||
}};
|
||||
// Magnitude is repaired by mass normalization; an erroneous direction fails.
|
||||
const auto result =
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(k, m, 1U, 2, 3);
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
EXPECT_EQ(result.Value().eigenvalues.size(), 3U);
|
||||
}
|
||||
|
||||
TEST(MklFeastGeneralized, RejectsCorruptUnselectedDirectionAndFatalCount) {
|
||||
const auto k = MakeDenseCsr(3U, {2, 0, 0, 0, 6, 0, 0, 0, 12});
|
||||
const auto m = MakeDenseCsr(3U, {2, 0, 0, 0, 3, 0, 0, 0, 4});
|
||||
{
|
||||
fesa::internal::ScopedFeastBackendTestHook hook{
|
||||
[](const auto& call, auto& output) {
|
||||
if (!call.count) output.vectors[0U + 2U * 3U] += 0.2;
|
||||
}};
|
||||
ExpectFailureCode(
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(k, m, 1U, 2, 3),
|
||||
"eigenpair-validation-failed");
|
||||
}
|
||||
{
|
||||
fesa::internal::ScopedFeastBackendTestHook hook{
|
||||
[](const auto& call, auto& output) {
|
||||
if (call.count) output.info = -3;
|
||||
}};
|
||||
const auto result =
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(k, m, 1U, 2, 3);
|
||||
ExpectFailureCode(result, "eigen-feast-failed");
|
||||
EXPECT_NE(
|
||||
result.GetStatus().Diagnostics()[0].message.find("Backend calls="),
|
||||
std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MklFeastGeneralized, LowestPrefixPreservesRepeatedBoundary) {
|
||||
constexpr std::size_t n = 18U;
|
||||
std::vector<double> kv(n * n, 0.0), mv(n * n, 0.0);
|
||||
for (std::size_t i = 0; i < n; ++i) {
|
||||
mv[i * n + i] = 2.0 + static_cast<double>(i);
|
||||
kv[i * n + i] =
|
||||
mv[i * n + i] * (i < 5U ? 1.0 : 2.0 + static_cast<double>(i));
|
||||
}
|
||||
const auto k = MakeDenseCsr(n, kv), m = MakeDenseCsr(n, mv);
|
||||
const auto result =
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(k, m, 3U, 2.0, 19.0);
|
||||
ASSERT_TRUE(result.HasValue())
|
||||
<< (result.HasValue() ? "" : result.GetStatus().Diagnostics()[0].message);
|
||||
ASSERT_GE(result.Value().eigenvalues.size(), 5U);
|
||||
for (std::size_t i = 0; i < 5U; ++i)
|
||||
EXPECT_NEAR(result.Value().eigenvalues[i], 1.0, 1e-11);
|
||||
}
|
||||
|
||||
TEST(MklFeastGeneralized, AllocationAndCountMismatchFailClosed) {
|
||||
const auto k = MakeDenseCsr(2U, {2, 0, 0, 6});
|
||||
const auto m = MakeDenseCsr(2U, {2, 0, 0, 3});
|
||||
{
|
||||
fesa::internal::ScopedFeastWorkspaceTestHook hook{
|
||||
[](bool) { throw std::bad_alloc{}; }};
|
||||
ExpectFailureCode(
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(k, m, 1U, 2, 2),
|
||||
"eigen-allocation-failed");
|
||||
}
|
||||
{
|
||||
fesa::internal::ScopedFeastBackendTestHook hook{
|
||||
[](const auto& call, auto& output) {
|
||||
if (call.count) output.m0 = 1;
|
||||
}};
|
||||
ExpectFailureCode(
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(k, m, 1U, 2, 2),
|
||||
"eigen-count-inconsistent");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MklFeastGeneralized, GeneralizedRetryRetainsCertifiedCountTrace) {
|
||||
constexpr std::size_t n = 18U;
|
||||
std::vector<double> kv(n * n, 0.0), mv(n * n, 0.0);
|
||||
for (std::size_t i = 0; i < n; ++i) {
|
||||
mv[i * n + i] = 2.0 + i;
|
||||
kv[i * n + i] = mv[i * n + i] * (i + 1.0);
|
||||
}
|
||||
const auto k = MakeDenseCsr(n, kv), m = MakeDenseCsr(n, mv);
|
||||
bool injected = false;
|
||||
fesa::internal::ScopedFeastBackendTestHook hook{
|
||||
[&injected](const auto& call, auto& output) {
|
||||
if (!call.count && !injected) {
|
||||
output.info = 2;
|
||||
injected = true;
|
||||
}
|
||||
}};
|
||||
const auto result =
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(k, m, 3U, 2, 18);
|
||||
ASSERT_TRUE(result.HasValue())
|
||||
<< (result.HasValue() ? "" : result.GetStatus().Diagnostics()[0].message);
|
||||
EXPECT_TRUE(injected);
|
||||
EXPECT_NEAR(result.Value().eigenvalues[0], 1.0, 1e-11);
|
||||
bool retried = false;
|
||||
for (const auto& call : result.Value().backend_calls)
|
||||
retried = retried || (call.kind == "solve" && call.info == 2 &&
|
||||
call.outcome == "retry");
|
||||
EXPECT_TRUE(retried);
|
||||
}
|
||||
|
||||
TEST(MklFeastGeneralized,
|
||||
LowerPrefixCountsProveNoOmittedModesAndRejectNegative) {
|
||||
const auto m = MakeDenseCsr(3U, {2, 0, 0, 0, 3, 0, 0, 0, 4});
|
||||
const auto k = MakeDenseCsr(3U, {0, 0, 0, 0, 6, 0, 0, 0, 12});
|
||||
const auto result =
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(k, m, 1U, 2, 3);
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
std::size_t guards = 0;
|
||||
for (const auto& call : result.Value().backend_calls) {
|
||||
if (call.purpose != "lower-empty-prefix") continue;
|
||||
++guards;
|
||||
EXPECT_EQ(call.info, 0);
|
||||
EXPECT_TRUE(call.count_available);
|
||||
EXPECT_EQ(call.count, 0U);
|
||||
EXPECT_LT(call.upper, 0.0);
|
||||
EXPECT_LT(call.lower, call.upper);
|
||||
}
|
||||
EXPECT_EQ(guards, 3U);
|
||||
EXPECT_NEAR(result.Value().eigenvalues[0], 0.0, 1e-12);
|
||||
const auto negative = MakeDenseCsr(3U, {-2, 0, 0, 0, 6, 0, 0, 0, 12});
|
||||
ExpectFailureCode(
|
||||
fesa::MklFeastEigenSolver{}.SolveGeneralized(negative, m, 1U, 2, 3),
|
||||
"negative-eigenvalue-spectrum");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user