Guard coarse exactness diagnostics and report complete setup work

This commit is contained in:
2026-09-17 16:45:55 -04:00
parent 48cb8c527f
commit 52474587dd

View File

@@ -253,8 +253,8 @@ void BenchmarkPreconditioners(State &state, const std::filesystem::path &output,
reference << "context_inverse_application_seconds," << detail::Seconds(start)
<< "\ncontext_inverse_correction_norm," << referenceCorrection.Norml2() << '\n';
reference.flush();
if (!detail::Finite(referenceCorrection))
throw std::runtime_error("The context reference inverse action is nonfinite.");
if (!detail::Finite(referenceCorrection) || !std::isfinite(referenceCorrection.Norml2()))
throw std::runtime_error("The context reference inverse action or its norm is nonfinite.");
detail::Save(output / "context_reference_normalized_correction.txt", referenceCorrection);
verify("context_reference");
@@ -336,6 +336,8 @@ void BenchmarkPreconditioners(State &state, const std::filesystem::path &output,
metric("setup_jacobian_applications", stats.setupOperatorApplications);
metric("setup_inverse_applications", stats.setupInverseApplications);
metric("setup_seconds", stats.lastSetupSeconds);
metric("setup_jacobian_seconds", stats.lastSetupOperatorSeconds);
metric("setup_inverse_seconds", stats.lastSetupInverseSeconds);
metric("persistent_vector_bytes", stats.persistentVectorBytes);
metric("applications", stats.applications);
metric("inverse_applications", stats.inverseApplications);
@@ -346,9 +348,19 @@ void BenchmarkPreconditioners(State &state, const std::filesystem::path &output,
sectionStart = detail::Clock::now();
const auto modes = MakeCoupledShapeModes(state.Problem(), physical);
const double basisSeconds = detail::Seconds(sectionStart);
recordWork("after_basis");
// Direct basis calls bypass the structure-factorization counters.
// The material inverse also counts its usual internal couplings.
for (const char *metric : {"basis_explicit_surface_to_material_applications",
"basis_material_inverse_applications",
"basis_material_to_gravity_applications",
"basis_gravity_inverse_applications"})
coarseReport << role << ',' << variant << ",constructed," << metric << ',' << modes.size() << '\n';
const auto coarseStart = detail::Clock::now();
coarse = std::make_unique<Coarse>(*state.normalizedOperator, normalized, modes,
state.Problem().GetCommunicator());
const double completeCoarseSeconds = detail::Seconds(sectionStart);
const double completeCoarseSeconds = basisSeconds + detail::Seconds(coarseStart);
recordWork("after_coarse_setup");
coarseReport << role << ',' << variant << ",constructed,physical_inverse_construction_seconds,"
<< constructionSeconds << '\n';
constructionSeconds += completeCoarseSeconds;
@@ -366,10 +378,20 @@ void BenchmarkPreconditioners(State &state, const std::filesystem::path &output,
state.normalizedOperator->Mult(value, image);
coarse->Mult(image, recovered);
recovered -= value;
maximumRelativeError = std::max(maximumRelativeError,
detail::Relative(recovered.Norml2(), value.Norml2()));
const double valueNorm = value.Norml2(), imageNorm = image.Norml2(), errorNorm = recovered.Norml2();
if (!detail::Finite(value) || !detail::Finite(image) || !detail::Finite(recovered) ||
!std::isfinite(valueNorm) || valueNorm <= 0.0 || !std::isfinite(imageNorm) ||
!std::isfinite(errorNorm))
throw std::runtime_error("The actual FEM coarse identity produced a nonfinite or null reference.");
const double relativeError = errorNorm / valueNorm;
// std::max can discard NaN, so validate each observation first.
if (!std::isfinite(relativeError))
throw std::runtime_error("The actual FEM coarse relative identity error is nonfinite.");
maximumRelativeError = std::max(maximumRelativeError, relativeError);
}
coarseReport << role << ',' << variant << ",validation,maximum_exact_coarse_relative_error,"
coarseReport << role << ',' << variant << ",validation,exact_coarse_modes_checked,"
<< coarse->GetSourceBasis().size() << '\n'
<< role << ',' << variant << ",validation,maximum_exact_coarse_relative_error,"
<< maximumRelativeError << '\n';
coarseReport.flush();
if (!std::isfinite(maximumRelativeError) || maximumRelativeError > 1.e-8)
@@ -380,11 +402,15 @@ void BenchmarkPreconditioners(State &state, const std::filesystem::path &output,
sectionStart = detail::Clock::now();
applicationInverse->Mult(rhs, inverseAction);
const double applicationSeconds = detail::Seconds(sectionStart);
if (!detail::Finite(inverseAction) || !std::isfinite(inverseAction.Norml2()))
throw std::runtime_error("The diagnostic inverse action or its norm is nonfinite.");
detail::Save(output / (role + "_inverse_rhs_action.txt"), inverseAction);
sectionStart = detail::Clock::now();
state.normalizedOperator->Mult(inverseAction, inverseJacobianAction);
const double actionSeconds = detail::Seconds(sectionStart);
const double actionNorm = inverseJacobianAction.Norml2();
if (!detail::Finite(inverseJacobianAction) || !std::isfinite(actionNorm))
throw std::runtime_error("The diagnostic inverse Jacobian image or its norm is nonfinite.");
inverseJacobianAction -= rhs;
mfem::Vector referenceDifference(inverseAction);
referenceDifference -= referenceCorrection;
@@ -518,12 +544,14 @@ void BenchmarkPreconditioners(State &state, const std::filesystem::path &output,
<< "The fresh independent inverse must match the existing context inverse on that RHS.\n"
<< "construction_seconds includes physical inverse and any selected coarse/basis construction.\n"
<< "Coarse exact-subspace validation is diagnostic work outside setup/solve totals.\n"
<< "An inactive coarse fallback has exact_coarse_modes_checked=0; its zero maximum error is vacuous.\n"
<< "Stellar-level inverse/cross counters exclude direct basis calls, recorded separately; gravity leaf counters include them.\n"
<< "unchanged_state_refresh_seconds measures an immediate Refresh; it is not a changed-state refresh benchmark.\n"
<< "Each candidate receives one diagnostic inverse/Jv application before the timed cold Krylov solve.\n"
<< "Application timers/counts in the summary include only Krylov work; the independent true check is separate.\n"
<< "setup_plus_solve_seconds includes construction, normalization binding, Krylov setup, and Krylov solve.\n"
<< "setup_solve_and_true_check_seconds adds the separately measured original-J true-residual gate.\n"
<< "measured_candidate_seconds also includes diagnostic refresh and the inverse/Jv comparison.\n"
<< "measured_candidate_seconds also includes diagnostic refresh, coarse exactness checks, and inverse/Jv comparison.\n"
<< "Common FEM/context construction and frozen-state preparation occur before this helper.\n"
<< "The helper performs no trial preparation or nonlinear update; WithState restores its accepted seed afterward.\n"
<< "Block norms are coefficient norms in the stated normalized/physical coordinates, not spatial error norms.\n"