feat: integrate generalized consistent-mass Solid frequency
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
#include "fesa/analysis/solid_frequency_procedure.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <mkl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/io/abaqus/domain_mapper.h"
|
||||
#include "fesa/io/abaqus/input_reader.h"
|
||||
#include "fesa/model/domain.h"
|
||||
#include "solvers/eigen/mkl_feast_eigen_solver_test_seam.h"
|
||||
|
||||
namespace {
|
||||
std::string StatusText(const fesa::Status& status) {
|
||||
std::string text;
|
||||
for (const auto& d : status.Diagnostics()) text += d.code + " " + d.message;
|
||||
return text;
|
||||
}
|
||||
fesa::Result<fesa::Domain> Brick(std::size_t requested = 6U,
|
||||
const std::string& boundary = "",
|
||||
double length = 1.0, double rho = 2.0,
|
||||
double modulus = 120.0) {
|
||||
constexpr int signs[8][3] = {{-1, -1, -1}, {1, -1, -1}, {1, 1, -1},
|
||||
{-1, 1, -1}, {-1, -1, 1}, {1, -1, 1},
|
||||
{1, 1, 1}, {-1, 1, 1}};
|
||||
std::ostringstream deck;
|
||||
deck.precision(17);
|
||||
deck << "*Part,name=P\n*Node\n";
|
||||
for (std::size_t a = 0; a < 8; ++a)
|
||||
deck << (a + 1) * 10 << ',' << length * signs[a][0] << ','
|
||||
<< 2 * length * signs[a][1] << ',' << 3 * length * signs[a][2] << '\n';
|
||||
deck << "*Element,type=C3D8,elset=E\n99,10,20,30,40,50,60,70,80\n"
|
||||
"*Solid Section,elset=E,material=M\n,\n*End Part\n"
|
||||
"*Assembly,name=A\n*Instance,name=I,part=P\n*End Instance\n"
|
||||
"*End Assembly\n*Material,name=M\n*Elastic\n"
|
||||
<< modulus << ",.25\n";
|
||||
if (rho != 0.0) deck << "*Density\n" << rho << '\n';
|
||||
deck << "*Step,name=Modes\n*Frequency\n" << requested << '\n';
|
||||
if (!boundary.empty()) deck << "*Boundary\n" << boundary;
|
||||
deck << "*End Step\n";
|
||||
static std::atomic<unsigned> serial{0};
|
||||
const auto path =
|
||||
std::filesystem::temp_directory_path() /
|
||||
("fesa-solid-frequency-" + std::to_string(serial++) + ".inp");
|
||||
{
|
||||
std::ofstream out(path);
|
||||
out << deck.str();
|
||||
}
|
||||
auto syntax = fesa::AbaqusInputReader{}.Read(path);
|
||||
std::filesystem::remove(path);
|
||||
if (!syntax.HasValue())
|
||||
return fesa::Result<fesa::Domain>::Failure(syntax.GetStatus());
|
||||
return fesa::AbaqusDomainMapper{}.Map(syntax.Value());
|
||||
}
|
||||
} // namespace
|
||||
TEST(SolidFrequencyProcedure, FreeBrickRetainsSixRigidModes) {
|
||||
auto domain = Brick();
|
||||
ASSERT_TRUE(domain.HasValue()) << StatusText(domain.GetStatus());
|
||||
const auto result =
|
||||
fesa::SolidFrequencyProcedureExecutor{}.Execute(domain.Value());
|
||||
ASSERT_TRUE(result.HasValue()) << StatusText(result.GetStatus());
|
||||
ASSERT_EQ(result.Value().modes.size(), 6U);
|
||||
for (const auto& mode : result.Value().modes)
|
||||
EXPECT_EQ(mode.classification, 0U);
|
||||
EXPECT_EQ(result.Value().vectors.Rows(), 6U);
|
||||
EXPECT_EQ(result.Value().vectors.Columns(), 24U);
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr int kSigns[8][3] = {{-1, -1, -1}, {1, -1, -1}, {1, 1, -1},
|
||||
{-1, 1, -1}, {-1, -1, 1}, {1, -1, 1},
|
||||
{1, 1, 1}, {-1, 1, 1}};
|
||||
// Exact tensor-polynomial integrals, independent of production Gauss assembly.
|
||||
double GradientIntegral(std::size_t a, std::size_t b, std::size_t i,
|
||||
std::size_t j) {
|
||||
double integral = 48.0 * kSigns[a][i] * kSigns[b][j] /
|
||||
(64.0 * static_cast<double>((i + 1U) * (j + 1U)));
|
||||
for (std::size_t d = 0; d < 3; ++d)
|
||||
if (d != i && d != j) integral *= 1.0 + kSigns[a][d] * kSigns[b][d] / 3.0;
|
||||
return integral;
|
||||
}
|
||||
std::vector<double> DenseOracle(const std::vector<std::size_t>& free) {
|
||||
const auto n = free.size();
|
||||
std::vector<double> k(n * n, 0.0), m(n * n, 0.0), eigen(n, 0.0);
|
||||
for (std::size_t row = 0; row < n; ++row)
|
||||
for (std::size_t col = 0; col < n; ++col) {
|
||||
const auto a = free[row] / 3U, b = free[col] / 3U;
|
||||
const auto i = free[row] % 3U, j = free[col] % 3U;
|
||||
double gradient_dot = 0.0;
|
||||
for (std::size_t d = 0; d < 3; ++d)
|
||||
gradient_dot += GradientIntegral(a, b, d, d);
|
||||
k[row * n + col] =
|
||||
48.0 * (GradientIntegral(a, b, i, j) + GradientIntegral(a, b, j, i) +
|
||||
(i == j ? gradient_dot : 0.0));
|
||||
if (i == j) {
|
||||
m[row * n + col] = 2.0 * 48.0 / 64.0;
|
||||
for (std::size_t d = 0; d < 3; ++d)
|
||||
m[row * n + col] *= 1.0 + kSigns[a][d] * kSigns[b][d] / 3.0;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(
|
||||
LAPACKE_dsygv(LAPACK_ROW_MAJOR, 1, 'N', 'U', static_cast<MKL_INT>(n),
|
||||
k.data(), static_cast<MKL_INT>(n), m.data(),
|
||||
static_cast<MKL_INT>(n), eigen.data()),
|
||||
0);
|
||||
return eigen;
|
||||
}
|
||||
const std::string kFaceFixed = "10,1,3\n40,1,3\n50,1,3\n80,1,3\n";
|
||||
std::vector<std::size_t> FaceFree() {
|
||||
std::vector<std::size_t> free;
|
||||
for (const auto node : {1U, 2U, 5U, 6U})
|
||||
for (std::size_t d = 0; d < 3; ++d) free.push_back(3U * node + d);
|
||||
return free;
|
||||
}
|
||||
} // namespace
|
||||
TEST(SolidFrequencyProcedure,
|
||||
OriginalConsistentPencilMatchesIndependentDenseOracle) {
|
||||
for (const bool constrained : {false, true}) {
|
||||
const std::size_t count = constrained ? 12U : 24U;
|
||||
auto domain = Brick(count, constrained ? kFaceFixed : "");
|
||||
ASSERT_TRUE(domain.HasValue()) << StatusText(domain.GetStatus());
|
||||
auto result =
|
||||
fesa::SolidFrequencyProcedureExecutor{}.Execute(domain.Value());
|
||||
ASSERT_TRUE(result.HasValue()) << StatusText(result.GetStatus());
|
||||
std::vector<std::size_t> free;
|
||||
if (constrained)
|
||||
free = FaceFree();
|
||||
else
|
||||
for (std::size_t i = 0; i < 24; ++i) free.push_back(i);
|
||||
const auto oracle = DenseOracle(free);
|
||||
const auto& state = result.Value();
|
||||
ASSERT_EQ(state.modes.size(), oracle.size());
|
||||
ASSERT_EQ(state.eigenpair_checks.size(), oracle.size());
|
||||
for (std::size_t c = 0; c < oracle.size(); ++c) {
|
||||
EXPECT_NEAR(state.modes[c].eigenvalue, oracle[c],
|
||||
1e-10 * state.classification_scale);
|
||||
EXPECT_EQ(state.modes[c].mode_id, c + 1);
|
||||
EXPECT_EQ(state.eigenpair_checks[c].sorted_rank, c + 1);
|
||||
EXPECT_LE(state.eigenpair_checks[c].eta_r, 1e-10);
|
||||
EXPECT_LE(state.eigenpair_checks[c].eta_energy, 1e-10);
|
||||
EXPECT_LE(state.eigenpair_checks[c].eta_norm, 1e-10);
|
||||
EXPECT_NEAR(state.eigenpair_checks[c].modal_mass, 1.0, 1e-10);
|
||||
std::size_t pivot = 0;
|
||||
for (std::size_t row = 0; row < 24; ++row) {
|
||||
if (std::abs(state.vectors(c, row)) > std::abs(state.vectors(c, pivot)))
|
||||
pivot = row;
|
||||
if (state.constraint_mask[row]) EXPECT_EQ(state.vectors(c, row), 0.0);
|
||||
}
|
||||
EXPECT_GT(state.vectors(c, pivot), 0.0);
|
||||
}
|
||||
EXPECT_LE(state.eta_gram, 1e-10);
|
||||
EXPECT_LE(state.max_eta_orth, 1e-10);
|
||||
EXPECT_NEAR(state.mass_ideal_lower_bound, 12.0 / 27.0, 1e-13);
|
||||
EXPECT_GT(state.mass_lower_bound, 0.0);
|
||||
EXPECT_LT(state.mass_lower_bound, state.mass_ideal_lower_bound);
|
||||
EXPECT_GT(state.mass_roundoff_guard, 0.0);
|
||||
EXPECT_EQ(state.backend.integer_abi, "ILP64");
|
||||
EXPECT_EQ(state.backend.eigenvectors.Rows(), 0U);
|
||||
EXPECT_EQ(state.backend.backend_calls.front().kind, "mass-spd");
|
||||
}
|
||||
}
|
||||
TEST(SolidFrequencyProcedure, DensityLengthAndMaterialScaling) {
|
||||
const auto base_domain = Brick(4U, kFaceFixed);
|
||||
ASSERT_TRUE(base_domain.HasValue());
|
||||
const auto base =
|
||||
fesa::SolidFrequencyProcedureExecutor{}.Execute(base_domain.Value());
|
||||
ASSERT_TRUE(base.HasValue()) << StatusText(base.GetStatus());
|
||||
for (const auto parameters : {std::array<double, 4>{1, 8, 120, .25},
|
||||
std::array<double, 4>{2, 2, 120, .25},
|
||||
std::array<double, 4>{1, 2, 480, 4}}) {
|
||||
const auto domain =
|
||||
Brick(4U, kFaceFixed, parameters[0], parameters[1], parameters[2]);
|
||||
ASSERT_TRUE(domain.HasValue());
|
||||
const auto result =
|
||||
fesa::SolidFrequencyProcedureExecutor{}.Execute(domain.Value());
|
||||
ASSERT_TRUE(result.HasValue()) << StatusText(result.GetStatus());
|
||||
for (std::size_t c = 0; c < 4; ++c)
|
||||
EXPECT_NEAR(result.Value().modes[c].eigenvalue,
|
||||
base.Value().modes[c].eigenvalue * parameters[3],
|
||||
1e-10 * result.Value().classification_scale);
|
||||
}
|
||||
}
|
||||
TEST(SolidFrequencyProcedure, OneFreeDofAndAllConstrainedAndExcessRequest) {
|
||||
const std::string one =
|
||||
"10,1,2\n20,1,3\n30,1,3\n40,1,3\n50,1,3\n60,1,3\n70,1,3\n80,1,3\n";
|
||||
auto domain = Brick(1U, one);
|
||||
ASSERT_TRUE(domain.HasValue());
|
||||
auto result = fesa::SolidFrequencyProcedureExecutor{}.Execute(domain.Value());
|
||||
ASSERT_TRUE(result.HasValue()) << StatusText(result.GetStatus());
|
||||
EXPECT_NEAR(result.Value().modes[0].eigenvalue, DenseOracle({2U})[0], 1e-11);
|
||||
const std::string all =
|
||||
"10,1,3\n20,1,3\n30,1,3\n40,1,3\n50,1,3\n60,1,3\n70,1,3\n80,1,3\n";
|
||||
for (const auto pair :
|
||||
{std::pair<std::size_t, std::string>{1U, all}, {13U, kFaceFixed}}) {
|
||||
auto invalid = Brick(pair.first, pair.second);
|
||||
ASSERT_TRUE(invalid.HasValue());
|
||||
EXPECT_FALSE(fesa::SolidFrequencyProcedureExecutor{}
|
||||
.Execute(invalid.Value())
|
||||
.HasValue());
|
||||
}
|
||||
}
|
||||
TEST(SolidFrequencyProcedure,
|
||||
MissingDensityAndFailedBackendPreservePriorCandidate) {
|
||||
auto missing = Brick(1U, kFaceFixed, 1.0, 0.0);
|
||||
if (missing.HasValue())
|
||||
EXPECT_FALSE(fesa::SolidFrequencyProcedureExecutor{}
|
||||
.Execute(missing.Value())
|
||||
.HasValue());
|
||||
auto domain = Brick(4U, kFaceFixed);
|
||||
ASSERT_TRUE(domain.HasValue());
|
||||
const auto good =
|
||||
fesa::SolidFrequencyProcedureExecutor{}.Execute(domain.Value());
|
||||
ASSERT_TRUE(good.HasValue());
|
||||
const auto previous = good.Value().modes[0].eigenvalue;
|
||||
{
|
||||
fesa::internal::ScopedFeastBackendTestHook hook{
|
||||
[](const auto& call, auto& output) {
|
||||
if (!call.count) output.eigenvalues.back() = 9999.0;
|
||||
}};
|
||||
EXPECT_FALSE(fesa::SolidFrequencyProcedureExecutor{}
|
||||
.Execute(domain.Value())
|
||||
.HasValue());
|
||||
}
|
||||
EXPECT_EQ(good.Value().modes[0].eigenvalue, previous);
|
||||
const auto again =
|
||||
fesa::SolidFrequencyProcedureExecutor{}.Execute(domain.Value());
|
||||
ASSERT_TRUE(again.HasValue());
|
||||
EXPECT_NEAR(again.Value().modes[0].eigenvalue, previous, 1e-12);
|
||||
}
|
||||
|
||||
TEST(SolidFrequencyProcedure, NearZeroNegativeRemainsRawWithoutFrequency) {
|
||||
auto domain = Brick();
|
||||
ASSERT_TRUE(domain.HasValue());
|
||||
fesa::internal::ScopedFeastBackendTestHook hook{
|
||||
[](const auto& call, auto& output) {
|
||||
if (!call.count) output.eigenvalues[0] = -1e-12;
|
||||
}};
|
||||
const auto result =
|
||||
fesa::SolidFrequencyProcedureExecutor{}.Execute(domain.Value());
|
||||
ASSERT_TRUE(result.HasValue()) << StatusText(result.GetStatus());
|
||||
EXPECT_LT(result.Value().modes[0].eigenvalue, 0.0);
|
||||
EXPECT_EQ(result.Value().modes[0].classification, 0U);
|
||||
EXPECT_FALSE(result.Value().modes[0].frequency.has_value());
|
||||
for (const auto& mode : result.Value().modes) EXPECT_EQ(mode.cluster_id, 1U);
|
||||
}
|
||||
|
||||
TEST(SolidFrequencyProcedure,
|
||||
SuppliedFrequencyInputHasIndependentNumericalEvidence) {
|
||||
auto root = std::filesystem::path{__FILE__}.parent_path();
|
||||
while (!root.empty() && !std::filesystem::exists(root / "CMakePresets.json"))
|
||||
root = root.parent_path();
|
||||
ASSERT_FALSE(root.empty());
|
||||
const auto syntax =
|
||||
fesa::AbaqusInputReader{}.Read(root / "reference/hexa/hexa_freq.inp");
|
||||
ASSERT_TRUE(syntax.HasValue()) << StatusText(syntax.GetStatus());
|
||||
const auto domain = fesa::AbaqusDomainMapper{}.Map(syntax.Value());
|
||||
ASSERT_TRUE(domain.HasValue()) << StatusText(domain.GetStatus());
|
||||
const auto result =
|
||||
fesa::SolidFrequencyProcedureExecutor{}.Execute(domain.Value());
|
||||
ASSERT_TRUE(result.HasValue()) << StatusText(result.GetStatus());
|
||||
EXPECT_EQ(result.Value().modes.size(), 10U);
|
||||
EXPECT_EQ(result.Value().vectors.Columns(), 3U * 442U);
|
||||
EXPECT_GE(result.Value().eigenpair_checks.size(), 10U);
|
||||
EXPECT_GT(result.Value().mass_lower_bound, 0.0);
|
||||
EXPECT_LE(result.Value().eta_gram, 1e-10);
|
||||
EXPECT_LE(result.Value().max_eta_orth, 1e-10);
|
||||
// Original modal Value output is pending. This proves only our independent
|
||||
// checks on the supplied input; it is not an external frequency comparison.
|
||||
}
|
||||
|
||||
TEST(SolidFrequencyProcedure, RejectsOverflowingOriginalPencilValidationScale) {
|
||||
// Every local K entry, eigenvalue, and spectral enclosure is representable.
|
||||
// The all-odd x Walsh mode still overflows the original residual/energy
|
||||
// denominators: finite / infinity must never become passing zero evidence.
|
||||
auto domain = Brick(24U, "", 1.0, 2.0, 3e306);
|
||||
ASSERT_TRUE(domain.HasValue()) << StatusText(domain.GetStatus());
|
||||
const auto result =
|
||||
fesa::SolidFrequencyProcedureExecutor{}.Execute(domain.Value());
|
||||
ASSERT_FALSE(result.HasValue());
|
||||
ASSERT_FALSE(result.GetStatus().Diagnostics().empty());
|
||||
EXPECT_EQ(result.GetStatus().Diagnostics()[0].code,
|
||||
"solid-eigenpair-validation-failed");
|
||||
EXPECT_NE(result.GetStatus().Diagnostics()[0].message.find("nonfinite"),
|
||||
std::string::npos);
|
||||
}
|
||||
@@ -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