fix(eigen): reject nonfinite modal validation scales
This commit is contained in:
@@ -265,11 +265,27 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
|
|||||||
Vector residual = kv;
|
Vector residual = kv;
|
||||||
residual.Axpy(-lambda, mv);
|
residual.Axpy(-lambda, mv);
|
||||||
const double norm_phi = vector.Norm();
|
const double norm_phi = vector.Norm();
|
||||||
const double denominator = (norm_k + std::abs(lambda) * norm_m) * norm_phi;
|
const double residual_norm = residual.Norm();
|
||||||
const double eta_r = Ratio(residual.Norm(), denominator);
|
const double lambda_mass = lambda * modal_mass;
|
||||||
|
const double energy_error = std::abs(energy - lambda_mass);
|
||||||
|
const double matrix_scale = norm_k + std::abs(lambda) * norm_m;
|
||||||
|
const double denominator = matrix_scale * norm_phi;
|
||||||
|
const double energy_denominator = denominator * norm_phi;
|
||||||
|
// Finite / infinity is zero in IEEE arithmetic but cannot certify a pair.
|
||||||
|
// Validate both original-pencil scales before either division, preserving
|
||||||
|
// the exact-zero branch only for representable finite numerator/scale.
|
||||||
|
if (!std::isfinite(norm_k) || !std::isfinite(norm_m) ||
|
||||||
|
!std::isfinite(norm_phi) || !std::isfinite(residual_norm) ||
|
||||||
|
!std::isfinite(lambda_mass) || !std::isfinite(energy_error) ||
|
||||||
|
!std::isfinite(matrix_scale) || !std::isfinite(denominator) ||
|
||||||
|
!std::isfinite(energy_denominator))
|
||||||
|
return Result<SolidFrequencyState>::Failure(
|
||||||
|
Failure("solid-eigenpair-validation-failed",
|
||||||
|
"A nonfinite original K/M validation numerator or scale "
|
||||||
|
"cannot certify a pair."));
|
||||||
|
const double eta_r = Ratio(residual_norm, denominator);
|
||||||
const double eta_norm = std::abs(modal_mass - 1.0);
|
const double eta_norm = std::abs(modal_mass - 1.0);
|
||||||
const double eta_energy =
|
const double eta_energy = Ratio(energy_error, energy_denominator);
|
||||||
Ratio(std::abs(energy - lambda * modal_mass), denominator * norm_phi);
|
|
||||||
if (!std::isfinite(lambda) || !std::isfinite(eta_r) || eta_r > 1e-10 ||
|
if (!std::isfinite(lambda) || !std::isfinite(eta_r) || eta_r > 1e-10 ||
|
||||||
!std::isfinite(eta_norm) || eta_norm > 1e-10 ||
|
!std::isfinite(eta_norm) || eta_norm > 1e-10 ||
|
||||||
!std::isfinite(eta_energy) || eta_energy > 1e-10 ||
|
!std::isfinite(eta_energy) || eta_energy > 1e-10 ||
|
||||||
|
|||||||
@@ -1091,12 +1091,17 @@ Result<FeastEigenResult> MklFeastEigenSolver::SolveImpl(
|
|||||||
euclidean = std::hypot(euclidean, vectors[row + input * n]);
|
euclidean = std::hypot(euclidean, vectors[row + input * n]);
|
||||||
residual_denominator = (norm_k + std::abs(mu) * norm_m) * euclidean;
|
residual_denominator = (norm_k + std::abs(mu) * norm_m) * euclidean;
|
||||||
}
|
}
|
||||||
|
if (!std::isfinite(residual_norm) || !std::isfinite(residual_denominator))
|
||||||
|
return Result<FeastEigenResult>::Failure(
|
||||||
|
TraceFailure("eigenpair-validation-failed", std::to_string(input),
|
||||||
|
"A nonfinite independent residual numerator or scale "
|
||||||
|
"cannot certify a pair."));
|
||||||
const double rho_b =
|
const double rho_b =
|
||||||
residual_denominator == 0.0
|
residual_denominator == 0.0
|
||||||
? (residual_norm == 0.0 ? 0.0
|
? (residual_norm == 0.0 ? 0.0
|
||||||
: (std::numeric_limits<double>::infinity)())
|
: (std::numeric_limits<double>::infinity)())
|
||||||
: std::sqrt(residual_norm) / residual_denominator;
|
: std::sqrt(residual_norm) / residual_denominator;
|
||||||
if (rho_b > 1.0e-10) {
|
if (!std::isfinite(rho_b) || rho_b > 1.0e-10) {
|
||||||
if (mass != nullptr)
|
if (mass != nullptr)
|
||||||
return Result<FeastEigenResult>::Failure(TraceFailure(
|
return Result<FeastEigenResult>::Failure(TraceFailure(
|
||||||
"eigenpair-validation-failed", std::to_string(input),
|
"eigenpair-validation-failed", std::to_string(input),
|
||||||
@@ -1198,7 +1203,7 @@ Result<FeastEigenResult> MklFeastEigenSolver::SolveImpl(
|
|||||||
}
|
}
|
||||||
gram_error = std::sqrt(gram_squared /
|
gram_error = std::sqrt(gram_squared /
|
||||||
(mass != nullptr ? 1.0 : static_cast<double>(count)));
|
(mass != nullptr ? 1.0 : static_cast<double>(count)));
|
||||||
if (gram_error > 1.0e-10) {
|
if (!std::isfinite(gram_error) || gram_error > 1.0e-10) {
|
||||||
return Result<FeastEigenResult>::Failure(TraceFailure(
|
return Result<FeastEigenResult>::Failure(TraceFailure(
|
||||||
"eigenpair-validation-failed", "gram",
|
"eigenpair-validation-failed", "gram",
|
||||||
"The normalized all-c Frobenius Gram error exceeds 1e-10: " +
|
"The normalized all-c Frobenius Gram error exceeds 1e-10: " +
|
||||||
|
|||||||
@@ -276,3 +276,19 @@ TEST(SolidFrequencyProcedure,
|
|||||||
// Original modal Value output is pending. This proves only our independent
|
// Original modal Value output is pending. This proves only our independent
|
||||||
// checks on the supplied input; it is not an external frequency comparison.
|
// 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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user