Scale prepared gravity consistency by uncancelled field contributions

This commit is contained in:
2026-09-17 10:21:05 -04:00
parent f77c7d2b29
commit 6a91cfaf06
4 changed files with 192 additions and 10 deletions

View File

@@ -1,7 +1,10 @@
module;
#include <concepts>
#include <cmath>
#include <cstdint>
#include <limits>
#include <span>
#include <type_traits>
#include <utility>
@@ -76,13 +79,53 @@ export namespace mean_field::solver::nonlinear {
std::uint64_t gravityIterations{0};
std::uint64_t gravityFailures{0};
std::uint64_t gravityPreparedFailures{0};
bool compactGravityOperator{false};
bool compactGravityInverse{false};
LinearReportSpace lastLinearReportSpace{LinearReportSpace::full_system};
double gravitySolveSeconds{0.0};
double lastGravityTrueResidual{0.0};
double lastGravityCorrectionNorm{0.0};
double lastGravityPreparedDefect{0.0};
double lastGravityAffineDisagreement{0.0};
double lastGravityArithmeticAllowance{0.0};
double lastGravityPreparedTargetThreshold{0.0};
double initialGravityResidual{0.0};
double completedInitialGravityResidual{0.0};
double lastLiftedRelativeTrueResidual{0.0};
};
}
export namespace mean_field::solver::detail {
struct GravityPreparedResidualAssessment final {
bool admissible{false};
double arithmeticAllowance{std::numeric_limits<double>::quiet_NaN()};
double targetThreshold{std::numeric_limits<double>::quiet_NaN()};
};
[[nodiscard]] inline GravityPreparedResidualAssessment AssessGravityPreparedResidual(
const std::span<const double> contributionNorms,
const double affineDisagreement,
const double targetDefect,
const double measuredInnerResidual
) {
GravityPreparedResidualAssessment result;
if (!std::isfinite(affineDisagreement) || affineDisagreement < 0.0 ||
!std::isfinite(targetDefect) || targetDefect < 0.0 ||
!std::isfinite(measuredInnerResidual) || measuredInnerResidual < 0.0)
return result;
long double scale = 0.0;
for (const double norm : contributionNorms) {
if (!std::isfinite(norm) || norm < 0.0) return result;
scale += norm;
}
// This arithmetic estimate includes large terms that cancel in G.
// It is not a rigorous error bound or a replacement for either the
// independently checked inner residual or the full nonlinear merit.
result.arithmeticAllowance = static_cast<double>(256.0L * std::numeric_limits<double>::epsilon() * scale);
result.targetThreshold = measuredInnerResidual + result.arithmeticAllowance;
result.admissible = std::isfinite(result.arithmeticAllowance) && std::isfinite(result.targetThreshold) &&
affineDisagreement <= result.arithmeticAllowance &&
targetDefect <= result.targetThreshold;
return result;
}
}

View File

@@ -108,6 +108,10 @@ namespace mean_field::solver::detail {
expectedResidual(subsystem.GetPartition().GravityResidualSize()),
gravityAction(subsystem.GetPartition().GravityResidualSize()),
desiredResidual(subsystem.GetPartition().GravityResidualSize()),
sourceResidual(subsystem.GetPartition().GravityResidualSize()),
scaleGravityValues(subsystem.GetPartition().GravityValueSize()),
scaleGravityAction(subsystem.GetPartition().GravityResidualSize()),
scaleRootValues(state.Problem().StateSize()),
fullCorrection(state.Problem().StateSize()), fullAction(state.Problem().EquationSize()) {
m_options.Validate();
}
@@ -119,6 +123,8 @@ namespace mean_field::solver::detail {
report.gravityIterations = subsystem.GetIterationCount();
report.gravityFailures = subsystem.GetFailureCount();
report.gravityPreparedFailures = preparedFailures;
report.compactGravityOperator = subsystem.UsesCompactOperator();
report.compactGravityInverse = subsystem.UsesCompactPreconditioner();
report.lastLinearReportSpace = lastLinearReportSpace;
report.gravitySolveSeconds = subsystem.GetSolveSeconds();
if (const auto &last = subsystem.GetLastSolveReport(); last.has_value()) {
@@ -126,6 +132,9 @@ namespace mean_field::solver::detail {
}
report.lastGravityCorrectionNorm = lastCorrectionNorm;
report.lastGravityPreparedDefect = lastPreparedDefect;
report.lastGravityAffineDisagreement = lastAffineDisagreement;
report.lastGravityArithmeticAllowance = lastArithmeticAllowance;
report.lastGravityPreparedTargetThreshold = lastPreparedTargetThreshold;
report.lastLiftedRelativeTrueResidual = liftedRelativeDefect;
}
@@ -147,11 +156,37 @@ namespace mean_field::solver::detail {
}
auto ApplyCorrection(State &state) {
lastPreparedDefect = lastAffineDisagreement = lastArithmeticAllowance =
lastPreparedTargetThreshold = std::numeric_limits<double>::quiet_NaN();
const auto &partition = subsystem.GetPartition();
partition.InjectGravityValues(gravityCorrection, fullCorrection);
state.normalizedOperator->Mult(fullCorrection, fullAction);
subsystem.GetOperator().Mult(gravityCorrection, gravityAction);
partition.GatherGravityResiduals(state.trialNormalizedResidual, expectedResidual);
partition.GatherGravityResiduals(fullAction, gravityAction);
sourceResidual = expectedResidual;
// Split the two gravity fields before taking norms: their large
// cancellation is precisely what the residual alone cannot expose.
using Form = typename Problem::FormType;
using namespace utils::blocks;
constexpr value_block<type_index_v<gravity::gradient::value, typename Form::value_blocks>> gradient{};
constexpr value_block<type_index_v<gravity::poisson::value, typename Form::value_blocks>> potential{};
const auto &layout = state.Problem().GetManifest().layout();
std::array<double, 8> contributionNorms{};
const auto measure = [&](const mfem::Vector &values, auto block, const bool source) {
scaleRootValues = 0.0;
for (int index = layout.offset(block); index < layout.offset(block) + layout.size(block); ++index)
scaleRootValues(index) = values(index);
partition.GatherGravityValues(scaleRootValues, scaleGravityValues);
subsystem.GetOperator().Mult(scaleGravityValues, scaleGravityAction);
if (source) sourceResidual -= scaleGravityAction;
return StrategyNorm(scaleGravityAction, subsystem.GetCommunicator());
};
contributionNorms[0] = measure(state.trialNormalizedState, gradient, true);
contributionNorms[1] = measure(state.trialNormalizedState, potential, true);
contributionNorms[2] = StrategyNorm(sourceResidual, subsystem.GetCommunicator());
contributionNorms[3] = measure(fullCorrection, gradient, false);
contributionNorms[4] = measure(fullCorrection, potential, false);
contributionNorms[5] = StrategyNorm(expectedResidual, subsystem.GetCommunicator());
contributionNorms[6] = StrategyNorm(desiredResidual, subsystem.GetCommunicator());
expectedResidual += gravityAction;
lastCorrectionNorm = StrategyNorm(gravityCorrection, subsystem.GetCommunicator());
partition.AddGravityValues(gravityCorrection, state.trialNormalizedState);
@@ -159,22 +194,21 @@ namespace mean_field::solver::detail {
auto preparation = state.PrepareTrial();
if (preparation) {
partition.GatherGravityResiduals(state.trialNormalizedResidual, gravityResidual);
const double scale = StrategyNorm(gravityResidual, subsystem.GetCommunicator()) +
StrategyNorm(expectedResidual, subsystem.GetCommunicator()) +
StrategyNorm(desiredResidual, subsystem.GetCommunicator()) +
StrategyNorm(gravityAction, subsystem.GetCommunicator());
contributionNorms[7] = StrategyNorm(gravityResidual, subsystem.GetCommunicator());
expectedResidual -= gravityResidual;
const double disagreement = StrategyNorm(expectedResidual, subsystem.GetCommunicator());
gravityResidual -= desiredResidual;
const double targetDefect = StrategyNorm(gravityResidual, subsystem.GetCommunicator());
const double tolerance = m_options.control.ConvergenceThreshold(lastSolve.rightHandSideNorm) +
256.0 * std::numeric_limits<double>::epsilon() * scale;
// G is affine in gravity for fixed retained variables. Verify
// that property against a fresh preparation, including its
// boundary rows, before trusting a completed candidate.
trialSolveSucceeded = std::isfinite(disagreement) && disagreement <= tolerance &&
std::isfinite(targetDefect) && targetDefect <= tolerance;
const auto assessment = AssessGravityPreparedResidual(
contributionNorms, disagreement, targetDefect, lastSolve.trueResidualNorm);
trialSolveSucceeded = assessment.admissible;
lastPreparedDefect = targetDefect;
lastAffineDisagreement = disagreement;
lastArithmeticAllowance = assessment.arithmeticAllowance;
lastPreparedTargetThreshold = assessment.targetThreshold;
}
if (!preparation || !trialSolveSucceeded) { ++preparedFailures; }
return preparation;
@@ -183,11 +217,15 @@ namespace mean_field::solver::detail {
nonlinear::GravitySolveOptions m_options;
Subsystem subsystem;
mfem::Vector gravityResidual, gravityCorrection, expectedResidual, gravityAction, desiredResidual;
mfem::Vector sourceResidual, scaleGravityValues, scaleGravityAction, scaleRootValues;
mfem::Vector fullCorrection, fullAction;
LinearSolveReport lastSolve{};
bool trialSolveSucceeded{true};
double lastCorrectionNorm{0.0};
double lastPreparedDefect{0.0};
double lastAffineDisagreement{0.0};
double lastArithmeticAllowance{0.0};
double lastPreparedTargetThreshold{0.0};
double liftedRelativeDefect{0.0};
std::uint64_t preparedFailures{0};
nonlinear::LinearReportSpace lastLinearReportSpace{nonlinear::LinearReportSpace::full_system};