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

@@ -19,6 +19,7 @@ export module mean_field:solver.newton;
export import :deformation.safe_newton_step;
export import :solver.linear_backend;
export import :solver.newton_numerics;
export namespace mean_field::solver::nonlinear {
/*
@@ -143,11 +144,12 @@ export namespace mean_field::solver::nonlinear {
return {.residualNorm = residualNorm, .merit = 0.5 * residualNorm * residualNorm};
}
template <typename Metric = NormalizedResidualMetric>
template <typename Metric = NormalizedResidualMetric, NewtonNumericalConfiguration Numerics = NewtonNumerics<>>
requires std::move_constructible<std::remove_cvref_t<Metric>>
class Newton final {
public:
using MetricType = std::remove_cvref_t<Metric>;
using NumericsType = std::remove_cvref_t<Numerics>;
Newton()
requires std::default_initializable<MetricType>
@@ -167,10 +169,12 @@ export namespace mean_field::solver::nonlinear {
Newton(
NewtonOptions options,
MetricType metric
MetricType metric,
NumericsType numerics = {}
)
: m_options(std::move(options)),
m_metric(std::move(metric)) {
m_metric(std::move(metric)),
m_numerics(std::move(numerics)) {
m_options.Validate();
}
@@ -182,9 +186,12 @@ export namespace mean_field::solver::nonlinear {
return m_metric;
}
[[nodiscard]] const NumericsType &numerics() const noexcept { return m_numerics; }
private:
NewtonOptions m_options;
[[no_unique_address]] MetricType m_metric;
[[no_unique_address]] NumericsType m_numerics;
};
Newton() -> Newton<NormalizedResidualMetric>;
@@ -196,9 +203,12 @@ export namespace mean_field::solver::nonlinear {
Metric
) -> Newton<std::remove_cvref_t<Metric>>;
template <typename Metric, typename Numerics>
Newton(NewtonOptions, Metric, Numerics) -> Newton<std::remove_cvref_t<Metric>, std::remove_cvref_t<Numerics>>;
template <typename Candidate> struct IsNewtonConfiguration : std::false_type { };
template <typename Metric> struct IsNewtonConfiguration<Newton<Metric>> : std::true_type { };
template <typename Metric, typename Numerics> struct IsNewtonConfiguration<Newton<Metric, Numerics>> : std::true_type { };
template <typename Candidate>
concept NewtonConfiguration = IsNewtonConfiguration<std::remove_cvref_t<Candidate>>::value;
@@ -237,7 +247,8 @@ export namespace mean_field::solver::nonlinear {
inadmissible_state,
non_finite_state,
non_finite_residual,
insufficient_decrease
insufficient_decrease,
auxiliary_solve_failure
};
struct AfterLineSearchTrial final {

View File

@@ -0,0 +1,83 @@
module;
#include <concepts>
#include <type_traits>
#include <utility>
export module mean_field:solver.newton_numerics;
export import :solver.enthalpy_admissibility;
export import :solver.linear_backend;
export namespace mean_field::solver::nonlinear {
struct SampledEnthalpy final { };
struct PolynomialEnthalpy final {
EnthalpyAdmissibilityOptions options{};
};
struct MonolithicGravity final { };
// These tolerances control an auxiliary equation solve, not the physical
// model's acceptance threshold. The complete residual remains authoritative.
struct GravitySolveOptions final {
LinearSolveControl control{.relativeTolerance = 1.0e-10, .absoluteTolerance = 1.0e-14,
.maximumIterations = 300};
linear::FGMRESOptions backend{.restartLength = 80};
void Validate() const { control.Validate(); backend.Validate(); }
};
struct GravityCurvatureCorrection final {
GravitySolveOptions gravity{};
};
struct EliminatedGravity final {
GravitySolveOptions gravity{};
};
template <typename Candidate>
concept EnthalpyNumerics = std::same_as<Candidate, SampledEnthalpy> ||
std::same_as<Candidate, PolynomialEnthalpy>;
template <typename Candidate>
concept GravityNumerics = std::same_as<Candidate, MonolithicGravity> ||
std::same_as<Candidate, GravityCurvatureCorrection> ||
std::same_as<Candidate, EliminatedGravity>;
// Types select algorithms; tolerances and the mesh-dependent work sizes
// remain runtime data. Adding a strategy cannot silently change a model's
// equation set or impose an additional specification such as rho_c.
template <EnthalpyNumerics Enthalpy = SampledEnthalpy, GravityNumerics Gravity = MonolithicGravity>
struct NewtonNumerics final {
using EnthalpyType = Enthalpy;
using GravityType = Gravity;
[[no_unique_address]] Enthalpy enthalpy{};
[[no_unique_address]] Gravity gravity{};
};
template <typename Enthalpy, typename Gravity>
NewtonNumerics(Enthalpy, Gravity) -> NewtonNumerics<Enthalpy, Gravity>;
template <typename> struct IsNewtonNumerics : std::false_type { };
template <typename Enthalpy, typename Gravity>
struct IsNewtonNumerics<NewtonNumerics<Enthalpy, Gravity>> : std::true_type { };
template <typename Candidate>
concept NewtonNumericalConfiguration = IsNewtonNumerics<std::remove_cvref_t<Candidate>>::value;
struct NumericalStrategyDiagnostics final {
int polynomialChecks{0};
int directionRepairs{0};
double lastRepairFraction{0.0};
double lastMeritDirectionalDerivative{0.0};
int gravitySolves{0};
int gravityIterations{0};
int gravityFailures{0};
double gravitySolveSeconds{0.0};
double lastGravityTrueResidual{0.0};
double lastGravityCorrectionNorm{0.0};
double lastGravityPreparedDefect{0.0};
double initialGravityResidual{0.0};
double completedInitialGravityResidual{0.0};
double lastLiftedRelativeTrueResidual{0.0};
};
}

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
)
) {

View File

@@ -8,6 +8,7 @@ export module mean_field:solver.stellar_equilibrium_types;
export import :deformation.safe_newton_step;
export import :solver.linear_backend;
export import :solver.newton_numerics;
export namespace mean_field::solver {
enum class StellarEquilibriumFailureReason : std::uint8_t {
@@ -60,5 +61,6 @@ export namespace mean_field::solver {
double totalRollbackSeconds{0.0};
std::optional<deformation::LargestSafeNewtonStepSizeEstimate> lastGeometryPreflight;
std::optional<LinearSolveReport> lastLinearSolve;
nonlinear::NumericalStrategyDiagnostics numericalStrategies{};
};
} // namespace mean_field::solver

View File

@@ -0,0 +1,396 @@
module;
#include <algorithm>
#include <array>
#include <cmath>
#include <concepts>
#include <limits>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
#include <mpi.h>
export module mean_field:solver.stellar_newton_strategy;
export import :solver.newton_numerics;
export import :solver.gravity_reduction;
export import :solver.stellar_enthalpy_strategy;
namespace mean_field::solver::detail {
struct NewtonStrategyStorage {
virtual ~NewtonStrategyStorage() = default;
};
inline double StrategyDot(const mfem::Vector &a, const mfem::Vector &b, const MPI_Comm communicator) {
if (a.Size() != b.Size()) { throw std::invalid_argument("Strategy dot product has incompatible vectors."); }
double local = a * b;
double global = 0.0;
if (MPI_Allreduce(&local, &global, 1, MPI_DOUBLE, MPI_SUM, communicator) != MPI_SUCCESS) {
throw std::runtime_error("A numerical strategy could not reduce its vector product.");
}
return global;
}
inline double StrategyNorm(const mfem::Vector &vector, const MPI_Comm communicator) {
return std::sqrt(StrategyDot(vector, vector, communicator));
}
template <nonlinear::NewtonNumericalConfiguration Recipe>
void RequireCollectivelyIdenticalNumerics(const Recipe &recipe, const MPI_Comm communicator) {
using Enthalpy = typename Recipe::EnthalpyType;
using Gravity = typename Recipe::GravityType;
std::array<int, 8> integers{};
std::array<double, 2> reals{};
integers[0] = std::same_as<Enthalpy, nonlinear::PolynomialEnthalpy>;
integers[1] = std::same_as<Gravity, nonlinear::GravityCurvatureCorrection> ? 1 :
std::same_as<Gravity, nonlinear::EliminatedGravity> ? 2 : 0;
if constexpr (std::same_as<Enthalpy, nonlinear::PolynomialEnthalpy>) {
integers[2] = recipe.enthalpy.options.maximumSubdivisionDepth;
integers[3] = recipe.enthalpy.options.maximumSubcells;
}
if constexpr (!std::same_as<Gravity, nonlinear::MonolithicGravity>) {
const auto &options = recipe.gravity.gravity;
integers[4] = options.control.maximumIterations;
integers[5] = options.backend.restartLength;
integers[6] = options.backend.printLevel;
reals = {options.control.relativeTolerance, options.control.absoluteTolerance};
}
integers[7] = std::isfinite(reals[0]) && std::isfinite(reals[1]);
std::array<int, 8> minimumIntegers{}, maximumIntegers{};
std::array<double, 2> minimumReals{}, maximumReals{};
if (MPI_Allreduce(integers.data(), minimumIntegers.data(), 8, MPI_INT, MPI_MIN, communicator) != MPI_SUCCESS ||
MPI_Allreduce(integers.data(), maximumIntegers.data(), 8, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS ||
MPI_Allreduce(reals.data(), minimumReals.data(), 2, MPI_DOUBLE, MPI_MIN, communicator) != MPI_SUCCESS ||
MPI_Allreduce(reals.data(), maximumReals.data(), 2, MPI_DOUBLE, MPI_MAX, communicator) != MPI_SUCCESS) {
throw std::runtime_error("Newton could not compare numerical strategies across ranks.");
}
if (minimumIntegers != maximumIntegers || minimumReals != maximumReals ||
minimumIntegers[7] == 0) {
throw std::invalid_argument("Newton numerical strategies and controls must agree on every rank.");
}
if constexpr (!std::same_as<Gravity, nonlinear::MonolithicGravity>) { recipe.gravity.gravity.Validate(); }
}
template <typename State, typename Policy> class PreparedGravityStrategy;
template <typename State>
class PreparedGravityStrategy<State, nonlinear::MonolithicGravity> final {
public:
PreparedGravityStrategy(State &, const nonlinear::MonolithicGravity &) { }
bool Initialize(State &, nonlinear::NumericalStrategyDiagnostics &) { return true; }
LinearSolveReport Solve(State &state, const LinearSolveControl &control) {
return state.linearBackend->Solve(state.linearRightHandSide, state.normalizedCorrection, control);
}
bool CaptureDirection(State &) { return true; }
auto PrepareTrial(State &state, double) { return state.PrepareTrial(); }
bool TrialSolveSucceeded() const noexcept { return true; }
void CopyDiagnostics(nonlinear::NumericalStrategyDiagnostics &) const { }
};
template <typename State>
class StellarGravityWorkspace {
public:
using Operation = std::remove_cvref_t<decltype(*std::declval<State>().normalizedOperator)>;
using Inverse = std::remove_cvref_t<decltype(*std::declval<State>().normalizedInverse)>;
using Problem = std::remove_cvref_t<decltype(std::declval<State>().Problem())>;
using Subsystem = PreparedGravitySubsystem<Operation, Inverse>;
StellarGravityWorkspace(State &state, const nonlinear::GravitySolveOptions &options)
: m_options(options),
subsystem(*state.normalizedOperator, *state.normalizedInverse,
MakeGravityBlockPartition<typename Problem::FormType>(state.Problem().GetManifest().layout()),
state.Problem().GetCommunicator(), linear::FGMRES(options.backend)),
gravityResidual(subsystem.GetPartition().GravityResidualSize()),
gravityCorrection(subsystem.GetPartition().GravityValueSize()),
expectedResidual(subsystem.GetPartition().GravityResidualSize()),
gravityAction(subsystem.GetPartition().GravityResidualSize()),
desiredResidual(subsystem.GetPartition().GravityResidualSize()),
fullCorrection(state.Problem().StateSize()), fullAction(state.Problem().EquationSize()) {
m_options.Validate();
}
bool TrialSolveSucceeded() const noexcept { return trialSolveSucceeded; }
void CopyDiagnostics(nonlinear::NumericalStrategyDiagnostics &report) const {
report.gravitySolves = subsystem.GetSolveCount();
report.gravityIterations = subsystem.GetIterationCount();
report.gravityFailures = subsystem.GetFailureCount();
report.gravitySolveSeconds = subsystem.GetSolveSeconds();
if (const auto &last = subsystem.GetLastSolveReport(); last.has_value()) {
report.lastGravityTrueResidual = last->trueResidualNorm;
}
report.lastGravityCorrectionNorm = lastCorrectionNorm;
report.lastGravityPreparedDefect = lastPreparedDefect;
report.lastLiftedRelativeTrueResidual = liftedRelativeDefect;
}
protected:
void Refresh(State &state) {
// Preparing a trial invalidates both normalized and physical
// inverses. Refreshing only the restricted wrapper would hide this.
state.physicalInverse->Refresh();
subsystem.Refresh();
}
bool SolveCompletion(State &state) {
subsystem.GetPartition().GatherGravityResiduals(state.trialNormalizedResidual, gravityResidual);
gravityResidual *= -1.0;
desiredResidual = 0.0;
gravityCorrection = 0.0;
lastSolve = subsystem.Solve(gravityResidual, gravityCorrection, m_options.control);
return lastSolve.Converged();
}
auto ApplyCorrection(State &state) {
const auto &partition = subsystem.GetPartition();
partition.InjectGravityValues(gravityCorrection, fullCorrection);
state.normalizedOperator->Mult(fullCorrection, fullAction);
partition.GatherGravityResiduals(state.trialNormalizedResidual, expectedResidual);
partition.GatherGravityResiduals(fullAction, gravityAction);
expectedResidual += gravityAction;
lastCorrectionNorm = StrategyNorm(gravityCorrection, subsystem.GetCommunicator());
partition.AddGravityValues(gravityCorrection, state.trialNormalizedState);
state.normalizedOperator->DenormalizeState(state.trialNormalizedState, state.candidatePhysicalState);
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());
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;
lastPreparedDefect = targetDefect;
}
return preparation;
}
nonlinear::GravitySolveOptions m_options;
Subsystem subsystem;
mfem::Vector gravityResidual, gravityCorrection, expectedResidual, gravityAction, desiredResidual;
mfem::Vector fullCorrection, fullAction;
LinearSolveReport lastSolve{};
bool trialSolveSucceeded{true};
double lastCorrectionNorm{0.0};
double lastPreparedDefect{0.0};
double liftedRelativeDefect{0.0};
};
template <typename State>
class PreparedGravityStrategy<State, nonlinear::GravityCurvatureCorrection> final
: public StellarGravityWorkspace<State> {
using Base = StellarGravityWorkspace<State>;
public:
PreparedGravityStrategy(State &state, const nonlinear::GravityCurvatureCorrection &policy)
: Base(state, policy.gravity), m_curvature(this->subsystem) { }
bool Initialize(State &, nonlinear::NumericalStrategyDiagnostics &) { return true; }
LinearSolveReport Solve(State &state, const LinearSolveControl &control) {
return state.linearBackend->Solve(state.linearRightHandSide, state.normalizedCorrection, control);
}
bool CaptureDirection(State &state) {
this->subsystem.Refresh();
m_curvature.CaptureBase(state.acceptedNormalizedResidual, state.normalizedCorrection);
return true;
}
auto PrepareTrial(State &state, const double alpha) {
this->trialSolveSucceeded = true;
auto preparation = state.PrepareTrial();
if (!preparation) { return preparation; }
this->Refresh(state);
m_curvature.BuildLinearPrediction(alpha, this->desiredResidual);
this->lastSolve = m_curvature.SolveCorrection(alpha, state.trialNormalizedResidual,
this->gravityCorrection, this->m_options.control);
this->trialSolveSucceeded = this->lastSolve.Converged();
if (!this->trialSolveSucceeded) { return preparation; }
return this->ApplyCorrection(state);
}
private:
PreparedGravityCurvatureCorrection<typename Base::Subsystem> m_curvature;
};
template <typename State>
class PreparedGravityStrategy<State, nonlinear::EliminatedGravity> final
: public StellarGravityWorkspace<State> {
using Base = StellarGravityWorkspace<State>;
using ReducedOperator = GravityReducedOperator<typename Base::Subsystem>;
using ReducedInverse = GravityReducedPreconditioner<typename Base::Subsystem, typename Base::Inverse>;
using Configuration = std::remove_cvref_t<decltype(std::declval<State>().linearBackend->GetConfiguration())>;
using Backend = PreparedLinearBackendType<Configuration, ReducedOperator, ReducedInverse>;
public:
PreparedGravityStrategy(State &state, const nonlinear::EliminatedGravity &policy)
: Base(state, policy.gravity), m_operator(this->subsystem, policy.gravity.control),
m_inverse(m_operator, *state.normalizedInverse),
m_backend(prepareLinearBackend(state.linearBackend->GetConfiguration(), m_operator, m_inverse,
state.Problem().GetCommunicator())),
m_rhs(m_operator.Height()), m_correction(m_operator.Width()) { }
bool Initialize(State &state, nonlinear::NumericalStrategyDiagnostics &diagnostics) {
const auto &partition = this->subsystem.GetPartition();
partition.GatherGravityResiduals(state.acceptedNormalizedResidual, this->gravityResidual);
diagnostics.initialGravityResidual = StrategyNorm(this->gravityResidual, this->subsystem.GetCommunicator());
state.trialNormalizedState = state.acceptedNormalizedState;
state.trialNormalizedResidual = state.acceptedNormalizedResidual;
this->trialSolveSucceeded = this->SolveCompletion(state);
if (!this->trialSolveSucceeded) { return false; }
const auto preparation = this->ApplyCorrection(state);
if (!preparation || !this->trialSolveSucceeded ||
!std::isfinite(StrategyNorm(state.trialNormalizedResidual, this->subsystem.GetCommunicator()))) { return false; }
partition.GatherGravityResiduals(state.trialNormalizedResidual, this->gravityResidual);
diagnostics.completedInitialGravityResidual =
StrategyNorm(this->gravityResidual, this->subsystem.GetCommunicator());
state.PrepareTrialCommit();
state.CommitPreparedTrial(preparation->minimumJacobianDeterminant);
this->subsystem.Refresh();
return true;
}
LinearSolveReport Solve(State &state, const LinearSolveControl &control) {
this->subsystem.Refresh();
try {
auto inner = m_operator.TryCondenseRightHandSide(state.linearRightHandSide, m_rhs);
if (!inner.Converged()) { return inner; }
this->subsystem.GetPartition().GatherRetainedValues(state.normalizedCorrection, m_correction);
auto report = m_backend.Solve(m_rhs, m_correction, control);
if (!report.Converged()) { return report; }
inner = m_operator.TryLiftCorrection(m_correction, state.linearRightHandSide, state.normalizedCorrection);
if (!inner.Converged()) { return inner; }
state.normalizedOperator->Mult(state.normalizedCorrection, this->fullAction);
this->fullAction -= state.linearRightHandSide;
const auto communicator = this->subsystem.GetCommunicator();
const double rhsNorm = StrategyNorm(state.linearRightHandSide, communicator);
const double defect = StrategyNorm(this->fullAction, communicator);
this->liftedRelativeDefect = rhsNorm > 0.0 ? defect / rhsNorm : (defect == 0.0 ? 0.0 : INFINITY);
// Inner Krylov truncation makes Schur actions approximate.
// The original equations decide whether the lifted result is
// actually an admissible inexact Newton correction.
// Keep backend recurrence/initial/true norms in their reduced
// coordinates. The separate lifted diagnostic reports the
// original-system check; these are different linear systems.
if (!std::isfinite(defect) || defect > control.ConvergenceThreshold(rhsNorm)) {
report.status = std::isfinite(defect) ? LinearSolveStatus::backend_failure : LinearSolveStatus::non_finite;
}
return report;
} catch (const GravityReductionFailure &failure) {
return failure.GetReport();
}
}
bool CaptureDirection(State &state) {
// A positivity repair changes retained coordinates. Lift that
// repaired tangent again so gravity follows the reduced manifold.
this->subsystem.GetPartition().GatherRetainedValues(state.normalizedCorrection, m_correction);
this->lastSolve = m_operator.TryLiftCorrection(m_correction, state.linearRightHandSide,
state.normalizedCorrection);
return this->lastSolve.Converged();
}
auto PrepareTrial(State &state, double) {
this->trialSolveSucceeded = true;
auto preparation = state.PrepareTrial();
if (!preparation) { return preparation; }
this->Refresh(state);
this->trialSolveSucceeded = this->SolveCompletion(state);
if (!this->trialSolveSucceeded) { return preparation; }
return this->ApplyCorrection(state);
}
private:
ReducedOperator m_operator;
ReducedInverse m_inverse;
Backend m_backend;
mfem::Vector m_rhs, m_correction;
};
template <typename State, typename Policy> class PreparedEnthalpyStrategy;
template <typename State>
class PreparedEnthalpyStrategy<State, nonlinear::SampledEnthalpy> final {
public:
PreparedEnthalpyStrategy(State &, const nonlinear::SampledEnthalpy &) { }
bool Check(const mfem::Vector &, nonlinear::NumericalStrategyDiagnostics &) { return true; }
bool Repair(State &, nonlinear::NumericalStrategyDiagnostics &) { return true; }
};
template <typename State>
class PreparedEnthalpyStrategy<State, nonlinear::PolynomialEnthalpy> final {
public:
PreparedEnthalpyStrategy(State &state, const nonlinear::PolynomialEnthalpy &policy)
: m_prepared(state, policy.options) { }
bool Check(const mfem::Vector &state, nonlinear::NumericalStrategyDiagnostics &diagnostics) {
++diagnostics.polynomialChecks;
return m_prepared.CheckState(state).status == PolynomialSignStatus::certified_nonnegative;
}
bool Repair(State &state, nonlinear::NumericalStrategyDiagnostics &diagnostics) {
const auto report = m_prepared.RepairDirection(state);
diagnostics.lastRepairFraction = report.chosenFraction;
diagnostics.lastMeritDirectionalDerivative = report.meritDirectionalDerivative;
if (report.chosenFraction > 0.0) { ++diagnostics.directionRepairs; }
return report.status == EnthalpyDirectionRepairStatus::feasible &&
std::isfinite(report.meritDirectionalDerivative) && report.meritDirectionalDerivative < 0.0;
}
private:
PreparedStellarEnthalpyStrategy<State> m_prepared;
};
template <typename State, nonlinear::NewtonNumericalConfiguration Recipe>
class PreparedNewtonStrategy final : public NewtonStrategyStorage {
public:
using Enthalpy = typename Recipe::EnthalpyType;
using Gravity = typename Recipe::GravityType;
static constexpr bool altersDirection = std::same_as<Enthalpy, nonlinear::PolynomialEnthalpy>;
PreparedNewtonStrategy(State &state, const Recipe &recipe)
: m_enthalpy(state, recipe.enthalpy), m_gravity(state, recipe.gravity) { }
bool CheckState(const mfem::Vector &state, nonlinear::NumericalStrategyDiagnostics &diagnostics) {
return m_enthalpy.Check(state, diagnostics);
}
bool Initialize(State &state, nonlinear::NumericalStrategyDiagnostics &diagnostics) {
const bool ready = m_gravity.Initialize(state, diagnostics);
m_gravity.CopyDiagnostics(diagnostics);
return ready;
}
LinearSolveReport Solve(State &state, const LinearSolveControl &control,
nonlinear::NumericalStrategyDiagnostics &diagnostics) {
auto report = m_gravity.Solve(state, control);
m_gravity.CopyDiagnostics(diagnostics);
return report;
}
bool CaptureDirection(State &state, nonlinear::NumericalStrategyDiagnostics &diagnostics) {
if (!m_enthalpy.Repair(state, diagnostics)) { return false; }
if constexpr (!std::same_as<Gravity, nonlinear::EliminatedGravity> || altersDirection) {
if (!m_gravity.CaptureDirection(state)) { m_gravity.CopyDiagnostics(diagnostics); return false; }
}
if constexpr (altersDirection && std::same_as<Gravity, nonlinear::EliminatedGravity>) {
mfem::Vector action(state.Problem().EquationSize());
state.normalizedOperator->Mult(state.normalizedCorrection, action);
diagnostics.lastMeritDirectionalDerivative =
StrategyDot(state.acceptedNormalizedResidual, action, state.Problem().GetCommunicator());
if (!std::isfinite(diagnostics.lastMeritDirectionalDerivative) ||
diagnostics.lastMeritDirectionalDerivative >= 0.0) { return false; }
}
m_gravity.CopyDiagnostics(diagnostics);
return true;
}
auto PrepareTrial(State &state, const double alpha, nonlinear::NumericalStrategyDiagnostics &diagnostics) {
auto preparation = m_gravity.PrepareTrial(state, alpha);
m_gravity.CopyDiagnostics(diagnostics);
return preparation;
}
bool TrialSolveSucceeded() const noexcept { return m_gravity.TrialSolveSucceeded(); }
private:
PreparedEnthalpyStrategy<State, Enthalpy> m_enthalpy;
PreparedGravityStrategy<State, Gravity> m_gravity;
};
}