Integrate typed Newton strategies with checked trial gravity and feasibility

This commit is contained in:
2026-09-17 09:45:05 -04:00
parent a7d646d049
commit c393847c6f
8 changed files with 643 additions and 21 deletions

View File

@@ -32,6 +32,7 @@ export import :seed.stellar_equilibrium_projection;
export import :solver.linear_backend;
export import :solver.newton;
export import :solver.stellar_structure;
export import :solver.stellar_newton_strategy;
export namespace mean_field::solver {
template <typename Model, typename Discretization, typename PreconditionerPrescription, typename LinearBackend>
@@ -523,6 +524,10 @@ export namespace mean_field::solver {
std::unique_ptr<PhysicalInverseType> physicalInverse;
std::unique_ptr<NormalizedInverseType> normalizedInverse;
std::unique_ptr<LinearBackendType> linearBackend;
// Only destruction is erased: strategy construction and all
// numerical calls retain their statically checked concrete types.
// Declaring this last keeps borrowed dependencies alive on teardown.
std::unique_ptr<detail::NewtonStrategyStorage> numericalStrategyStorage;
private:
[[nodiscard]] static ProblemType *ProblemPointer(const std::shared_ptr<Storage> &candidate) noexcept {
@@ -1209,9 +1214,37 @@ export namespace mean_field::solver {
const auto &options = m_newton.options();
options.Validate();
detail::RequireCollectivelyIdenticalNewtonOptions(options, communicator);
detail::RequireCollectivelyIdenticalNumerics(m_newton.numerics(), communicator);
state.BeginEvaluation();
using Strategy = detail::PreparedNewtonStrategy<std::remove_reference_t<decltype(state)>,
typename NewtonType::NumericsType>;
static_assert(!Strategy::altersDirection ||
std::same_as<typename NewtonType::MetricType, nonlinear::NormalizedResidualMetric>,
"Polynomial direction repair currently requires the normalized residual merit.");
state.numericalStrategyStorage.reset();
auto preparedStrategy = std::make_unique<Strategy>(state, m_newton.numerics());
auto &strategy = *preparedStrategy;
state.numericalStrategyStorage = std::move(preparedStrategy);
StellarEquilibriumEvaluationDiagnostics diagnostics{};
if (!strategy.CheckState(state.acceptedNormalizedState, diagnostics.numericalStrategies)) {
const auto initial = EvaluateMetric(state.acceptedNormalizedResidual);
diagnostics.initialResidualNorm = diagnostics.finalResidualNorm = initial.residualNorm;
return Failure(state, std::move(diagnostics), StellarEquilibriumFailureReason::inadmissible_state,
"The initial enthalpy polynomial could not be certified nonnegative.");
}
try {
if (!strategy.Initialize(state, diagnostics.numericalStrategies)) {
state.RestoreAccepted();
const auto initial = EvaluateMetric(state.acceptedNormalizedResidual);
diagnostics.initialResidualNorm = diagnostics.finalResidualNorm = initial.residualNorm;
return Failure(state, std::move(diagnostics), StellarEquilibriumFailureReason::linear_solve_failure,
"Initial gravity completion failed its checked inner solve or prepared residual.");
}
} catch (...) {
state.RestoreAccepted();
throw;
}
nonlinear::MetricEvaluation acceptedMetric = EvaluateMetric(state.acceptedNormalizedResidual);
diagnostics.initialResidualNorm = acceptedMetric.residualNorm;
diagnostics.finalResidualNorm = acceptedMetric.residualNorm;
@@ -1240,9 +1273,7 @@ export namespace mean_field::solver {
state.linearRightHandSide = state.acceptedNormalizedResidual;
state.linearRightHandSide *= -1.0;
LinearSolveReport linearReport = state.linearBackend->Solve(
state.linearRightHandSide, state.normalizedCorrection, options.linearSolve
);
LinearSolveReport linearReport = strategy.Solve(state, options.linearSolve, diagnostics.numericalStrategies);
diagnostics.lastLinearSolve = linearReport;
diagnostics.totalLinearSolveSeconds += linearReport.solveSeconds;
@@ -1275,6 +1306,13 @@ export namespace mean_field::solver {
);
}
if (!strategy.CaptureDirection(state, diagnostics.numericalStrategies)) {
NotifyAfter(iteration, nonlinear::IterationDisposition::globalization_failure, false, 0.0, 0,
diagnostics.initialResidualNorm, acceptedMetric, acceptedMetric, linearReport, timings, state);
return Failure(state, std::move(diagnostics), StellarEquilibriumFailureReason::globalization_failure,
"No certified enthalpy repair with a decreasing linear merit was available.");
}
const nonlinear::MetricEvaluation previousMetric = acceptedMetric;
if (options.backtracking.geometryPreflight) {
const Clock::time_point geometryPreflightStart = Clock::now();
@@ -1345,9 +1383,15 @@ export namespace mean_field::solver {
trialDisposition = nonlinear::LineSearchTrialDisposition::non_finite_state;
rejectionSource = "candidate state";
++diagnostics.nonFiniteLineSearchTrials;
} else if (!strategy.CheckState(state.trialNormalizedState, diagnostics.numericalStrategies)) {
rejectionReason = StellarEquilibriumFailureReason::inadmissible_state;
rejectionMessage = "The candidate enthalpy polynomial could not be certified nonnegative.";
trialDisposition = nonlinear::LineSearchTrialDisposition::inadmissible_state;
rejectionSource = "enthalpy polynomial";
++diagnostics.inadmissibleLineSearchTrials;
} else {
const Clock::time_point preparationStart = Clock::now();
auto preparationResult = state.PrepareTrial();
auto preparationResult = strategy.PrepareTrial(state, stepLength, diagnostics.numericalStrategies);
preparationSeconds = std::chrono::duration<double>(Clock::now() - preparationStart).count();
timings.trialPreparationSeconds += preparationSeconds;
diagnostics.totalTrialPreparationSeconds += preparationSeconds;
@@ -1422,6 +1466,12 @@ export namespace mean_field::solver {
minimumJacobianDeterminant = preparationRejection.minimumJacobianDeterminant;
}
}
} else if (!strategy.TrialSolveSucceeded()) {
rejectionReason = StellarEquilibriumFailureReason::linear_solve_failure;
rejectionMessage = "The candidate gravity correction failed its checked solve or prepared residual.";
trialDisposition = nonlinear::LineSearchTrialDisposition::auxiliary_solve_failure;
rejectionSource = "gravity correction";
minimumJacobianDeterminant = preparationResult->minimumJacobianDeterminant;
} else if (!detail::AllStellarRanksSatisfy(
AllFinite(state.trialNormalizedResidual), communicator
)) {
@@ -1453,7 +1503,15 @@ export namespace mean_field::solver {
++diagnostics.nonFiniteLineSearchTrials;
} else if (
detail::AllStellarRanksSatisfy(
AcceptStep(acceptedMetric, trialMetric, stepLength, options.backtracking),
[&] {
if constexpr (Strategy::altersDirection) {
return trialMetric.merit <= acceptedMetric.merit +
options.backtracking.sufficientDecrease * stepLength *
diagnostics.numericalStrategies.lastMeritDirectionalDerivative;
} else {
return AcceptStep(acceptedMetric, trialMetric, stepLength, options.backtracking);
}
}(),
communicator
)
) {