feat(newton): first newton solver implementation
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
module;
|
||||
#include <cmath>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
|
||||
@@ -9,6 +10,30 @@ import :operators.context.gravity_field;
|
||||
namespace {
|
||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
[[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldPreparationRejection
|
||||
make_gravity_field_rejection(const mean_field::operators::HDivMassPreparationRejection &rejection) {
|
||||
using ChildReason = mean_field::operators::HDivMassPreparationRejectionReason;
|
||||
using Failure = mean_field::operators::context::gravity_field::GravityFieldPreparationRejection;
|
||||
using Reason = mean_field::operators::context::gravity_field::GravityFieldPreparationRejectionReason;
|
||||
return Failure{
|
||||
.reason = rejection.reason == ChildReason::invalid_mapping ? Reason::invalid_mapping
|
||||
: Reason::non_finite_arithmetic,
|
||||
.mappingStatus = rejection.mappingStatus
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldPreparationRejection
|
||||
make_gravity_field_rejection(const mean_field::operators::GravitySourcePreparationRejection &rejection) {
|
||||
using ChildReason = mean_field::operators::GravitySourcePreparationRejectionReason;
|
||||
using Failure = mean_field::operators::context::gravity_field::GravityFieldPreparationRejection;
|
||||
using Reason = mean_field::operators::context::gravity_field::GravityFieldPreparationRejectionReason;
|
||||
return Failure{
|
||||
.reason = rejection.reason == ChildReason::invalid_mapping ? Reason::invalid_mapping
|
||||
: Reason::non_finite_arithmetic,
|
||||
.mappingStatus = rejection.mappingStatus
|
||||
};
|
||||
}
|
||||
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finite_element_space,
|
||||
const mfem::Vector &true_vector,
|
||||
@@ -295,7 +320,21 @@ namespace mean_field::operators::context::gravity_field {
|
||||
const DiscretizationRevision discretization_revision,
|
||||
const DisplacementRevision displacement_revision
|
||||
) {
|
||||
return PrepareImpl(
|
||||
auto result = TryPrepareImpl(
|
||||
displacement, discretization_revision, displacement_revision, PreparationMode::linearization
|
||||
);
|
||||
if (!result.has_value()) {
|
||||
throwGravityFieldPreparationRejection(result.error());
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
GravityFieldPreparationResult<GravityFieldGeometryPreparation> GravityFieldGeometryContext::TryPrepare(
|
||||
const mfem::Vector &displacement,
|
||||
const DiscretizationRevision discretization_revision,
|
||||
const DisplacementRevision displacement_revision
|
||||
) {
|
||||
return TryPrepareImpl(
|
||||
displacement, discretization_revision, displacement_revision, PreparationMode::linearization
|
||||
);
|
||||
}
|
||||
@@ -305,10 +344,23 @@ namespace mean_field::operators::context::gravity_field {
|
||||
const DiscretizationRevision discretization_revision,
|
||||
const DisplacementRevision displacement_revision
|
||||
) {
|
||||
return PrepareImpl(displacement, discretization_revision, displacement_revision, PreparationMode::primal);
|
||||
auto result =
|
||||
TryPrepareImpl(displacement, discretization_revision, displacement_revision, PreparationMode::primal);
|
||||
if (!result.has_value()) {
|
||||
throwGravityFieldPreparationRejection(result.error());
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
GravityFieldGeometryPreparation GravityFieldGeometryContext::PrepareImpl(
|
||||
GravityFieldPreparationResult<GravityFieldGeometryPreparation> GravityFieldGeometryContext::TryPreparePrimal(
|
||||
const mfem::Vector &displacement,
|
||||
const DiscretizationRevision discretization_revision,
|
||||
const DisplacementRevision displacement_revision
|
||||
) {
|
||||
return TryPrepareImpl(displacement, discretization_revision, displacement_revision, PreparationMode::primal);
|
||||
}
|
||||
|
||||
GravityFieldPreparationResult<GravityFieldGeometryPreparation> GravityFieldGeometryContext::TryPrepareImpl(
|
||||
const mfem::Vector &displacement,
|
||||
const DiscretizationRevision discretization_revision,
|
||||
const DisplacementRevision displacement_revision,
|
||||
@@ -340,19 +392,24 @@ namespace mean_field::operators::context::gravity_field {
|
||||
return preparation;
|
||||
}
|
||||
|
||||
const auto prepare_mass = [&](PreparedMappedHDivMassOperator &mass_operator) {
|
||||
// The existing child operators may be mutated by a fallible
|
||||
// preparation below. Stop advertising the parent as prepared until
|
||||
// every child has accepted the same candidate and the parent state is
|
||||
// committed.
|
||||
m_is_prepared = false;
|
||||
m_variation_state_prepared = false;
|
||||
|
||||
const auto prepare_mass = [&](PreparedMappedHDivMassOperator &mass_operator) {
|
||||
if (requires_variation) {
|
||||
mass_operator.Prepare(displacement);
|
||||
} else {
|
||||
mass_operator.PreparePrimal(displacement);
|
||||
return mass_operator.TryPrepare(displacement);
|
||||
}
|
||||
return mass_operator.TryPreparePrimal(displacement);
|
||||
};
|
||||
const auto prepare_source = [&](PreparedMappedGravitySourceOperator &source_operator) {
|
||||
if (requires_variation) {
|
||||
source_operator.Prepare(displacement);
|
||||
} else {
|
||||
source_operator.PreparePrimal(displacement);
|
||||
return source_operator.TryPrepare(displacement);
|
||||
}
|
||||
return source_operator.TryPreparePrimal(displacement);
|
||||
};
|
||||
|
||||
if (discretization_changed) {
|
||||
@@ -361,8 +418,14 @@ namespace mean_field::operators::context::gravity_field {
|
||||
auto divergence_operator = make_divergence_operator(m_fem);
|
||||
auto transpose_divergence_operator = std::make_unique<mfem::TransposeOperator>(divergence_operator.get());
|
||||
|
||||
prepare_mass(*mass_operator);
|
||||
prepare_source(*source_operator);
|
||||
auto massResult = prepare_mass(*mass_operator);
|
||||
if (!massResult.has_value()) {
|
||||
return std::unexpected(make_gravity_field_rejection(massResult.error()));
|
||||
}
|
||||
auto sourceResult = prepare_source(*source_operator);
|
||||
if (!sourceResult.has_value()) {
|
||||
return std::unexpected(make_gravity_field_rejection(sourceResult.error()));
|
||||
}
|
||||
|
||||
m_mass_operator = std::move(mass_operator);
|
||||
m_source_operator = std::move(source_operator);
|
||||
@@ -383,8 +446,14 @@ namespace mean_field::operators::context::gravity_field {
|
||||
"operator."
|
||||
);
|
||||
|
||||
prepare_mass(*m_mass_operator);
|
||||
prepare_source(*m_source_operator);
|
||||
auto massResult = prepare_mass(*m_mass_operator);
|
||||
if (!massResult.has_value()) {
|
||||
return std::unexpected(make_gravity_field_rejection(massResult.error()));
|
||||
}
|
||||
auto sourceResult = prepare_source(*m_source_operator);
|
||||
if (!sourceResult.has_value()) {
|
||||
return std::unexpected(make_gravity_field_rejection(sourceResult.error()));
|
||||
}
|
||||
|
||||
preparation.rebuilt_mass_operator = true;
|
||||
preparation.rebuilt_source_operator = true;
|
||||
@@ -510,6 +579,17 @@ namespace mean_field::operators::context::gravity_field {
|
||||
GravityFieldPreparationReport GravityFieldLinearizationContext::Prepare(
|
||||
const GravityFieldStateView &state,
|
||||
const GravityFieldRevisions &revisions
|
||||
) {
|
||||
auto result = TryPrepare(state, revisions);
|
||||
if (!result.has_value()) {
|
||||
throwGravityFieldPreparationRejection(result.error());
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
GravityFieldPreparationResult<GravityFieldPreparationReport> GravityFieldLinearizationContext::TryPrepare(
|
||||
const GravityFieldStateView &state,
|
||||
const GravityFieldRevisions &revisions
|
||||
) {
|
||||
validate_linearization_state(
|
||||
m_density_map, m_geometry_context.GetDisplacementMap(), m_gravity_gradient_map, m_gravity_potential_map,
|
||||
@@ -554,8 +634,17 @@ namespace mean_field::operators::context::gravity_field {
|
||||
|
||||
GravityFieldPreparationReport report;
|
||||
|
||||
report.geometry =
|
||||
m_geometry_context.Prepare(state.displacement, revisions.discretization, revisions.displacement);
|
||||
// Geometry preparation is fallible and may invalidate one of its
|
||||
// prepared children. The linearization context must therefore remain
|
||||
// inaccessible until the complete shared state has been committed.
|
||||
m_is_prepared = false;
|
||||
|
||||
auto geometryResult =
|
||||
m_geometry_context.TryPrepare(state.displacement, revisions.discretization, revisions.displacement);
|
||||
if (!geometryResult.has_value()) {
|
||||
return std::unexpected(geometryResult.error());
|
||||
}
|
||||
report.geometry = std::move(geometryResult).value();
|
||||
|
||||
if (density_changed) {
|
||||
m_density_true.SetSize(m_density_map.full_size());
|
||||
|
||||
@@ -4,6 +4,7 @@ module;
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <mfem.hpp>
|
||||
#include <utility>
|
||||
|
||||
module mean_field;
|
||||
import :operators.gravity_field;
|
||||
@@ -273,6 +274,18 @@ namespace mean_field::operators {
|
||||
context::gravity_field::GravityFieldPreparationReport GravityFieldOperator::Prepare(
|
||||
const mfem::Vector &state,
|
||||
const context::gravity_field::GravityFieldRevisions &revisions
|
||||
) {
|
||||
auto result = TryPrepare(state, revisions);
|
||||
if (!result.has_value()) {
|
||||
context::gravity_field::throwGravityFieldPreparationRejection(result.error());
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
context::gravity_field::GravityFieldPreparationResult<context::gravity_field::GravityFieldPreparationReport>
|
||||
GravityFieldOperator::TryPrepare(
|
||||
const mfem::Vector &state,
|
||||
const context::gravity_field::GravityFieldRevisions &revisions
|
||||
) {
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
@@ -295,7 +308,7 @@ namespace mean_field::operators {
|
||||
const mfem::Vector gravity_potential =
|
||||
make_read_only_value_view(state, m_state_offsets, gravity_potential_block);
|
||||
|
||||
return m_linearization_context.Prepare(
|
||||
return m_linearization_context.TryPrepare(
|
||||
{.density = density,
|
||||
.displacement = displacement,
|
||||
.gravity_gradient = gravity_gradient,
|
||||
|
||||
@@ -2,9 +2,12 @@ module;
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
module mean_field;
|
||||
|
||||
@@ -19,6 +22,70 @@ namespace {
|
||||
|
||||
enum class GravityDisplacementForceAction { residual, density, gravityGradient, displacement, complete };
|
||||
|
||||
using Rejection = mean_field::operators::kernels::GravityDisplacementForceRejection;
|
||||
using Reason = mean_field::operators::kernels::GravityDisplacementForceRejectionReason;
|
||||
using Result = mean_field::operators::kernels::GravityDisplacementForceResult;
|
||||
|
||||
[[nodiscard]] Rejection mapping_rejection(const mean_field::mapping::MappingStatus status) {
|
||||
MFEM_VERIFY(
|
||||
status != mean_field::mapping::MappingStatus::invalid_dimension,
|
||||
"The gravity-displacement-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]] 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]] int encode_rejection(const std::optional<Rejection> &rejection) noexcept {
|
||||
if (!rejection.has_value()) {
|
||||
return 0;
|
||||
}
|
||||
if (rejection->reason == Reason::non_finite_arithmetic) {
|
||||
return 256;
|
||||
}
|
||||
return static_cast<int>(rejection->mappingStatus) + 1;
|
||||
}
|
||||
|
||||
[[nodiscard]] Rejection decode_rejection(const int encoded) noexcept {
|
||||
if (encoded >= 256) {
|
||||
return non_finite_rejection();
|
||||
}
|
||||
return mapping_rejection(static_cast<mean_field::mapping::MappingStatus>(encoded - 1));
|
||||
}
|
||||
|
||||
[[nodiscard]] Result 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("Could not synchronize gravity-displacement-force candidate validity.");
|
||||
}
|
||||
if (globalEncoded != 0) {
|
||||
return std::unexpected(decode_rejection(globalEncoded));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[noreturn]] void throw_rejection(const Rejection &rejection) {
|
||||
if (rejection.reason == Reason::non_finite_arithmetic) {
|
||||
throw std::domain_error("The gravity-displacement force produced non-finite arithmetic.");
|
||||
}
|
||||
throw std::domain_error("The gravity-displacement force encountered an invalid mapped domain.");
|
||||
}
|
||||
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &trueVector,
|
||||
@@ -187,11 +254,6 @@ namespace {
|
||||
"The gravity-displacement-force displacement dimension does not "
|
||||
"match the mesh dimension."
|
||||
);
|
||||
|
||||
validate_finite_vector(
|
||||
displacementTrue, "The gravity-displacement-force displacement contains a "
|
||||
"non-finite value."
|
||||
);
|
||||
}
|
||||
|
||||
void validate_density(
|
||||
@@ -200,7 +262,6 @@ namespace {
|
||||
const char *message
|
||||
) {
|
||||
MFEM_VERIFY(density.Size() == f.densityFes->GetTrueVSize(), message);
|
||||
validate_finite_vector(density, message);
|
||||
}
|
||||
|
||||
void validate_gravity_gradient(
|
||||
@@ -209,11 +270,9 @@ namespace {
|
||||
const char *message
|
||||
) {
|
||||
MFEM_VERIFY(gravityGradient.Size() == f.gravityFluxFes->GetTrueVSize(), message);
|
||||
|
||||
validate_finite_vector(gravityGradient, message);
|
||||
}
|
||||
|
||||
void apply_gravity_displacement_force_action(
|
||||
[[nodiscard]] Result apply_gravity_displacement_force_action(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::mapping::DomainMapper &domainMapper,
|
||||
const GravityDisplacementForceAction requestedAction,
|
||||
@@ -223,7 +282,8 @@ namespace {
|
||||
const mfem::Vector *gravityGradientVariationTrue,
|
||||
const mfem::Vector *displacementVariationTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &actionTrue
|
||||
mfem::Vector &actionTrue,
|
||||
const bool reportCandidateRejection
|
||||
) {
|
||||
validate_common_inputs(f, domainMapper, displacementTrue);
|
||||
|
||||
@@ -308,6 +368,34 @@ namespace {
|
||||
);
|
||||
}
|
||||
|
||||
bool inputsAreFinite = vector_is_finite(displacementTrue);
|
||||
if (needsBaseDensity) {
|
||||
inputsAreFinite = inputsAreFinite && vector_is_finite(*baseDensityTrue);
|
||||
}
|
||||
if (needsDensityVariation) {
|
||||
inputsAreFinite = inputsAreFinite && vector_is_finite(*densityVariationTrue);
|
||||
}
|
||||
if (needsBaseGravityGradient) {
|
||||
inputsAreFinite = inputsAreFinite && vector_is_finite(*baseGravityGradientTrue);
|
||||
}
|
||||
if (needsGravityGradientVariation) {
|
||||
inputsAreFinite = inputsAreFinite && vector_is_finite(*gravityGradientVariationTrue);
|
||||
}
|
||||
if (needsDisplacementVariation) {
|
||||
inputsAreFinite = inputsAreFinite && vector_is_finite(*displacementVariationTrue);
|
||||
}
|
||||
|
||||
if (!reportCandidateRejection) {
|
||||
MFEM_VERIFY(inputsAreFinite, "The gravity-displacement-force action contains non-finite input data.");
|
||||
} else {
|
||||
const std::optional<Rejection> inputRejection =
|
||||
inputsAreFinite ? std::optional<Rejection>{} : std::optional<Rejection>{non_finite_rejection()};
|
||||
auto synchronized = synchronize_rejection(inputRejection, f.mesh->GetComm());
|
||||
if (!synchronized.has_value()) {
|
||||
return synchronized;
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Vector baseDensityLocal;
|
||||
mfem::Vector densityVariationLocal;
|
||||
mfem::Vector baseGravityGradientLocal;
|
||||
@@ -374,6 +462,8 @@ namespace {
|
||||
|
||||
const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering();
|
||||
|
||||
std::optional<Rejection> candidateRejection;
|
||||
|
||||
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
||||
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
|
||||
|
||||
@@ -516,13 +606,19 @@ namespace {
|
||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
mappingStatus == mean_field::mapping::MappingStatus::valid,
|
||||
"Stateless mapping failed in the gravity-displacement-"
|
||||
"force kernel. Element: "
|
||||
<< elementId << ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadratureIndex << ", status: " << static_cast<int>(mappingStatus)
|
||||
);
|
||||
if (mappingStatus != mean_field::mapping::MappingStatus::valid) {
|
||||
if (!reportCandidateRejection) {
|
||||
MFEM_VERIFY(
|
||||
false, "Stateless mapping failed in the gravity-displacement-"
|
||||
"force kernel. Element: "
|
||||
<< elementId << ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadratureIndex
|
||||
<< ", status: " << static_cast<int>(mappingStatus)
|
||||
);
|
||||
}
|
||||
candidateRejection = mapping_rejection(mappingStatus);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (needsDisplacementVariation) {
|
||||
const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation(
|
||||
@@ -530,13 +626,19 @@ namespace {
|
||||
workspace, mappingVariation
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
variationStatus == mean_field::mapping::MappingStatus::valid,
|
||||
"Stateless mapping variation failed in the gravity-"
|
||||
"displacement-force kernel. Element: "
|
||||
<< elementId << ", attribute: " << transformation->Attribute << ", quadrature point: "
|
||||
<< quadratureIndex << ", status: " << static_cast<int>(variationStatus)
|
||||
);
|
||||
if (variationStatus != mean_field::mapping::MappingStatus::valid) {
|
||||
if (!reportCandidateRejection) {
|
||||
MFEM_VERIFY(
|
||||
false, "Stateless mapping variation failed in the gravity-"
|
||||
"displacement-force kernel. Element: "
|
||||
<< elementId << ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadratureIndex
|
||||
<< ", status: " << static_cast<int>(variationStatus)
|
||||
);
|
||||
}
|
||||
candidateRejection = mapping_rejection(variationStatus);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
densityElement.CalcShape(integrationPoint, densityShape);
|
||||
@@ -624,10 +726,16 @@ namespace {
|
||||
|
||||
const double contribution = displacementShape(scalarDof) * forceValue(component);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(contribution), "The gravity-displacement-force kernel "
|
||||
"encountered a non-finite contribution."
|
||||
);
|
||||
if (!std::isfinite(contribution)) {
|
||||
if (!reportCandidateRejection) {
|
||||
MFEM_VERIFY(
|
||||
false, "The gravity-displacement-force kernel "
|
||||
"encountered a non-finite contribution."
|
||||
);
|
||||
}
|
||||
candidateRejection = non_finite_rejection();
|
||||
continue;
|
||||
}
|
||||
|
||||
elementAction(vectorDof) += contribution;
|
||||
}
|
||||
@@ -641,11 +749,48 @@ namespace {
|
||||
localAction.AddElementVector(displacementDofs, elementAction);
|
||||
}
|
||||
|
||||
if (reportCandidateRejection && !vector_is_finite(localAction)) {
|
||||
candidateRejection = non_finite_rejection();
|
||||
}
|
||||
|
||||
if (reportCandidateRejection) {
|
||||
auto synchronized = synchronize_rejection(candidateRejection, f.mesh->GetComm());
|
||||
if (!synchronized.has_value()) {
|
||||
return synchronized;
|
||||
}
|
||||
}
|
||||
|
||||
local_to_true(*f.displacementFes, localAction, actionTrue);
|
||||
|
||||
if (reportCandidateRejection) {
|
||||
const std::optional<Rejection> outputRejection = vector_is_finite(actionTrue)
|
||||
? std::optional<Rejection>{}
|
||||
: std::optional<Rejection>{non_finite_rejection()};
|
||||
auto synchronized = synchronize_rejection(outputRejection, f.mesh->GetComm());
|
||||
if (!synchronized.has_value()) {
|
||||
return synchronized;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators::kernels {
|
||||
GravityDisplacementForceResult try_apply_gravity_displacement_force_residual(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapper &domainMapper,
|
||||
const mfem::Vector &densityTrue,
|
||||
const mfem::Vector &gravityGradientTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &residualTrue
|
||||
) {
|
||||
return apply_gravity_displacement_force_action(
|
||||
f, domainMapper, GravityDisplacementForceAction::residual, &densityTrue, nullptr, &gravityGradientTrue,
|
||||
nullptr, nullptr, displacementTrue, residualTrue, true
|
||||
);
|
||||
}
|
||||
|
||||
void apply_gravity_displacement_force_residual(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapper &domainMapper,
|
||||
@@ -654,10 +799,12 @@ namespace mean_field::operators::kernels {
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &residualTrue
|
||||
) {
|
||||
apply_gravity_displacement_force_action(
|
||||
f, domainMapper, GravityDisplacementForceAction::residual, &densityTrue, nullptr, &gravityGradientTrue,
|
||||
nullptr, nullptr, displacementTrue, residualTrue
|
||||
auto result = try_apply_gravity_displacement_force_residual(
|
||||
f, domainMapper, densityTrue, gravityGradientTrue, displacementTrue, residualTrue
|
||||
);
|
||||
if (!result.has_value()) {
|
||||
throw_rejection(result.error());
|
||||
}
|
||||
}
|
||||
|
||||
void apply_gravity_displacement_force_density_action(
|
||||
@@ -668,9 +815,9 @@ namespace mean_field::operators::kernels {
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &actionTrue
|
||||
) {
|
||||
apply_gravity_displacement_force_action(
|
||||
(void)apply_gravity_displacement_force_action(
|
||||
f, domainMapper, GravityDisplacementForceAction::density, nullptr, &densityVariationTrue,
|
||||
&baseGravityGradientTrue, nullptr, nullptr, displacementTrue, actionTrue
|
||||
&baseGravityGradientTrue, nullptr, nullptr, displacementTrue, actionTrue, false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -682,9 +829,9 @@ namespace mean_field::operators::kernels {
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &actionTrue
|
||||
) {
|
||||
apply_gravity_displacement_force_action(
|
||||
(void)apply_gravity_displacement_force_action(
|
||||
f, domainMapper, GravityDisplacementForceAction::gravityGradient, &baseDensityTrue, nullptr, nullptr,
|
||||
&gravityGradientVariationTrue, nullptr, displacementTrue, actionTrue
|
||||
&gravityGradientVariationTrue, nullptr, displacementTrue, actionTrue, false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -697,9 +844,9 @@ namespace mean_field::operators::kernels {
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &actionTrue
|
||||
) {
|
||||
apply_gravity_displacement_force_action(
|
||||
(void)apply_gravity_displacement_force_action(
|
||||
f, domainMapper, GravityDisplacementForceAction::displacement, &baseDensityTrue, nullptr,
|
||||
&baseGravityGradientTrue, nullptr, &displacementVariationTrue, displacementTrue, actionTrue
|
||||
&baseGravityGradientTrue, nullptr, &displacementVariationTrue, displacementTrue, actionTrue, false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -714,10 +861,10 @@ namespace mean_field::operators::kernels {
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &actionTrue
|
||||
) {
|
||||
apply_gravity_displacement_force_action(
|
||||
(void)apply_gravity_displacement_force_action(
|
||||
f, domainMapper, GravityDisplacementForceAction::complete, &baseDensityTrue, &densityVariationTrue,
|
||||
&baseGravityGradientTrue, &gravityGradientVariationTrue, &displacementVariationTrue, displacementTrue,
|
||||
actionTrue
|
||||
actionTrue, false
|
||||
);
|
||||
}
|
||||
} // namespace mean_field::operators::kernels
|
||||
|
||||
@@ -2,9 +2,12 @@ module;
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
module mean_field;
|
||||
|
||||
@@ -19,6 +22,70 @@ namespace {
|
||||
|
||||
enum class RotationalDisplacementForceAction { residual, density, displacement, complete };
|
||||
|
||||
using Rejection = mean_field::operators::kernels::RotationalDisplacementForceRejection;
|
||||
using Reason = mean_field::operators::kernels::RotationalDisplacementForceRejectionReason;
|
||||
using Result = mean_field::operators::kernels::RotationalDisplacementForceResult;
|
||||
|
||||
[[nodiscard]] Rejection mapping_rejection(const mean_field::mapping::MappingStatus status) {
|
||||
MFEM_VERIFY(
|
||||
status != mean_field::mapping::MappingStatus::invalid_dimension,
|
||||
"The rotational-displacement-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]] 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]] int encode_rejection(const std::optional<Rejection> &rejection) noexcept {
|
||||
if (!rejection.has_value()) {
|
||||
return 0;
|
||||
}
|
||||
if (rejection->reason == Reason::non_finite_arithmetic) {
|
||||
return 256;
|
||||
}
|
||||
return static_cast<int>(rejection->mappingStatus) + 1;
|
||||
}
|
||||
|
||||
[[nodiscard]] Rejection decode_rejection(const int encoded) noexcept {
|
||||
if (encoded >= 256) {
|
||||
return non_finite_rejection();
|
||||
}
|
||||
return mapping_rejection(static_cast<mean_field::mapping::MappingStatus>(encoded - 1));
|
||||
}
|
||||
|
||||
[[nodiscard]] Result 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("Could not synchronize rotational-displacement-force candidate validity.");
|
||||
}
|
||||
if (globalEncoded != 0) {
|
||||
return std::unexpected(decode_rejection(globalEncoded));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[noreturn]] void throw_rejection(const Rejection &rejection) {
|
||||
if (rejection.reason == Reason::non_finite_arithmetic) {
|
||||
throw std::domain_error("The rotational-displacement force produced non-finite arithmetic.");
|
||||
}
|
||||
throw std::domain_error("The rotational-displacement force encountered an invalid mapped domain.");
|
||||
}
|
||||
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &trueVector,
|
||||
@@ -186,11 +253,6 @@ namespace {
|
||||
"The rotational-displacement-force displacement dimension does "
|
||||
"not match the mesh dimension."
|
||||
);
|
||||
|
||||
validate_finite_vector(
|
||||
displacementTrue, "The rotational-displacement-force displacement contains a "
|
||||
"non-finite value."
|
||||
);
|
||||
}
|
||||
|
||||
void validate_density(
|
||||
@@ -199,10 +261,9 @@ namespace {
|
||||
const char *message
|
||||
) {
|
||||
MFEM_VERIFY(density.Size() == f.densityFes->GetTrueVSize(), message);
|
||||
validate_finite_vector(density, message);
|
||||
}
|
||||
|
||||
void apply_rotational_displacement_force_action(
|
||||
[[nodiscard]] Result apply_rotational_displacement_force_action(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::mapping::DomainMapper &domainMapper,
|
||||
const mean_field::physics::RigidRotation &rotation,
|
||||
@@ -211,7 +272,8 @@ namespace {
|
||||
const mfem::Vector *densityVariationTrue,
|
||||
const mfem::Vector *displacementVariationTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &actionTrue
|
||||
mfem::Vector &actionTrue,
|
||||
const bool reportCandidateRejection
|
||||
) {
|
||||
validate_common_inputs(f, domainMapper, displacementTrue);
|
||||
|
||||
@@ -261,6 +323,28 @@ namespace {
|
||||
);
|
||||
}
|
||||
|
||||
bool inputsAreFinite = vector_is_finite(displacementTrue);
|
||||
if (needsBaseDensity) {
|
||||
inputsAreFinite = inputsAreFinite && vector_is_finite(*baseDensityTrue);
|
||||
}
|
||||
if (needsDensityVariation) {
|
||||
inputsAreFinite = inputsAreFinite && vector_is_finite(*densityVariationTrue);
|
||||
}
|
||||
if (needsDisplacementVariation) {
|
||||
inputsAreFinite = inputsAreFinite && vector_is_finite(*displacementVariationTrue);
|
||||
}
|
||||
|
||||
if (!reportCandidateRejection) {
|
||||
MFEM_VERIFY(inputsAreFinite, "The rotational-displacement-force action contains non-finite input data.");
|
||||
} else {
|
||||
const std::optional<Rejection> inputRejection =
|
||||
inputsAreFinite ? std::optional<Rejection>{} : std::optional<Rejection>{non_finite_rejection()};
|
||||
auto synchronized = synchronize_rejection(inputRejection, f.mesh->GetComm());
|
||||
if (!synchronized.has_value()) {
|
||||
return synchronized;
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Vector baseDensityLocal;
|
||||
mfem::Vector densityVariationLocal;
|
||||
mfem::Vector displacementLocal;
|
||||
@@ -311,6 +395,8 @@ namespace {
|
||||
|
||||
const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering();
|
||||
|
||||
std::optional<Rejection> candidateRejection;
|
||||
|
||||
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
||||
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
|
||||
|
||||
@@ -427,13 +513,19 @@ namespace {
|
||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
mappingStatus == mean_field::mapping::MappingStatus::valid,
|
||||
"Stateless mapping failed in the rotational-"
|
||||
"displacement-force kernel. Element: "
|
||||
<< elementId << ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadratureIndex << ", status: " << static_cast<int>(mappingStatus)
|
||||
);
|
||||
if (mappingStatus != mean_field::mapping::MappingStatus::valid) {
|
||||
if (!reportCandidateRejection) {
|
||||
MFEM_VERIFY(
|
||||
false, "Stateless mapping failed in the rotational-"
|
||||
"displacement-force kernel. Element: "
|
||||
<< elementId << ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadratureIndex
|
||||
<< ", status: " << static_cast<int>(mappingStatus)
|
||||
);
|
||||
}
|
||||
candidateRejection = mapping_rejection(mappingStatus);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (needsDisplacementVariation) {
|
||||
const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation(
|
||||
@@ -441,13 +533,19 @@ namespace {
|
||||
workspace, mappingVariation
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
variationStatus == mean_field::mapping::MappingStatus::valid,
|
||||
"Stateless mapping variation failed in the "
|
||||
"rotational-displacement-force kernel. Element: "
|
||||
<< elementId << ", attribute: " << transformation->Attribute << ", quadrature point: "
|
||||
<< quadratureIndex << ", status: " << static_cast<int>(variationStatus)
|
||||
);
|
||||
if (variationStatus != mean_field::mapping::MappingStatus::valid) {
|
||||
if (!reportCandidateRejection) {
|
||||
MFEM_VERIFY(
|
||||
false, "Stateless mapping variation failed in the "
|
||||
"rotational-displacement-force kernel. Element: "
|
||||
<< elementId << ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadratureIndex
|
||||
<< ", status: " << static_cast<int>(variationStatus)
|
||||
);
|
||||
}
|
||||
candidateRejection = mapping_rejection(variationStatus);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
densityElement.CalcShape(integrationPoint, densityShape);
|
||||
@@ -512,10 +610,16 @@ namespace {
|
||||
|
||||
const double contribution = displacementShape(scalarDof) * weightedForce(component);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(contribution), "The rotational-displacement-force kernel "
|
||||
"encountered a non-finite contribution."
|
||||
);
|
||||
if (!std::isfinite(contribution)) {
|
||||
if (!reportCandidateRejection) {
|
||||
MFEM_VERIFY(
|
||||
false, "The rotational-displacement-force kernel "
|
||||
"encountered a non-finite contribution."
|
||||
);
|
||||
}
|
||||
candidateRejection = non_finite_rejection();
|
||||
continue;
|
||||
}
|
||||
|
||||
elementAction(vectorDof) += contribution;
|
||||
}
|
||||
@@ -529,11 +633,48 @@ namespace {
|
||||
localAction.AddElementVector(displacementDofs, elementAction);
|
||||
}
|
||||
|
||||
if (reportCandidateRejection && !vector_is_finite(localAction)) {
|
||||
candidateRejection = non_finite_rejection();
|
||||
}
|
||||
|
||||
if (reportCandidateRejection) {
|
||||
auto synchronized = synchronize_rejection(candidateRejection, f.mesh->GetComm());
|
||||
if (!synchronized.has_value()) {
|
||||
return synchronized;
|
||||
}
|
||||
}
|
||||
|
||||
local_to_true(*f.displacementFes, localAction, actionTrue);
|
||||
|
||||
if (reportCandidateRejection) {
|
||||
const std::optional<Rejection> outputRejection = vector_is_finite(actionTrue)
|
||||
? std::optional<Rejection>{}
|
||||
: std::optional<Rejection>{non_finite_rejection()};
|
||||
auto synchronized = synchronize_rejection(outputRejection, f.mesh->GetComm());
|
||||
if (!synchronized.has_value()) {
|
||||
return synchronized;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators::kernels {
|
||||
RotationalDisplacementForceResult try_apply_rotational_displacement_force_residual(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapper &domainMapper,
|
||||
const physics::RigidRotation &rotation,
|
||||
const mfem::Vector &densityTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &residualTrue
|
||||
) {
|
||||
return apply_rotational_displacement_force_action(
|
||||
f, domainMapper, rotation, RotationalDisplacementForceAction::residual, &densityTrue, nullptr, nullptr,
|
||||
displacementTrue, residualTrue, true
|
||||
);
|
||||
}
|
||||
|
||||
void apply_rotational_displacement_force_residual(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapper &domainMapper,
|
||||
@@ -542,10 +683,12 @@ namespace mean_field::operators::kernels {
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &residualTrue
|
||||
) {
|
||||
apply_rotational_displacement_force_action(
|
||||
f, domainMapper, rotation, RotationalDisplacementForceAction::residual, &densityTrue, nullptr, nullptr,
|
||||
displacementTrue, residualTrue
|
||||
auto result = try_apply_rotational_displacement_force_residual(
|
||||
f, domainMapper, rotation, densityTrue, displacementTrue, residualTrue
|
||||
);
|
||||
if (!result.has_value()) {
|
||||
throw_rejection(result.error());
|
||||
}
|
||||
}
|
||||
|
||||
void apply_rotational_displacement_force_density_action(
|
||||
@@ -556,9 +699,9 @@ namespace mean_field::operators::kernels {
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &actionTrue
|
||||
) {
|
||||
apply_rotational_displacement_force_action(
|
||||
(void)apply_rotational_displacement_force_action(
|
||||
f, domainMapper, rotation, RotationalDisplacementForceAction::density, nullptr, &densityVariationTrue,
|
||||
nullptr, displacementTrue, actionTrue
|
||||
nullptr, displacementTrue, actionTrue, false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -571,9 +714,9 @@ namespace mean_field::operators::kernels {
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &actionTrue
|
||||
) {
|
||||
apply_rotational_displacement_force_action(
|
||||
(void)apply_rotational_displacement_force_action(
|
||||
f, domainMapper, rotation, RotationalDisplacementForceAction::displacement, &baseDensityTrue, nullptr,
|
||||
&displacementVariationTrue, displacementTrue, actionTrue
|
||||
&displacementVariationTrue, displacementTrue, actionTrue, false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -587,9 +730,9 @@ namespace mean_field::operators::kernels {
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &actionTrue
|
||||
) {
|
||||
apply_rotational_displacement_force_action(
|
||||
(void)apply_rotational_displacement_force_action(
|
||||
f, domainMapper, rotation, RotationalDisplacementForceAction::complete, &baseDensityTrue,
|
||||
&densityVariationTrue, &displacementVariationTrue, displacementTrue, actionTrue
|
||||
&densityVariationTrue, &displacementVariationTrue, displacementTrue, actionTrue, false
|
||||
);
|
||||
}
|
||||
} // namespace mean_field::operators::kernels
|
||||
|
||||
@@ -3,9 +3,14 @@ module;
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
module mean_field;
|
||||
|
||||
@@ -18,10 +23,71 @@ namespace {
|
||||
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
||||
}
|
||||
|
||||
void validate_finite_vector(const mfem::Vector &vector, const char *message) {
|
||||
[[nodiscard]] bool is_finite_vector(const mfem::Vector &vector) {
|
||||
for (int index = 0; index < vector.Size(); ++index) {
|
||||
MFEM_VERIFY(std::isfinite(vector(index)), message);
|
||||
if (!std::isfinite(vector(index))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void verify_finite_vector(
|
||||
const mfem::Vector &vector,
|
||||
const char *message
|
||||
) {
|
||||
MFEM_VERIFY(is_finite_vector(vector), message);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_candidate_mapping_failure(const mean_field::mapping::MappingStatus status) {
|
||||
using mean_field::mapping::MappingStatus;
|
||||
return status == MappingStatus::non_finite_input || status == MappingStatus::non_finite_result ||
|
||||
status == MappingStatus::non_positive_determinant;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<mean_field::mapping::MappingStatus> synchronize_mapping_failure(
|
||||
const std::optional<mean_field::mapping::MappingStatus> localFailure,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
int localFailures[2]{0, 0};
|
||||
if (localFailure.has_value()) {
|
||||
const int encodedStatus = static_cast<int>(*localFailure) + 1;
|
||||
if (is_candidate_mapping_failure(*localFailure)) {
|
||||
localFailures[0] = encodedStatus;
|
||||
} else {
|
||||
localFailures[1] = encodedStatus;
|
||||
}
|
||||
}
|
||||
|
||||
int globalFailures[2]{0, 0};
|
||||
if (MPI_Allreduce(localFailures, globalFailures, 2, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||
throw std::runtime_error("PreparedAngularMomentumOperator could not synchronize mapped-geometry validity.");
|
||||
}
|
||||
if (globalFailures[1] != 0) {
|
||||
throw std::runtime_error(
|
||||
"PreparedAngularMomentumOperator encountered a structural mapping failure with status " +
|
||||
std::to_string(globalFailures[1] - 1) + "."
|
||||
);
|
||||
}
|
||||
if (globalFailures[0] == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<mean_field::mapping::MappingStatus>(globalFailures[0] - 1);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool synchronize_non_finite_failure(
|
||||
const bool localFailure,
|
||||
const MPI_Comm communicator,
|
||||
const char *operation
|
||||
) {
|
||||
const int localStatus = localFailure ? 1 : 0;
|
||||
int globalStatus = 0;
|
||||
if (MPI_Allreduce(&localStatus, &globalStatus, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||
throw std::runtime_error(
|
||||
std::string("PreparedAngularMomentumOperator could not synchronize ") + operation + "."
|
||||
);
|
||||
}
|
||||
return globalStatus != 0;
|
||||
}
|
||||
|
||||
void true_to_local(
|
||||
@@ -51,11 +117,8 @@ namespace {
|
||||
);
|
||||
const mean_field::quadrature::Query query =
|
||||
DensityField::make_query<mean_field::field::Density::Form::Quadrupole>(
|
||||
mean_field::quadrature::QuadratureRole::discretization,
|
||||
transformation.OrderW(),
|
||||
std::array<int, 1>{2},
|
||||
mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::general
|
||||
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array<int, 1>{2},
|
||||
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
|
||||
);
|
||||
const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType());
|
||||
MFEM_VERIFY(
|
||||
@@ -108,9 +171,8 @@ namespace mean_field::operators {
|
||||
"PreparedAngularMomentumOperator currently requires a three-dimensional mapped domain."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr &&
|
||||
m_fem.compactificationFes != nullptr && m_fem.compactificationCoordinate != nullptr &&
|
||||
m_fem.quadratureFactory != nullptr,
|
||||
m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr && m_fem.compactificationFes != nullptr &&
|
||||
m_fem.compactificationCoordinate != nullptr && m_fem.quadratureFactory != nullptr,
|
||||
"PreparedAngularMomentumOperator requires density, displacement, compactification, and quadrature data."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
@@ -126,31 +188,34 @@ namespace mean_field::operators {
|
||||
const double angularVelocity,
|
||||
const AngularMomentumDependencies &dependencies
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(angularVelocity),
|
||||
"PreparedAngularMomentumOperator requires a finite angular-velocity coordinate."
|
||||
);
|
||||
auto result = TryPrepare(angularVelocity, dependencies);
|
||||
if (!result.has_value()) {
|
||||
throwAngularMomentumPreparationRejection(result.error());
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
AngularMomentumPreparationResult PreparedAngularMomentumOperator::TryPrepare(
|
||||
const double angularVelocity,
|
||||
const AngularMomentumDependencies &dependencies
|
||||
) {
|
||||
validate_shared_gravity_revisions(m_gravityContext, dependencies);
|
||||
|
||||
if (m_isPrepared) {
|
||||
validate_identity_transition(
|
||||
m_preparedDependencies.discretization,
|
||||
dependencies.discretization,
|
||||
m_preparedDependencies.discretization, dependencies.discretization,
|
||||
"A new angular-momentum discretization identity must change its revision."
|
||||
);
|
||||
validate_identity_transition(
|
||||
m_preparedDependencies.density,
|
||||
dependencies.density,
|
||||
m_preparedDependencies.density, dependencies.density,
|
||||
"A new angular-momentum density identity must change its revision."
|
||||
);
|
||||
validate_identity_transition(
|
||||
m_preparedDependencies.displacement,
|
||||
dependencies.displacement,
|
||||
m_preparedDependencies.displacement, dependencies.displacement,
|
||||
"A new angular-momentum displacement identity must change its revision."
|
||||
);
|
||||
validate_identity_transition(
|
||||
m_preparedDependencies.rotation,
|
||||
dependencies.rotation,
|
||||
m_preparedDependencies.rotation, dependencies.rotation,
|
||||
"A new angular-momentum rotation identity must change its revision."
|
||||
);
|
||||
}
|
||||
@@ -159,36 +224,67 @@ namespace mean_field::operators {
|
||||
!m_isPrepared || dependencies.discretization != m_preparedDependencies.discretization;
|
||||
const bool refreshGeometry =
|
||||
rebuildStaticPlan || dependencies.displacement != m_preparedDependencies.displacement;
|
||||
const bool refreshDensity = rebuildStaticPlan || dependencies.density != m_preparedDependencies.density;
|
||||
const bool updateAngularVelocity =
|
||||
!m_isPrepared || dependencies.rotation != m_preparedDependencies.rotation ||
|
||||
angularVelocity != m_angularVelocity;
|
||||
const bool refreshDensity = rebuildStaticPlan || dependencies.density != m_preparedDependencies.density;
|
||||
const bool updateAngularVelocity = !m_isPrepared || dependencies.rotation != m_preparedDependencies.rotation ||
|
||||
angularVelocity != m_angularVelocity;
|
||||
|
||||
m_isPrepared = false;
|
||||
if (synchronize_non_finite_failure(
|
||||
!std::isfinite(angularVelocity), m_fem.mesh->GetComm(), "angular-velocity validity"
|
||||
)) {
|
||||
return std::unexpected(
|
||||
AngularMomentumPreparationRejection{
|
||||
.reason = AngularMomentumPreparationRejectionReason::non_finite_angular_velocity
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
m_isPrepared = false;
|
||||
PreparedAngularMomentumReport report;
|
||||
if (rebuildStaticPlan) {
|
||||
BuildStaticPlan();
|
||||
report.rebuiltStaticPlan = true;
|
||||
}
|
||||
if (refreshGeometry) {
|
||||
RefreshGeometry(m_gravityContext.GetGeometryContext().GetDisplacementTrue());
|
||||
const auto mappingFailure = synchronize_mapping_failure(
|
||||
RefreshGeometry(m_gravityContext.GetGeometryContext().GetDisplacementTrue()), m_fem.mesh->GetComm()
|
||||
);
|
||||
if (mappingFailure.has_value()) {
|
||||
const auto reason = *mappingFailure == mapping::MappingStatus::non_positive_determinant
|
||||
? AngularMomentumPreparationRejectionReason::inverted_geometry
|
||||
: AngularMomentumPreparationRejectionReason::non_finite_geometry;
|
||||
return std::unexpected(
|
||||
AngularMomentumPreparationRejection{.reason = reason, .mappingStatus = *mappingFailure}
|
||||
);
|
||||
}
|
||||
report.refreshedGeometry = true;
|
||||
}
|
||||
if (refreshDensity) {
|
||||
RefreshDensity(m_gravityContext.GetDensityTrue());
|
||||
if (synchronize_non_finite_failure(
|
||||
!RefreshDensity(m_gravityContext.GetDensityTrue()), m_fem.mesh->GetComm(),
|
||||
"interpolated-density validity"
|
||||
)) {
|
||||
return std::unexpected(
|
||||
AngularMomentumPreparationRejection{
|
||||
.reason = AngularMomentumPreparationRejectionReason::non_finite_density
|
||||
}
|
||||
);
|
||||
}
|
||||
report.refreshedDensity = true;
|
||||
}
|
||||
if (updateAngularVelocity) {
|
||||
m_angularVelocity = angularVelocity;
|
||||
m_angularVelocity = angularVelocity;
|
||||
report.updatedAngularVelocity = true;
|
||||
}
|
||||
if (refreshGeometry || refreshDensity || updateAngularVelocity) {
|
||||
AssembleResidual();
|
||||
auto rejection = TryAssembleResidual();
|
||||
if (rejection.has_value()) {
|
||||
return std::unexpected(*rejection);
|
||||
}
|
||||
report.assembledResidual = true;
|
||||
}
|
||||
|
||||
m_preparedDependencies = dependencies;
|
||||
m_isPrepared = true;
|
||||
m_isPrepared = true;
|
||||
return report;
|
||||
}
|
||||
|
||||
@@ -204,8 +300,8 @@ namespace mean_field::operators {
|
||||
}
|
||||
++localStellarElementCount;
|
||||
m_elements.emplace_back();
|
||||
ElementPAData &data = m_elements.back();
|
||||
data.elementId = elementId;
|
||||
ElementPAData &data = m_elements.back();
|
||||
data.elementId = elementId;
|
||||
data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
|
||||
data.displacementDofTransformation =
|
||||
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
|
||||
@@ -218,31 +314,35 @@ namespace mean_field::operators {
|
||||
data.quadraturePoints.resize(integrationRule.GetNPoints());
|
||||
for (int quadraturePoint = 0; quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) {
|
||||
QuadraturePointData &point = data.quadraturePoints[quadraturePoint];
|
||||
point.integrationPoint = integrationRule.IntPoint(quadraturePoint);
|
||||
point.integrationPoint = integrationRule.IntPoint(quadraturePoint);
|
||||
point.densityShape.SetSize(densityElement.GetDof());
|
||||
densityElement.CalcShape(point.integrationPoint, point.densityShape);
|
||||
}
|
||||
}
|
||||
int globalStellarElementCount = 0;
|
||||
MPI_Allreduce(
|
||||
&localStellarElementCount,
|
||||
&globalStellarElementCount,
|
||||
1,
|
||||
MPI_INT,
|
||||
MPI_SUM,
|
||||
m_fem.mesh->GetComm()
|
||||
MFEM_VERIFY(
|
||||
MPI_Allreduce(
|
||||
&localStellarElementCount, &globalStellarElementCount, 1, MPI_INT, MPI_SUM, m_fem.mesh->GetComm()
|
||||
) == MPI_SUCCESS,
|
||||
"PreparedAngularMomentumOperator could not count stellar elements."
|
||||
);
|
||||
MFEM_VERIFY(globalStellarElementCount > 0, "PreparedAngularMomentumOperator found no stellar elements.");
|
||||
}
|
||||
|
||||
void PreparedAngularMomentumOperator::RefreshGeometry(const mfem::Vector &displacement) {
|
||||
std::optional<mapping::MappingStatus>
|
||||
PreparedAngularMomentumOperator::RefreshGeometry(const mfem::Vector &displacement) {
|
||||
MFEM_VERIFY(
|
||||
displacement.Size() == m_fem.displacementFes->GetTrueVSize(),
|
||||
"Angular-momentum geometry has the wrong displacement size."
|
||||
);
|
||||
validate_finite_vector(displacement, "Angular-momentum geometry contains a non-finite displacement.");
|
||||
if (!is_finite_vector(displacement)) {
|
||||
return mapping::MappingStatus::non_finite_input;
|
||||
}
|
||||
mfem::Vector displacementLocal;
|
||||
true_to_local(*m_fem.displacementFes, displacement, displacementLocal);
|
||||
if (!is_finite_vector(displacementLocal)) {
|
||||
return mapping::MappingStatus::non_finite_result;
|
||||
}
|
||||
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
|
||||
|
||||
for (ElementPAData &data : m_elements) {
|
||||
@@ -254,75 +354,109 @@ namespace mean_field::operators {
|
||||
if (data.compactificationDofTransformation != nullptr) {
|
||||
data.compactificationDofTransformation->InvTransformPrimal(data.compactification);
|
||||
}
|
||||
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
|
||||
if (!is_finite_vector(data.baseDisplacement)) {
|
||||
return mapping::MappingStatus::non_finite_result;
|
||||
}
|
||||
MFEM_VERIFY(
|
||||
is_finite_vector(data.compactification),
|
||||
"Angular-momentum preparation encountered invalid static compactification data."
|
||||
);
|
||||
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
|
||||
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
|
||||
const mapping::ElementDisplacementData displacementData =
|
||||
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
|
||||
const mapping::ElementCompactificationData compactificationData(
|
||||
compactificationElement,
|
||||
data.compactification
|
||||
compactificationElement, data.compactification
|
||||
);
|
||||
const mapping::ElementMappingData mappingData{
|
||||
.displacement = displacementData,
|
||||
.compactification = compactificationData
|
||||
.displacement = displacementData, .compactification = compactificationData
|
||||
};
|
||||
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
|
||||
for (QuadraturePointData &point : data.quadraturePoints) {
|
||||
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
|
||||
mappingData,
|
||||
*transformation,
|
||||
point.integrationPoint,
|
||||
workspace,
|
||||
point.mappingContext
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
status == mapping::MappingStatus::valid && !point.mappingContext.mapping.compactified,
|
||||
"Mapped angular-momentum geometry is invalid. Element: " << data.elementId
|
||||
mappingData, *transformation, point.integrationPoint, workspace, point.mappingContext
|
||||
);
|
||||
if (status != mapping::MappingStatus::valid) {
|
||||
return status;
|
||||
}
|
||||
if (point.mappingContext.mapping.compactified) {
|
||||
return mapping::MappingStatus::at_compactified_infinity;
|
||||
}
|
||||
point.cylindricalRadiusSquared =
|
||||
CylindricalRadiusSquared(point.mappingContext.mapping.physical_position);
|
||||
if (!std::isfinite(point.cylindricalRadiusSquared)) {
|
||||
return mapping::MappingStatus::non_finite_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void PreparedAngularMomentumOperator::RefreshDensity(const mfem::Vector &density) {
|
||||
MFEM_VERIFY(
|
||||
density.Size() == m_fem.densityFes->GetTrueVSize(),
|
||||
"Angular-momentum density has the wrong size."
|
||||
);
|
||||
validate_finite_vector(density, "Angular-momentum density contains a non-finite value.");
|
||||
bool PreparedAngularMomentumOperator::RefreshDensity(const mfem::Vector &density) {
|
||||
MFEM_VERIFY(density.Size() == m_fem.densityFes->GetTrueVSize(), "Angular-momentum density has the wrong size.");
|
||||
if (!is_finite_vector(density)) {
|
||||
return false;
|
||||
}
|
||||
mfem::Vector densityLocal;
|
||||
true_to_local(*m_fem.densityFes, density, densityLocal);
|
||||
if (!is_finite_vector(densityLocal)) {
|
||||
return false;
|
||||
}
|
||||
mfem::Vector elementDensity;
|
||||
for (ElementPAData &data : m_elements) {
|
||||
densityLocal.GetSubVector(data.densityDofs, elementDensity);
|
||||
if (data.densityDofTransformation != nullptr) {
|
||||
data.densityDofTransformation->InvTransformPrimal(elementDensity);
|
||||
}
|
||||
if (!is_finite_vector(elementDensity)) {
|
||||
return false;
|
||||
}
|
||||
for (QuadraturePointData &point : data.quadraturePoints) {
|
||||
point.density = elementDensity * point.densityShape;
|
||||
MFEM_VERIFY(std::isfinite(point.density), "Angular-momentum quadrature density is non-finite.");
|
||||
if (!std::isfinite(point.density)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PreparedAngularMomentumOperator::AssembleResidual() {
|
||||
std::optional<AngularMomentumPreparationRejection> PreparedAngularMomentumOperator::TryAssembleResidual() {
|
||||
double localMomentOfInertia = 0.0;
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
for (const QuadraturePointData &point : data.quadraturePoints) {
|
||||
localMomentOfInertia += point.density * point.cylindricalRadiusSquared *
|
||||
point.mappingContext.quadrature.weight;
|
||||
localMomentOfInertia +=
|
||||
point.density * point.cylindricalRadiusSquared * point.mappingContext.quadrature.weight;
|
||||
}
|
||||
}
|
||||
m_momentOfInertia = GlobalSum(localMomentOfInertia);
|
||||
if (!std::isfinite(m_momentOfInertia)) {
|
||||
return AngularMomentumPreparationRejection{
|
||||
.reason = AngularMomentumPreparationRejectionReason::non_finite_moment_of_inertia,
|
||||
.momentOfInertia = m_momentOfInertia
|
||||
};
|
||||
}
|
||||
if (m_momentOfInertia < 0.0) {
|
||||
return AngularMomentumPreparationRejection{
|
||||
.reason = AngularMomentumPreparationRejectionReason::negative_moment_of_inertia,
|
||||
.momentOfInertia = m_momentOfInertia
|
||||
};
|
||||
}
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(m_momentOfInertia) && m_momentOfInertia >= 0.0,
|
||||
"PreparedAngularMomentumOperator assembled an invalid moment of inertia."
|
||||
std::isfinite(m_constraint.targetAngularMomentum().value()),
|
||||
"PreparedAngularMomentumOperator has a non-finite configured target angular momentum."
|
||||
);
|
||||
m_currentAngularMomentum = m_angularVelocity * m_momentOfInertia;
|
||||
m_cachedResidual.SetSize(1);
|
||||
m_cachedResidual(0) = m_currentAngularMomentum - m_constraint.targetAngularMomentum().value();
|
||||
if (!std::isfinite(m_currentAngularMomentum) || !std::isfinite(m_cachedResidual(0))) {
|
||||
return AngularMomentumPreparationRejection{
|
||||
.reason = AngularMomentumPreparationRejectionReason::non_finite_residual,
|
||||
.momentOfInertia = m_momentOfInertia
|
||||
};
|
||||
}
|
||||
++m_preparationCount;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void PreparedAngularMomentumOperator::BuildResidual(mfem::Vector &residual) const {
|
||||
@@ -331,9 +465,8 @@ namespace mean_field::operators {
|
||||
++m_residualApplicationCount;
|
||||
}
|
||||
|
||||
double PreparedAngularMomentumOperator::EvaluateDensityMomentActionLocal(
|
||||
const mfem::Vector &densityVariation
|
||||
) const {
|
||||
double
|
||||
PreparedAngularMomentumOperator::EvaluateDensityMomentActionLocal(const mfem::Vector &densityVariation) const {
|
||||
MFEM_VERIFY(
|
||||
densityVariation.Size() == m_fem.densityFes->GetTrueVSize(),
|
||||
"Angular-momentum density action has the wrong true-vector size."
|
||||
@@ -346,8 +479,8 @@ namespace mean_field::operators {
|
||||
data.densityDofTransformation->InvTransformPrimal(m_elementDensityVariation);
|
||||
}
|
||||
for (const QuadraturePointData &point : data.quadraturePoints) {
|
||||
localAction += (m_elementDensityVariation * point.densityShape) *
|
||||
point.cylindricalRadiusSquared * point.mappingContext.quadrature.weight;
|
||||
localAction += (m_elementDensityVariation * point.densityShape) * point.cylindricalRadiusSquared *
|
||||
point.mappingContext.quadrature.weight;
|
||||
}
|
||||
}
|
||||
return localAction;
|
||||
@@ -369,42 +502,33 @@ namespace mean_field::operators {
|
||||
if (data.displacementDofTransformation != nullptr) {
|
||||
data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation);
|
||||
}
|
||||
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
|
||||
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
|
||||
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
|
||||
const mapping::ElementDisplacementData baseDisplacementData =
|
||||
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
|
||||
const mapping::ElementDisplacementData directionData =
|
||||
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
|
||||
const mapping::ElementCompactificationData compactificationData(
|
||||
compactificationElement,
|
||||
data.compactification
|
||||
compactificationElement, data.compactification
|
||||
);
|
||||
const mapping::ElementMappingData mappingData{
|
||||
.displacement = baseDisplacementData,
|
||||
.compactification = compactificationData
|
||||
.displacement = baseDisplacementData, .compactification = compactificationData
|
||||
};
|
||||
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
|
||||
for (const QuadraturePointData &point : data.quadraturePoints) {
|
||||
const mapping::MappingStatus status = m_domainMapper.EvaluateVolumeVariation(
|
||||
mappingData,
|
||||
directionData,
|
||||
*transformation,
|
||||
point.integrationPoint,
|
||||
point.mappingContext,
|
||||
workspace,
|
||||
variation
|
||||
mappingData, directionData, *transformation, point.integrationPoint, point.mappingContext,
|
||||
workspace, variation
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
status == mapping::MappingStatus::valid,
|
||||
"Mapped angular-momentum variation is invalid. Element: " << data.elementId
|
||||
);
|
||||
const double radiusSquaredVariation = CylindricalRadiusSquaredVariation(
|
||||
point.mappingContext.mapping.physical_position,
|
||||
variation.mapping.physical_position_variation
|
||||
point.mappingContext.mapping.physical_position, variation.mapping.physical_position_variation
|
||||
);
|
||||
localAction += point.density *
|
||||
(radiusSquaredVariation * point.mappingContext.quadrature.weight +
|
||||
point.cylindricalRadiusSquared * variation.weight_variation);
|
||||
localAction += point.density * (radiusSquaredVariation * point.mappingContext.quadrature.weight +
|
||||
point.cylindricalRadiusSquared * variation.weight_variation);
|
||||
}
|
||||
}
|
||||
return localAction;
|
||||
@@ -419,7 +543,7 @@ namespace mean_field::operators {
|
||||
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size(),
|
||||
"Angular-momentum density action has the wrong reduced size."
|
||||
);
|
||||
validate_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
|
||||
verify_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
|
||||
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
|
||||
action.SetSize(1);
|
||||
action(0) = m_angularVelocity * GlobalSum(EvaluateDensityMomentActionLocal(m_densityVariationTrue));
|
||||
@@ -435,11 +559,10 @@ namespace mean_field::operators {
|
||||
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
|
||||
"Angular-momentum displacement action has the wrong reduced size."
|
||||
);
|
||||
validate_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite.");
|
||||
verify_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite.");
|
||||
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
|
||||
action.SetSize(1);
|
||||
action(0) = m_angularVelocity *
|
||||
GlobalSum(EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue));
|
||||
action(0) = m_angularVelocity * GlobalSum(EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue));
|
||||
++m_actionStatistics.displacementApplications;
|
||||
}
|
||||
|
||||
@@ -466,24 +589,22 @@ namespace mean_field::operators {
|
||||
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
|
||||
"Angular-momentum complete action has incompatible reduced coordinates."
|
||||
);
|
||||
validate_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
|
||||
validate_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite.");
|
||||
verify_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
|
||||
verify_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite.");
|
||||
MFEM_VERIFY(std::isfinite(angularVelocityVariation), "Angular-velocity direction is non-finite.");
|
||||
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
|
||||
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
|
||||
const double localMomentAction = EvaluateDensityMomentActionLocal(m_densityVariationTrue) +
|
||||
EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue);
|
||||
action.SetSize(1);
|
||||
action(0) = m_angularVelocity * GlobalSum(localMomentAction) +
|
||||
m_momentOfInertia * angularVelocityVariation;
|
||||
action(0) = m_angularVelocity * GlobalSum(localMomentAction) + m_momentOfInertia * angularVelocityVariation;
|
||||
++m_actionStatistics.completeApplications;
|
||||
}
|
||||
|
||||
double PreparedAngularMomentumOperator::CylindricalRadiusSquared(
|
||||
const mfem::Vector &physicalPosition
|
||||
) const noexcept {
|
||||
const auto &axis = m_constraint.specification().axis();
|
||||
const auto ¢er = m_constraint.specification().center();
|
||||
double
|
||||
PreparedAngularMomentumOperator::CylindricalRadiusSquared(const mfem::Vector &physicalPosition) const noexcept {
|
||||
const auto &axis = m_constraint.specification().axis();
|
||||
const auto ¢er = m_constraint.specification().center();
|
||||
double radiusSquared = 0.0;
|
||||
double axialPosition = 0.0;
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
@@ -491,18 +612,22 @@ namespace mean_field::operators {
|
||||
radiusSquared += relative * relative;
|
||||
axialPosition += axis[static_cast<std::size_t>(component)] * relative;
|
||||
}
|
||||
return std::max(0.0, radiusSquared - axialPosition * axialPosition);
|
||||
const double perpendicularRadiusSquared = radiusSquared - axialPosition * axialPosition;
|
||||
if (!std::isfinite(perpendicularRadiusSquared)) {
|
||||
return perpendicularRadiusSquared;
|
||||
}
|
||||
return std::max(0.0, perpendicularRadiusSquared);
|
||||
}
|
||||
|
||||
double PreparedAngularMomentumOperator::CylindricalRadiusSquaredVariation(
|
||||
const mfem::Vector &physicalPosition,
|
||||
const mfem::Vector &physicalPositionVariation
|
||||
) const noexcept {
|
||||
const auto &axis = m_constraint.specification().axis();
|
||||
const auto ¢er = m_constraint.specification().center();
|
||||
const auto &axis = m_constraint.specification().axis();
|
||||
const auto ¢er = m_constraint.specification().center();
|
||||
double relativeDotVariation = 0.0;
|
||||
double axialPosition = 0.0;
|
||||
double axialVariation = 0.0;
|
||||
double axialPosition = 0.0;
|
||||
double axialVariation = 0.0;
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
const double relative = physicalPosition(component) - center[static_cast<std::size_t>(component)];
|
||||
relativeDotVariation += relative * physicalPositionVariation(component);
|
||||
@@ -514,7 +639,9 @@ namespace mean_field::operators {
|
||||
|
||||
double PreparedAngularMomentumOperator::GlobalSum(const double localValue) const {
|
||||
double globalValue = 0.0;
|
||||
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, m_fem.mesh->GetComm());
|
||||
if (MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, m_fem.mesh->GetComm()) != MPI_SUCCESS) {
|
||||
throw std::runtime_error("PreparedAngularMomentumOperator could not reduce the moment of inertia.");
|
||||
}
|
||||
return globalValue;
|
||||
}
|
||||
|
||||
@@ -554,15 +681,15 @@ namespace mean_field::operators {
|
||||
|
||||
AngularMomentumConstraintReport PreparedAngularMomentumOperator::GetConstraintReport() const {
|
||||
VerifyPrepared();
|
||||
const double target = GetTargetAngularMomentum();
|
||||
const double target = GetTargetAngularMomentum();
|
||||
const double residual = m_currentAngularMomentum - target;
|
||||
return {
|
||||
.targetAngularMomentum = target,
|
||||
.targetAngularMomentum = target,
|
||||
.achievedAngularMomentum = m_currentAngularMomentum,
|
||||
.momentOfInertia = m_momentOfInertia,
|
||||
.angularVelocity = m_angularVelocity,
|
||||
.dimensionalResidual = residual,
|
||||
.scaledResidual = residual / std::max(std::abs(target), 1.0e-300)
|
||||
.momentOfInertia = m_momentOfInertia,
|
||||
.angularVelocity = m_angularVelocity,
|
||||
.dimensionalResidual = residual,
|
||||
.scaledResidual = residual / std::max(std::abs(target), 1.0e-300)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
module;
|
||||
|
||||
#include <cmath>
|
||||
#include <expected>
|
||||
#include <mfem.hpp>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <mpi.h>
|
||||
|
||||
module mean_field;
|
||||
|
||||
@@ -8,6 +15,75 @@ import :operators.prepared_displacement_residual;
|
||||
|
||||
namespace {
|
||||
using Dependencies = mean_field::operators::DisplacementResidualDependencies;
|
||||
using Rejection = mean_field::operators::DisplacementResidualPreparationRejection;
|
||||
using Source = mean_field::operators::DisplacementResidualPreparationRejectionSource;
|
||||
using Reason = mean_field::operators::DisplacementResidualPreparationRejectionReason;
|
||||
|
||||
[[nodiscard]] Rejection
|
||||
pressure_rejection(const mean_field::operators::PressureForcePreparationRejection &rejection) noexcept {
|
||||
using PressureReason = mean_field::operators::PressureForcePreparationRejectionReason;
|
||||
switch (rejection.reason) {
|
||||
case PressureReason::equation_of_state:
|
||||
return {
|
||||
.source = Source::pressure,
|
||||
.reason = Reason::equation_of_state,
|
||||
.equationOfStateCode = rejection.equationOfStateCode
|
||||
};
|
||||
case PressureReason::invalid_mapping:
|
||||
return {
|
||||
.source = Source::pressure, .reason = Reason::invalid_mapping, .mappingStatus = rejection.mappingStatus
|
||||
};
|
||||
case PressureReason::non_finite_arithmetic:
|
||||
default:
|
||||
return {.source = Source::pressure, .reason = Reason::non_finite_arithmetic};
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] Rejection
|
||||
gravity_rejection(const mean_field::operators::kernels::GravityDisplacementForceRejection &rejection) noexcept {
|
||||
if (rejection.reason ==
|
||||
mean_field::operators::kernels::GravityDisplacementForceRejectionReason::invalid_mapping) {
|
||||
return {
|
||||
.source = Source::gravity, .reason = Reason::invalid_mapping, .mappingStatus = rejection.mappingStatus
|
||||
};
|
||||
}
|
||||
return {.source = Source::gravity, .reason = Reason::non_finite_arithmetic};
|
||||
}
|
||||
|
||||
[[nodiscard]] Rejection
|
||||
rotation_rejection(const mean_field::operators::kernels::RotationalDisplacementForceRejection &rejection) noexcept {
|
||||
if (rejection.reason ==
|
||||
mean_field::operators::kernels::RotationalDisplacementForceRejectionReason::invalid_mapping) {
|
||||
return {
|
||||
.source = Source::rotation, .reason = Reason::invalid_mapping, .mappingStatus = rejection.mappingStatus
|
||||
};
|
||||
}
|
||||
return {.source = Source::rotation, .reason = Reason::non_finite_arithmetic};
|
||||
}
|
||||
|
||||
[[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;
|
||||
}
|
||||
|
||||
[[noreturn]] void throw_rejection(const Rejection &rejection) {
|
||||
switch (rejection.reason) {
|
||||
case Reason::equation_of_state:
|
||||
throw mean_field::eos::EvaluationError(
|
||||
rejection.equationOfStateCode,
|
||||
"PreparedDisplacementResidualOperator encountered invalid thermodynamic data."
|
||||
);
|
||||
case Reason::invalid_mapping:
|
||||
throw std::domain_error("PreparedDisplacementResidualOperator encountered an invalid mapped domain.");
|
||||
case Reason::non_finite_arithmetic:
|
||||
default:
|
||||
throw std::domain_error("PreparedDisplacementResidualOperator produced non-finite arithmetic.");
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::operators::context::pressure_force::PressureForceDependencies
|
||||
make_pressure_dependencies(const Dependencies &dependencies) {
|
||||
@@ -118,6 +194,21 @@ namespace mean_field::operators {
|
||||
const DisplacementResidualStateView &state,
|
||||
const DisplacementResidualDependencies &dependencies,
|
||||
const physics::RigidRotation &rotation
|
||||
) {
|
||||
auto result = TryPrepare(state, dependencies, rotation);
|
||||
if (!result.has_value()) {
|
||||
throw_rejection(result.error());
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
std::expected<
|
||||
PreparedDisplacementResidualReport,
|
||||
DisplacementResidualPreparationRejection>
|
||||
PreparedDisplacementResidualOperator::TryPrepare(
|
||||
const DisplacementResidualStateView &state,
|
||||
const DisplacementResidualDependencies &dependencies,
|
||||
const physics::RigidRotation &rotation
|
||||
) {
|
||||
validate_shared_gravity_revisions(m_gravityContext, dependencies);
|
||||
|
||||
@@ -161,20 +252,47 @@ namespace mean_field::operators {
|
||||
|
||||
PreparedDisplacementResidualReport report;
|
||||
|
||||
report.pressure = m_pressureOperator.Prepare(
|
||||
auto pressureResult = m_pressureOperator.TryPrepare(
|
||||
{.enthalpy = state.enthalpy, .displacement = displacement}, make_pressure_dependencies(dependencies)
|
||||
);
|
||||
if (!pressureResult.has_value()) {
|
||||
return std::unexpected(pressure_rejection(pressureResult.error()));
|
||||
}
|
||||
report.pressure = std::move(pressureResult).value();
|
||||
|
||||
report.gravity = m_gravityOperator.Prepare();
|
||||
auto gravityResult = m_gravityOperator.TryPrepare();
|
||||
if (!gravityResult.has_value()) {
|
||||
return std::unexpected(gravity_rejection(gravityResult.error()));
|
||||
}
|
||||
report.gravity = std::move(gravityResult).value();
|
||||
|
||||
report.rotation = m_rotationalOperator.Prepare(
|
||||
auto rotationResult = m_rotationalOperator.TryPrepare(
|
||||
{.density = density, .displacement = displacement}, make_rotational_dependencies(dependencies), rotation
|
||||
);
|
||||
if (!rotationResult.has_value()) {
|
||||
return std::unexpected(rotation_rejection(rotationResult.error()));
|
||||
}
|
||||
report.rotation = std::move(rotationResult).value();
|
||||
|
||||
if (report.DidAnyChildWork() ||
|
||||
m_cachedResidual.Size() != m_gravityContext.GetDisplacementMap().reduced_size()) {
|
||||
AssembleResidual();
|
||||
const auto localAssemblyRejection = AssembleResidual();
|
||||
const int localRejected = localAssemblyRejection.has_value() ? 1 : 0;
|
||||
int globallyRejected = 0;
|
||||
if (MPI_Allreduce(
|
||||
&localRejected, &globallyRejected, 1, MPI_INT, MPI_MAX, m_fem.displacementFes->GetComm()
|
||||
) != MPI_SUCCESS) {
|
||||
throw std::runtime_error(
|
||||
"PreparedDisplacementResidualOperator could not synchronize residual validity."
|
||||
);
|
||||
}
|
||||
if (globallyRejected != 0) {
|
||||
return std::unexpected(
|
||||
Rejection{.source = Source::composition, .reason = Reason::non_finite_arithmetic}
|
||||
);
|
||||
}
|
||||
report.assembledResidual = true;
|
||||
++m_residualPreparationCount;
|
||||
}
|
||||
|
||||
MFEM_VERIFY(
|
||||
@@ -188,7 +306,7 @@ namespace mean_field::operators {
|
||||
return report;
|
||||
}
|
||||
|
||||
void PreparedDisplacementResidualOperator::AssembleResidual() {
|
||||
std::optional<DisplacementResidualPreparationRejection> PreparedDisplacementResidualOperator::AssembleResidual() {
|
||||
mfem::Vector pressureResidual;
|
||||
mfem::Vector gravityResidual;
|
||||
mfem::Vector rotationalResidual;
|
||||
@@ -211,7 +329,10 @@ namespace mean_field::operators {
|
||||
"different sizes."
|
||||
);
|
||||
|
||||
++m_residualPreparationCount;
|
||||
if (!vector_is_finite(m_cachedResidual)) {
|
||||
return Rejection{.source = Source::composition, .reason = Reason::non_finite_arithmetic};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void PreparedDisplacementResidualOperator::BuildResidual(mfem::Vector &residual) const {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
module;
|
||||
|
||||
#include <cmath>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
module mean_field;
|
||||
|
||||
@@ -9,6 +16,8 @@ import :operators.prepared_gravity_displacement_force;
|
||||
|
||||
namespace {
|
||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
using Rejection = mean_field::operators::kernels::GravityDisplacementForceRejection;
|
||||
using Reason = mean_field::operators::kernels::GravityDisplacementForceRejectionReason;
|
||||
|
||||
[[nodiscard]] bool relevant_revisions_match(
|
||||
const mean_field::operators::context::gravity_field::GravityFieldRevisions &left,
|
||||
@@ -22,6 +31,69 @@ namespace {
|
||||
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
||||
}
|
||||
|
||||
[[nodiscard]] Rejection mapping_rejection(const mean_field::mapping::MappingStatus status) {
|
||||
MFEM_VERIFY(
|
||||
status != mean_field::mapping::MappingStatus::invalid_dimension,
|
||||
"Prepared gravity 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;
|
||||
}
|
||||
if (rejection->reason == Reason::non_finite_arithmetic) {
|
||||
return 256;
|
||||
}
|
||||
return static_cast<int>(rejection->mappingStatus) + 1;
|
||||
}
|
||||
|
||||
[[nodiscard]] Rejection decode_rejection(const int encoded) {
|
||||
if (encoded >= 256) {
|
||||
return non_finite_rejection();
|
||||
}
|
||||
return mapping_rejection(static_cast<mean_field::mapping::MappingStatus>(encoded - 1));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<
|
||||
void,
|
||||
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("Could not synchronize prepared gravity-force candidate validity.");
|
||||
}
|
||||
if (globalEncoded != 0) {
|
||||
return std::unexpected(decode_rejection(globalEncoded));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[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;
|
||||
}
|
||||
|
||||
[[noreturn]] void throw_rejection(const Rejection &rejection) {
|
||||
if (rejection.reason == Reason::non_finite_arithmetic) {
|
||||
throw std::domain_error("Prepared gravity force produced non-finite arithmetic.");
|
||||
}
|
||||
throw std::domain_error("Prepared gravity force encountered an invalid mapped domain.");
|
||||
}
|
||||
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &trueVector,
|
||||
@@ -108,7 +180,10 @@ namespace mean_field::operators {
|
||||
);
|
||||
}
|
||||
|
||||
void PreparedGravityDisplacementForceOperator::PrepareElementData() {
|
||||
std::expected<
|
||||
void,
|
||||
kernels::GravityDisplacementForceRejection>
|
||||
PreparedGravityDisplacementForceOperator::TryPrepareElementData() {
|
||||
m_elements.clear();
|
||||
m_elements.reserve(m_fem.mesh->GetNE());
|
||||
|
||||
@@ -199,9 +274,12 @@ namespace mean_field::operators {
|
||||
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
|
||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||
);
|
||||
if (status != mapping::MappingStatus::valid) {
|
||||
return std::unexpected(mapping_rejection(status));
|
||||
}
|
||||
MFEM_VERIFY(
|
||||
status == mapping::MappingStatus::valid && !mappingContext.mapping.compactified,
|
||||
"Prepared gravity force encountered an invalid stellar mapping."
|
||||
!mappingContext.mapping.compactified,
|
||||
"Prepared gravity force encountered compactification on a stellar element."
|
||||
);
|
||||
|
||||
densityElement.CalcShape(integrationPoint, densityShape);
|
||||
@@ -220,11 +298,38 @@ namespace mean_field::operators {
|
||||
data.inverseMeshJacobians(quadraturePoint, entry) = inverseMeshJacobian(row, column);
|
||||
}
|
||||
}
|
||||
|
||||
if (!std::isfinite(data.baseDensityValues(quadraturePoint)) ||
|
||||
!std::isfinite(data.referenceWeights(quadraturePoint)) ||
|
||||
!vector_is_finite(baseGravityReferenceValue)) {
|
||||
return std::unexpected(non_finite_rejection());
|
||||
}
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
const int entry = row * dimension + column;
|
||||
if (!std::isfinite(data.mappingJacobians(quadraturePoint, entry)) ||
|
||||
!std::isfinite(data.inverseMeshJacobians(quadraturePoint, entry))) {
|
||||
return std::unexpected(non_finite_rejection());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
PreparedGravityDisplacementForceReport PreparedGravityDisplacementForceOperator::Prepare() {
|
||||
auto result = TryPrepare();
|
||||
if (!result.has_value()) {
|
||||
throw_rejection(result.error());
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
std::expected<
|
||||
PreparedGravityDisplacementForceReport,
|
||||
kernels::GravityDisplacementForceRejection>
|
||||
PreparedGravityDisplacementForceOperator::TryPrepare() {
|
||||
MFEM_VERIFY(
|
||||
m_gravityContext.IsPrepared(), "PreparedGravityDisplacementForceOperator requires the shared "
|
||||
"gravity linearization context to be prepared first."
|
||||
@@ -236,19 +341,45 @@ namespace mean_field::operators {
|
||||
return {};
|
||||
}
|
||||
|
||||
kernels::apply_gravity_displacement_force_residual(
|
||||
m_isPrepared = false;
|
||||
|
||||
/*
|
||||
* Build the reusable element plan before assembling the residual.
|
||||
* This pass stops at the first invalid mapped quadrature point, so a
|
||||
* rejected line-search candidate need not traverse the full stateless
|
||||
* residual kernel. Synchronize before proceeding so every rank takes
|
||||
* the same branch.
|
||||
*/
|
||||
const auto elementResult = TryPrepareElementData();
|
||||
const std::optional<Rejection> localElementRejection =
|
||||
elementResult.has_value() ? std::optional<Rejection>{} : std::optional<Rejection>{elementResult.error()};
|
||||
auto synchronizedElement = synchronize_rejection(localElementRejection, m_fem.mesh->GetComm());
|
||||
if (!synchronizedElement.has_value()) {
|
||||
return std::unexpected(synchronizedElement.error());
|
||||
}
|
||||
|
||||
auto residualResult = kernels::try_apply_gravity_displacement_force_residual(
|
||||
m_fem, m_domainMapper, m_gravityContext.GetDensityTrue(), m_gravityContext.GetGravityGradientTrue(),
|
||||
m_gravityContext.GetGeometryContext().GetDisplacementTrue(), m_actionTrue
|
||||
);
|
||||
if (!residualResult.has_value()) {
|
||||
return std::unexpected(residualResult.error());
|
||||
}
|
||||
m_cachedResidual.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
|
||||
m_gravityContext.GetDisplacementMap().gather(m_actionTrue, m_cachedResidual);
|
||||
PrepareElementData();
|
||||
|
||||
std::optional<Rejection> localRejection;
|
||||
if (!vector_is_finite(m_cachedResidual)) {
|
||||
localRejection = non_finite_rejection();
|
||||
}
|
||||
auto synchronized = synchronize_rejection(localRejection, m_fem.mesh->GetComm());
|
||||
if (!synchronized.has_value()) {
|
||||
return std::unexpected(synchronized.error());
|
||||
}
|
||||
m_preparedRevisions = requestedRevisions;
|
||||
++m_residualPreparationCount;
|
||||
m_isPrepared = true;
|
||||
|
||||
return {.preparedResidual = true};
|
||||
return PreparedGravityDisplacementForceReport{.preparedResidual = true};
|
||||
}
|
||||
|
||||
void PreparedGravityDisplacementForceOperator::BuildResidual(mfem::Vector &residual) const {
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
module;
|
||||
#include "profile.h"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
#include <numbers>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <mpi.h>
|
||||
|
||||
module mean_field;
|
||||
import :operators.prepared_gravity_source;
|
||||
@@ -12,6 +18,54 @@ import :operators.prepared_gravity_source;
|
||||
namespace {
|
||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
[[nodiscard]] bool is_candidate_mapping_failure(const mean_field::mapping::MappingStatus status) {
|
||||
using mean_field::mapping::MappingStatus;
|
||||
return status == MappingStatus::non_finite_input || status == MappingStatus::non_finite_result ||
|
||||
status == MappingStatus::non_positive_determinant;
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::operators::GravitySourcePreparationResult synchronize_preparation_failure(
|
||||
const mean_field::mapping::MappingStatus localMappingStatus,
|
||||
const bool localNonFiniteArithmetic,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
std::array<int, 3> localFailures{0, 0, localNonFiniteArithmetic ? 1 : 0};
|
||||
if (localMappingStatus != mean_field::mapping::MappingStatus::valid) {
|
||||
const int encodedStatus = static_cast<int>(localMappingStatus) + 1;
|
||||
localFailures[is_candidate_mapping_failure(localMappingStatus) ? 0 : 1] = encodedStatus;
|
||||
}
|
||||
|
||||
std::array<int, 3> globalFailures{};
|
||||
if (MPI_Allreduce(
|
||||
localFailures.data(), globalFailures.data(), static_cast<int>(localFailures.size()), MPI_INT, MPI_MAX,
|
||||
communicator
|
||||
) != MPI_SUCCESS) {
|
||||
throw std::runtime_error("PreparedMappedGravitySourceOperator could not synchronize candidate validity.");
|
||||
}
|
||||
if (globalFailures[1] != 0) {
|
||||
throw std::runtime_error(
|
||||
"PreparedMappedGravitySourceOperator encountered a structural mapping failure with status " +
|
||||
std::to_string(globalFailures[1] - 1) + "."
|
||||
);
|
||||
}
|
||||
if (globalFailures[0] != 0) {
|
||||
return std::unexpected(
|
||||
mean_field::operators::GravitySourcePreparationRejection{
|
||||
.reason = mean_field::operators::GravitySourcePreparationRejectionReason::invalid_mapping,
|
||||
.mappingStatus = static_cast<mean_field::mapping::MappingStatus>(globalFailures[0] - 1)
|
||||
}
|
||||
);
|
||||
}
|
||||
if (globalFailures[2] != 0) {
|
||||
return std::unexpected(
|
||||
mean_field::operators::GravitySourcePreparationRejection{
|
||||
.reason = mean_field::operators::GravitySourcePreparationRejectionReason::non_finite_arithmetic
|
||||
}
|
||||
);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int get_operator_height(const mean_field::fem::FEM &f) {
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the "
|
||||
@@ -135,55 +189,33 @@ namespace {
|
||||
);
|
||||
|
||||
if (status != mean_field::mapping::MappingStatus::valid) {
|
||||
const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id);
|
||||
const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id);
|
||||
|
||||
mfem::Vector displacement_shape(displacement_element.GetDof());
|
||||
mfem::Vector compactification_shape(compactification_element.GetDof());
|
||||
mfem::Vector reference_position(m_domain_mapper.GetDimension());
|
||||
mfem::Vector displacement_value(m_domain_mapper.GetDimension());
|
||||
|
||||
displacement_element.CalcShape(integration_point, displacement_shape);
|
||||
compactification_element.CalcShape(integration_point, compactification_shape);
|
||||
transformation.Transform(integration_point, reference_position);
|
||||
m_displacement_data->GetDofMatrix().MultTranspose(displacement_shape, displacement_value);
|
||||
|
||||
const double compactification_coordinate = m_compactification_data->GetDofs() * compactification_shape;
|
||||
|
||||
MFEM_ABORT(
|
||||
"Stateless domain mapping failed while preparing the "
|
||||
"gravity "
|
||||
"source operator."
|
||||
<< "\nMapping status = " << static_cast<int>(status) << "\nElement ID = " << element_id
|
||||
<< "\nElement attribute = " << transformation.Attribute
|
||||
<< "\nIntegration-point index = " << integration_point.index << "\nIntegration point = <"
|
||||
<< integration_point.x << ", " << integration_point.y << ", " << integration_point.z << ">"
|
||||
<< "\nReference position = <" << reference_position(0) << ", " << reference_position(1) << ", "
|
||||
<< reference_position(2) << ">"
|
||||
<< "\nReference radius = " << reference_position.Norml2() << "\nDisplacement value = <"
|
||||
<< displacement_value(0) << ", " << displacement_value(1) << ", " << displacement_value(2) << ">"
|
||||
<< "\nDisplacement magnitude = " << displacement_value.Norml2()
|
||||
<< "\nCompactification coordinate = " << compactification_coordinate
|
||||
<< "\nDisplacement ordering = " << static_cast<int>(m_fem.displacementFes->GetOrdering())
|
||||
);
|
||||
m_mappingFailure = status;
|
||||
return 0.0;
|
||||
}
|
||||
const double mapping_determinant = mapping_context.mapping.mapping_determinant;
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
|
||||
"Prepared gravity source operator encountered a non-positive "
|
||||
"or "
|
||||
"non-finite mapping determinant."
|
||||
);
|
||||
|
||||
m_inverse_element_jacobian = mapping_context.quadrature.J_inv;
|
||||
m_inverse_element_jacobian = mapping_context.quadrature.J_inv;
|
||||
|
||||
return 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant;
|
||||
const double value = 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant;
|
||||
if (!std::isfinite(value)) {
|
||||
m_nonFiniteArithmetic = true;
|
||||
return 0.0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::DenseMatrix &GetInverseElementJacobian() const noexcept {
|
||||
return m_inverse_element_jacobian;
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::mapping::MappingStatus GetMappingFailure() const noexcept {
|
||||
return m_mappingFailure;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasNonFiniteArithmetic() const noexcept {
|
||||
return m_nonFiniteArithmetic;
|
||||
}
|
||||
|
||||
private:
|
||||
void LoadElement(const int element_id) {
|
||||
if (element_id == m_cached_element_id) {
|
||||
@@ -239,6 +271,8 @@ namespace {
|
||||
mean_field::mapping::DomainMapper::Workspace m_workspace;
|
||||
mfem::DenseMatrix m_inverse_element_jacobian;
|
||||
int m_cached_element_id{-1};
|
||||
mean_field::mapping::MappingStatus m_mappingFailure{mean_field::mapping::MappingStatus::valid};
|
||||
bool m_nonFiniteArithmetic{false};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -306,15 +340,32 @@ namespace mean_field::operators {
|
||||
|
||||
void PreparedMappedGravitySourceOperator::Prepare(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedGravitySourceOperator::Prepare linearization", 0);
|
||||
PrepareImpl(displacement, PreparationMode::linearization);
|
||||
auto result = TryPrepareImpl(displacement, PreparationMode::linearization);
|
||||
if (!result.has_value()) {
|
||||
throwGravitySourcePreparationRejection(result.error());
|
||||
}
|
||||
}
|
||||
|
||||
void PreparedMappedGravitySourceOperator::PreparePrimal(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedGravitySourceOperator::Prepare primal", 0);
|
||||
PrepareImpl(displacement, PreparationMode::primal);
|
||||
auto result = TryPrepareImpl(displacement, PreparationMode::primal);
|
||||
if (!result.has_value()) {
|
||||
throwGravitySourcePreparationRejection(result.error());
|
||||
}
|
||||
}
|
||||
|
||||
void PreparedMappedGravitySourceOperator::PrepareImpl(
|
||||
GravitySourcePreparationResult PreparedMappedGravitySourceOperator::TryPrepare(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedGravitySourceOperator::TryPrepare linearization", 0);
|
||||
return TryPrepareImpl(displacement, PreparationMode::linearization);
|
||||
}
|
||||
|
||||
GravitySourcePreparationResult
|
||||
PreparedMappedGravitySourceOperator::TryPreparePrimal(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedGravitySourceOperator::TryPrepare primal", 0);
|
||||
return TryPrepareImpl(displacement, PreparationMode::primal);
|
||||
}
|
||||
|
||||
GravitySourcePreparationResult PreparedMappedGravitySourceOperator::TryPrepareImpl(
|
||||
const mfem::Vector &displacement,
|
||||
const PreparationMode mode
|
||||
) {
|
||||
@@ -325,11 +376,18 @@ namespace mean_field::operators {
|
||||
"with the wrong size."
|
||||
);
|
||||
|
||||
bool localNonFiniteInput = false;
|
||||
for (int i = 0; i < displacement.Size(); ++i) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(displacement(i)), "PreparedMappedGravitySourceOperator received a non-finite "
|
||||
"displacement value."
|
||||
localNonFiniteInput = localNonFiniteInput || !std::isfinite(displacement(i));
|
||||
}
|
||||
if (auto inputResult = synchronize_preparation_failure(
|
||||
localNonFiniteInput ? mapping::MappingStatus::non_finite_input : mapping::MappingStatus::valid, false,
|
||||
m_fem.mesh->GetComm()
|
||||
);
|
||||
!inputResult.has_value()) {
|
||||
m_is_prepared = false;
|
||||
m_has_variation_data = false;
|
||||
return inputResult;
|
||||
}
|
||||
|
||||
m_is_prepared = false;
|
||||
@@ -340,6 +398,7 @@ namespace mean_field::operators {
|
||||
m_elements.reserve(m_fem.mesh->GetNE());
|
||||
|
||||
FrozenMappedGravitySourceCoefficient source_coefficient(m_fem, m_domain_mapper, m_displacement_true);
|
||||
bool localNonFiniteQuadrature = false;
|
||||
|
||||
for (int element_id = 0; element_id < m_fem.mesh->GetNE(); ++element_id) {
|
||||
const int attribute = m_fem.mesh->GetAttribute(element_id);
|
||||
@@ -414,6 +473,11 @@ namespace mean_field::operators {
|
||||
|
||||
const double coefficient_value = source_coefficient.Eval(transformation, integration_point);
|
||||
|
||||
if (source_coefficient.GetMappingFailure() != mapping::MappingStatus::valid ||
|
||||
source_coefficient.HasNonFiniteArithmetic()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mode == PreparationMode::linearization) {
|
||||
const mfem::DenseMatrix &inverse_element_jacobian = source_coefficient.GetInverseElementJacobian();
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
@@ -428,15 +492,26 @@ namespace mean_field::operators {
|
||||
|
||||
const double quadrature_value = integration_point.weight * transformation.Weight() * coefficient_value;
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(quadrature_value) && quadrature_value > 0.0,
|
||||
"Prepared gravity source operator encountered invalid "
|
||||
"quadrature data on element "
|
||||
<< element_id << ", quadrature point " << quadrature_point << "."
|
||||
);
|
||||
if (!std::isfinite(quadrature_value) || quadrature_value <= 0.0) {
|
||||
localNonFiniteQuadrature = true;
|
||||
break;
|
||||
}
|
||||
|
||||
data.quadrature_data(quadrature_point) = quadrature_value;
|
||||
}
|
||||
|
||||
if (source_coefficient.GetMappingFailure() != mapping::MappingStatus::valid ||
|
||||
source_coefficient.HasNonFiniteArithmetic() || localNonFiniteQuadrature) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const bool localNonFiniteArithmetic = source_coefficient.HasNonFiniteArithmetic() || localNonFiniteQuadrature;
|
||||
auto preparationResult = synchronize_preparation_failure(
|
||||
source_coefficient.GetMappingFailure(), localNonFiniteArithmetic, m_fem.mesh->GetComm()
|
||||
);
|
||||
if (!preparationResult.has_value()) {
|
||||
return preparationResult;
|
||||
}
|
||||
|
||||
MFEM_VERIFY(!m_elements.empty(), "PreparedMappedGravitySourceOperator found no stellar elements.");
|
||||
@@ -444,6 +519,7 @@ namespace mean_field::operators {
|
||||
m_is_prepared = true;
|
||||
m_has_variation_data = mode == PreparationMode::linearization;
|
||||
++m_preparation_count;
|
||||
return {};
|
||||
}
|
||||
void PreparedMappedGravitySourceOperator::Mult(
|
||||
const mfem::Vector &density,
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
module;
|
||||
#include "profile.h"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <mpi.h>
|
||||
|
||||
module mean_field;
|
||||
import :operators.prepared_hdiv_mass;
|
||||
@@ -11,6 +18,83 @@ import :operators.prepared_hdiv_mass;
|
||||
namespace {
|
||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
[[nodiscard]] bool is_candidate_mapping_failure(const mean_field::mapping::MappingStatus status) {
|
||||
using mean_field::mapping::MappingStatus;
|
||||
return status == MappingStatus::non_finite_input || status == MappingStatus::non_finite_result ||
|
||||
status == MappingStatus::non_positive_determinant;
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::operators::HDivMassPreparationResult synchronize_preparation_failure(
|
||||
const mean_field::mapping::MappingStatus localMappingStatus,
|
||||
const bool localNonFiniteArithmetic,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
std::array<int, 3> localFailures{0, 0, localNonFiniteArithmetic ? 1 : 0};
|
||||
if (localMappingStatus != mean_field::mapping::MappingStatus::valid) {
|
||||
const int encodedStatus = static_cast<int>(localMappingStatus) + 1;
|
||||
localFailures[is_candidate_mapping_failure(localMappingStatus) ? 0 : 1] = encodedStatus;
|
||||
}
|
||||
|
||||
std::array<int, 3> globalFailures{};
|
||||
if (MPI_Allreduce(
|
||||
localFailures.data(), globalFailures.data(), static_cast<int>(localFailures.size()), MPI_INT, MPI_MAX,
|
||||
communicator
|
||||
) != MPI_SUCCESS) {
|
||||
throw std::runtime_error("PreparedMappedHDivMassOperator could not synchronize candidate validity.");
|
||||
}
|
||||
if (globalFailures[1] != 0) {
|
||||
throw std::runtime_error(
|
||||
"PreparedMappedHDivMassOperator encountered a structural mapping failure with status " +
|
||||
std::to_string(globalFailures[1] - 1) + "."
|
||||
);
|
||||
}
|
||||
if (globalFailures[0] != 0) {
|
||||
return std::unexpected(
|
||||
mean_field::operators::HDivMassPreparationRejection{
|
||||
.reason = mean_field::operators::HDivMassPreparationRejectionReason::invalid_mapping,
|
||||
.mappingStatus = static_cast<mean_field::mapping::MappingStatus>(globalFailures[0] - 1)
|
||||
}
|
||||
);
|
||||
}
|
||||
if (globalFailures[2] != 0) {
|
||||
return std::unexpected(
|
||||
mean_field::operators::HDivMassPreparationRejection{
|
||||
.reason = mean_field::operators::HDivMassPreparationRejectionReason::non_finite_arithmetic
|
||||
}
|
||||
);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::mapping::MappingStatus higher_priority_mapping_status(
|
||||
const mean_field::mapping::MappingStatus left,
|
||||
const mean_field::mapping::MappingStatus right
|
||||
) noexcept {
|
||||
if (left == mean_field::mapping::MappingStatus::valid) {
|
||||
return right;
|
||||
}
|
||||
if (right == mean_field::mapping::MappingStatus::valid) {
|
||||
return left;
|
||||
}
|
||||
const bool leftIsCandidate = is_candidate_mapping_failure(left);
|
||||
const bool rightIsCandidate = is_candidate_mapping_failure(right);
|
||||
if (leftIsCandidate != rightIsCandidate) {
|
||||
return leftIsCandidate ? right : left;
|
||||
}
|
||||
return static_cast<int>(right) > static_cast<int>(left) ? right : left;
|
||||
}
|
||||
|
||||
[[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;
|
||||
}
|
||||
|
||||
int get_operator_size(const mean_field::fem::FEM &f) {
|
||||
MFEM_VERIFY(
|
||||
f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the "
|
||||
@@ -270,27 +354,30 @@ namespace {
|
||||
mapping_data, transformation, integration_point, m_workspace, mapping_context
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
status == mean_field::mapping::MappingStatus::valid,
|
||||
"Stateless domain mapping failed while preparing the H(div) "
|
||||
"mass "
|
||||
"operator. Mapping status = "
|
||||
<< static_cast<int>(status) << ", element ID = " << element_id
|
||||
<< ", element attribute = " << transformation.Attribute
|
||||
<< ", coefficient domain = " << (m_elevates_vacuum ? "vacuum" : "stellar")
|
||||
);
|
||||
if (status != mean_field::mapping::MappingStatus::valid) {
|
||||
m_mappingFailure = higher_priority_mapping_status(m_mappingFailure, status);
|
||||
mass_tensor.SetSize(m_domain_mapper.GetDimension());
|
||||
mass_tensor = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::DenseMatrix &mapping_jacobian = mapping_context.mapping.mapping_jacobian;
|
||||
const double mapping_determinant = mapping_context.mapping.mapping_determinant;
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
|
||||
"Prepared H(div) mass operator encountered a non-positive or "
|
||||
"non-finite mapping determinant."
|
||||
);
|
||||
|
||||
mfem::MultAtB(mapping_jacobian, mapping_jacobian, mass_tensor);
|
||||
mass_tensor *= 1.0 / mapping_determinant;
|
||||
if (!matrix_is_finite(mass_tensor)) {
|
||||
m_nonFiniteArithmetic = true;
|
||||
mass_tensor = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::mapping::MappingStatus GetMappingFailure() const noexcept {
|
||||
return m_mappingFailure;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasNonFiniteArithmetic() const noexcept {
|
||||
return m_nonFiniteArithmetic;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -348,6 +435,8 @@ namespace {
|
||||
mean_field::mapping::DomainMapper::Workspace m_workspace;
|
||||
int m_cached_element_id{-1};
|
||||
bool m_elevates_vacuum;
|
||||
mean_field::mapping::MappingStatus m_mappingFailure{mean_field::mapping::MappingStatus::valid};
|
||||
bool m_nonFiniteArithmetic{false};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -417,7 +506,7 @@ namespace mean_field::operators {
|
||||
validate_uniform_domain_discretization(f, m_vacuum_marker, vacuum_element_id);
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::PrepareVariationData() {
|
||||
mapping::MappingStatus PreparedMappedHDivMassOperator::PrepareVariationData() {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::PrepareVariationData", 0);
|
||||
|
||||
m_variationElements.clear();
|
||||
@@ -480,28 +569,42 @@ namespace mean_field::operators {
|
||||
const mapping::MappingStatus status = m_domain_mapper.EvaluateVolume(
|
||||
mappingData, *transformation, integrationPoint, m_variationWorkspace, mappingContext
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
status == mapping::MappingStatus::valid,
|
||||
"Prepared H(div) variation data encountered an invalid mapping. Element: "
|
||||
<< elementId << ", quadrature point: " << quadraturePoint
|
||||
<< ", status: " << static_cast<int>(status)
|
||||
);
|
||||
if (status != mapping::MappingStatus::valid) {
|
||||
return status;
|
||||
}
|
||||
freeze_mapping_context(mappingContext, quadraturePoint, data.frozenMappingData);
|
||||
}
|
||||
}
|
||||
return mapping::MappingStatus::valid;
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::Prepare linearization", 0);
|
||||
PrepareImpl(displacement, PreparationMode::linearization);
|
||||
auto result = TryPrepareImpl(displacement, PreparationMode::linearization);
|
||||
if (!result.has_value()) {
|
||||
throwHDivMassPreparationRejection(result.error());
|
||||
}
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::PreparePrimal(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::Prepare primal", 0);
|
||||
PrepareImpl(displacement, PreparationMode::primal);
|
||||
auto result = TryPrepareImpl(displacement, PreparationMode::primal);
|
||||
if (!result.has_value()) {
|
||||
throwHDivMassPreparationRejection(result.error());
|
||||
}
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::PrepareImpl(
|
||||
HDivMassPreparationResult PreparedMappedHDivMassOperator::TryPrepare(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::TryPrepare linearization", 0);
|
||||
return TryPrepareImpl(displacement, PreparationMode::linearization);
|
||||
}
|
||||
|
||||
HDivMassPreparationResult PreparedMappedHDivMassOperator::TryPreparePrimal(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::TryPrepare primal", 0);
|
||||
return TryPrepareImpl(displacement, PreparationMode::primal);
|
||||
}
|
||||
|
||||
HDivMassPreparationResult PreparedMappedHDivMassOperator::TryPrepareImpl(
|
||||
const mfem::Vector &displacement,
|
||||
const PreparationMode mode
|
||||
) {
|
||||
@@ -512,12 +615,18 @@ namespace mean_field::operators {
|
||||
"the wrong size."
|
||||
);
|
||||
|
||||
bool localNonFiniteInput = false;
|
||||
for (int i = 0; i < displacement.Size(); ++i) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(displacement(i)), "PreparedMappedHDivMassOperator received a non-finite "
|
||||
"displacement "
|
||||
"value."
|
||||
localNonFiniteInput = localNonFiniteInput || !std::isfinite(displacement(i));
|
||||
}
|
||||
if (auto inputResult = synchronize_preparation_failure(
|
||||
localNonFiniteInput ? mapping::MappingStatus::non_finite_input : mapping::MappingStatus::valid, false,
|
||||
m_fem.mesh->GetComm()
|
||||
);
|
||||
!inputResult.has_value()) {
|
||||
m_is_prepared = false;
|
||||
m_has_variation_data = false;
|
||||
return inputResult;
|
||||
}
|
||||
|
||||
m_is_prepared = false;
|
||||
@@ -540,13 +649,17 @@ namespace mean_field::operators {
|
||||
m_stellar_mass_coefficient.reset();
|
||||
m_vacuum_mass_coefficient.reset();
|
||||
|
||||
m_stellar_mass_coefficient =
|
||||
auto stellarMassCoefficient =
|
||||
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, false);
|
||||
m_vacuum_mass_coefficient =
|
||||
auto vacuumMassCoefficient =
|
||||
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, true);
|
||||
auto *stellarMassCoefficientView = stellarMassCoefficient.get();
|
||||
auto *vacuumMassCoefficientView = vacuumMassCoefficient.get();
|
||||
m_stellar_mass_coefficient = std::move(stellarMassCoefficient);
|
||||
m_vacuum_mass_coefficient = std::move(vacuumMassCoefficient);
|
||||
|
||||
m_stellar_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
|
||||
m_vacuum_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
|
||||
m_stellar_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
|
||||
m_vacuum_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
|
||||
m_stellar_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||
m_vacuum_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||
|
||||
@@ -568,15 +681,30 @@ namespace mean_field::operators {
|
||||
m_stellar_mass_form->Assemble();
|
||||
m_vacuum_mass_form->Assemble();
|
||||
|
||||
mapping::MappingStatus localMappingFailure = higher_priority_mapping_status(
|
||||
stellarMassCoefficientView->GetMappingFailure(), vacuumMassCoefficientView->GetMappingFailure()
|
||||
);
|
||||
bool localNonFiniteArithmetic =
|
||||
stellarMassCoefficientView->HasNonFiniteArithmetic() || vacuumMassCoefficientView->HasNonFiniteArithmetic();
|
||||
|
||||
if (mode == PreparationMode::linearization) {
|
||||
PrepareVariationData();
|
||||
m_has_variation_data = true;
|
||||
if (localMappingFailure == mapping::MappingStatus::valid && !localNonFiniteArithmetic) {
|
||||
localMappingFailure = PrepareVariationData();
|
||||
}
|
||||
} else {
|
||||
m_variationElements.clear();
|
||||
}
|
||||
|
||||
m_is_prepared = true;
|
||||
auto preparationResult =
|
||||
synchronize_preparation_failure(localMappingFailure, localNonFiniteArithmetic, m_fem.mesh->GetComm());
|
||||
if (!preparationResult.has_value()) {
|
||||
return preparationResult;
|
||||
}
|
||||
|
||||
m_has_variation_data = mode == PreparationMode::linearization;
|
||||
m_is_prepared = true;
|
||||
++m_preparation_count;
|
||||
return {};
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::Mult(
|
||||
|
||||
@@ -4,6 +4,11 @@ module;
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
@@ -18,6 +23,82 @@ namespace {
|
||||
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_candidate_mapping_failure(const mean_field::mapping::MappingStatus status) {
|
||||
using mean_field::mapping::MappingStatus;
|
||||
|
||||
return status == MappingStatus::non_finite_input || status == MappingStatus::non_finite_result ||
|
||||
status == MappingStatus::non_positive_determinant;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<mean_field::mapping::MappingStatus> synchronize_mapping_failure(
|
||||
const std::optional<mean_field::mapping::MappingStatus> localFailure,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
int localFailures[2]{0, 0};
|
||||
if (localFailure.has_value()) {
|
||||
const int encodedStatus = static_cast<int>(*localFailure) + 1;
|
||||
if (is_candidate_mapping_failure(*localFailure)) {
|
||||
localFailures[0] = encodedStatus;
|
||||
} else {
|
||||
localFailures[1] = encodedStatus;
|
||||
}
|
||||
}
|
||||
|
||||
int globalFailures[2]{0, 0};
|
||||
if (MPI_Allreduce(localFailures, globalFailures, 2, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||
throw std::runtime_error(
|
||||
"PreparedHydrostaticEquilibriumOperator could not synchronize mapped-geometry validity."
|
||||
);
|
||||
}
|
||||
|
||||
if (globalFailures[1] != 0) {
|
||||
throw std::runtime_error(
|
||||
"PreparedHydrostaticEquilibriumOperator encountered a structural mapping failure with status " +
|
||||
std::to_string(globalFailures[1] - 1) + "."
|
||||
);
|
||||
}
|
||||
|
||||
if (globalFailures[0] == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return static_cast<mean_field::mapping::MappingStatus>(globalFailures[0] - 1);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool synchronize_non_finite_failure(
|
||||
const bool localFailure,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
const int localStatus = localFailure ? 1 : 0;
|
||||
int globalStatus = 0;
|
||||
if (MPI_Allreduce(&localStatus, &globalStatus, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||
throw std::runtime_error(
|
||||
"PreparedHydrostaticEquilibriumOperator could not synchronize finite-arithmetic validity."
|
||||
);
|
||||
}
|
||||
return globalStatus != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_finite(const mfem::Vector &vector) {
|
||||
for (int entry = 0; entry < vector.Size(); ++entry) {
|
||||
if (!std::isfinite(vector(entry))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_finite(const mfem::DenseMatrix &matrix) {
|
||||
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;
|
||||
}
|
||||
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &trueVector,
|
||||
@@ -291,8 +372,21 @@ namespace mean_field::operators {
|
||||
const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies,
|
||||
const physics::RigidRotation &rotation
|
||||
) {
|
||||
auto result = TryPrepare(state, dependencies, rotation);
|
||||
if (!result.has_value()) {
|
||||
throwHydrostaticEquilibriumPreparationRejection(result.error());
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
HydrostaticEquilibriumPreparationResult PreparedHydrostaticEquilibriumOperator::TryPrepare(
|
||||
const context::hydrostatic::HydrostaticEquilibriumStateView &state,
|
||||
const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies,
|
||||
const physics::RigidRotation &rotation
|
||||
) {
|
||||
const bool wasPrepared = m_isPrepared;
|
||||
const bool rotationObjectChanged =
|
||||
!m_context.IsPrepared() || dependencies.rotation != m_context.GetDependencies().rotation;
|
||||
!wasPrepared || !m_context.IsPrepared() || dependencies.rotation != m_context.GetDependencies().rotation;
|
||||
|
||||
PreparedHydrostaticEquilibriumReport report;
|
||||
|
||||
@@ -310,25 +404,58 @@ namespace mean_field::operators {
|
||||
|
||||
m_isPrepared = false;
|
||||
|
||||
if (report.contextReport.preparedStaticDependencies) {
|
||||
if (report.contextReport.preparedStaticDependencies || !wasPrepared) {
|
||||
PrepareStaticPlan();
|
||||
}
|
||||
|
||||
if (report.contextReport.preparedGeometryState) {
|
||||
PrepareGeometry();
|
||||
PrepareAlgebraicJacobianBlocks();
|
||||
if (report.contextReport.preparedGeometryState || !wasPrepared) {
|
||||
const auto mappingFailure = synchronize_mapping_failure(PrepareGeometry(), m_fem.mesh->GetComm());
|
||||
if (mappingFailure.has_value()) {
|
||||
const auto reason = *mappingFailure == mapping::MappingStatus::non_positive_determinant
|
||||
? HydrostaticEquilibriumPreparationRejectionReason::inverted_geometry
|
||||
: HydrostaticEquilibriumPreparationRejectionReason::non_finite_geometry;
|
||||
return std::unexpected(
|
||||
HydrostaticEquilibriumPreparationRejection{.reason = reason, .mappingStatus = *mappingFailure}
|
||||
);
|
||||
}
|
||||
|
||||
if (synchronize_non_finite_failure(PrepareAlgebraicJacobianBlocks(), m_fem.mesh->GetComm())) {
|
||||
return std::unexpected(
|
||||
HydrostaticEquilibriumPreparationRejection{
|
||||
.reason = HydrostaticEquilibriumPreparationRejectionReason::non_finite_geometry,
|
||||
.mappingStatus = mapping::MappingStatus::non_finite_result
|
||||
}
|
||||
);
|
||||
}
|
||||
report.preparedAlgebraicJacobianBlocks = true;
|
||||
}
|
||||
|
||||
if (report.contextReport.preparedRotationDependencies) {
|
||||
PrepareRotation();
|
||||
if (report.contextReport.preparedRotationDependencies || !wasPrepared) {
|
||||
if (synchronize_non_finite_failure(PrepareRotation(), m_fem.mesh->GetComm())) {
|
||||
return std::unexpected(
|
||||
HydrostaticEquilibriumPreparationRejection{
|
||||
.reason = HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (report.contextReport.preparedBaseState) {
|
||||
PrepareBaseState();
|
||||
if (report.contextReport.preparedBaseState || !wasPrepared) {
|
||||
if (synchronize_non_finite_failure(PrepareBaseState(), m_fem.mesh->GetComm())) {
|
||||
return std::unexpected(
|
||||
HydrostaticEquilibriumPreparationRejection{
|
||||
.reason = HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual
|
||||
}
|
||||
);
|
||||
}
|
||||
FinalizeDisplacementJacobianPreparation();
|
||||
AssembleCachedResidual();
|
||||
++m_residualPreparationCount;
|
||||
if (synchronize_non_finite_failure(AssembleCachedResidual(), m_fem.mesh->GetComm())) {
|
||||
return std::unexpected(
|
||||
HydrostaticEquilibriumPreparationRejection{
|
||||
.reason = HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual
|
||||
}
|
||||
);
|
||||
}
|
||||
report.preparedDisplacementJacobianData = true;
|
||||
report.preparedResidual = true;
|
||||
}
|
||||
@@ -343,6 +470,16 @@ namespace mean_field::operators {
|
||||
"The prepared hydrostatic residual has the wrong supported size."
|
||||
);
|
||||
|
||||
if (report.preparedAlgebraicJacobianBlocks) {
|
||||
++m_algebraicJacobianStatistics.preparations;
|
||||
}
|
||||
if (report.preparedDisplacementJacobianData) {
|
||||
++m_displacementJacobianStatistics.preparations;
|
||||
}
|
||||
if (report.preparedResidual) {
|
||||
++m_residualPreparationCount;
|
||||
}
|
||||
|
||||
m_isPrepared = true;
|
||||
return report;
|
||||
}
|
||||
@@ -422,7 +559,7 @@ namespace mean_field::operators {
|
||||
}
|
||||
}
|
||||
|
||||
void PreparedHydrostaticEquilibriumOperator::PrepareGeometry() {
|
||||
std::optional<mapping::MappingStatus> PreparedHydrostaticEquilibriumOperator::PrepareGeometry() {
|
||||
mfem::Vector displacementLocal;
|
||||
|
||||
true_to_local(*m_fem.displacementFes, m_context.GetDisplacementTrue(), displacementLocal);
|
||||
@@ -491,39 +628,37 @@ namespace mean_field::operators {
|
||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
mappingStatus == mapping::MappingStatus::valid,
|
||||
"Stateless mapping failed while preparing "
|
||||
"hydrostatic geometry. Element: "
|
||||
<< data.elementId << ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
|
||||
);
|
||||
if (mappingStatus != mapping::MappingStatus::valid) {
|
||||
return mappingStatus;
|
||||
}
|
||||
|
||||
const double quadratureWeight = mappingContext.quadrature.weight;
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(quadratureWeight) && quadratureWeight > 0.0,
|
||||
"Prepared hydrostatic geometry encountered "
|
||||
"an invalid quadrature weight."
|
||||
);
|
||||
if (!std::isfinite(quadratureWeight)) {
|
||||
return mapping::MappingStatus::non_finite_result;
|
||||
}
|
||||
if (quadratureWeight <= 0.0) {
|
||||
return mapping::MappingStatus::non_positive_determinant;
|
||||
}
|
||||
|
||||
data.quadratureWeights(quadraturePoint) = quadratureWeight;
|
||||
|
||||
for (int component = 0; component < m_fem.mesh->Dimension(); ++component) {
|
||||
const double position = mappingContext.mapping.physical_position(component);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(position), "Prepared hydrostatic geometry encountered "
|
||||
"a non-finite physical position."
|
||||
);
|
||||
if (!std::isfinite(position)) {
|
||||
return mapping::MappingStatus::non_finite_result;
|
||||
}
|
||||
|
||||
data.physicalPositions(quadraturePoint, component) = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void PreparedHydrostaticEquilibriumOperator::PrepareAlgebraicJacobianBlocks() {
|
||||
bool PreparedHydrostaticEquilibriumOperator::PrepareAlgebraicJacobianBlocks() {
|
||||
for (ElementPAData &data : m_elements) {
|
||||
const int quadraturePointCount = data.quadratureWeights.Size();
|
||||
|
||||
@@ -567,12 +702,17 @@ namespace mean_field::operators {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_finite(data.enthalpyJacobian) || !is_finite(data.gravityPotentialJacobian) ||
|
||||
!is_finite(data.bernoulliConstantJacobian)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
++m_algebraicJacobianStatistics.preparations;
|
||||
return false;
|
||||
}
|
||||
|
||||
void PreparedHydrostaticEquilibriumOperator::PrepareRotation() {
|
||||
bool PreparedHydrostaticEquilibriumOperator::PrepareRotation() {
|
||||
MFEM_VERIFY(m_rotation.has_value(), "Prepared hydrostatic rotation has no frozen state.");
|
||||
|
||||
mfem::Vector physicalPosition(m_fem.mesh->Dimension());
|
||||
@@ -598,10 +738,9 @@ namespace mean_field::operators {
|
||||
|
||||
const double rotationPotential = m_rotation->potential(physicalPosition);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(rotationPotential), "Prepared hydrostatic rotation encountered "
|
||||
"a non-finite potential."
|
||||
);
|
||||
if (!std::isfinite(rotationPotential)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
data.rotationPotential(quadraturePoint) = rotationPotential;
|
||||
|
||||
@@ -612,18 +751,19 @@ namespace mean_field::operators {
|
||||
const double gradientComponent =
|
||||
m_rotation->potential_directional_derivative(physicalPosition, coordinateDirection);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(gradientComponent), "Prepared hydrostatic rotation encountered "
|
||||
"a non-finite potential gradient."
|
||||
);
|
||||
if (!std::isfinite(gradientComponent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
data.rotationGradient(quadraturePoint, component) = gradientComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PreparedHydrostaticEquilibriumOperator::PrepareBaseState() {
|
||||
bool PreparedHydrostaticEquilibriumOperator::PrepareBaseState() {
|
||||
mfem::Vector enthalpyLocal;
|
||||
mfem::Vector gravityPotentialLocal;
|
||||
|
||||
@@ -675,16 +815,17 @@ namespace mean_field::operators {
|
||||
|
||||
const double weightedResidual = data.quadratureWeights(quadraturePoint) * imbalance;
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(weightedResidual), "Prepared hydrostatic base state encountered "
|
||||
"a non-finite residual value."
|
||||
);
|
||||
if (!std::isfinite(imbalance) || !std::isfinite(weightedResidual)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
data.weightedResidual(quadraturePoint) = weightedResidual;
|
||||
|
||||
data.hydrostaticImbalance(quadraturePoint) = imbalance;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PreparedHydrostaticEquilibriumOperator::FinalizeDisplacementJacobianPreparation() {
|
||||
@@ -703,11 +844,9 @@ namespace mean_field::operators {
|
||||
"has inconsistent frozen data."
|
||||
);
|
||||
}
|
||||
|
||||
++m_displacementJacobianStatistics.preparations;
|
||||
}
|
||||
|
||||
void PreparedHydrostaticEquilibriumOperator::AssembleCachedResidual() {
|
||||
bool PreparedHydrostaticEquilibriumOperator::AssembleCachedResidual() {
|
||||
mfem::Vector localResidual(m_fem.enthalpyFes->GetVSize());
|
||||
|
||||
localResidual = 0.0;
|
||||
@@ -729,6 +868,8 @@ namespace mean_field::operators {
|
||||
|
||||
m_cachedResidual.SetSize(m_context.GetEnthalpyMap().reduced_size());
|
||||
m_context.GetEnthalpyMap().gather(m_fullEnthalpyAction, m_cachedResidual);
|
||||
|
||||
return !is_finite(m_cachedResidual);
|
||||
}
|
||||
|
||||
void PreparedHydrostaticEquilibriumOperator::BuildResidual(mfem::Vector &residual) const {
|
||||
@@ -926,9 +1067,9 @@ namespace mean_field::operators {
|
||||
);
|
||||
weightedVariation.SetSize(quadraturePointCount);
|
||||
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
|
||||
weightedVariation(quadraturePoint) =
|
||||
-2.0 * fractionalAngularVelocityVariation * data.quadratureWeights(quadraturePoint) *
|
||||
data.rotationPotential(quadraturePoint);
|
||||
weightedVariation(quadraturePoint) = -2.0 * fractionalAngularVelocityVariation *
|
||||
data.quadratureWeights(quadraturePoint) *
|
||||
data.rotationPotential(quadraturePoint);
|
||||
}
|
||||
elementAction.SetSize(data.enthalpyDofs.Size());
|
||||
data.enthalpyBasis.MultTranspose(weightedVariation, elementAction);
|
||||
|
||||
@@ -2,7 +2,13 @@ module;
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <expected>
|
||||
#include <mfem.hpp>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <mpi.h>
|
||||
|
||||
module mean_field;
|
||||
|
||||
@@ -116,6 +122,109 @@ namespace {
|
||||
) {
|
||||
MFEM_VERIFY(prepared.identity == requested.identity || prepared.revision != requested.revision, message);
|
||||
}
|
||||
|
||||
using MassRejection = mean_field::operators::MassNormalizationPreparationRejection;
|
||||
using MassRejectionReason = mean_field::operators::MassNormalizationPreparationRejectionReason;
|
||||
|
||||
[[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 mass-normalization candidate rejection.");
|
||||
case Status::invalid_dimension:
|
||||
throw std::logic_error("A mapping dimension error cannot be a mass-normalization candidate rejection.");
|
||||
}
|
||||
throw std::logic_error("Unknown mapping status in mass-normalization 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 mass normalization.");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase priority is explicit and independent of enum representation:
|
||||
* mapping wins over interpolation, which wins over assembled-mass
|
||||
* arithmetic. The mapping detail is likewise selected explicitly.
|
||||
*/
|
||||
[[nodiscard]] int rejection_priority(const MassRejection &rejection) {
|
||||
switch (rejection.reason) {
|
||||
case MassRejectionReason::mapping_failure:
|
||||
return 300 + mapping_status_priority(rejection.mappingStatus);
|
||||
case MassRejectionReason::non_finite_density_interpolation:
|
||||
return 200;
|
||||
case MassRejectionReason::non_finite_assembled_mass:
|
||||
return 100;
|
||||
}
|
||||
throw std::logic_error("Unknown mass-normalization candidate-rejection reason.");
|
||||
}
|
||||
|
||||
[[nodiscard]] MassRejection rejection_from_priority(const int priority) {
|
||||
if (priority >= 300) {
|
||||
return {
|
||||
.reason = MassRejectionReason::mapping_failure,
|
||||
.mappingStatus = mapping_status_from_priority(priority - 300)
|
||||
};
|
||||
}
|
||||
if (priority == 200) {
|
||||
return {.reason = MassRejectionReason::non_finite_density_interpolation};
|
||||
}
|
||||
if (priority == 100) {
|
||||
return {.reason = MassRejectionReason::non_finite_assembled_mass};
|
||||
}
|
||||
throw std::logic_error("Invalid synchronized mass-normalization candidate-rejection priority.");
|
||||
}
|
||||
|
||||
void retain_higher_priority_rejection(
|
||||
std::optional<MassRejection> ¤t,
|
||||
const MassRejection candidate
|
||||
) {
|
||||
if (!current.has_value() || rejection_priority(candidate) > rejection_priority(*current)) {
|
||||
current = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<MassRejection> synchronize_rejection(
|
||||
const std::optional<MassRejection> &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("PreparedMassNormalizationOperator could not synchronize candidate validity.");
|
||||
}
|
||||
if (globalPriority == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return rejection_from_priority(globalPriority);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators {
|
||||
@@ -154,6 +263,26 @@ namespace mean_field::operators {
|
||||
PreparedMassNormalizationReport PreparedMassNormalizationOperator::Prepare(
|
||||
const MassNormalizationStateView &state,
|
||||
const MassNormalizationDependencies &dependencies
|
||||
) {
|
||||
auto result = TryPrepare(state, dependencies);
|
||||
if (!result.has_value()) {
|
||||
const MassNormalizationPreparationRejection &rejection = result.error();
|
||||
switch (rejection.reason) {
|
||||
case MassNormalizationPreparationRejectionReason::mapping_failure:
|
||||
throw std::domain_error("PreparedMassNormalizationOperator could not map the candidate geometry.");
|
||||
case MassNormalizationPreparationRejectionReason::non_finite_density_interpolation:
|
||||
throw std::domain_error("PreparedMassNormalizationOperator produced a non-finite quadrature density.");
|
||||
case MassNormalizationPreparationRejectionReason::non_finite_assembled_mass:
|
||||
throw std::domain_error("PreparedMassNormalizationOperator assembled a non-finite mass residual.");
|
||||
}
|
||||
throw std::logic_error("Unknown mass-normalization candidate-rejection reason.");
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
MassNormalizationPreparationResult PreparedMassNormalizationOperator::TryPrepare(
|
||||
const MassNormalizationStateView &state,
|
||||
const MassNormalizationDependencies &dependencies
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(state.targetMass) && state.targetMass > 0.0,
|
||||
@@ -195,6 +324,7 @@ namespace mean_field::operators {
|
||||
m_isPrepared = false;
|
||||
|
||||
PreparedMassNormalizationReport report;
|
||||
std::optional<MassNormalizationPreparationRejection> localRejection;
|
||||
|
||||
if (rebuildStaticPlan) {
|
||||
BuildStaticPlan();
|
||||
@@ -202,27 +332,37 @@ namespace mean_field::operators {
|
||||
}
|
||||
|
||||
if (refreshGeometry) {
|
||||
RefreshGeometry(m_gravityContext.GetGeometryContext().GetDisplacementTrue());
|
||||
localRejection = RefreshGeometry(m_gravityContext.GetGeometryContext().GetDisplacementTrue());
|
||||
report.refreshedGeometry = true;
|
||||
}
|
||||
|
||||
if (refreshDensity) {
|
||||
RefreshDensity(m_gravityContext.GetDensityTrue());
|
||||
if (auto densityRejection = RefreshDensity(m_gravityContext.GetDensityTrue());
|
||||
densityRejection.has_value()) {
|
||||
retain_higher_priority_rejection(localRejection, *densityRejection);
|
||||
}
|
||||
report.refreshedDensity = true;
|
||||
}
|
||||
|
||||
if (auto globalRejection = synchronize_rejection(localRejection, m_fem.mesh->GetComm());
|
||||
globalRejection.has_value()) {
|
||||
return std::unexpected(*globalRejection);
|
||||
}
|
||||
|
||||
if (updateTargetMass) {
|
||||
m_targetMass = state.targetMass;
|
||||
report.updatedTargetMass = true;
|
||||
}
|
||||
|
||||
if (refreshGeometry || refreshDensity) {
|
||||
AssembleResidual();
|
||||
if (auto rejection = AssembleResidual(); rejection.has_value()) {
|
||||
return std::unexpected(*rejection);
|
||||
}
|
||||
report.assembledResidual = true;
|
||||
} else if (updateTargetMass) {
|
||||
m_cachedResidual.SetSize(1);
|
||||
m_cachedResidual(0) = m_currentMass - m_targetMass;
|
||||
++m_preparationCount;
|
||||
if (auto rejection = UpdateResidualForTargetMass(); rejection.has_value()) {
|
||||
return std::unexpected(*rejection);
|
||||
}
|
||||
report.assembledResidual = true;
|
||||
}
|
||||
|
||||
@@ -238,6 +378,13 @@ namespace mean_field::operators {
|
||||
return Prepare({.targetMass = constraint.targetMass().value()}, dependencies);
|
||||
}
|
||||
|
||||
MassNormalizationPreparationResult PreparedMassNormalizationOperator::TryPrepare(
|
||||
const models::CompiledFixedMass &constraint,
|
||||
const MassNormalizationDependencies &dependencies
|
||||
) {
|
||||
return TryPrepare({.targetMass = constraint.targetMass().value()}, dependencies);
|
||||
}
|
||||
|
||||
void PreparedMassNormalizationOperator::BuildStaticPlan() {
|
||||
m_elements.clear();
|
||||
m_elements.reserve(m_fem.mesh->GetNE());
|
||||
@@ -287,14 +434,17 @@ namespace mean_field::operators {
|
||||
}
|
||||
|
||||
int globalStellarElementCount = 0;
|
||||
MPI_Allreduce(
|
||||
&localStellarElementCount, &globalStellarElementCount, 1, MPI_INT, MPI_SUM, m_fem.mesh->GetComm()
|
||||
);
|
||||
if (MPI_Allreduce(
|
||||
&localStellarElementCount, &globalStellarElementCount, 1, MPI_INT, MPI_SUM, m_fem.mesh->GetComm()
|
||||
) != MPI_SUCCESS) {
|
||||
throw std::runtime_error("PreparedMassNormalizationOperator could not count stellar elements.");
|
||||
}
|
||||
|
||||
MFEM_VERIFY(globalStellarElementCount > 0, "PreparedMassNormalizationOperator found no stellar elements.");
|
||||
}
|
||||
|
||||
void PreparedMassNormalizationOperator::RefreshGeometry(const mfem::Vector &displacement) {
|
||||
std::optional<MassNormalizationPreparationRejection>
|
||||
PreparedMassNormalizationOperator::RefreshGeometry(const mfem::Vector &displacement) {
|
||||
MFEM_VERIFY(
|
||||
displacement.Size() == m_fem.displacementFes->GetTrueVSize(),
|
||||
"PreparedMassNormalizationOperator received a displacement "
|
||||
@@ -309,6 +459,7 @@ namespace mean_field::operators {
|
||||
true_to_local(*m_fem.displacementFes, displacement, displacementLocal);
|
||||
|
||||
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
|
||||
std::optional<MassNormalizationPreparationRejection> rejection;
|
||||
|
||||
for (ElementPAData &data : m_elements) {
|
||||
displacementLocal.GetSubVector(data.displacementDofs, data.baseDisplacement);
|
||||
@@ -346,17 +497,23 @@ namespace mean_field::operators {
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
status == mapping::MappingStatus::valid, "Stateless mapping failed while preparing mass "
|
||||
"normalization. Element: "
|
||||
<< data.elementId
|
||||
<< ", attribute: " << transformation->Attribute
|
||||
<< ", status: " << static_cast<int>(status)
|
||||
status != mapping::MappingStatus::invalid_dimension,
|
||||
"Stateless mapping reported a dimension error while preparing mass normalization."
|
||||
);
|
||||
if (status != mapping::MappingStatus::valid) {
|
||||
retain_higher_priority_rejection(
|
||||
rejection, {.reason = MassNormalizationPreparationRejectionReason::mapping_failure,
|
||||
.mappingStatus = status}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rejection;
|
||||
}
|
||||
|
||||
void PreparedMassNormalizationOperator::RefreshDensity(const mfem::Vector &density) {
|
||||
std::optional<MassNormalizationPreparationRejection>
|
||||
PreparedMassNormalizationOperator::RefreshDensity(const mfem::Vector &density) {
|
||||
MFEM_VERIFY(
|
||||
density.Size() == m_fem.densityFes->GetTrueVSize(),
|
||||
"PreparedMassNormalizationOperator received a density vector "
|
||||
@@ -371,6 +528,7 @@ namespace mean_field::operators {
|
||||
true_to_local(*m_fem.densityFes, density, densityLocal);
|
||||
|
||||
mfem::Vector elementDensity;
|
||||
std::optional<MassNormalizationPreparationRejection> rejection;
|
||||
|
||||
for (ElementPAData &data : m_elements) {
|
||||
densityLocal.GetSubVector(data.densityDofs, elementDensity);
|
||||
@@ -381,29 +539,62 @@ namespace mean_field::operators {
|
||||
|
||||
for (QuadraturePointData &point : data.quadraturePoints) {
|
||||
point.density = elementDensity * point.densityShape;
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(point.density), "PreparedMassNormalizationOperator produced a non-finite "
|
||||
"quadrature density."
|
||||
);
|
||||
if (!std::isfinite(point.density)) {
|
||||
retain_higher_priority_rejection(
|
||||
rejection,
|
||||
{.reason = MassNormalizationPreparationRejectionReason::non_finite_density_interpolation}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rejection;
|
||||
}
|
||||
|
||||
void PreparedMassNormalizationOperator::AssembleResidual() {
|
||||
std::optional<MassNormalizationPreparationRejection> PreparedMassNormalizationOperator::AssembleResidual() {
|
||||
double localMass = 0.0;
|
||||
std::optional<MassNormalizationPreparationRejection> localRejection;
|
||||
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
for (const QuadraturePointData &point : data.quadraturePoints) {
|
||||
localMass += point.density * point.mappingContext.quadrature.weight;
|
||||
const double contribution = point.density * point.mappingContext.quadrature.weight;
|
||||
if (!std::isfinite(contribution) || !std::isfinite(localMass + contribution)) {
|
||||
localRejection = {.reason = MassNormalizationPreparationRejectionReason::non_finite_assembled_mass};
|
||||
continue;
|
||||
}
|
||||
localMass += contribution;
|
||||
}
|
||||
}
|
||||
|
||||
m_currentMass = GlobalSum(localMass);
|
||||
MFEM_VERIFY(std::isfinite(m_currentMass), "PreparedMassNormalizationOperator assembled a non-finite mass.");
|
||||
if (auto globalRejection = synchronize_rejection(localRejection, m_fem.mesh->GetComm());
|
||||
globalRejection.has_value()) {
|
||||
return globalRejection;
|
||||
}
|
||||
|
||||
m_currentMass = GlobalSum(localMass);
|
||||
if (!std::isfinite(m_currentMass)) {
|
||||
return MassNormalizationPreparationRejection{
|
||||
.reason = MassNormalizationPreparationRejectionReason::non_finite_assembled_mass
|
||||
};
|
||||
}
|
||||
|
||||
return UpdateResidualForTargetMass();
|
||||
}
|
||||
|
||||
std::optional<MassNormalizationPreparationRejection>
|
||||
PreparedMassNormalizationOperator::UpdateResidualForTargetMass() {
|
||||
m_cachedResidual.SetSize(1);
|
||||
m_cachedResidual(0) = m_currentMass - m_targetMass;
|
||||
std::optional<MassNormalizationPreparationRejection> localRejection;
|
||||
if (!std::isfinite(m_cachedResidual(0))) {
|
||||
localRejection = {.reason = MassNormalizationPreparationRejectionReason::non_finite_assembled_mass};
|
||||
}
|
||||
if (auto globalRejection = synchronize_rejection(localRejection, m_fem.mesh->GetComm());
|
||||
globalRejection.has_value()) {
|
||||
return globalRejection;
|
||||
}
|
||||
++m_preparationCount;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void PreparedMassNormalizationOperator::BuildResidual(mfem::Vector &residual) const {
|
||||
@@ -716,7 +907,9 @@ namespace mean_field::operators {
|
||||
|
||||
double PreparedMassNormalizationOperator::GlobalSum(const double localValue) const {
|
||||
double globalValue = 0.0;
|
||||
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, m_fem.mesh->GetComm());
|
||||
if (MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, m_fem.mesh->GetComm()) != MPI_SUCCESS) {
|
||||
throw std::runtime_error("PreparedMassNormalizationOperator could not assemble a distributed scalar.");
|
||||
}
|
||||
return globalValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
module;
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <expected>
|
||||
#include <mfem.hpp>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <mpi.h>
|
||||
|
||||
module mean_field;
|
||||
|
||||
@@ -10,11 +17,76 @@ import :operators.prepared_rotational_displacement_force;
|
||||
|
||||
namespace {
|
||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
using Rejection = mean_field::operators::kernels::RotationalDisplacementForceRejection;
|
||||
using Reason = mean_field::operators::kernels::RotationalDisplacementForceRejectionReason;
|
||||
|
||||
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
|
||||
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
||||
}
|
||||
|
||||
[[nodiscard]] Rejection mapping_rejection(const mean_field::mapping::MappingStatus status) {
|
||||
MFEM_VERIFY(
|
||||
status != mean_field::mapping::MappingStatus::invalid_dimension,
|
||||
"Prepared rotational 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;
|
||||
}
|
||||
if (rejection->reason == Reason::non_finite_arithmetic) {
|
||||
return 256;
|
||||
}
|
||||
return static_cast<int>(rejection->mappingStatus) + 1;
|
||||
}
|
||||
|
||||
[[nodiscard]] Rejection decode_rejection(const int encoded) {
|
||||
if (encoded >= 256) {
|
||||
return non_finite_rejection();
|
||||
}
|
||||
return mapping_rejection(static_cast<mean_field::mapping::MappingStatus>(encoded - 1));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<
|
||||
void,
|
||||
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("Could not synchronize prepared rotational-force candidate validity.");
|
||||
}
|
||||
if (globalEncoded != 0) {
|
||||
return std::unexpected(decode_rejection(globalEncoded));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[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;
|
||||
}
|
||||
|
||||
[[noreturn]] void throw_rejection(const Rejection &rejection) {
|
||||
if (rejection.reason == Reason::non_finite_arithmetic) {
|
||||
throw std::domain_error("Prepared rotational force produced non-finite arithmetic.");
|
||||
}
|
||||
throw std::domain_error("Prepared rotational force encountered an invalid mapped domain.");
|
||||
}
|
||||
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &trueVector,
|
||||
@@ -119,7 +191,10 @@ namespace mean_field::operators {
|
||||
);
|
||||
}
|
||||
|
||||
void PreparedRotationalDisplacementForceOperator::PrepareElementData() {
|
||||
std::expected<
|
||||
void,
|
||||
kernels::RotationalDisplacementForceRejection>
|
||||
PreparedRotationalDisplacementForceOperator::TryPrepareElementData() {
|
||||
MFEM_VERIFY(m_rotation.has_value(), "Prepared rotational force has no frozen rotation state.");
|
||||
|
||||
m_elements.clear();
|
||||
@@ -197,9 +272,12 @@ namespace mean_field::operators {
|
||||
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
|
||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||
);
|
||||
if (status != mapping::MappingStatus::valid) {
|
||||
return std::unexpected(mapping_rejection(status));
|
||||
}
|
||||
MFEM_VERIFY(
|
||||
status == mapping::MappingStatus::valid && !mappingContext.mapping.compactified,
|
||||
"Prepared rotational force encountered an invalid stellar mapping."
|
||||
!mappingContext.mapping.compactified,
|
||||
"Prepared rotational force encountered compactification on a stellar element."
|
||||
);
|
||||
|
||||
densityElement.CalcShape(integrationPoint, densityShape);
|
||||
@@ -214,8 +292,21 @@ namespace mean_field::operators {
|
||||
mappingContext.quadrature.J_inv(row, column);
|
||||
}
|
||||
}
|
||||
|
||||
if (!std::isfinite(data.baseDensityValues(quadraturePoint)) ||
|
||||
!std::isfinite(data.quadratureWeights(quadraturePoint)) || !vector_is_finite(potentialGradient)) {
|
||||
return std::unexpected(non_finite_rejection());
|
||||
}
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
if (!std::isfinite(data.inverseElementJacobians(quadraturePoint, row * dimension + column))) {
|
||||
return std::unexpected(non_finite_rejection());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
PreparedRotationalDisplacementForceReport PreparedRotationalDisplacementForceOperator::Prepare(
|
||||
@@ -223,13 +314,29 @@ namespace mean_field::operators {
|
||||
const context::rotational_displacement_force::RotationalDisplacementForceDependencies &dependencies,
|
||||
const physics::RigidRotation &rotation
|
||||
) {
|
||||
auto result = TryPrepare(state, dependencies, rotation);
|
||||
if (!result.has_value()) {
|
||||
throw_rejection(result.error());
|
||||
}
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
std::expected<
|
||||
PreparedRotationalDisplacementForceReport,
|
||||
kernels::RotationalDisplacementForceRejection>
|
||||
PreparedRotationalDisplacementForceOperator::TryPrepare(
|
||||
const context::rotational_displacement_force::RotationalDisplacementForceStateView &state,
|
||||
const context::rotational_displacement_force::RotationalDisplacementForceDependencies &dependencies,
|
||||
const physics::RigidRotation &rotation
|
||||
) {
|
||||
const bool wasPrepared = m_isPrepared;
|
||||
const bool rotationChanged =
|
||||
!m_context.IsPrepared() || dependencies.rotation != m_context.GetDependencies().rotation;
|
||||
|
||||
PreparedRotationalDisplacementForceReport report;
|
||||
report.contextReport = m_context.Prepare(state, dependencies);
|
||||
|
||||
if (!report.contextReport.DidAnyWork()) {
|
||||
if (!report.contextReport.DidAnyWork() && wasPrepared) {
|
||||
return report;
|
||||
}
|
||||
|
||||
@@ -245,14 +352,28 @@ namespace mean_field::operators {
|
||||
"rotation state."
|
||||
);
|
||||
|
||||
if (report.contextReport.preparedBaseState) {
|
||||
kernels::apply_rotational_displacement_force_residual(
|
||||
if (report.contextReport.preparedBaseState || !wasPrepared) {
|
||||
auto residualResult = kernels::try_apply_rotational_displacement_force_residual(
|
||||
m_fem, m_domainMapper, *m_rotation, m_context.GetBaseDensityTrue(), m_context.GetDisplacementTrue(),
|
||||
m_actionTrue
|
||||
);
|
||||
if (!residualResult.has_value()) {
|
||||
return std::unexpected(residualResult.error());
|
||||
}
|
||||
m_cachedResidual.SetSize(m_context.GetDisplacementMap().reduced_size());
|
||||
m_context.GetDisplacementMap().gather(m_actionTrue, m_cachedResidual);
|
||||
PrepareElementData();
|
||||
|
||||
const auto elementResult = TryPrepareElementData();
|
||||
std::optional<Rejection> localRejection = elementResult.has_value()
|
||||
? std::optional<Rejection>{}
|
||||
: std::optional<Rejection>{elementResult.error()};
|
||||
if (!vector_is_finite(m_cachedResidual)) {
|
||||
localRejection = non_finite_rejection();
|
||||
}
|
||||
auto synchronized = synchronize_rejection(localRejection, m_fem.mesh->GetComm());
|
||||
if (!synchronized.has_value()) {
|
||||
return std::unexpected(synchronized.error());
|
||||
}
|
||||
|
||||
++m_residualPreparationCount;
|
||||
report.preparedResidual = true;
|
||||
|
||||
@@ -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