feat(newton): first newton solver implementation
This commit is contained in:
@@ -3,10 +3,15 @@ module;
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <limits>
|
||||
#include <mfem.hpp>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <mpi.h>
|
||||
|
||||
module mean_field;
|
||||
|
||||
import :operators.prepared_barotropic_closure;
|
||||
@@ -148,6 +153,154 @@ namespace {
|
||||
|
||||
return *resolution.integration_rule;
|
||||
}
|
||||
|
||||
using BarotropicRejection = mean_field::operators::BarotropicClosurePreparationRejection;
|
||||
using BarotropicRejectionReason = mean_field::operators::BarotropicClosurePreparationRejectionReason;
|
||||
|
||||
[[nodiscard]] bool vector_is_finite(const mfem::Vector &vector) {
|
||||
for (int index = 0; index < vector.Size(); ++index) {
|
||||
if (!std::isfinite(vector(index))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rejections are selected by preparation phase, then by an explicit
|
||||
* detail priority. An earlier phase wins: mapping, quadrature algebra,
|
||||
* then EOS evaluation. Never depend on the declaration order or the
|
||||
* underlying integer representation of either public enum.
|
||||
*/
|
||||
[[nodiscard]] int mapping_status_priority(const mean_field::mapping::MappingStatus status) {
|
||||
using Status = mean_field::mapping::MappingStatus;
|
||||
switch (status) {
|
||||
case Status::non_positive_determinant:
|
||||
return 7;
|
||||
case Status::non_finite_result:
|
||||
return 6;
|
||||
case Status::non_finite_input:
|
||||
return 5;
|
||||
case Status::outside_reference_domain:
|
||||
return 4;
|
||||
case Status::at_compactified_infinity:
|
||||
return 3;
|
||||
case Status::invalid_reference_radius:
|
||||
return 2;
|
||||
case Status::valid:
|
||||
throw std::logic_error("A valid mapping cannot be a barotropic candidate rejection.");
|
||||
case Status::invalid_dimension:
|
||||
throw std::logic_error("A mapping dimension error cannot be a barotropic candidate rejection.");
|
||||
}
|
||||
throw std::logic_error("Unknown mapping status in barotropic candidate rejection.");
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::mapping::MappingStatus mapping_status_from_priority(const int priority) {
|
||||
using Status = mean_field::mapping::MappingStatus;
|
||||
switch (priority) {
|
||||
case 7:
|
||||
return Status::non_positive_determinant;
|
||||
case 6:
|
||||
return Status::non_finite_result;
|
||||
case 5:
|
||||
return Status::non_finite_input;
|
||||
case 4:
|
||||
return Status::outside_reference_domain;
|
||||
case 3:
|
||||
return Status::at_compactified_infinity;
|
||||
case 2:
|
||||
return Status::invalid_reference_radius;
|
||||
default:
|
||||
throw std::logic_error("Invalid synchronized mapping priority for barotropic preparation.");
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] int eos_error_priority(const mean_field::eos::EvaluationErrorCode code) {
|
||||
using Code = mean_field::eos::EvaluationErrorCode;
|
||||
switch (code) {
|
||||
case Code::outside_domain:
|
||||
return 3;
|
||||
case Code::nonfinite_input:
|
||||
return 2;
|
||||
case Code::nonfinite_result:
|
||||
return 1;
|
||||
case Code::unsupported_relation:
|
||||
case Code::unsupported_derivative:
|
||||
case Code::wrong_input_count:
|
||||
case Code::wrong_input_quantity:
|
||||
throw std::logic_error("A structural EOS error cannot be a barotropic candidate rejection.");
|
||||
}
|
||||
throw std::logic_error("Unknown EOS error in barotropic candidate rejection.");
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::eos::EvaluationErrorCode eos_error_from_priority(const int priority) {
|
||||
using Code = mean_field::eos::EvaluationErrorCode;
|
||||
switch (priority) {
|
||||
case 3:
|
||||
return Code::outside_domain;
|
||||
case 2:
|
||||
return Code::nonfinite_input;
|
||||
case 1:
|
||||
return Code::nonfinite_result;
|
||||
default:
|
||||
throw std::logic_error("Invalid synchronized EOS priority for barotropic preparation.");
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] int rejection_priority(const BarotropicRejection &rejection) {
|
||||
switch (rejection.reason) {
|
||||
case BarotropicRejectionReason::mapping_failure:
|
||||
return 300 + mapping_status_priority(rejection.mappingStatus);
|
||||
case BarotropicRejectionReason::invalid_quadrature_data:
|
||||
return 200;
|
||||
case BarotropicRejectionReason::equation_of_state:
|
||||
return 100 + eos_error_priority(rejection.equationOfStateError);
|
||||
}
|
||||
throw std::logic_error("Unknown barotropic candidate-rejection reason.");
|
||||
}
|
||||
|
||||
[[nodiscard]] BarotropicRejection rejection_from_priority(const int priority) {
|
||||
if (priority >= 300) {
|
||||
return {
|
||||
.reason = BarotropicRejectionReason::mapping_failure,
|
||||
.mappingStatus = mapping_status_from_priority(priority - 300)
|
||||
};
|
||||
}
|
||||
if (priority == 200) {
|
||||
return {.reason = BarotropicRejectionReason::invalid_quadrature_data};
|
||||
}
|
||||
if (priority >= 100) {
|
||||
return {
|
||||
.reason = BarotropicRejectionReason::equation_of_state,
|
||||
.equationOfStateError = eos_error_from_priority(priority - 100)
|
||||
};
|
||||
}
|
||||
throw std::logic_error("Invalid synchronized barotropic candidate-rejection priority.");
|
||||
}
|
||||
|
||||
void retain_higher_priority_rejection(
|
||||
std::optional<BarotropicRejection> ¤t,
|
||||
const BarotropicRejection candidate
|
||||
) {
|
||||
if (!current.has_value() || rejection_priority(candidate) > rejection_priority(*current)) {
|
||||
current = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<BarotropicRejection> synchronize_rejection(
|
||||
const std::optional<BarotropicRejection> &local,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
const int localPriority = local.has_value() ? rejection_priority(*local) : 0;
|
||||
int globalPriority = 0;
|
||||
if (MPI_Allreduce(&localPriority, &globalPriority, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||
throw std::runtime_error("PreparedBarotropicClosureOperator could not synchronize candidate validity.");
|
||||
}
|
||||
if (globalPriority == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return rejection_from_priority(globalPriority);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators {
|
||||
@@ -256,6 +409,29 @@ namespace mean_field::operators {
|
||||
PreparedBarotropicClosureReport PreparedBarotropicClosureOperator::Prepare(
|
||||
const context::barotropic::BarotropicClosureStateView &state,
|
||||
const context::barotropic::BarotropicClosureDependencies &dependencies
|
||||
) {
|
||||
auto result = TryPrepare(state, dependencies);
|
||||
if (!result.has_value()) {
|
||||
const BarotropicClosurePreparationRejection &rejection = result.error();
|
||||
switch (rejection.reason) {
|
||||
case BarotropicClosurePreparationRejectionReason::equation_of_state:
|
||||
throw eos::EvaluationError(
|
||||
rejection.equationOfStateError,
|
||||
"PreparedBarotropicClosureOperator encountered invalid thermodynamic data."
|
||||
);
|
||||
case BarotropicClosurePreparationRejectionReason::invalid_quadrature_data:
|
||||
throw std::domain_error("PreparedBarotropicClosureOperator encountered non-finite quadrature data.");
|
||||
case BarotropicClosurePreparationRejectionReason::mapping_failure:
|
||||
throw std::domain_error("PreparedBarotropicClosureOperator could not map the candidate geometry.");
|
||||
}
|
||||
throw std::logic_error("Unknown barotropic candidate-rejection reason.");
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
BarotropicClosurePreparationResult PreparedBarotropicClosureOperator::TryPrepare(
|
||||
const context::barotropic::BarotropicClosureStateView &state,
|
||||
const context::barotropic::BarotropicClosureDependencies &dependencies
|
||||
) {
|
||||
PreparedBarotropicClosureReport report;
|
||||
report.contextReport = m_context.Prepare(state, dependencies);
|
||||
@@ -296,6 +472,7 @@ namespace mean_field::operators {
|
||||
|
||||
mfem::Vector densityShape;
|
||||
mfem::Vector enthalpyShape;
|
||||
std::optional<BarotropicClosurePreparationRejection> localRejection;
|
||||
|
||||
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
|
||||
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
|
||||
@@ -385,11 +562,16 @@ namespace mean_field::operators {
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
mappingStatus == mapping::MappingStatus::valid,
|
||||
"Stateless mapping failed while preparing the barotropic closure operator. Element: "
|
||||
<< elementId << ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
|
||||
mappingStatus != mapping::MappingStatus::invalid_dimension,
|
||||
"Stateless mapping reported a dimension error while preparing the barotropic closure operator."
|
||||
);
|
||||
if (mappingStatus != mapping::MappingStatus::valid) {
|
||||
retain_higher_priority_rejection(
|
||||
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::mapping_failure,
|
||||
.mappingStatus = mappingStatus}
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
MFEM_VERIFY(
|
||||
!mappingContext.mapping.compactified,
|
||||
@@ -406,6 +588,13 @@ namespace mean_field::operators {
|
||||
densityElement.CalcShape(integrationPoint, densityShape);
|
||||
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
|
||||
|
||||
if (!vector_is_finite(densityShape) || !vector_is_finite(enthalpyShape)) {
|
||||
retain_higher_priority_rejection(
|
||||
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::invalid_quadrature_data}
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int densityDof = 0; densityDof < densityDofCount; ++densityDof) {
|
||||
data.densityBasis(quadraturePoint, densityDof) = densityShape(densityDof);
|
||||
}
|
||||
@@ -416,6 +605,35 @@ namespace mean_field::operators {
|
||||
const double density = elementBaseDensity * densityShape;
|
||||
const double enthalpy = elementBaseEnthalpy * enthalpyShape;
|
||||
const double quadratureWeight = mappingContext.quadrature.weight;
|
||||
|
||||
if (!std::isfinite(quadratureWeight) || quadratureWeight <= 0.0) {
|
||||
retain_higher_priority_rejection(
|
||||
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::invalid_quadrature_data}
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!std::isfinite(density)) {
|
||||
retain_higher_priority_rejection(
|
||||
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::invalid_quadrature_data}
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (!std::isfinite(enthalpy)) {
|
||||
retain_higher_priority_rejection(
|
||||
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::equation_of_state,
|
||||
.equationOfStateError = eos::EvaluationErrorCode::nonfinite_input}
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (enthalpy < 0.0) {
|
||||
retain_higher_priority_rejection(
|
||||
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::equation_of_state,
|
||||
.equationOfStateError = eos::EvaluationErrorCode::outside_domain}
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const dimensions::SpecificEnthalpyValue specificEnthalpy{enthalpy};
|
||||
const double eosDensity =
|
||||
eos::evaluate<eos::quantity::Density>(m_equationOfState, specificEnthalpy).value();
|
||||
@@ -425,18 +643,34 @@ namespace mean_field::operators {
|
||||
)
|
||||
.value();
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(quadratureWeight) && quadratureWeight > 0.0 && std::isfinite(eosDensity) &&
|
||||
std::isfinite(enthalpyDerivative),
|
||||
"PreparedBarotropicClosureOperator encountered invalid quadrature data."
|
||||
);
|
||||
if (!std::isfinite(eosDensity) || !std::isfinite(enthalpyDerivative)) {
|
||||
retain_higher_priority_rejection(
|
||||
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::equation_of_state,
|
||||
.equationOfStateError = eos::EvaluationErrorCode::nonfinite_result}
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const double weightedResidual = quadratureWeight * (density - eosDensity);
|
||||
const double weightedEnthalpyDerivative = quadratureWeight * enthalpyDerivative;
|
||||
if (!std::isfinite(weightedResidual) || !std::isfinite(weightedEnthalpyDerivative)) {
|
||||
retain_higher_priority_rejection(
|
||||
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::invalid_quadrature_data}
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
data.quadratureWeights(quadraturePoint) = quadratureWeight;
|
||||
data.weightedResidual(quadraturePoint) = quadratureWeight * (density - eosDensity);
|
||||
data.weightedEnthalpyDerivative(quadraturePoint) = quadratureWeight * enthalpyDerivative;
|
||||
data.weightedResidual(quadraturePoint) = weightedResidual;
|
||||
data.weightedEnthalpyDerivative(quadraturePoint) = weightedEnthalpyDerivative;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto globalRejection = synchronize_rejection(localRejection, m_fem.densityFes->GetComm());
|
||||
globalRejection.has_value()) {
|
||||
return std::unexpected(*globalRejection);
|
||||
}
|
||||
|
||||
MFEM_VERIFY(!m_elements.empty(), "PreparedBarotropicClosureOperator found no elements in Density::Support.");
|
||||
|
||||
m_isPrepared = true;
|
||||
|
||||
Reference in New Issue
Block a user