feat(newton): first newton solver implementation

This commit is contained in:
2026-09-08 06:36:39 -04:00
parent 76818f2f82
commit b3c04d507a
98 changed files with 20397 additions and 11040 deletions

View File

@@ -3,6 +3,8 @@ module;
#include <array>
#include <cmath>
#include <cstdint>
#include <expected>
#include <stdexcept>
#include <utility>
#include <mfem.hpp>
@@ -24,6 +26,133 @@ namespace {
using StellarRootForm = mean_field::utils::blocks::surface_deformed_stellar_equilibrium_form;
[[nodiscard]] mean_field::operators::StellarEquilibriumPreparationRejection with_preparation_stage(
mean_field::operators::StellarEquilibriumPreparationRejection rejection,
const mean_field::operators::StellarEquilibriumPreparationStage stage
) noexcept {
rejection.stage = stage;
return rejection;
}
[[nodiscard]] mean_field::operators::StellarEquilibriumPreparationRejection
make_thermodynamic_rejection(const mean_field::eos::EvaluationErrorCode code) {
using Failure = mean_field::operators::StellarEquilibriumPreparationRejection;
using Reason = mean_field::operators::StellarEquilibriumPreparationRejectionReason;
switch (code) {
case mean_field::eos::EvaluationErrorCode::outside_domain:
return Failure{.reason = Reason::thermodynamic_domain, .thermodynamicErrorCode = code};
case mean_field::eos::EvaluationErrorCode::nonfinite_input:
case mean_field::eos::EvaluationErrorCode::nonfinite_result:
return Failure{.reason = Reason::non_finite_thermodynamics, .thermodynamicErrorCode = code};
default:
throw std::logic_error(
"A non-retryable equation-of-state error was incorrectly returned as a stellar trial rejection."
);
}
}
[[nodiscard]] mean_field::operators::StellarEquilibriumPreparationRejection
make_mapping_rejection(const mean_field::mapping::MappingStatus status) {
using Failure = mean_field::operators::StellarEquilibriumPreparationRejection;
using Reason = mean_field::operators::StellarEquilibriumPreparationRejectionReason;
return Failure{
.reason = status == mean_field::mapping::MappingStatus::non_positive_determinant
? Reason::inverted_geometry
: Reason::non_finite_geometry
};
}
[[nodiscard]] mean_field::operators::StellarEquilibriumPreparationRejection make_gravity_rejection(
const mean_field::operators::context::gravity_field::GravityFieldPreparationRejection &rejection
) {
using ChildReason = mean_field::operators::context::gravity_field::GravityFieldPreparationRejectionReason;
if (rejection.reason == ChildReason::invalid_mapping) {
return make_mapping_rejection(rejection.mappingStatus);
}
return {.reason = mean_field::operators::StellarEquilibriumPreparationRejectionReason::non_finite_physics};
}
[[nodiscard]] mean_field::operators::StellarEquilibriumPreparationRejection
make_barotropic_rejection(const mean_field::operators::BarotropicClosurePreparationRejection &rejection) {
using ChildReason = mean_field::operators::BarotropicClosurePreparationRejectionReason;
switch (rejection.reason) {
case ChildReason::mapping_failure:
return make_mapping_rejection(rejection.mappingStatus);
case ChildReason::equation_of_state:
return make_thermodynamic_rejection(rejection.equationOfStateError);
case ChildReason::invalid_quadrature_data:
return {.reason = mean_field::operators::StellarEquilibriumPreparationRejectionReason::non_finite_physics};
}
throw std::logic_error("An unknown barotropic trial rejection reached the stellar root.");
}
[[nodiscard]] mean_field::operators::StellarEquilibriumPreparationRejection
make_displacement_rejection(const mean_field::operators::DisplacementResidualPreparationRejection &rejection) {
using ChildReason = mean_field::operators::DisplacementResidualPreparationRejectionReason;
using ChildSource = mean_field::operators::DisplacementResidualPreparationRejectionSource;
using RootStage = mean_field::operators::StellarEquilibriumPreparationStage;
const RootStage stage = [&] {
switch (rejection.source) {
case ChildSource::pressure:
return RootStage::pressure_force;
case ChildSource::gravity:
return RootStage::gravity_displacement_force;
case ChildSource::rotation:
return RootStage::rotational_displacement_force;
case ChildSource::composition:
return RootStage::displacement_composition;
}
return RootStage::displacement_residual;
}();
switch (rejection.reason) {
case ChildReason::equation_of_state: {
auto rootRejection = make_thermodynamic_rejection(rejection.equationOfStateCode);
rootRejection.stage = stage;
return rootRejection;
}
case ChildReason::invalid_mapping: {
auto rootRejection = make_mapping_rejection(rejection.mappingStatus);
rootRejection.stage = stage;
return rootRejection;
}
case ChildReason::non_finite_arithmetic:
return {
.reason = mean_field::operators::StellarEquilibriumPreparationRejectionReason::non_finite_physics,
.stage = stage
};
}
throw std::logic_error("An unknown displacement trial rejection reached the stellar root.");
}
[[nodiscard]] mean_field::operators::StellarEquilibriumPreparationRejection
make_mass_rejection(const mean_field::operators::MassNormalizationPreparationRejection &rejection) {
using ChildReason = mean_field::operators::MassNormalizationPreparationRejectionReason;
if (rejection.reason == ChildReason::mapping_failure) {
return make_mapping_rejection(rejection.mappingStatus);
}
return {.reason = mean_field::operators::StellarEquilibriumPreparationRejectionReason::non_finite_physics};
}
[[nodiscard]] mean_field::operators::StellarEquilibriumPreparationRejection
make_hydrostatic_rejection(const mean_field::operators::HydrostaticEquilibriumPreparationRejection &rejection) {
using ChildReason = mean_field::operators::HydrostaticEquilibriumPreparationRejectionReason;
using Failure = mean_field::operators::StellarEquilibriumPreparationRejection;
using Reason = mean_field::operators::StellarEquilibriumPreparationRejectionReason;
switch (rejection.reason) {
case ChildReason::inverted_geometry:
return Failure{.reason = Reason::inverted_geometry};
case ChildReason::non_finite_geometry:
return Failure{.reason = Reason::non_finite_geometry};
case ChildReason::non_finite_residual:
return Failure{.reason = Reason::non_finite_physics};
}
throw std::logic_error("An unknown hydrostatic trial rejection reached the stellar root.");
}
[[nodiscard]] std::array<
int,
StellarRootForm::value_block_count>
@@ -132,6 +261,15 @@ namespace {
}
}
[[nodiscard]] bool vector_is_finite(const mfem::Vector &vector) noexcept {
for (int index = 0; index < vector.Size(); ++index) {
if (!std::isfinite(vector(index))) {
return false;
}
}
return true;
}
void validate_dependency_transition(
const mean_field::operators::StellarEquilibriumDependencyStamp &prepared,
const mean_field::operators::StellarEquilibriumDependencyStamp &requested,
@@ -358,11 +496,12 @@ namespace mean_field::operators {
constructionData.residualSizes,
StellarEquilibriumSpecificationModel{
equationOfState,
surface::Isobaric{
dimensions::PressureValue{surfaceConstraint.descriptor().targetPressure}},
fixedMassConstraint.specification()},
surface::Isobaric{dimensions::PressureValue{surfaceConstraint.descriptor().targetPressure}},
fixedMassConstraint.specification()
},
constructionData.pressureSurfaceRows.size()
),
m_communicator(f.mesh->GetComm()),
m_gravityStateOffsets(constructionData.gravityStateOffsets),
m_gravityContext(
f,
@@ -452,11 +591,38 @@ namespace mean_field::operators {
const mfem::Vector &state,
const StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) {
auto result = TryPrepare(state, dependencies, rotation);
if (!result.has_value()) {
throwStellarEquilibriumPreparationRejection(result.error());
}
return std::move(result).value();
}
StellarEquilibriumPreparationResult<PreparedStellarEquilibriumReport>
PreparedStellarEquilibriumOperator::TryPrepare(
const mfem::Vector &state,
const StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) {
MFEM_VERIFY(
state.Size() == Width(), "PreparedStellarEquilibriumOperator received a state with the wrong size."
);
validate_finite_vector(state, "PreparedStellarEquilibriumOperator received a non-finite state.");
const int localStateIsFinite = vector_is_finite(state) ? 1 : 0;
int globalStateIsFinite = 0;
if (MPI_Allreduce(&localStateIsFinite, &globalStateIsFinite, 1, MPI_INT, MPI_MIN, m_communicator) !=
MPI_SUCCESS) {
throw std::runtime_error("PreparedStellarEquilibriumOperator could not synchronize state validity.");
}
if (globalStateIsFinite == 0) {
m_isPrepared = false;
return std::unexpected(
StellarEquilibriumPreparationRejection{
.reason = StellarEquilibriumPreparationRejectionReason::non_finite_physics
}
);
}
const bool wasPrepared = m_isPrepared;
if (wasPrepared) {
@@ -495,9 +661,9 @@ namespace mean_field::operators {
);
}
m_isPrepared = false;
m_isPrepared = false;
const auto rootState = m_rootManifest.stateView(state);
const auto rootState = m_rootManifest.stateView(state);
const auto reducedDensity = rootState.block(utils::blocks::density_field.mass_term);
const auto surfaceDeformationParameters =
@@ -505,8 +671,7 @@ namespace mean_field::operators {
const auto gravityGradient = rootState.block(utils::blocks::gravity_field.gradient_term);
const auto gravityPotential = rootState.block(utils::blocks::gravity_field.poisson_term);
const auto reducedEnthalpy = rootState.block(utils::blocks::enthalpy_field.specific_term);
const auto bernoulli =
rootState.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
const auto bernoulli = rootState.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
const bool generatedGeometryChanged =
!wasPrepared || dependencies.discretization != m_preparedDependencies.discretization ||
@@ -515,9 +680,28 @@ namespace mean_field::operators {
PreparedStellarEquilibriumReport report;
if (generatedGeometryChanged) {
m_surfaceDeformationParameters = surfaceDeformationParameters;
m_generatedGeometryReport = m_domainDeformation.buildValidatedVolumeDisplacement(
m_surfaceDeformationParameters, m_generatedVolumeDisplacement
);
m_domainDeformation.buildVolumeDisplacement(m_surfaceDeformationParameters, m_generatedVolumeDisplacement);
const deformation::DomainDeformationGeometryReport generatedGeometry =
m_domainDeformation.inspectMappedGeometry(m_generatedVolumeDisplacement);
if (!std::isfinite(generatedGeometry.minimumJacobianDeterminant)) {
return std::unexpected(
StellarEquilibriumPreparationRejection{
.reason = StellarEquilibriumPreparationRejectionReason::non_finite_geometry,
.stage = StellarEquilibriumPreparationStage::generated_geometry,
.minimumJacobianDeterminant = generatedGeometry.minimumJacobianDeterminant
}
);
}
if (!generatedGeometry.isOrientationPreserving()) {
return std::unexpected(
StellarEquilibriumPreparationRejection{
.reason = StellarEquilibriumPreparationRejectionReason::inverted_geometry,
.stage = StellarEquilibriumPreparationStage::generated_geometry,
.minimumJacobianDeterminant = generatedGeometry.minimumJacobianDeterminant
}
);
}
m_generatedGeometryReport = generatedGeometry;
++m_generatedDisplacementDependency.revision;
++m_statistics.generatedGeometryBuilds;
report.generatedVolumeDisplacement = true;
@@ -530,31 +714,68 @@ namespace mean_field::operators {
gravityPotential
);
report.gravity = m_gravityOperator.Prepare(
auto gravityResult = m_gravityOperator.TryPrepare(
m_gravityState, make_gravity_revisions(dependencies, m_generatedDisplacementDependency)
);
if (!gravityResult.has_value()) {
return std::unexpected(with_preparation_stage(
make_gravity_rejection(gravityResult.error()), StellarEquilibriumPreparationStage::gravity
));
}
report.gravity = std::move(gravityResult).value();
report.barotropicClosure = m_barotropicClosureOperator.Prepare(
/*
* Mechanical-force preparation consumes the shared gravity context,
* but it is independent of the closure and hydrostatic rows. Prepare
* it as soon as that dependency is ready so a mapped-force rejection
* does not pay for unrelated candidate rows first.
*/
auto displacementResult = m_displacementOperator.TryPrepare(
{.enthalpy = reducedEnthalpy},
make_displacement_dependencies(dependencies, m_generatedDisplacementDependency), rotation
);
if (!displacementResult.has_value()) {
return std::unexpected(make_displacement_rejection(displacementResult.error()));
}
report.displacement = std::move(displacementResult).value();
auto barotropicClosureResult = m_barotropicClosureOperator.TryPrepare(
{.density = reducedDensity, .enthalpy = reducedEnthalpy, .displacement = m_generatedVolumeDisplacement},
make_barotropic_closure_dependencies(dependencies, m_generatedDisplacementDependency)
);
if (!barotropicClosureResult.has_value()) {
return std::unexpected(with_preparation_stage(
make_barotropic_rejection(barotropicClosureResult.error()),
StellarEquilibriumPreparationStage::barotropic_closure
));
}
report.barotropicClosure = std::move(barotropicClosureResult).value();
report.hydrostatic = m_hydrostaticOperator.Prepare(
auto hydrostaticResult = m_hydrostaticOperator.TryPrepare(
{.enthalpy = reducedEnthalpy,
.gravityPotential = gravityPotential,
.displacement = m_generatedVolumeDisplacement,
.bernoulliConstant = bernoulli(0)},
make_hydrostatic_dependencies(dependencies, m_generatedDisplacementDependency), rotation
);
if (!hydrostaticResult.has_value()) {
return std::unexpected(with_preparation_stage(
make_hydrostatic_rejection(hydrostaticResult.error()),
StellarEquilibriumPreparationStage::hydrostatic_equilibrium
));
}
report.hydrostatic = std::move(hydrostaticResult).value();
report.displacement = m_displacementOperator.Prepare(
{.enthalpy = reducedEnthalpy},
make_displacement_dependencies(dependencies, m_generatedDisplacementDependency), rotation
);
report.massNormalization = m_massNormalizationOperator.Prepare(
auto massNormalizationResult = m_massNormalizationOperator.TryPrepare(
m_fixedMassConstraint, make_mass_dependencies(dependencies, m_generatedDisplacementDependency)
);
if (!massNormalizationResult.has_value()) {
return std::unexpected(with_preparation_stage(
make_mass_rejection(massNormalizationResult.error()),
StellarEquilibriumPreparationStage::mass_normalization
));
}
report.massNormalization = std::move(massNormalizationResult).value();
report.surfaceConstraint = m_surfaceConstraintOperator.Prepare(
reducedEnthalpy, !wasPrepared || dependencies.enthalpy != m_preparedDependencies.enthalpy
@@ -636,7 +857,7 @@ namespace mean_field::operators {
direction, "PreparedStellarEquilibriumOperator received a non-finite Jacobian direction."
);
const auto rootDirection = m_rootManifest.directionView(direction);
const auto rootDirection = m_rootManifest.directionView(direction);
const auto reducedDensityDirection = rootDirection.block(utils::blocks::density_field.mass_term);
const auto surfaceDeformationDirection =
@@ -803,13 +1024,9 @@ namespace mean_field::operators {
const mfem::Vector &densityDirection
) const {
VerifyPrepared();
m_massNormalizationOperator.ApplyDensityJacobianAction(
densityDirection,
m_densityVolumeIntegralAction
);
m_massNormalizationOperator.ApplyDensityJacobianAction(densityDirection, m_densityVolumeIntegralAction);
MFEM_VERIFY(
m_densityVolumeIntegralAction.Size() == 1,
"The density-volume integral must produce one global scalar."
m_densityVolumeIntegralAction.Size() == 1, "The density-volume integral must produce one global scalar."
);
return m_densityVolumeIntegralAction(0);
}
@@ -819,13 +1036,10 @@ namespace mean_field::operators {
) const {
VerifyPrepared();
m_domainDeformation.applyJacobian(
m_surfaceDeformationParameters,
surfaceShapeDirection,
m_volumeDisplacementDirection
m_surfaceDeformationParameters, surfaceShapeDirection, m_volumeDisplacementDirection
);
m_massNormalizationOperator.ApplyDisplacementJacobianAction(
m_volumeDisplacementDirection,
m_densityVolumeIntegralAction
m_volumeDisplacementDirection, m_densityVolumeIntegralAction
);
MFEM_VERIFY(
m_densityVolumeIntegralAction.Size() == 1,