feat(newton): first newton solver implementation
This commit is contained in:
@@ -3,10 +3,14 @@ module;
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
module mean_field;
|
||||
|
||||
@@ -20,6 +24,99 @@ namespace {
|
||||
|
||||
using PressureDomain = mean_field::field::FieldDomainT<mean_field::field::Enthalpy>;
|
||||
|
||||
using Rejection = mean_field::operators::PressureForcePreparationRejection;
|
||||
using Reason = mean_field::operators::PressureForcePreparationRejectionReason;
|
||||
|
||||
[[nodiscard]] Rejection equation_of_state_rejection(const mean_field::eos::EvaluationErrorCode code) noexcept {
|
||||
return {.reason = Reason::equation_of_state, .equationOfStateCode = code};
|
||||
}
|
||||
|
||||
[[nodiscard]] Rejection mapping_rejection(const mean_field::mapping::MappingStatus status) {
|
||||
MFEM_VERIFY(
|
||||
status != mean_field::mapping::MappingStatus::invalid_dimension,
|
||||
"Prepared pressure-force mapping reported an invariant dimension mismatch."
|
||||
);
|
||||
return {.reason = Reason::invalid_mapping, .mappingStatus = status};
|
||||
}
|
||||
|
||||
[[nodiscard]] Rejection non_finite_rejection() noexcept {
|
||||
return {.reason = Reason::non_finite_arithmetic};
|
||||
}
|
||||
|
||||
[[nodiscard]] int encode_rejection(const std::optional<Rejection> &rejection) noexcept {
|
||||
if (!rejection.has_value()) {
|
||||
return 0;
|
||||
}
|
||||
switch (rejection->reason) {
|
||||
case Reason::equation_of_state:
|
||||
return static_cast<int>(rejection->equationOfStateCode) + 1;
|
||||
case Reason::invalid_mapping:
|
||||
return 128 + static_cast<int>(rejection->mappingStatus);
|
||||
case Reason::non_finite_arithmetic:
|
||||
default:
|
||||
return 256;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] Rejection decode_rejection(const int encoded) {
|
||||
if (encoded >= 256) {
|
||||
return non_finite_rejection();
|
||||
}
|
||||
if (encoded >= 128) {
|
||||
return mapping_rejection(static_cast<mean_field::mapping::MappingStatus>(encoded - 128));
|
||||
}
|
||||
return equation_of_state_rejection(static_cast<mean_field::eos::EvaluationErrorCode>(encoded - 1));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<Rejection> synchronize_rejection(
|
||||
const std::optional<Rejection> &localRejection,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
const int localEncoded = encode_rejection(localRejection);
|
||||
int globalEncoded = 0;
|
||||
if (MPI_Allreduce(&localEncoded, &globalEncoded, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||
throw std::runtime_error("PreparedPressureForceOperator could not synchronize candidate validity.");
|
||||
}
|
||||
if (globalEncoded == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return decode_rejection(globalEncoded);
|
||||
}
|
||||
|
||||
[[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;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool matrix_is_finite(const mfem::DenseMatrix &matrix) noexcept {
|
||||
for (int row = 0; row < matrix.Height(); ++row) {
|
||||
for (int column = 0; column < matrix.Width(); ++column) {
|
||||
if (!std::isfinite(matrix(row, column))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[noreturn]] void throw_rejection(const Rejection &rejection) {
|
||||
switch (rejection.reason) {
|
||||
case Reason::equation_of_state:
|
||||
throw mean_field::eos::EvaluationError(
|
||||
rejection.equationOfStateCode, "PreparedPressureForceOperator encountered invalid thermodynamic data."
|
||||
);
|
||||
case Reason::invalid_mapping:
|
||||
throw std::domain_error("PreparedPressureForceOperator encountered an invalid mapped domain.");
|
||||
case Reason::non_finite_arithmetic:
|
||||
default:
|
||||
throw std::domain_error("PreparedPressureForceOperator produced non-finite arithmetic.");
|
||||
}
|
||||
}
|
||||
|
||||
void verify_required_spaces(const mean_field::fem::FEM &f) {
|
||||
MFEM_VERIFY(f.mesh != nullptr, "PreparedPressureForceOperator requires a mesh.");
|
||||
|
||||
@@ -296,11 +393,26 @@ namespace mean_field::operators {
|
||||
const context::pressure_force::PressureForceStateView &state,
|
||||
const context::pressure_force::PressureForceDependencies &dependencies
|
||||
) {
|
||||
auto result = TryPrepare(state, dependencies);
|
||||
if (!result.has_value()) {
|
||||
throw_rejection(result.error());
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
std::expected<
|
||||
PreparedPressureForceReport,
|
||||
PressureForcePreparationRejection>
|
||||
PreparedPressureForceOperator::TryPrepare(
|
||||
const context::pressure_force::PressureForceStateView &state,
|
||||
const context::pressure_force::PressureForceDependencies &dependencies
|
||||
) {
|
||||
const bool wasPrepared = m_isPrepared;
|
||||
PreparedPressureForceReport report;
|
||||
|
||||
report.contextReport = m_context.Prepare(state, dependencies);
|
||||
|
||||
if (!report.contextReport.DidAnyWork() && m_isPrepared) {
|
||||
if (!report.contextReport.DidAnyWork() && wasPrepared) {
|
||||
return report;
|
||||
}
|
||||
|
||||
@@ -317,20 +429,38 @@ namespace mean_field::operators {
|
||||
|
||||
m_isPrepared = false;
|
||||
|
||||
if (report.contextReport.preparedStaticDependencies) {
|
||||
if (report.contextReport.preparedStaticDependencies || !wasPrepared) {
|
||||
PrepareStaticPlan();
|
||||
}
|
||||
|
||||
if (report.contextReport.preparedGeometryState) {
|
||||
PrepareGeometry();
|
||||
if (report.contextReport.preparedGeometryState || !wasPrepared) {
|
||||
const auto globalGeometryFailure = synchronize_rejection(PrepareGeometry(), m_fem.enthalpyFes->GetComm());
|
||||
if (globalGeometryFailure.has_value()) {
|
||||
return std::unexpected(*globalGeometryFailure);
|
||||
}
|
||||
}
|
||||
|
||||
if (report.contextReport.preparedMaterialState) {
|
||||
PrepareMaterialState();
|
||||
std::optional<PressureForcePreparationRejection> localMaterialFailure;
|
||||
if (report.contextReport.preparedMaterialState || !wasPrepared) {
|
||||
localMaterialFailure = PrepareMaterialState();
|
||||
}
|
||||
|
||||
const auto globalMaterialFailure = synchronize_rejection(localMaterialFailure, m_fem.enthalpyFes->GetComm());
|
||||
if (globalMaterialFailure.has_value()) {
|
||||
return std::unexpected(*globalMaterialFailure);
|
||||
}
|
||||
|
||||
if (report.contextReport.preparedMaterialState || !wasPrepared) {
|
||||
FinalizeDisplacementJacobianPreparation();
|
||||
|
||||
AssembleCachedResidual();
|
||||
const auto globalAssemblyFailure =
|
||||
synchronize_rejection(AssembleCachedResidual(), m_fem.enthalpyFes->GetComm());
|
||||
if (globalAssemblyFailure.has_value()) {
|
||||
return std::unexpected(*globalAssemblyFailure);
|
||||
}
|
||||
|
||||
++m_enthalpyJacobianStatistics.preparations;
|
||||
++m_displacementJacobianStatistics.preparations;
|
||||
|
||||
++m_residualPreparationCount;
|
||||
|
||||
@@ -455,7 +585,7 @@ namespace mean_field::operators {
|
||||
}
|
||||
}
|
||||
|
||||
void PreparedPressureForceOperator::PrepareGeometry() {
|
||||
std::optional<PressureForcePreparationRejection> PreparedPressureForceOperator::PrepareGeometry() {
|
||||
mfem::Vector displacementLocal;
|
||||
|
||||
true_to_local(*m_fem.displacementFes, m_baseDisplacementTrue, displacementLocal);
|
||||
@@ -526,21 +656,18 @@ namespace mean_field::operators {
|
||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
mappingStatus == mapping::MappingStatus::valid,
|
||||
"Stateless mapping failed while preparing "
|
||||
"pressure-force geometry. Element: "
|
||||
<< data.elementId << ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
|
||||
);
|
||||
if (mappingStatus != mapping::MappingStatus::valid) {
|
||||
return mapping_rejection(mappingStatus);
|
||||
}
|
||||
|
||||
const double quadratureWeight = mappingContext.quadrature.weight;
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(quadratureWeight) && quadratureWeight > 0.0,
|
||||
"Prepared pressure-force geometry encountered an "
|
||||
"invalid quadrature weight."
|
||||
);
|
||||
if (!std::isfinite(quadratureWeight)) {
|
||||
return mapping_rejection(mapping::MappingStatus::non_finite_result);
|
||||
}
|
||||
if (quadratureWeight <= 0.0) {
|
||||
return mapping_rejection(mapping::MappingStatus::non_positive_determinant);
|
||||
}
|
||||
|
||||
data.quadratureWeights(quadraturePoint) = quadratureWeight;
|
||||
|
||||
@@ -559,11 +686,15 @@ namespace mean_field::operators {
|
||||
physicalTestGradient.SetSize(referenceTestGradient.Height(), mappingContext.quadrature.J_inv.Width());
|
||||
|
||||
mfem::Mult(referenceTestGradient, mappingContext.quadrature.J_inv, physicalTestGradient);
|
||||
if (!matrix_is_finite(physicalTestGradient)) {
|
||||
return non_finite_rejection();
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void PreparedPressureForceOperator::PrepareMaterialState() {
|
||||
std::optional<PressureForcePreparationRejection> PreparedPressureForceOperator::PrepareMaterialState() {
|
||||
mfem::Vector enthalpyLocal;
|
||||
|
||||
true_to_local(*m_fem.enthalpyFes, m_baseEnthalpyTrue, enthalpyLocal);
|
||||
@@ -574,6 +705,7 @@ namespace mean_field::operators {
|
||||
const int dimension = m_fem.mesh->Dimension();
|
||||
|
||||
const mfem::Ordering::Type displacementOrdering = m_fem.displacementFes->GetOrdering();
|
||||
std::optional<PressureForcePreparationRejection> materialFailure;
|
||||
|
||||
for (ElementPAData &data : m_elements) {
|
||||
enthalpyLocal.GetSubVector(data.enthalpyDofs, elementEnthalpy);
|
||||
@@ -619,6 +751,19 @@ namespace mean_field::operators {
|
||||
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
|
||||
const double enthalpy = quadratureEnthalpy(quadraturePoint);
|
||||
|
||||
if (!std::isfinite(enthalpy)) {
|
||||
materialFailure = equation_of_state_rejection(eos::EvaluationErrorCode::nonfinite_input);
|
||||
data.pressure(quadraturePoint) = 0.0;
|
||||
data.pressureDerivative(quadraturePoint) = 0.0;
|
||||
continue;
|
||||
}
|
||||
if (enthalpy < 0.0) {
|
||||
materialFailure = equation_of_state_rejection(eos::EvaluationErrorCode::outside_domain);
|
||||
data.pressure(quadraturePoint) = 0.0;
|
||||
data.pressureDerivative(quadraturePoint) = 0.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
const dimensions::SpecificEnthalpyValue specificEnthalpy{enthalpy};
|
||||
const double pressure =
|
||||
eos::evaluate<eos::quantity::Pressure>(m_equationOfState, specificEnthalpy).value();
|
||||
@@ -631,11 +776,12 @@ namespace mean_field::operators {
|
||||
|
||||
const double quadratureWeight = data.quadratureWeights(quadraturePoint);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(pressure) && std::isfinite(pressureDerivative),
|
||||
"Prepared pressure-force material state encountered "
|
||||
"a non-finite EOS value."
|
||||
);
|
||||
if (!std::isfinite(pressure) || !std::isfinite(pressureDerivative)) {
|
||||
materialFailure = equation_of_state_rejection(eos::EvaluationErrorCode::nonfinite_result);
|
||||
data.pressure(quadraturePoint) = 0.0;
|
||||
data.pressureDerivative(quadraturePoint) = 0.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
data.pressure(quadraturePoint) = pressure;
|
||||
|
||||
@@ -659,19 +805,32 @@ namespace mean_field::operators {
|
||||
const double weightedTestGradient =
|
||||
quadratureWeight * physicalTestGradient(scalarDof, component);
|
||||
|
||||
data.elementResidual(vectorDof) -= pressure * weightedTestGradient;
|
||||
const double residualContribution = pressure * weightedTestGradient;
|
||||
if (!std::isfinite(weightedTestGradient) || !std::isfinite(residualContribution)) {
|
||||
materialFailure = non_finite_rejection();
|
||||
continue;
|
||||
}
|
||||
|
||||
data.elementResidual(vectorDof) -= residualContribution;
|
||||
|
||||
for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount; ++enthalpyDof) {
|
||||
data.enthalpyJacobian(vectorDof, enthalpyDof) -=
|
||||
pressureDerivative * weightedTestGradient *
|
||||
data.enthalpyBasis(quadraturePoint, enthalpyDof);
|
||||
const double jacobianContribution = pressureDerivative * weightedTestGradient *
|
||||
data.enthalpyBasis(quadraturePoint, enthalpyDof);
|
||||
if (!std::isfinite(jacobianContribution)) {
|
||||
materialFailure = non_finite_rejection();
|
||||
continue;
|
||||
}
|
||||
data.enthalpyJacobian(vectorDof, enthalpyDof) -= jacobianContribution;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++m_enthalpyJacobianStatistics.preparations;
|
||||
if (!vector_is_finite(data.elementResidual) || !matrix_is_finite(data.enthalpyJacobian)) {
|
||||
materialFailure = non_finite_rejection();
|
||||
}
|
||||
}
|
||||
return materialFailure;
|
||||
}
|
||||
|
||||
void PreparedPressureForceOperator::FinalizeDisplacementJacobianPreparation() {
|
||||
@@ -700,11 +859,9 @@ namespace mean_field::operators {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
++m_displacementJacobianStatistics.preparations;
|
||||
}
|
||||
|
||||
void PreparedPressureForceOperator::AssembleCachedResidual() {
|
||||
std::optional<PressureForcePreparationRejection> PreparedPressureForceOperator::AssembleCachedResidual() {
|
||||
mfem::Vector localResidual(m_fem.displacementFes->GetVSize());
|
||||
|
||||
localResidual = 0.0;
|
||||
@@ -729,6 +886,10 @@ namespace mean_field::operators {
|
||||
* FieldDofMap::gather does not resize its destination.
|
||||
*/
|
||||
m_displacementMap.gather(m_fullDisplacementAction, m_cachedResidual);
|
||||
if (!vector_is_finite(m_fullDisplacementAction) || !vector_is_finite(m_cachedResidual)) {
|
||||
return non_finite_rejection();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void PreparedPressureForceOperator::BuildResidual(mfem::Vector &residual) const {
|
||||
@@ -816,15 +977,12 @@ namespace mean_field::operators {
|
||||
mfem::Vector elementDisplacementVariation;
|
||||
mfem::Vector elementAction;
|
||||
|
||||
mfem::DenseMatrix referenceDisplacementDShape;
|
||||
mfem::DenseMatrix referenceDisplacementJacobian;
|
||||
mfem::DenseMatrix inverseElementJacobianVariation;
|
||||
mfem::DenseMatrix matrixTemporary;
|
||||
mfem::DenseMatrix physicalTestGradientVariation;
|
||||
|
||||
const int dimension = m_fem.mesh->Dimension();
|
||||
|
||||
const mfem::Ordering::Type displacementOrdering = m_fem.displacementFes->GetOrdering();
|
||||
const int dimension = m_fem.mesh->Dimension();
|
||||
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
MFEM_VERIFY(
|
||||
@@ -849,14 +1007,13 @@ namespace mean_field::operators {
|
||||
|
||||
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
|
||||
|
||||
const mapping::ElementDisplacementData directionData =
|
||||
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacementVariation);
|
||||
const int quadraturePointCount = data.integrationRule->GetNPoints();
|
||||
|
||||
const int quadraturePointCount = data.integrationRule->GetNPoints();
|
||||
const int scalarDisplacementDofCount = displacementElement.GetDof();
|
||||
|
||||
const int scalarDisplacementDofCount = displacementElement.GetDof();
|
||||
|
||||
const mfem::DenseMatrix &directionDofs = directionData.GetDofMatrix();
|
||||
const mfem::DenseMatrix directionDofs(
|
||||
elementDisplacementVariation.GetData(), scalarDisplacementDofCount, dimension
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
static_cast<int>(data.baseMappingContexts.size()) == quadraturePointCount &&
|
||||
@@ -869,17 +1026,15 @@ namespace mean_field::operators {
|
||||
|
||||
elementAction = 0.0;
|
||||
|
||||
referenceDisplacementDShape.SetSize(scalarDisplacementDofCount, dimension);
|
||||
referenceDisplacementJacobian.SetSize(dimension, dimension);
|
||||
inverseElementJacobianVariation.SetSize(dimension, dimension);
|
||||
matrixTemporary.SetSize(dimension, dimension);
|
||||
physicalTestGradientVariation.SetSize(scalarDisplacementDofCount, dimension);
|
||||
|
||||
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
|
||||
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
|
||||
const mfem::DenseMatrix &referenceTestGradient = data.referenceTestGradients[quadraturePoint];
|
||||
|
||||
displacementElement.CalcDShape(integrationPoint, referenceDisplacementDShape);
|
||||
mfem::MultAtB(directionDofs, referenceDisplacementDShape, referenceDisplacementJacobian);
|
||||
mfem::MultAtB(directionDofs, referenceTestGradient, referenceDisplacementJacobian);
|
||||
|
||||
const mfem::DenseMatrix &inverseElementJacobian =
|
||||
data.baseMappingContexts[quadraturePoint].quadrature.J_inv;
|
||||
@@ -893,39 +1048,35 @@ namespace mean_field::operators {
|
||||
mfem::Mult(matrixTemporary, inverseElementJacobian, inverseElementJacobianVariation);
|
||||
inverseElementJacobianVariation *= -1.0;
|
||||
|
||||
mfem::Mult(
|
||||
data.referenceTestGradients[quadraturePoint], inverseElementJacobianVariation,
|
||||
physicalTestGradientVariation
|
||||
);
|
||||
mfem::Mult(referenceTestGradient, inverseElementJacobianVariation, physicalTestGradientVariation);
|
||||
|
||||
const mfem::DenseMatrix &physicalTestGradient = data.physicalTestGradients[quadraturePoint];
|
||||
const double quadratureWeight = data.quadratureWeights(quadraturePoint);
|
||||
const double pressure = data.pressure(quadraturePoint);
|
||||
|
||||
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
const int vectorDof = vector_dof_index(
|
||||
displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension
|
||||
);
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
const double *variationColumn =
|
||||
physicalTestGradientVariation.GetData() + component * scalarDisplacementDofCount;
|
||||
const double *physicalColumn =
|
||||
physicalTestGradient.GetData() + component * scalarDisplacementDofCount;
|
||||
double *actionColumn = elementAction.GetData() + component * scalarDisplacementDofCount;
|
||||
|
||||
const double gradientWeightVariation = data.quadratureWeights(quadraturePoint) *
|
||||
physicalTestGradientVariation(scalarDof, component) +
|
||||
data.quadratureWeights(quadraturePoint) *
|
||||
logarithmicJacobianVariation *
|
||||
physicalTestGradient(scalarDof, component);
|
||||
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
|
||||
const double gradientWeightVariation =
|
||||
quadratureWeight * variationColumn[scalarDof] +
|
||||
quadratureWeight * logarithmicJacobianVariation * physicalColumn[scalarDof];
|
||||
const double contribution = pressure * gradientWeightVariation;
|
||||
|
||||
const double contribution = data.pressure(quadraturePoint) * gradientWeightVariation;
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(gradientWeightVariation) && std::isfinite(contribution),
|
||||
"Prepared pressure-force displacement "
|
||||
"Jacobian encountered a non-finite "
|
||||
"contribution."
|
||||
);
|
||||
|
||||
elementAction(vectorDof) -= contribution;
|
||||
actionColumn[scalarDof] -= contribution;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MFEM_VERIFY(
|
||||
vector_is_finite(elementAction),
|
||||
"Prepared pressure-force displacement Jacobian encountered a non-finite element action."
|
||||
);
|
||||
|
||||
if (data.displacementDofTransformation != nullptr) {
|
||||
data.displacementDofTransformation->TransformDual(elementAction);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user