Add bounded coupled damping and prediction-based attempt selection

This commit is contained in:
2026-09-18 10:45:45 -04:00
parent 46249d0758
commit 422c46bcbf
11 changed files with 2296 additions and 101 deletions

View File

@@ -47,6 +47,21 @@ namespace stellar_numerical_strategy_test {
static_assert(!MakesTrialNumerics<nonlinear::PolynomialEnthalpy, nonlinear::EliminatedGravity,
nonlinear::CoupledCurvatureCorrection>);
static_assert(!MakesTrialNumerics<nonlinear::SampledEnthalpy, nonlinear::MonolithicGravity, UnregisteredTrial>);
static_assert(nonlinear::CoupledTrialNumerics<nonlinear::CoupledCurvatureCorrection>);
static_assert(nonlinear::CoupledTrialNumerics<nonlinear::BacktrackedCoupledCurvatureCorrection>);
static_assert(!nonlinear::CoupledTrialNumerics<nonlinear::UncorrectedTrial>);
static_assert(!nonlinear::TrialPathNumerics<solver::BacktrackedCoupledStep>);
static_assert(MakesTrialNumerics<nonlinear::SampledEnthalpy, nonlinear::MonolithicGravity,
nonlinear::BacktrackedCoupledCurvatureCorrection>);
static_assert(MakesTrialNumerics<nonlinear::PolynomialEnthalpy, nonlinear::GravityCurvatureCorrection,
nonlinear::BacktrackedCoupledCurvatureCorrection>);
static_assert(!MakesTrialNumerics<nonlinear::SampledEnthalpy, nonlinear::EliminatedGravity,
nonlinear::BacktrackedCoupledCurvatureCorrection>);
static_assert(!MakesTrialNumerics<nonlinear::PolynomialEnthalpy, nonlinear::EliminatedGravity,
nonlinear::BacktrackedCoupledCurvatureCorrection>);
static_assert(std::same_as<typename decltype(nonlinear::NewtonNumerics{nonlinear::SampledEnthalpy{},
nonlinear::MonolithicGravity{}, nonlinear::BacktrackedCoupledCurvatureCorrection{}})::TrialType,
nonlinear::BacktrackedCoupledCurvatureCorrection>);
[[nodiscard]] double matchingCentralDensity() {
@@ -186,6 +201,11 @@ TEST_CASE("Typed numerical policies preserve the existing Newton configuration",
const auto selected = nonlinear::Newton(nonlinear::NewtonOptions{}, nonlinear::NormalizedResidualMetric{},
nonlinear::NewtonNumerics{nonlinear::SampledEnthalpy{}, nonlinear::GravityCurvatureCorrection{}});
CHECK(selected.numerics().gravity.gravity.control.relativeTolerance == 1.e-10);
const nonlinear::BacktrackedCoupledCurvatureCorrection damped;
CHECK(damped.search.maximumSteps == 4);
CHECK(damped.search.reduction == .5);
CHECK(damped.attempt.maximumExcessRatio == 1.0);
CHECK(damped.maximumAttemptsPerIteration == 1);
}
TEST_CASE("Production gravity strategies retain optional model borders and restore rejected trials",
@@ -300,25 +320,28 @@ TEST_CASE("Polynomial admissibility is invoked by each production gravity strate
check(nonlinear::EliminatedGravity{permissiveGravity()});
REQUIRE(context.isReady());
checkPreserved(original, snapshot(context));
{
const auto checkTrial = [&](auto correction) {
const auto report = evaluate(context, nonlinear::NewtonNumerics{polynomial,
nonlinear::MonolithicGravity{}, nonlinear::CoupledCurvatureCorrection{}});
nonlinear::MonolithicGravity{}, correction});
CHECK_FALSE(report.converged());
CHECK(report.diagnostics().numericalStrategies.polynomialChecks > 0);
CHECK(report.diagnostics().acceptedNonlinearIterations == 0);
CHECK(report.lastAcceptedCheckpointView().valid());
}
};
checkTrial(nonlinear::CoupledCurvatureCorrection{});
REQUIRE(context.isReady());
checkPreserved(original, snapshot(context));
checkTrial(nonlinear::BacktrackedCoupledCurvatureCorrection{});
REQUIRE(context.isReady());
checkPreserved(original, snapshot(context));
}
TEST_CASE("Coupled trial selection keeps the outer linear report and rejected stellar checkpoint",
"[solver][numerical_strategies][coupled_trial_correction][rollback]") {
"[solver][numerical_strategies][coupled_trial_correction][backtracked][rollback]") {
using namespace stellar_numerical_strategy_test;
auto context = makeContext<false>();
const auto original = snapshot(context);
const auto check = [&](auto gravity) {
const nonlinear::CoupledCurvatureCorrection correction{};
const auto check = [&](auto gravity, auto correction) {
const auto report = evaluate(context, nonlinear::NewtonNumerics{
nonlinear::SampledEnthalpy{}, gravity, correction});
CHECK_FALSE(report.converged());
@@ -329,18 +352,36 @@ TEST_CASE("Coupled trial selection keeps the outer linear report and rejected st
CHECK(outer.control.absoluteTolerance == 1.e100);
CHECK(report.diagnostics().numericalStrategies.lastLinearReportSpace == nonlinear::LinearReportSpace::full_system);
const auto &auxiliary = report.diagnostics().numericalStrategies.coupledTrial;
REQUIRE(auxiliary.currentSolve.has_value());
CHECK(auxiliary.attempts == 1);
CHECK(auxiliary.kept == 0);
CHECK(auxiliary.fallbacks == 1);
CHECK(auxiliary.currentSolve->linearSolve.control.relativeTolerance == correction.correction.control.relativeTolerance);
CHECK(auxiliary.currentSolve->linearSolve.control.absoluteTolerance == correction.correction.control.absoluteTolerance);
if constexpr (std::same_as<decltype(correction), nonlinear::CoupledCurvatureCorrection>) {
REQUIRE(auxiliary.currentSolve.has_value());
CHECK(auxiliary.attempts == 1);
CHECK(auxiliary.fallbacks == 1);
CHECK(auxiliary.currentSolve->linearSolve.control.relativeTolerance == correction.correction.control.relativeTolerance);
CHECK(auxiliary.currentSolve->linearSolve.control.absoluteTolerance == correction.correction.control.absoluteTolerance);
} else {
// The captured zero outer correction predicts no decrease. The
// optional selector must leave the auxiliary budget untouched.
CHECK(auxiliary.status == solver::CoupledTrialAttemptStatus::prediction_deferred);
CHECK(auxiliary.deferredAttempts == 1);
CHECK(auxiliary.attempts == 0);
CHECK_FALSE(auxiliary.currentSolve.has_value());
CHECK(auxiliary.candidateCount == 0);
CHECK(auxiliary.candidates.empty());
CHECK(std::isnan(auxiliary.selectedBeta));
}
CHECK(report.lastAcceptedCheckpointView().valid());
};
check(nonlinear::MonolithicGravity{});
check(nonlinear::MonolithicGravity{}, nonlinear::CoupledCurvatureCorrection{});
REQUIRE(context.isReady());
checkPreserved(original, snapshot(context));
check(nonlinear::GravityCurvatureCorrection{permissiveGravity()});
check(nonlinear::GravityCurvatureCorrection{permissiveGravity()}, nonlinear::CoupledCurvatureCorrection{});
REQUIRE(context.isReady());
checkPreserved(original, snapshot(context));
check(nonlinear::MonolithicGravity{}, nonlinear::BacktrackedCoupledCurvatureCorrection{});
REQUIRE(context.isReady());
checkPreserved(original, snapshot(context));
check(nonlinear::GravityCurvatureCorrection{permissiveGravity()}, nonlinear::BacktrackedCoupledCurvatureCorrection{});
REQUIRE(context.isReady());
checkPreserved(original, snapshot(context));
}