feat(newton): first newton solver implementation
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user