feat(newton): first newton solver implementation

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

View File

@@ -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());

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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 &center = m_constraint.specification().center();
double
PreparedAngularMomentumOperator::CylindricalRadiusSquared(const mfem::Vector &physicalPosition) const noexcept {
const auto &axis = m_constraint.specification().axis();
const auto &center = 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 &center = m_constraint.specification().center();
const auto &axis = m_constraint.specification().axis();
const auto &center = 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)
};
}

View File

@@ -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> &current,
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;

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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(

View File

@@ -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);

View File

@@ -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> &current,
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;
}

View File

@@ -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);
}

View File

@@ -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;

View File

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

View File

@@ -182,12 +182,11 @@ namespace mean_field::seed::detail {
double radialMomentIntegral = 0.0;
for (int index = 0; index + 1 < profile.radius.Size(); ++index) {
const double leftRadius = profile.radius(index);
const double rightRadius = profile.radius(index + 1);
const double leftIntegrand = profile.density(index) * std::pow(leftRadius, 4);
const double leftRadius = profile.radius(index);
const double rightRadius = profile.radius(index + 1);
const double leftIntegrand = profile.density(index) * std::pow(leftRadius, 4);
const double rightIntegrand = profile.density(index + 1) * std::pow(rightRadius, 4);
radialMomentIntegral +=
0.5 * (rightRadius - leftRadius) * (leftIntegrand + rightIntegrand);
radialMomentIntegral += 0.5 * (rightRadius - leftRadius) * (leftIntegrand + rightIntegrand);
}
const double sphericalMomentOfInertia = (8.0 * std::numbers::pi / 3.0) * radialMomentIntegral;
if (!std::isfinite(sphericalMomentOfInertia) || sphericalMomentOfInertia <= 0.0) {
@@ -208,11 +207,11 @@ namespace mean_field::seed::detail {
);
return {
.density = densityAdapter.gather(densityField),
.gravityGradient = gravityFluxAdapter.gather(gravitySolution.gradPhi),
.gravityPotential = gravityPotentialAdapter.gather(gravitySolution.phi),
.specificEnthalpy = enthalpyAdapter.gather(enthalpyField),
.bernoulliConstant = -utils::G * targetMass.value() / targetRadius,
.density = densityAdapter.gather(densityField),
.gravityGradient = gravityFluxAdapter.gather(gravitySolution.gradPhi),
.gravityPotential = gravityPotentialAdapter.gather(gravitySolution.phi),
.specificEnthalpy = enthalpyAdapter.gather(enthalpyField),
.bernoulliConstant = -utils::G * targetMass.value() / targetRadius,
.sphericalMomentOfInertia = sphericalMomentOfInertia
};
}

View File

@@ -6,130 +6,124 @@ module;
#include <type_traits>
#include <utility>
#include <mpi.h>
export module mean_field:equilibrium.stellar_discretization;
export import :fem;
export import :mapping.domain_mapper;
export import :normalization.physical_riesz;
namespace mean_field::equilibrium::detail {
struct StellarEquilibriumProblemFactory;
}
export namespace mean_field::equilibrium {
/*
* An explicit, non-owning view of the numerical discretization used by a
* stellar equilibrium problem. The referenced FEM and mapper must outlive
* every problem and structure that uses this view.
*
* Ownership cannot move here yet because FEM currently also contains
* mutable field workspaces. Separating those workspaces is a prerequisite
* for shared discretization ownership by solved Structure objects.
* The complete numerical discretization used by a stellar equilibrium
* problem. Moving the FEM into stable heap storage lets the problem and a
* completed Structure transfer unique ownership without invalidating the
* references retained by prepared operators. The mapper is part of that
* owned FEM and therefore has the same lifetime.
*/
template <normalization::NormalizationPrescription Normalization>
class StellarDiscretizationFor final {
template <normalization::NormalizationPrescription Normalization> class StellarDiscretizationFor final {
public:
using NormalizationPrescriptionType = std::remove_cvref_t<Normalization>;
explicit StellarDiscretizationFor(fem::FEM &finiteElementModel)
requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized>
explicit StellarDiscretizationFor(fem::FEM &&finiteElementModel)
requires std::same_as<
NormalizationPrescriptionType,
normalization::Unnormalized>
: StellarDiscretizationFor(
finiteElementModel,
RequireDomainMapper(finiteElementModel),
std::move(finiteElementModel),
normalization::Unnormalized{}
) {
}
StellarDiscretizationFor(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper
)
requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized>
: StellarDiscretizationFor(
finiteElementModel,
domainMapper,
normalization::Unnormalized{}
) {
}
explicit StellarDiscretizationFor(fem::FEM &)
requires std::same_as<
NormalizationPrescriptionType,
normalization::Unnormalized>
= delete;
StellarDiscretizationFor(
fem::FEM &,
mapping::DomainMapper &&
) requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized> = delete;
StellarDiscretizationFor(
fem::FEM &,
const mapping::DomainMapper &&
) requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized> = delete;
StellarDiscretizationFor(
fem::FEM &finiteElementModel,
fem::FEM &&finiteElementModel,
NormalizationPrescriptionType normalizationPrescription
)
: StellarDiscretizationFor(
finiteElementModel,
RequireDomainMapper(finiteElementModel),
std::move(normalizationPrescription)
) {
}
StellarDiscretizationFor(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper,
NormalizationPrescriptionType normalizationPrescription
)
: m_finiteElementModel(std::addressof(finiteElementModel)),
m_domainMapper(std::addressof(domainMapper)),
: m_finiteElementModel(TakeOwnership(std::move(finiteElementModel))),
m_normalizationPrescription(std::move(normalizationPrescription)) {
if (!finiteElementModel.okay()) {
throw std::invalid_argument("A stellar discretization requires a complete finite-element model.");
}
}
StellarDiscretizationFor(
fem::FEM &,
mapping::DomainMapper &&,
NormalizationPrescriptionType
) = delete;
) = delete;
StellarDiscretizationFor(
fem::FEM &,
const mapping::DomainMapper &&,
NormalizationPrescriptionType
) = delete;
StellarDiscretizationFor(const StellarDiscretizationFor &) = delete;
StellarDiscretizationFor &operator=(const StellarDiscretizationFor &) = delete;
StellarDiscretizationFor(StellarDiscretizationFor &&) = default;
StellarDiscretizationFor &operator=(StellarDiscretizationFor &&) = delete;
[[nodiscard]] fem::FEM &finiteElementModel() const noexcept {
return *m_finiteElementModel;
}
[[nodiscard]] const mapping::DomainMapper &domainMapper() const noexcept {
return *m_domainMapper;
}
[[nodiscard]] const NormalizationPrescriptionType &normalizationPrescription() const noexcept {
return m_normalizationPrescription;
}
[[nodiscard]] bool isCurrent() const noexcept {
return m_finiteElementModel != nullptr && m_domainMapper != nullptr && m_finiteElementModel->okay();
}
private:
[[nodiscard]] static const mapping::DomainMapper &RequireDomainMapper(const fem::FEM &finiteElementModel) {
[[nodiscard]] const mapping::DomainMapper &domainMapper() const & {
const auto &finiteElementModel = RequireFiniteElementModel();
if (finiteElementModel.domainMapperStateless == nullptr) {
throw std::invalid_argument("A stellar discretization requires a domain mapper.");
throw std::logic_error("The stellar discretization has no domain mapper.");
}
return *finiteElementModel.domainMapperStateless;
}
fem::FEM *m_finiteElementModel;
const mapping::DomainMapper *m_domainMapper;
[[nodiscard]] const mapping::DomainMapper &domainMapper() const && = delete;
[[nodiscard]] MPI_Comm communicator() const & {
const auto &finiteElementModel = RequireFiniteElementModel();
if (finiteElementModel.mesh == nullptr) {
throw std::logic_error("The stellar discretization has no parallel mesh.");
}
return finiteElementModel.mesh->GetComm();
}
[[nodiscard]] MPI_Comm communicator() const && = delete;
[[nodiscard]] const NormalizationPrescriptionType &normalizationPrescription() const & noexcept {
return m_normalizationPrescription;
}
[[nodiscard]] const NormalizationPrescriptionType &normalizationPrescription() const && = delete;
[[nodiscard]] bool isCurrent() const noexcept {
return m_finiteElementModel != nullptr && m_finiteElementModel->okay();
}
private:
friend struct detail::StellarEquilibriumProblemFactory;
[[nodiscard]] fem::FEM &MutableFiniteElementModelForAssembly() & {
return const_cast<fem::FEM &>(RequireFiniteElementModel());
}
[[nodiscard]] const fem::FEM &RequireFiniteElementModel() const {
if (m_finiteElementModel == nullptr) {
throw std::logic_error("A moved-from stellar discretization has no finite-element model.");
}
return *m_finiteElementModel;
}
[[nodiscard]] static std::unique_ptr<fem::FEM> TakeOwnership(fem::FEM &&finiteElementModel) {
if (!finiteElementModel.okay()) {
throw std::invalid_argument("A stellar discretization requires a complete finite-element model.");
}
return std::make_unique<fem::FEM>(std::move(finiteElementModel));
}
std::unique_ptr<fem::FEM> m_finiteElementModel;
NormalizationPrescriptionType m_normalizationPrescription;
};
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor(fem::FEM &, Normalization)
-> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor(fem::FEM &, const mapping::DomainMapper &, Normalization)
-> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
StellarDiscretizationFor(
fem::FEM &&,
Normalization
) -> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
using StellarDiscretization = StellarDiscretizationFor<normalization::Unnormalized>;
@@ -143,41 +137,17 @@ export namespace mean_field::equilibrium {
template <normalization::NormalizationPrescription Normalization>
[[nodiscard]] auto makeStellarDiscretization(
fem::FEM &finiteElementModel,
fem::FEM &&finiteElementModel,
Normalization normalizationPrescription
) {
return StellarDiscretizationFor<std::remove_cvref_t<Normalization>>{
finiteElementModel,
std::move(normalizationPrescription)
std::move(finiteElementModel), std::move(normalizationPrescription)
};
}
template <normalization::NormalizationPrescription Normalization>
[[nodiscard]] auto makeStellarDiscretization(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper,
Normalization normalizationPrescription
) {
return StellarDiscretizationFor<std::remove_cvref_t<Normalization>>{
finiteElementModel,
domainMapper,
std::move(normalizationPrescription)
};
}
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor<std::remove_cvref_t<Normalization>>
makeStellarDiscretization(
StellarDiscretizationFor<std::remove_cvref_t<Normalization>> makeStellarDiscretization(
fem::FEM &,
mapping::DomainMapper &&,
Normalization
) = delete;
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor<std::remove_cvref_t<Normalization>>
makeStellarDiscretization(
fem::FEM &,
const mapping::DomainMapper &&,
Normalization
) = delete;
} // namespace mean_field::equilibrium

View File

@@ -25,7 +25,10 @@ export import :integrators.viscosity;
export import :quadrature.policy;
export import :quadrature.mfem;
export import :solver.fields;
export import :solver.linear_backend;
export import :solver.preconditioning_diagnostics;
export import :solver.stellar_equilibrium_types;
export import :solver.stellar_equilibrium;
export import :preconditioning;
export import :normalization;
export import :utils.blocks;

View File

@@ -27,8 +27,8 @@ export namespace mean_field::models {
class CompiledFixedAngularMomentum final {
public:
using SpecificationType = FixedAngularMomentum;
using LayoutRequest = FixedAngularMomentumLayoutRequest;
using SpecificationType = FixedAngularMomentum;
using LayoutRequest = FixedAngularMomentumLayoutRequest;
using AngularVelocityType = typename LayoutRequest::GeneratedValueType;
using ResidualType = typename LayoutRequest::GeneratedResidualType;
using AngularVelocityField = field::AngularVelocity;
@@ -63,9 +63,8 @@ export namespace mean_field::models {
FixedAngularMomentum m_specification;
};
[[nodiscard]] inline CompiledFixedAngularMomentum compileConstraint(
const FixedAngularMomentum specification
) noexcept {
[[nodiscard]] inline CompiledFixedAngularMomentum
compileConstraint(const FixedAngularMomentum specification) noexcept {
return CompiledFixedAngularMomentum{specification};
}

View File

@@ -57,11 +57,9 @@ export namespace mean_field::models {
static constexpr std::size_t size = sizeof...(Types);
};
template <typename... ValueBlocks>
using DependsOn = ModelTypeList<ValueBlocks...>;
template <typename... ValueBlocks> using DependsOn = ModelTypeList<ValueBlocks...>;
template <typename... ResidualBlocks>
using Affects = ModelTypeList<ResidualBlocks...>;
template <typename... ResidualBlocks> using Affects = ModelTypeList<ResidualBlocks...>;
/*
* Physics vocabulary for declaring how a stellar specification couples to
@@ -83,8 +81,7 @@ export namespace mean_field::models {
* the physics-facing spelling for coupled global constraints: an
* extension names the constraint it reads, never its solver block.
*/
template <typename Specification>
struct GeneratedCoordinateOf final {
template <typename Specification> struct GeneratedCoordinateOf final {
using SpecificationType = Specification;
};
@@ -108,8 +105,7 @@ export namespace mean_field::models {
struct OwnConstraint final { };
/* The scalar constraint equation owned by another specification. */
template <typename Specification>
struct ConstraintOf final {
template <typename Specification> struct ConstraintOf final {
using SpecificationType = Specification;
};
} // namespace equation
@@ -119,8 +115,7 @@ export namespace mean_field::models {
* Runtime providers consume this vocabulary without learning backend
* row and column block types.
*/
template <typename Equation, typename State>
struct Derivative final {
template <typename Equation, typename State> struct Derivative final {
using EquationType = Equation;
using StateType = State;
};
@@ -220,11 +215,7 @@ export namespace mean_field::models {
concept PhysicalScaleRepresentedQuantity =
dimensions::PhysicalQuantityType<Quantity> &&
physicalScaleForQuantity<Quantity> != PhysicalScaleLaw::unavailable &&
requires {
typename std::bool_constant<
!static_cast<std::string_view>(
Quantity::identifier).empty()>;
};
requires { typename std::bool_constant<!static_cast<std::string_view>(Quantity::identifier).empty()>; };
namespace detail {
template <typename Candidate> [[nodiscard]] consteval bool declaredCoordinateNormalizationIsAvailable() {
@@ -270,8 +261,7 @@ export namespace mean_field::models {
case SpecificationRole::boundary_condition:
return kind == GeneratedStateKind::none;
case SpecificationRole::invariant:
return kind == GeneratedStateKind::multiplier ||
kind == GeneratedStateKind::physical_coordinate;
return kind == GeneratedStateKind::multiplier || kind == GeneratedStateKind::physical_coordinate;
case SpecificationRole::phase_condition:
case SpecificationRole::gauge_choice:
return kind == GeneratedStateKind::solver_border;
@@ -304,11 +294,12 @@ export namespace mean_field::models {
using UnavailableCoordinateNormalization =
CoordinateNormalization<RieszTopology::unavailable, PhysicalScaleLaw::unavailable>;
template <typename ValueNormalization = UnavailableCoordinateNormalization,
typename ResidualNormalization = UnavailableCoordinateNormalization>
template <
typename ValueNormalization = UnavailableCoordinateNormalization,
typename ResidualNormalization = UnavailableCoordinateNormalization>
struct GeneratedNormalization final {
using Value = ValueNormalization;
using Residual = ResidualNormalization;
using Value = ValueNormalization;
using Residual = ResidualNormalization;
static constexpr bool available = detail::declaredCoordinateNormalizationIsAvailable<Value>() &&
detail::declaredCoordinateNormalizationIsAvailable<Residual>();
@@ -321,8 +312,13 @@ export namespace mean_field::models {
CoordinateNormalization<RieszTopology::global_scalar, ValueScale>,
CoordinateNormalization<RieszTopology::global_scalar, ResidualScale>>;
template <FixedString ValueStableId = "", FixedString ValueSymbol = "", FixedString ResidualStableId = "",
FixedString ResidualSymbol = "", FixedString TargetUnits = "", FixedString ResidualUnits = "">
template <
FixedString ValueStableId = "",
FixedString ValueSymbol = "",
FixedString ResidualStableId = "",
FixedString ResidualSymbol = "",
FixedString TargetUnits = "",
FixedString ResidualUnits = "">
struct GeneratedManifest final {
private:
inline static constexpr auto valueStableIdStorage = ValueStableId;
@@ -366,21 +362,19 @@ export namespace mean_field::models {
FixedString ResidualStableId,
FixedString ResidualSymbol>
struct DimensionalScalarConstraint final {
using TargetQuantity = TargetQuantityT;
using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT;
using ConstraintResidualQuantity = ConstraintResidualQuantityT;
using TargetValue = dimensions::QuantityValue<TargetQuantity>;
static constexpr PhysicalScaleLaw targetScale =
physicalScaleForQuantity<TargetQuantity>;
using TargetQuantity = TargetQuantityT;
using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT;
using ConstraintResidualQuantity = ConstraintResidualQuantityT;
using TargetValue = dimensions::QuantityValue<TargetQuantity>;
static constexpr PhysicalScaleLaw targetScale = physicalScaleForQuantity<TargetQuantity>;
struct Normalization final {
using TargetQuantity = TargetQuantityT;
using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT;
using ConstraintResidualQuantity = ConstraintResidualQuantityT;
using TargetValue = dimensions::QuantityValue<TargetQuantity>;
static constexpr PhysicalScaleLaw targetScale =
physicalScaleForQuantity<TargetQuantity>;
using Value = CoordinateNormalization<
using TargetQuantity = TargetQuantityT;
using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT;
using ConstraintResidualQuantity = ConstraintResidualQuantityT;
using TargetValue = dimensions::QuantityValue<TargetQuantity>;
static constexpr PhysicalScaleLaw targetScale = physicalScaleForQuantity<TargetQuantity>;
using Value = CoordinateNormalization<
RieszTopology::global_scalar,
physicalScaleForQuantity<GeneratedCoordinateQuantity>>;
using Residual = CoordinateNormalization<
@@ -408,20 +402,23 @@ export namespace mean_field::models {
static constexpr std::string_view residualSymbol = residualSymbolStorage.view();
static constexpr std::string_view targetUnits = TargetQuantity::identifier;
static constexpr std::string_view residualUnits = ConstraintResidualQuantity::identifier;
static constexpr bool available =
!valueStableId.empty() && !valueSymbol.empty() &&
!residualStableId.empty() && !residualSymbol.empty() &&
!targetUnits.empty() && !residualUnits.empty();
static constexpr bool available = !valueStableId.empty() && !valueSymbol.empty() &&
!residualStableId.empty() && !residualSymbol.empty() &&
!targetUnits.empty() && !residualUnits.empty();
};
static constexpr bool dimensionallyTyped = true;
};
template <typename Specification, FixedString StableName, SpecificationRole Role,
GeneratedStateKind StateKind = GeneratedStateKind::none, typename DependsOnBlocks = ModelTypeList<>,
typename AffectedResidualBlocks = ModelTypeList<>,
typename NormalizationDefinition = UnavailableGeneratedNormalization,
typename ManifestDefinition = UnavailableGeneratedManifest>
template <
typename Specification,
FixedString StableName,
SpecificationRole Role,
GeneratedStateKind StateKind = GeneratedStateKind::none,
typename DependsOnBlocks = ModelTypeList<>,
typename AffectedResidualBlocks = ModelTypeList<>,
typename NormalizationDefinition = UnavailableGeneratedNormalization,
typename ManifestDefinition = UnavailableGeneratedManifest>
struct ModelDefinition final {
using SpecificationType = Specification;
using DependsOn = DependsOnBlocks;
@@ -447,26 +444,56 @@ export namespace mean_field::models {
template <typename Specification, FixedString Name>
using BoundaryCondition = ModelDefinition<Specification, Name, SpecificationRole::boundary_condition>;
template <typename Specification, FixedString Name, typename DependsOn = ModelTypeList<>,
typename Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization,
typename Manifest = UnavailableGeneratedManifest>
using FixedIntegralWithMultiplier =
ModelDefinition<Specification, Name, SpecificationRole::invariant, GeneratedStateKind::multiplier, DependsOn,
Affects, Normalization, Manifest>;
template <
typename Specification,
FixedString Name,
typename DependsOn = ModelTypeList<>,
typename Affects = ModelTypeList<>,
typename Normalization = UnavailableGeneratedNormalization,
typename Manifest = UnavailableGeneratedManifest>
using FixedIntegralWithMultiplier = ModelDefinition<
Specification,
Name,
SpecificationRole::invariant,
GeneratedStateKind::multiplier,
DependsOn,
Affects,
Normalization,
Manifest>;
template <typename Specification, FixedString Name, typename DependsOn = ModelTypeList<>,
typename Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization,
typename Manifest = UnavailableGeneratedManifest>
using FixedIntegralWithPhysicalCoordinate =
ModelDefinition<Specification, Name, SpecificationRole::invariant, GeneratedStateKind::physical_coordinate,
DependsOn, Affects, Normalization, Manifest>;
template <
typename Specification,
FixedString Name,
typename DependsOn = ModelTypeList<>,
typename Affects = ModelTypeList<>,
typename Normalization = UnavailableGeneratedNormalization,
typename Manifest = UnavailableGeneratedManifest>
using FixedIntegralWithPhysicalCoordinate = ModelDefinition<
Specification,
Name,
SpecificationRole::invariant,
GeneratedStateKind::physical_coordinate,
DependsOn,
Affects,
Normalization,
Manifest>;
template <typename Specification, FixedString Name, typename DependsOn = ModelTypeList<>,
typename Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization,
typename Manifest = UnavailableGeneratedManifest>
using PhaseCondition =
ModelDefinition<Specification, Name, SpecificationRole::phase_condition, GeneratedStateKind::solver_border,
DependsOn, Affects, Normalization, Manifest>;
template <
typename Specification,
FixedString Name,
typename DependsOn = ModelTypeList<>,
typename Affects = ModelTypeList<>,
typename Normalization = UnavailableGeneratedNormalization,
typename Manifest = UnavailableGeneratedManifest>
using PhaseCondition = ModelDefinition<
Specification,
Name,
SpecificationRole::phase_condition,
GeneratedStateKind::solver_border,
DependsOn,
Affects,
Normalization,
Manifest>;
struct SpecificationKey final {
SpecificationRole role;
@@ -510,35 +537,42 @@ export namespace mean_field::models {
};
namespace detail {
template <typename Candidate> struct IsModelTypeList : std::false_type {};
template <typename Candidate> struct IsModelTypeList : std::false_type { };
template <typename... Types> struct IsModelTypeList<ModelTypeList<Types...>> : std::true_type {};
template <typename... Types> struct IsModelTypeList<ModelTypeList<Types...>> : std::true_type { };
template <typename Candidate> struct IsModelDefinition : std::false_type {};
template <typename Candidate> struct IsModelDefinition : std::false_type { };
template <typename Specification, FixedString StableName, SpecificationRole Role, GeneratedStateKind StateKind,
typename DependsOn, typename Affects, typename Normalization, typename Manifest>
template <
typename Specification,
FixedString StableName,
SpecificationRole Role,
GeneratedStateKind StateKind,
typename DependsOn,
typename Affects,
typename Normalization,
typename Manifest>
struct IsModelDefinition<
ModelDefinition<Specification, StableName, Role, StateKind, DependsOn, Affects, Normalization, Manifest>>
: std::bool_constant<(StableName.view().size() > 0) &&
CompatibleSpecificationRoleAndGeneratedState<Role, StateKind> &&
IsModelTypeList<DependsOn>::value &&
IsModelTypeList<Affects>::value>{};
: std::bool_constant<
(StableName.view().size() > 0) && CompatibleSpecificationRoleAndGeneratedState<Role, StateKind> &&
IsModelTypeList<DependsOn>::value && IsModelTypeList<Affects>::value> { };
template <typename Definition, typename Candidate, bool = IsModelDefinition<Definition>::value>
struct DefinitionDescribesCandidate : std::false_type {};
struct DefinitionDescribesCandidate : std::false_type { };
template <typename Definition, typename Candidate>
struct DefinitionDescribesCandidate<Definition, Candidate, true>
: std::bool_constant<std::same_as<typename Definition::SpecificationType, Candidate>> {};
: std::bool_constant<std::same_as<typename Definition::SpecificationType, Candidate>> { };
template <typename Candidate, typename = void> struct SpecificationDefinitionFor {
static constexpr bool available = false;
};
template <typename Candidate>
struct SpecificationDefinitionFor<Candidate,
std::void_t<typename std::remove_cvref_t<Candidate>::ModelDefinition>> {
struct SpecificationDefinitionFor<
Candidate,
std::void_t<typename std::remove_cvref_t<Candidate>::ModelDefinition>> {
using Type = typename std::remove_cvref_t<Candidate>::ModelDefinition;
static constexpr bool available = DefinitionDescribesCandidate<Type, std::remove_cvref_t<Candidate>>::value;
};
@@ -570,7 +604,7 @@ export namespace mean_field::models {
template <typename Candidate>
requires detail::SpecificationDefinitionFor<std::remove_cvref_t<Candidate>>::available
struct SpecificationTraits<Candidate> {
using Definition = ModelDefinitionForT<Candidate>;
using Definition = ModelDefinitionForT<Candidate>;
static constexpr std::string_view name = Definition::name;
static constexpr SpecificationRole role = Definition::role;
@@ -604,7 +638,8 @@ export namespace mean_field::models {
"R_M">;
using TargetValue = typename ScalarDescription::TargetValue;
using ModelDefinition = FixedIntegralWithMultiplier<
FixedTotalMass, "FixedTotalMass",
FixedTotalMass,
"FixedTotalMass",
DependsOn<stellar::state::Density, stellar::state::SurfaceShape>,
Affects<stellar::equation::HydrostaticBalance>,
typename ScalarDescription::Normalization,
@@ -615,9 +650,13 @@ export namespace mean_field::models {
explicit FixedTotalMass(const TargetValue targetMass) : m_targetMass(targetMass) {
if (!std::isfinite(targetMass.value()) || targetMass.value() <= 0.0) {
throw std::invalid_argument(std::format("The fixed total mass must be finite and positive. "
"Instead M = {} was provided.",
targetMass.value()));
throw std::invalid_argument(
std::format(
"The fixed total mass must be finite and positive. "
"Instead M = {} was provided.",
targetMass.value()
)
);
}
}
@@ -651,29 +690,35 @@ export namespace mean_field::models {
"R_J">;
using TargetValue = typename ScalarDescription::TargetValue;
using ModelDefinition = FixedIntegralWithPhysicalCoordinate<
FixedAngularMomentum, "FixedAngularMomentum",
DependsOn<
stellar::state::Density,
stellar::state::SurfaceShape,
stellar::state::OwnGeneratedCoordinate>,
FixedAngularMomentum,
"FixedAngularMomentum",
DependsOn<stellar::state::Density, stellar::state::SurfaceShape, stellar::state::OwnGeneratedCoordinate>,
Affects<stellar::equation::SurfaceShapeBalance, stellar::equation::HydrostaticBalance>,
typename ScalarDescription::Normalization,
typename ScalarDescription::Manifest>;
explicit FixedAngularMomentum(const Parameters parameters)
: m_targetAngularMomentum(parameters.Jtotal), m_axis(parameters.axis), m_center(parameters.center) {
: m_targetAngularMomentum(parameters.Jtotal),
m_axis(parameters.axis),
m_center(parameters.center) {
if (!std::isfinite(m_targetAngularMomentum.value()) || m_targetAngularMomentum.value() < 0.0) {
throw std::invalid_argument(std::format("The fixed total angular momentum must be finite and "
"nonnegative. Instead J = {} was "
"provided.",
m_targetAngularMomentum.value()));
throw std::invalid_argument(
std::format(
"The fixed total angular momentum must be finite and "
"nonnegative. Instead J = {} was "
"provided.",
m_targetAngularMomentum.value()
)
);
}
double axisNormSquared = 0.0;
for (std::size_t component = 0; component < m_axis.size(); ++component) {
if (!std::isfinite(m_axis[component]) || !std::isfinite(m_center[component])) {
throw std::invalid_argument("A fixed-angular-momentum rotation axis and center must contain "
"only finite values.");
throw std::invalid_argument(
"A fixed-angular-momentum rotation axis and center must contain "
"only finite values."
);
}
axisNormSquared += m_axis[component] * m_axis[component];
}
@@ -698,11 +743,17 @@ export namespace mean_field::models {
return m_targetAngularMomentum;
}
[[nodiscard]] const std::array<double, 3> &axis() const noexcept {
[[nodiscard]] const std::array<
double,
3> &
axis() const noexcept {
return m_axis;
}
[[nodiscard]] const std::array<double, 3> &center() const noexcept {
[[nodiscard]] const std::array<
double,
3> &
center() const noexcept {
return m_center;
}
@@ -728,7 +779,8 @@ export namespace mean_field::models {
"R_rho_c">;
using TargetValue = typename ScalarDescription::TargetValue;
using ModelDefinition = PhaseCondition<
FixedCentralDensity, "FixedCentralDensity",
FixedCentralDensity,
"FixedCentralDensity",
DependsOn<stellar::state::SpecificEnthalpy>,
Affects<stellar::equation::HydrostaticBalance>,
typename ScalarDescription::Normalization,
@@ -739,9 +791,13 @@ export namespace mean_field::models {
explicit FixedCentralDensity(const TargetValue targetDensity) : m_targetDensity(targetDensity) {
if (!std::isfinite(targetDensity.value()) || targetDensity.value() <= 0.0) {
throw std::invalid_argument(std::format("The fixed central density must be finite and positive. "
"Instead rho_c = {} was provided.",
targetDensity.value()));
throw std::invalid_argument(
std::format(
"The fixed central density must be finite and positive. "
"Instead rho_c = {} was provided.",
targetDensity.value()
)
);
}
}
@@ -761,19 +817,19 @@ export namespace mean_field::models {
template <typename Query, typename... Types>
struct ModelTypeListContains<Query, ModelTypeList<Types...>>
: std::bool_constant<(std::same_as<Query, Types> || ...)> {};
: std::bool_constant<(std::same_as<Query, Types> || ...)> { };
template <typename Query, typename List>
inline constexpr bool modelTypeListContains = ModelTypeListContains<Query, List>::value;
template <typename Specification> struct ResidualFor final {
using SpecificationType = Specification;
using SpecificationType = Specification;
static constexpr std::size_t scalarArity = 1;
};
template <typename Specification> struct MultiplierFor final {
using SpecificationType = Specification;
using SpecificationType = Specification;
static constexpr std::size_t scalarArity = 1;
};
@@ -781,13 +837,13 @@ export namespace mean_field::models {
// A generated state variable that participates directly in the physical
// equations, rather than serving only as a Lagrange multiplier or border.
template <typename Specification> struct PhysicalCoordinateFor final {
using SpecificationType = Specification;
using SpecificationType = Specification;
static constexpr std::size_t scalarArity = 1;
};
template <typename Specification> struct BorderFor final {
using SpecificationType = Specification;
using SpecificationType = Specification;
static constexpr std::size_t scalarArity = 1;
};
@@ -881,15 +937,15 @@ export namespace mean_field::models {
static constexpr std::size_t generatedValueArity = Definition::generatedValueArity;
static constexpr std::size_t generatedResidualArity = Definition::generatedResidualArity;
static constexpr bool isDefined = Definition::structurallyAvailable;
static constexpr bool hasDeclarativeDefinition = Definition::structurallyAvailable;
static constexpr bool hasDeclarativeDefinition = Definition::structurallyAvailable;
};
namespace detail {
template <ModelSpecification Specification>
[[nodiscard]] consteval bool generatedScalarDimensionsAreCoherent() {
using Contribution = SpecificationContribution<Specification>;
using Contribution = SpecificationContribution<Specification>;
using Normalization = typename Contribution::Normalization;
using Manifest = typename Contribution::Manifest;
using Manifest = typename Contribution::Manifest;
if constexpr (Contribution::generatedValueArity == 0) {
return true;
@@ -899,9 +955,7 @@ export namespace mean_field::models {
typename Normalization::GeneratedCoordinateQuantity;
typename Normalization::ConstraintResidualQuantity;
typename Normalization::TargetValue;
{
Normalization::targetScale
} -> std::convertible_to<PhysicalScaleLaw>;
{ Normalization::targetScale } -> std::convertible_to<PhysicalScaleLaw>;
};
constexpr bool manifestIsTyped = requires {
typename Manifest::TargetQuantity;
@@ -918,72 +972,53 @@ export namespace mean_field::models {
} else if constexpr (!normalizationIsTyped || !manifestIsTyped) {
return false;
} else {
using TargetQuantity = typename Normalization::TargetQuantity;
using GeneratedCoordinateQuantity =
typename Normalization::GeneratedCoordinateQuantity;
using ConstraintResidualQuantity =
typename Normalization::ConstraintResidualQuantity;
using ValueNormalization = typename Normalization::Value;
using ResidualNormalization = typename Normalization::Residual;
using TargetQuantity = typename Normalization::TargetQuantity;
using GeneratedCoordinateQuantity = typename Normalization::GeneratedCoordinateQuantity;
using ConstraintResidualQuantity = typename Normalization::ConstraintResidualQuantity;
using ValueNormalization = typename Normalization::Value;
using ResidualNormalization = typename Normalization::Residual;
if constexpr (
!PhysicalScaleRepresentedQuantity<TargetQuantity> ||
!PhysicalScaleRepresentedQuantity<GeneratedCoordinateQuantity> ||
!PhysicalScaleRepresentedQuantity<ConstraintResidualQuantity>) {
!PhysicalScaleRepresentedQuantity<ConstraintResidualQuantity>
) {
return false;
} else if constexpr (!requires {
typename std::integral_constant<
PhysicalScaleLaw,
static_cast<PhysicalScaleLaw>(
Normalization::targetScale)>;
typename std::integral_constant<
PhysicalScaleLaw,
static_cast<PhysicalScaleLaw>(
ValueNormalization::scale)>;
typename std::integral_constant<
PhysicalScaleLaw,
static_cast<PhysicalScaleLaw>(
ResidualNormalization::scale)>;
typename std::bool_constant<
static_cast<std::string_view>(
Manifest::targetUnits) ==
TargetQuantity::identifier>;
typename std::bool_constant<
static_cast<std::string_view>(
Manifest::residualUnits) ==
ConstraintResidualQuantity::identifier>;
}) {
} else if constexpr (
!requires {
typename std::integral_constant<
PhysicalScaleLaw, static_cast<PhysicalScaleLaw>(Normalization::targetScale)>;
typename std::integral_constant<
PhysicalScaleLaw, static_cast<PhysicalScaleLaw>(ValueNormalization::scale)>;
typename std::integral_constant<
PhysicalScaleLaw, static_cast<PhysicalScaleLaw>(ResidualNormalization::scale)>;
typename std::bool_constant<
static_cast<std::string_view>(Manifest::targetUnits) == TargetQuantity::identifier>;
typename std::bool_constant<
static_cast<std::string_view>(Manifest::residualUnits) ==
ConstraintResidualQuantity::identifier>;
}
) {
return false;
} else if constexpr (!requires(const Specification &specification) {
specification.target();
}) {
} else if constexpr (!requires(const Specification &specification) { specification.target(); }) {
return false;
} else {
return
std::same_as<
typename Normalization::TargetValue,
dimensions::QuantityValue<TargetQuantity>> &&
Normalization::targetScale ==
physicalScaleForQuantity<TargetQuantity> &&
std::same_as<TargetQuantity, typename Manifest::TargetQuantity> &&
std::same_as<
GeneratedCoordinateQuantity,
typename Manifest::GeneratedCoordinateQuantity> &&
std::same_as<
ConstraintResidualQuantity,
typename Manifest::ConstraintResidualQuantity> &&
ValueNormalization::scale ==
physicalScaleForQuantity<GeneratedCoordinateQuantity> &&
ResidualNormalization::scale ==
physicalScaleForQuantity<ConstraintResidualQuantity> &&
static_cast<std::string_view>(Manifest::targetUnits) ==
TargetQuantity::identifier &&
static_cast<std::string_view>(Manifest::residualUnits) ==
ConstraintResidualQuantity::identifier &&
std::same_as<
std::remove_cvref_t<decltype(
std::declval<const Specification &>().target())>,
typename Normalization::TargetValue>;
return std::same_as<
typename Normalization::TargetValue, dimensions::QuantityValue<TargetQuantity>> &&
Normalization::targetScale == physicalScaleForQuantity<TargetQuantity> &&
std::same_as<TargetQuantity, typename Manifest::TargetQuantity> &&
std::same_as<
GeneratedCoordinateQuantity, typename Manifest::GeneratedCoordinateQuantity> &&
std::same_as<
ConstraintResidualQuantity, typename Manifest::ConstraintResidualQuantity> &&
ValueNormalization::scale == physicalScaleForQuantity<GeneratedCoordinateQuantity> &&
ResidualNormalization::scale == physicalScaleForQuantity<ConstraintResidualQuantity> &&
static_cast<std::string_view>(Manifest::targetUnits) == TargetQuantity::identifier &&
static_cast<std::string_view>(Manifest::residualUnits) ==
ConstraintResidualQuantity::identifier &&
std::same_as<
std::remove_cvref_t<decltype(std::declval<const Specification &>().target())>,
typename Normalization::TargetValue>;
}
}
}
@@ -993,20 +1028,17 @@ export namespace mean_field::models {
template <typename Specification>
concept CompleteGeneratedScalarDimensionsFor =
ModelSpecification<Specification> &&
detail::generatedScalarDimensionsAreCoherent<
std::remove_cvref_t<Specification>>();
detail::generatedScalarDimensionsAreCoherent<std::remove_cvref_t<Specification>>();
template <typename Specification>
concept CompleteGeneratedNormalizationFor =
ModelSpecification<Specification> &&
CompleteGeneratedScalarDimensionsFor<Specification> &&
ModelSpecification<Specification> && CompleteGeneratedScalarDimensionsFor<Specification> &&
(SpecificationContribution<std::remove_cvref_t<Specification>>::generatedValueArity == 0 ||
SpecificationContribution<std::remove_cvref_t<Specification>>::Normalization::available);
template <typename Specification>
concept CompleteGeneratedManifestFor =
ModelSpecification<Specification> &&
CompleteGeneratedScalarDimensionsFor<Specification> &&
ModelSpecification<Specification> && CompleteGeneratedScalarDimensionsFor<Specification> &&
(SpecificationContribution<std::remove_cvref_t<Specification>>::generatedValueArity == 0 ||
SpecificationContribution<std::remove_cvref_t<Specification>>::Manifest::available);
@@ -1035,7 +1067,7 @@ export namespace mean_field::models {
};
template <SpecificationRole Role, typename SpecificationSet> struct SpecificationsForRole {
using Type = ModelTypeList<>;
using Type = ModelTypeList<>;
static constexpr bool available = false;
static constexpr std::size_t count = 0;
@@ -1043,9 +1075,10 @@ export namespace mean_field::models {
template <SpecificationRole Role, ModelSpecification... Specifications>
struct SpecificationsForRole<Role, SpecificationSetStorage<Specifications...>> {
using Type = typename ConcatenateModelTypeLists<
std::conditional_t<SpecificationTraits<Specifications>::role == Role, ModelTypeList<Specifications>,
ModelTypeList<>>...>::Type;
using Type = typename ConcatenateModelTypeLists<std::conditional_t<
SpecificationTraits<Specifications>::role == Role,
ModelTypeList<Specifications>,
ModelTypeList<>>...>::Type;
static constexpr bool available = true;
static constexpr std::size_t count = Type::size;
@@ -1077,9 +1110,10 @@ export namespace mean_field::models {
};
public:
using Type = std::conditional_t<(SpecificationTraits<Specification>::key < SpecificationTraits<Head>::key),
SpecificationSetStorage<Specification, Head, Tail...>,
typename PrependSpecification<Head, InsertedTail>::Type>;
using Type = std::conditional_t<
(SpecificationTraits<Specification>::key < SpecificationTraits<Head>::key),
SpecificationSetStorage<Specification, Head, Tail...>,
typename PrependSpecification<Head, InsertedTail>::Type>;
};
template <typename Set, ModelSpecification... Specifications> struct CanonicalizeSpecifications;
@@ -1098,21 +1132,26 @@ export namespace mean_field::models {
using CanonicalSpecificationSet =
typename CanonicalizeSpecifications<SpecificationSetStorage<>, Specifications...>::Type;
template <ModelSpecification Head, ModelSpecification... Tail> consteval bool specificationKeyIsUnique() {
template <
ModelSpecification Head,
ModelSpecification... Tail>
consteval bool specificationKeyIsUnique() {
constexpr auto headKey = SpecificationTraits<Head>::key;
return ((headKey.role != SpecificationTraits<Tail>::key.role ||
headKey.stableName != SpecificationTraits<Tail>::key.stableName) &&
...);
return (
(headKey.role != SpecificationTraits<Tail>::key.role ||
headKey.stableName != SpecificationTraits<Tail>::key.stableName) &&
...
);
}
template <ModelSpecification... Specifications> struct SpecificationKeysAreUnique;
template <> struct SpecificationKeysAreUnique<> : std::true_type {};
template <> struct SpecificationKeysAreUnique<> : std::true_type { };
template <ModelSpecification Head, ModelSpecification... Tail>
struct SpecificationKeysAreUnique<Head, Tail...>
: std::bool_constant<specificationKeyIsUnique<Head, Tail...>() &&
SpecificationKeysAreUnique<Tail...>::value> {};
: std::bool_constant<
specificationKeyIsUnique<Head, Tail...>() && SpecificationKeysAreUnique<Tail...>::value> { };
template <SpecificationRole Role, ModelSpecification... Specifications>
inline constexpr std::size_t specificationRoleCount =
@@ -1122,7 +1161,7 @@ export namespace mean_field::models {
template <typename... Types>
struct ModelTypeListScalarArity<ModelTypeList<Types...>>
: std::integral_constant<std::size_t, (std::size_t{0} + ... + Types::scalarArity)> {};
: std::integral_constant<std::size_t, (std::size_t{0} + ... + Types::scalarArity)> { };
template <typename Query, typename... Types>
inline constexpr bool isOneOf = (std::same_as<Query, Types> || ...);
@@ -1136,9 +1175,10 @@ export namespace mean_field::models {
template <ModelSpecification... CanonicalSpecifications, typename... Arguments>
struct ArgumentsMatchCanonicalSpecifications<SpecificationSetStorage<CanonicalSpecifications...>, Arguments...>
: std::bool_constant<sizeof...(CanonicalSpecifications) == sizeof...(Arguments) &&
(isOneOf<std::remove_cvref_t<Arguments>, CanonicalSpecifications...> && ...) &&
((typeCount<CanonicalSpecifications, Arguments...> == 1) && ...)> {};
: std::bool_constant<
sizeof...(CanonicalSpecifications) == sizeof...(Arguments) &&
(isOneOf<std::remove_cvref_t<Arguments>, CanonicalSpecifications...> && ...) &&
((typeCount<CanonicalSpecifications, Arguments...> == 1) && ...)> { };
} // namespace detail
template <typename... Specifications>
@@ -1150,12 +1190,12 @@ export namespace mean_field::models {
concept ValidModelSpecificationPack =
(ResolvedModelSpecification<std::remove_cvref_t<Specifications>> && ...) &&
specificationKeysAreUnique<std::remove_cvref_t<Specifications>...> &&
detail::specificationRoleCount<SpecificationRole::constitutive_law,
std::remove_cvref_t<Specifications>...> == 1;
detail::specificationRoleCount<SpecificationRole::constitutive_law, std::remove_cvref_t<Specifications>...> ==
1;
template <typename... Specifications>
requires(ModelSpecification<std::remove_cvref_t<Specifications>> && ...) &&
specificationKeysAreUnique<std::remove_cvref_t<Specifications>...>
specificationKeysAreUnique<std::remove_cvref_t<Specifications>...>
using SpecificationSet = detail::CanonicalSpecificationSet<std::remove_cvref_t<Specifications>...>;
template <SpecificationRole Role, typename SpecificationSet>
@@ -1203,12 +1243,13 @@ export namespace mean_field::models {
[[nodiscard]] consteval SpecificationDescriptor specificationDescriptor() {
using Contribution = SpecificationContribution<Specification>;
return {.name = SpecificationTraits<Specification>::name,
.role = SpecificationTraits<Specification>::role,
.key = SpecificationTraits<Specification>::key,
.generatedValueArity = detail::ModelTypeListScalarArity<typename Contribution::GeneratedValues>::value,
.generatedResidualArity =
detail::ModelTypeListScalarArity<typename Contribution::GeneratedResiduals>::value};
return {
.name = SpecificationTraits<Specification>::name,
.role = SpecificationTraits<Specification>::role,
.key = SpecificationTraits<Specification>::key,
.generatedValueArity = detail::ModelTypeListScalarArity<typename Contribution::GeneratedValues>::value,
.generatedResidualArity = detail::ModelTypeListScalarArity<typename Contribution::GeneratedResiduals>::value
};
}
namespace detail {
@@ -1231,14 +1272,12 @@ export namespace mean_field::models {
std::tuple<ArgumentTypes...> &&arguments,
CanonicalArgumentsTag
)
: m_specifications(
std::get<Specifications>(std::move(arguments))...
) {
: m_specifications(std::get<Specifications>(std::move(arguments))...) {
}
public:
using SpecificationTypes = SpecificationSetStorage<Specifications...>;
using OperatorSignature = SpecificationOperatorSignature<SpecificationTypes>;
using SpecificationTypes = SpecificationSetStorage<Specifications...>;
using OperatorSignature = SpecificationOperatorSignature<SpecificationTypes>;
static constexpr bool symbolicallySquare = OperatorSignature::symbolicallySquare;
@@ -1249,22 +1288,27 @@ export namespace mean_field::models {
symbolicallySquare && (SpecificationContribution<Specifications>::hasDeclarativeDefinition && ...);
template <typename... Arguments>
requires ArgumentsMatchCanonicalSpecifications<SpecificationTypes, Arguments...>::value &&
requires ArgumentsMatchCanonicalSpecifications<
SpecificationTypes,
Arguments...>::value &&
std::constructible_from<
std::tuple<std::remove_cvref_t<Arguments>...>,
Arguments...> &&
(std::constructible_from<Specifications, Specifications &&> && ...)
(std::constructible_from<
Specifications,
Specifications &&> &&
...)
explicit SpecifiedModel(Arguments &&...arguments)
: SpecifiedModel(
std::tuple<std::remove_cvref_t<Arguments>...>{
std::forward<Arguments>(arguments)...
},
std::tuple<std::remove_cvref_t<Arguments>...>{std::forward<Arguments>(arguments)...},
CanonicalArgumentsTag{}
) {
}
template <ModelSpecification Specification>
requires isOneOf<Specification, Specifications...>
requires isOneOf<
Specification,
Specifications...>
[[nodiscard]] const Specification &specification() const noexcept {
return std::get<Specification>(m_specifications);
}
@@ -1282,10 +1326,11 @@ export namespace mean_field::models {
runtimeDescriptors = [] {
std::array<RuntimeSpecificationDescriptor, sizeof...(Specifications)> descriptors{};
std::size_t index = 0;
((descriptors[index] = {.specification = specificationDescriptor<Specifications>(),
.canonicalIndex = index,
.hasDeclarativeDefinition =
SpecificationContribution<Specifications>::hasDeclarativeDefinition},
((descriptors[index] =
{.specification = specificationDescriptor<Specifications>(),
.canonicalIndex = index,
.hasDeclarativeDefinition =
SpecificationContribution<Specifications>::hasDeclarativeDefinition},
++index),
...);
return descriptors;
@@ -1346,27 +1391,23 @@ export namespace mean_field::stellar {
} // namespace state
namespace equation {
using GravityGradientDefinition = models::stellar::equation::GravityGradientDefinition;
using PoissonEquation = models::stellar::equation::PoissonEquation;
using DensityClosure = models::stellar::equation::DensityClosure;
using SurfaceShapeBalance = models::stellar::equation::SurfaceShapeBalance;
using HydrostaticBalance = models::stellar::equation::HydrostaticBalance;
using OwnConstraint = models::stellar::equation::OwnConstraint;
using GravityGradientDefinition = models::stellar::equation::GravityGradientDefinition;
using PoissonEquation = models::stellar::equation::PoissonEquation;
using DensityClosure = models::stellar::equation::DensityClosure;
using SurfaceShapeBalance = models::stellar::equation::SurfaceShapeBalance;
using HydrostaticBalance = models::stellar::equation::HydrostaticBalance;
using OwnConstraint = models::stellar::equation::OwnConstraint;
template <typename Specification>
using ConstraintOf = models::stellar::equation::ConstraintOf<Specification>;
template <typename Specification> using ConstraintOf = models::stellar::equation::ConstraintOf<Specification>;
} // namespace equation
template <typename Equation, typename State>
using Derivative = models::stellar::Derivative<Equation, State>;
template <typename Equation, typename State> using Derivative = models::stellar::Derivative<Equation, State>;
template <typename... Quantities>
using Reads = models::DependsOn<Quantities...>;
template <typename... Quantities> using Reads = models::DependsOn<Quantities...>;
template <typename... Equations>
using Changes = models::Affects<Equations...>;
template <typename... Equations> using Changes = models::Affects<Equations...>;
using PhysicalScale = models::PhysicalScaleLaw;
using PhysicalScale = models::PhysicalScaleLaw;
template <
models::PhysicalScaleRepresentedQuantity TargetQuantity,
@@ -1393,10 +1434,8 @@ export namespace mean_field::stellar {
typename std::remove_cvref_t<Candidate>::GeneratedCoordinateQuantity;
typename std::remove_cvref_t<Candidate>::ConstraintResidualQuantity;
typename std::remove_cvref_t<Candidate>::TargetValue;
requires models::GeneratedNormalizationDefinition<
typename std::remove_cvref_t<Candidate>::Normalization>;
requires models::GeneratedManifestDefinition<
typename std::remove_cvref_t<Candidate>::Manifest>;
requires models::GeneratedNormalizationDefinition<typename std::remove_cvref_t<Candidate>::Normalization>;
requires models::GeneratedManifestDefinition<typename std::remove_cvref_t<Candidate>::Manifest>;
requires std::remove_cvref_t<Candidate>::Normalization::available;
requires std::remove_cvref_t<Candidate>::Manifest::available;
requires std::remove_cvref_t<Candidate>::dimensionallyTyped;
@@ -1407,31 +1446,43 @@ export namespace mean_field::integral {
using FixedTotalMass = models::FixedTotalMass;
using FixedAngularMomentum = models::FixedAngularMomentum;
template <typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>,
typename Affects = models::ModelTypeList<>,
typename Normalization = models::UnavailableGeneratedNormalization,
typename Manifest = models::UnavailableGeneratedManifest>
template <
typename Specification,
models::FixedString Name,
typename DependsOn = models::ModelTypeList<>,
typename Affects = models::ModelTypeList<>,
typename Normalization = models::UnavailableGeneratedNormalization,
typename Manifest = models::UnavailableGeneratedManifest>
using FixedIntegralWithMultiplier =
models::FixedIntegralWithMultiplier<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
template <typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>,
typename Affects = models::ModelTypeList<>,
typename Normalization = models::UnavailableGeneratedNormalization,
typename Manifest = models::UnavailableGeneratedManifest>
template <
typename Specification,
models::FixedString Name,
typename DependsOn = models::ModelTypeList<>,
typename Affects = models::ModelTypeList<>,
typename Normalization = models::UnavailableGeneratedNormalization,
typename Manifest = models::UnavailableGeneratedManifest>
using FixedWithMultiplier =
FixedIntegralWithMultiplier<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
template <typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>,
typename Affects = models::ModelTypeList<>,
typename Normalization = models::UnavailableGeneratedNormalization,
typename Manifest = models::UnavailableGeneratedManifest>
template <
typename Specification,
models::FixedString Name,
typename DependsOn = models::ModelTypeList<>,
typename Affects = models::ModelTypeList<>,
typename Normalization = models::UnavailableGeneratedNormalization,
typename Manifest = models::UnavailableGeneratedManifest>
using FixedIntegralWithPhysicalCoordinate =
models::FixedIntegralWithPhysicalCoordinate<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
template <typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>,
typename Affects = models::ModelTypeList<>,
typename Normalization = models::UnavailableGeneratedNormalization,
typename Manifest = models::UnavailableGeneratedManifest>
template <
typename Specification,
models::FixedString Name,
typename DependsOn = models::ModelTypeList<>,
typename Affects = models::ModelTypeList<>,
typename Normalization = models::UnavailableGeneratedNormalization,
typename Manifest = models::UnavailableGeneratedManifest>
using FixedWithPhysicalCoordinate =
FixedIntegralWithPhysicalCoordinate<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
@@ -1467,10 +1518,13 @@ export namespace mean_field::integral {
export namespace mean_field::constraint {
using FixedCentralDensity = models::FixedCentralDensity;
template <typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>,
typename Affects = models::ModelTypeList<>,
typename Normalization = models::UnavailableGeneratedNormalization,
typename Manifest = models::UnavailableGeneratedManifest>
template <
typename Specification,
models::FixedString Name,
typename DependsOn = models::ModelTypeList<>,
typename Affects = models::ModelTypeList<>,
typename Normalization = models::UnavailableGeneratedNormalization,
typename Manifest = models::UnavailableGeneratedManifest>
using PhaseCondition = models::PhaseCondition<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
template <

View File

@@ -25,10 +25,9 @@ export namespace mean_field::model {
using EquationOfStateType =
models::SpecificationForRoleT<models::SpecificationRole::constitutive_law, SpecificationTypes>;
static constexpr std::size_t specificationCount = sizeof...(CanonicalSpecifications);
static constexpr bool symbolicallySquare = Storage::symbolicallySquare;
static constexpr bool hasCompleteEquilibriumDeclaration =
Storage::hasCompleteEquilibriumDeclaration;
static constexpr std::size_t specificationCount = sizeof...(CanonicalSpecifications);
static constexpr bool symbolicallySquare = Storage::symbolicallySquare;
static constexpr bool hasCompleteEquilibriumDeclaration = Storage::hasCompleteEquilibriumDeclaration;
template <models::SpecificationRole Role>
using SpecificationsForRole = models::SpecificationsForRoleT<Role, SpecificationTypes>;
@@ -48,7 +47,9 @@ export namespace mean_field::model {
models::HasUniqueSpecificationForRole<Role, SpecificationTypes>;
template <typename... Arguments>
requires std::constructible_from<Storage, Arguments...>
requires std::constructible_from<
Storage,
Arguments...>
explicit StellarModel(Arguments &&...arguments) : m_specifications(std::forward<Arguments>(arguments)...) {
}
@@ -62,8 +63,12 @@ export namespace mean_field::model {
static constexpr bool containsSpecification = Storage::template containsSpecification<Specification>;
template <models::SpecificationRole Role>
requires models::HasUniqueSpecificationForRole<Role, SpecificationTypes>
[[nodiscard]] const models::SpecificationForRoleT<Role, SpecificationTypes> &
requires models::HasUniqueSpecificationForRole<
Role,
SpecificationTypes>
[[nodiscard]] const models::SpecificationForRoleT<
Role,
SpecificationTypes> &
specificationForRole() const noexcept {
using Specification = models::SpecificationForRoleT<Role, SpecificationTypes>;
return specification<Specification>();
@@ -74,8 +79,9 @@ export namespace mean_field::model {
}
template <typename = void>
requires models::HasUniqueSpecificationForRole<models::SpecificationRole::boundary_condition,
SpecificationTypes>
requires models::HasUniqueSpecificationForRole<
models::SpecificationRole::boundary_condition,
SpecificationTypes>
[[nodiscard]] const auto &surfaceCondition() const noexcept {
return specificationForRole<models::SpecificationRole::boundary_condition>();
}
@@ -95,20 +101,20 @@ export namespace mean_field::model {
-> StellarModel<models::SpecificationSet<std::remove_cvref_t<Specifications>...>>;
namespace detail {
template <typename Candidate, typename = void> struct IsStellarModel : std::false_type {};
template <typename Candidate, typename = void> struct IsStellarModel : std::false_type { };
template <typename SpecificationSet>
struct IsStellarModel<
StellarModel<SpecificationSet>,
std::void_t<typename StellarModel<SpecificationSet>::SpecificationTypes,
typename StellarModel<SpecificationSet>::OperatorSignature,
decltype(StellarModel<SpecificationSet>::specificationCount),
decltype(StellarModel<SpecificationSet>::hasCompleteEquilibriumDeclaration)>>
: std::true_type {};
std::void_t<
typename StellarModel<SpecificationSet>::SpecificationTypes,
typename StellarModel<SpecificationSet>::OperatorSignature,
decltype(StellarModel<SpecificationSet>::specificationCount),
decltype(StellarModel<SpecificationSet>::hasCompleteEquilibriumDeclaration)>> : std::true_type { };
template <models::SpecificationRole Role, typename Candidate, bool = IsStellarModel<Candidate>::value>
struct StellarModelRoleSelection {
using Types = models::ModelTypeList<>;
using Types = models::ModelTypeList<>;
static constexpr std::size_t count = 0;
};

View File

@@ -39,7 +39,7 @@ export namespace mean_field::normalization {
}
mfem::Vector state(stateSize);
mfem::Vector residual(residualSize);
state = 1.0;
state = 1.0;
residual = 1.0;
return {std::move(state), std::move(residual)};
}
@@ -126,7 +126,7 @@ export namespace mean_field::normalization {
}
for (int index = 0; index < input.Size(); ++index) {
const double value = input(index);
output(index) = inverse ? value / factors(index) : factors(index) * value;
output(index) = inverse ? value / factors(index) : factors(index) * value;
}
}
@@ -156,22 +156,19 @@ export namespace mean_field::normalization {
* the policy and is found by ADL, so adding a normalization family does
* not edit a library registry or switch. */
template <typename Problem>
concept RuntimePreparedNormalizationOperation =
requires(const std::remove_cvref_t<Problem> &problem) {
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType;
typename std::remove_cvref_t<Problem>::FormType;
requires RuntimePreparedNormalizationFor<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
typename std::remove_cvref_t<Problem>::FormType>;
{
problem.GetNormalizationPrescription()
} -> std::same_as<const typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType &>;
{
prepareStellarNormalization(
problem.GetNormalizationPrescription(),
problem)
} -> std::same_as<DiagonalNormalization>;
};
concept RuntimePreparedNormalizationOperation = requires(const std::remove_cvref_t<Problem> &problem) {
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType;
typename std::remove_cvref_t<Problem>::FormType;
requires RuntimePreparedNormalizationFor<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
typename std::remove_cvref_t<Problem>::FormType>;
{
problem.GetNormalizationPrescription()
} -> std::same_as<const typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType &>;
{
prepareStellarNormalization(problem.GetNormalizationPrescription(), problem)
} -> std::same_as<DiagonalNormalization>;
};
template <typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
@@ -183,16 +180,14 @@ export namespace mean_field::normalization {
m_residualFactors(layout.residual_offsets().Last()) {
}
explicit DiagonalNormalizationBuilder(
utils::blocks::form_layout<Form> &&
) = delete;
explicit DiagonalNormalizationBuilder(utils::blocks::form_layout<Form> &&) = delete;
explicit DiagonalNormalizationBuilder(
const utils::blocks::form_layout<Form> &&
) = delete;
explicit DiagonalNormalizationBuilder(const utils::blocks::form_layout<Form> &&) = delete;
template <typename Block>
requires utils::blocks::contains_type_v<Block, typename Form::value_blocks>
requires utils::blocks::contains_type_v<
Block,
typename Form::value_blocks>
void SetValueBlock(
const double physicalScale,
const mfem::Vector &primalGramDiagonal
@@ -200,18 +195,17 @@ export namespace mean_field::normalization {
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
RequireUnassigned(m_valueAssigned[block], "value");
AssignBlock(
m_stateFactors,
m_layout->value_offsets()[block],
m_layout->value_offsets()[block + 1] - m_layout->value_offsets()[block],
physicalScale,
primalGramDiagonal,
false
m_stateFactors, m_layout->value_offsets()[block],
m_layout->value_offsets()[block + 1] - m_layout->value_offsets()[block], physicalScale,
primalGramDiagonal, false
);
m_valueAssigned[block] = true;
}
template <typename Block>
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
requires utils::blocks::contains_type_v<
Block,
typename Form::residual_blocks>
void SetResidualBlock(
const double physicalScale,
const mfem::Vector &primalGramDiagonal
@@ -219,25 +213,26 @@ export namespace mean_field::normalization {
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
RequireUnassigned(m_residualAssigned[block], "residual");
AssignBlock(
m_residualFactors,
m_layout->residual_offsets()[block],
m_layout->residual_offsets()[block + 1] - m_layout->residual_offsets()[block],
physicalScale,
primalGramDiagonal,
true
m_residualFactors, m_layout->residual_offsets()[block],
m_layout->residual_offsets()[block + 1] - m_layout->residual_offsets()[block], physicalScale,
primalGramDiagonal, true
);
m_residualAssigned[block] = true;
}
template <typename Block>
requires utils::blocks::contains_type_v<Block, typename Form::value_blocks>
requires utils::blocks::contains_type_v<
Block,
typename Form::value_blocks>
void SetValueGlobal(const double physicalScale) {
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
SetConstantMetricValueBlock<Block>(physicalScale, BlockSize(m_layout->value_offsets(), block));
}
template <typename Block>
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
requires utils::blocks::contains_type_v<
Block,
typename Form::residual_blocks>
void SetResidualGlobal(const double physicalScale) {
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
mfem::Vector metric(BlockSize(m_layout->residual_offsets(), block));
@@ -246,7 +241,9 @@ export namespace mean_field::normalization {
}
template <typename Block>
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
requires utils::blocks::contains_type_v<
Block,
typename Form::residual_blocks>
void SetHybridResidualBlock(
const double physicalScale,
const mfem::Vector &bulkPrimalGramDiagonal,
@@ -254,7 +251,7 @@ export namespace mean_field::normalization {
const double pointMetric = 1.0
) {
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
const int size = BlockSize(m_layout->residual_offsets(), block);
const int size = BlockSize(m_layout->residual_offsets(), block);
if (bulkPrimalGramDiagonal.Size() != size) {
throw std::invalid_argument("The hybrid residual Gram diagonal has the wrong size.");
}
@@ -273,9 +270,7 @@ export namespace mean_field::normalization {
mfem::Vector metric(size);
for (int row = 0; row < size; ++row) {
metric(row) = isPointRow[static_cast<std::size_t>(row)]
? pointMetric
: bulkPrimalGramDiagonal(row);
metric(row) = isPointRow[static_cast<std::size_t>(row)] ? pointMetric : bulkPrimalGramDiagonal(row);
}
SetResidualBlock<Block>(physicalScale, metric);
}
@@ -345,9 +340,7 @@ export namespace mean_field::normalization {
const double metric = primalGramDiagonal(index);
ValidateMetric(metric);
const double rieszFactor = std::sqrt(metric);
const double factor = dual
? 1.0 / (physicalScale * rieszFactor)
: rieszFactor / physicalScale;
const double factor = dual ? 1.0 / (physicalScale * rieszFactor) : rieszFactor / physicalScale;
if (!std::isfinite(factor) || factor <= 0.0) {
throw std::overflow_error("A normalization factor is not finite and positive.");
}
@@ -368,7 +361,10 @@ export namespace mean_field::normalization {
const mfem::Operator &physicalJacobian,
const DiagonalNormalization &normalization
)
: mfem::Operator(normalization.ResidualSize(), normalization.StateSize()),
: mfem::Operator(
normalization.ResidualSize(),
normalization.StateSize()
),
m_physicalJacobian(&physicalJacobian),
m_normalization(&normalization),
m_physicalDirection(normalization.StateSize()),
@@ -421,7 +417,10 @@ export namespace mean_field::normalization {
const mfem::Operator &physicalInverse,
const DiagonalNormalization &normalization
)
: mfem::Operator(normalization.StateSize(), normalization.ResidualSize()),
: mfem::Operator(
normalization.StateSize(),
normalization.ResidualSize()
),
m_physicalInverse(&physicalInverse),
m_normalization(&normalization),
m_physicalResidual(normalization.ResidualSize()),
@@ -548,7 +547,7 @@ export namespace mean_field::normalization {
const mfem::Operator &,
const mfem::Operator &,
const DiagonalNormalization &&
) = delete;
) = delete;
ScaledPreconditioner(const ScaledPreconditioner &) = delete;
ScaledPreconditioner &operator=(const ScaledPreconditioner &) = delete;
@@ -557,9 +556,7 @@ export namespace mean_field::normalization {
void SetOperator(const mfem::Operator &normalizedJacobian) override {
if (normalizedJacobian.Width() != Width() || normalizedJacobian.Height() != Height()) {
throw std::invalid_argument(
"The scaled preconditioner received an incompatible normalized Jacobian."
);
throw std::invalid_argument("The scaled preconditioner received an incompatible normalized Jacobian.");
}
if (&normalizedJacobian != m_expectedNormalizedJacobian) {
throw std::invalid_argument(

View File

@@ -27,10 +27,10 @@ export namespace mean_field::normalization {
template <
RieszGeometryPolicy GeometryPolicy = ReferenceGeometry,
ReferenceScalePolicy ScalePolicy = FixedMassBranchReference>
ReferenceScalePolicy ScalePolicy = FixedMassBranchReference>
class PhysicalRieszDiagonal final : public NormalizationPrescriptionTag {
public:
using Geometry = GeometryPolicy;
using Geometry = GeometryPolicy;
using ScaleSource = ScalePolicy;
explicit PhysicalRieszDiagonal(
@@ -62,8 +62,13 @@ export namespace mean_field::normalization {
double m_gravitationalConstant;
};
PhysicalRieszDiagonal(dimensions::LengthValue, double = 1.0)
-> PhysicalRieszDiagonal<ReferenceGeometry, FixedMassBranchReference>;
PhysicalRieszDiagonal(
dimensions::LengthValue,
double = 1.0
)
-> PhysicalRieszDiagonal<
ReferenceGeometry,
FixedMassBranchReference>;
template <typename Candidate> struct IsPhysicalRieszDiagonal : std::false_type { };
@@ -71,8 +76,7 @@ export namespace mean_field::normalization {
struct IsPhysicalRieszDiagonal<PhysicalRieszDiagonal<Geometry, ScaleSource>> : std::true_type { };
template <typename Candidate>
concept PhysicalRieszDiagonalPrescription =
IsPhysicalRieszDiagonal<std::remove_cvref_t<Candidate>>::value;
concept PhysicalRieszDiagonalPrescription = IsPhysicalRieszDiagonal<std::remove_cvref_t<Candidate>>::value;
struct StellarCharacteristicScales final {
dimensions::MassValue mass;
@@ -93,7 +97,7 @@ export namespace mean_field::normalization {
const dimensions::LengthValue radius,
const double gravitationalConstant = 1.0
) {
const double massValue = mass.value();
const double massValue = mass.value();
const double radiusValue = radius.value();
if (!std::isfinite(massValue) || massValue <= 0.0) {
throw std::invalid_argument("Characteristic stellar scales require a finite, positive mass.");
@@ -107,28 +111,19 @@ export namespace mean_field::normalization {
);
}
const double radiusSquared = radiusValue * radiusValue;
const double radiusCubed = radiusSquared * radiusValue;
const double density = massValue / radiusCubed;
const double acceleration = gravitationalConstant * massValue / radiusSquared;
const double radiusSquared = radiusValue * radiusValue;
const double radiusCubed = radiusSquared * radiusValue;
const double density = massValue / radiusCubed;
const double acceleration = gravitationalConstant * massValue / radiusSquared;
const double inverseTimeSquared = gravitationalConstant * massValue / radiusCubed;
const double specificEnergy = gravitationalConstant * massValue / radiusValue;
const double pressure = gravitationalConstant * massValue * massValue /
(radiusSquared * radiusSquared);
const double specificEnergy = gravitationalConstant * massValue / radiusValue;
const double pressure = gravitationalConstant * massValue * massValue / (radiusSquared * radiusSquared);
const double angularVelocity = std::sqrt(inverseTimeSquared);
const double angularMomentum = massValue * std::sqrt(gravitationalConstant * massValue * radiusValue);
const double force = gravitationalConstant * massValue * massValue / radiusSquared;
const double force = gravitationalConstant * massValue * massValue / radiusSquared;
const double derived[] = {
density,
acceleration,
inverseTimeSquared,
specificEnergy,
pressure,
angularVelocity,
angularMomentum,
force
};
const double derived[] = {density, acceleration, inverseTimeSquared, specificEnergy,
pressure, angularVelocity, angularMomentum, force};
for (const double value : derived) {
if (!std::isfinite(value) || value <= 0.0) {
throw std::overflow_error("A derived characteristic stellar scale is not finite and positive.");
@@ -136,36 +131,38 @@ export namespace mean_field::normalization {
}
return {
.mass = mass,
.radius = radius,
.mass = mass,
.radius = radius,
.gravitationalConstant = gravitationalConstant,
.density = density,
.acceleration = acceleration,
.inverseTimeSquared = inverseTimeSquared,
.specificEnergy = specificEnergy,
.pressure = pressure,
.angularVelocity = angularVelocity,
.angularMomentum = angularMomentum,
.force = force
.density = density,
.acceleration = acceleration,
.inverseTimeSquared = inverseTimeSquared,
.specificEnergy = specificEnergy,
.pressure = pressure,
.angularVelocity = angularVelocity,
.angularMomentum = angularMomentum,
.force = force
};
}
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource, typename Model>
template <
RieszGeometryPolicy Geometry,
ReferenceScalePolicy ScaleSource,
typename Model>
requires requires(const Model &model) {
{
model.template specification<models::FixedTotalMass>()
} -> std::same_as<const models::FixedTotalMass &>;
{ model.template specification<models::FixedTotalMass>() } -> std::same_as<const models::FixedTotalMass &>;
{
model.template specification<models::FixedTotalMass>().targetMass()
} -> std::same_as<dimensions::MassValue>;
}
[[nodiscard]] StellarCharacteristicScales deriveStellarCharacteristicScales(
const PhysicalRieszDiagonal<Geometry, ScaleSource> &prescription,
const PhysicalRieszDiagonal<
Geometry,
ScaleSource> &prescription,
const Model &model
) {
return deriveStellarCharacteristicScales(
model.template specification<models::FixedTotalMass>().targetMass(),
prescription.referenceRadius(),
model.template specification<models::FixedTotalMass>().targetMass(), prescription.referenceRadius(),
prescription.gravitationalConstant()
);
}
@@ -179,14 +176,14 @@ export namespace mean_field::normalization {
* normalization plan used by the discretization.
*/
template <models::RieszTopology Topology> struct DeclaredRieszTopology {
static constexpr bool available = false;
static constexpr bool available = false;
static constexpr RieszTopology value = RieszTopology::identity;
};
#define MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(Name) \
template <> struct DeclaredRieszTopology<models::RieszTopology::Name> { \
static constexpr bool available = true; \
static constexpr RieszTopology value = RieszTopology::Name; \
#define MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(Name) \
template <> struct DeclaredRieszTopology<models::RieszTopology::Name> { \
static constexpr bool available = true; \
static constexpr RieszTopology value = RieszTopology::Name; \
}
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(identity);
@@ -199,14 +196,14 @@ export namespace mean_field::normalization {
#undef MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY
template <models::PhysicalScaleLaw Scale> struct DeclaredPhysicalScale {
static constexpr bool available = false;
static constexpr bool available = false;
static constexpr PhysicalScaleKind value = PhysicalScaleKind::dimensionless;
};
#define MEAN_FIELD_DECLARED_PHYSICAL_SCALE(Name) \
template <> struct DeclaredPhysicalScale<models::PhysicalScaleLaw::Name> { \
static constexpr bool available = true; \
static constexpr PhysicalScaleKind value = PhysicalScaleKind::Name; \
#define MEAN_FIELD_DECLARED_PHYSICAL_SCALE(Name) \
template <> struct DeclaredPhysicalScale<models::PhysicalScaleLaw::Name> { \
static constexpr bool available = true; \
static constexpr PhysicalScaleKind value = PhysicalScaleKind::Name; \
}
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(dimensionless);
@@ -223,9 +220,8 @@ export namespace mean_field::normalization {
#undef MEAN_FIELD_DECLARED_PHYSICAL_SCALE
template <typename Declaration, typename = void>
struct CompileDeclaredPhysicalRieszCoordinate {
using Method = UnsupportedPhysicalRieszCoordinate;
template <typename Declaration, typename = void> struct CompileDeclaredPhysicalRieszCoordinate {
using Method = UnsupportedPhysicalRieszCoordinate;
static constexpr bool registered = false;
};
@@ -246,11 +242,11 @@ export namespace mean_field::normalization {
static constexpr models::PhysicalScaleLaw declaredScale =
static_cast<models::PhysicalScaleLaw>(Declaration::scale);
using Topology = DeclaredRieszTopology<declaredTopology>;
using Scale = DeclaredPhysicalScale<declaredScale>;
using Scale = DeclaredPhysicalScale<declaredScale>;
public:
static constexpr bool registered = static_cast<bool>(Declaration::available) &&
Topology::available && Scale::available;
static constexpr bool registered =
static_cast<bool>(Declaration::available) && Topology::available && Scale::available;
using Method = std::conditional_t<
registered,
PhysicalRieszCoordinate<Topology::value, Scale::value>,
@@ -259,7 +255,7 @@ export namespace mean_field::normalization {
template <typename Generated, CoordinateKind Kind, typename = void>
struct DeclaredGeneratedPhysicalRieszCoordinate {
using Method = UnsupportedPhysicalRieszCoordinate;
using Method = UnsupportedPhysicalRieszCoordinate;
static constexpr bool registered = false;
};
@@ -271,9 +267,8 @@ export namespace mean_field::normalization {
typename Generated::SpecificationType,
typename models::SpecificationContribution<
typename Generated::SpecificationType>::Normalization::Value>>
: CompileDeclaredPhysicalRieszCoordinate<
typename models::SpecificationContribution<
typename Generated::SpecificationType>::Normalization::Value> { };
: CompileDeclaredPhysicalRieszCoordinate<typename models::SpecificationContribution<
typename Generated::SpecificationType>::Normalization::Value> { };
template <typename Generated>
struct DeclaredGeneratedPhysicalRieszCoordinate<
@@ -283,12 +278,10 @@ export namespace mean_field::normalization {
typename Generated::SpecificationType,
typename models::SpecificationContribution<
typename Generated::SpecificationType>::Normalization::Residual>>
: CompileDeclaredPhysicalRieszCoordinate<
typename models::SpecificationContribution<
typename Generated::SpecificationType>::Normalization::Residual> { };
: CompileDeclaredPhysicalRieszCoordinate<typename models::SpecificationContribution<
typename Generated::SpecificationType>::Normalization::Residual> { };
template <typename GeneratedValues, typename GeneratedResiduals>
struct GeneratedPhysicalRieszCoverage {
template <typename GeneratedValues, typename GeneratedResiduals> struct GeneratedPhysicalRieszCoverage {
static constexpr bool complete = false;
};
@@ -297,16 +290,12 @@ export namespace mean_field::normalization {
models::ModelTypeList<GeneratedValues...>,
models::ModelTypeList<GeneratedResiduals...>> {
static constexpr bool complete =
(DeclaredGeneratedPhysicalRieszCoordinate<
GeneratedValues,
CoordinateKind::value>::registered && ...) &&
(DeclaredGeneratedPhysicalRieszCoordinate<
GeneratedResiduals,
CoordinateKind::residual>::registered && ...);
(DeclaredGeneratedPhysicalRieszCoordinate<GeneratedValues, CoordinateKind::value>::registered && ...) &&
(DeclaredGeneratedPhysicalRieszCoordinate<GeneratedResiduals, CoordinateKind::residual>::registered &&
...);
};
template <typename Specification, typename = void>
struct SpecificationPhysicalRieszCoverage {
template <typename Specification, typename = void> struct SpecificationPhysicalRieszCoverage {
static constexpr bool complete = false;
};
@@ -355,29 +344,17 @@ export namespace mean_field::normalization {
* adapter can consult the same authority without importing one another.
*/
template <typename Candidate>
concept PhysicalRieszCoreRuntime =
requires(const std::remove_cvref_t<Candidate> &core) {
{
core.GetGravityContext().GetDensityMap()
} -> std::same_as<const field::FieldDofMap &>;
{
core.GetGravityContext().GetGravityGradientMap()
} -> std::same_as<const field::FieldDofMap &>;
{
core.GetGravityContext().GetGravityPotentialMap()
} -> std::same_as<const field::FieldDofMap &>;
{
core.GetHydrostaticOperator().GetEnthalpyMap()
} -> std::same_as<const field::FieldDofMap &>;
{
core.GetDomainDeformation().parameterCount()
} -> std::same_as<int>;
};
concept PhysicalRieszCoreRuntime = requires(const std::remove_cvref_t<Candidate> &core) {
{ core.GetGravityContext().GetDensityMap() } -> std::same_as<const field::FieldDofMap &>;
{ core.GetGravityContext().GetGravityGradientMap() } -> std::same_as<const field::FieldDofMap &>;
{ core.GetGravityContext().GetGravityPotentialMap() } -> std::same_as<const field::FieldDofMap &>;
{ core.GetHydrostaticOperator().GetEnthalpyMap() } -> std::same_as<const field::FieldDofMap &>;
{ core.GetDomainDeformation().parameterCount() } -> std::same_as<int>;
};
namespace detail {
template <typename Generated, CoordinateKind Kind>
using GeneratedPhysicalRieszMethod =
typename DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::Method;
using GeneratedPhysicalRieszMethod = typename DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::Method;
template <typename Generated, CoordinateKind Kind, typename = void>
struct GeneratedPhysicalRieszRuntimeCoordinate : std::false_type { };
@@ -389,8 +366,7 @@ export namespace mean_field::normalization {
std::void_t<decltype(GeneratedPhysicalRieszMethod<Generated, Kind>::topology)>>
: std::bool_constant<
DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::registered &&
GeneratedPhysicalRieszMethod<Generated, Kind>::topology ==
RieszTopology::global_scalar> { };
GeneratedPhysicalRieszMethod<Generated, Kind>::topology == RieszTopology::global_scalar> { };
template <typename Specification, typename = void>
struct SpecificationPhysicalRieszRuntimeCoverage : std::false_type { };
@@ -420,21 +396,18 @@ export namespace mean_field::normalization {
struct SpecificationSetPhysicalRieszRuntimeCoverage : std::false_type { };
template <models::ModelSpecification... Specifications>
struct SpecificationSetPhysicalRieszRuntimeCoverage<
models::detail::SpecificationSetStorage<Specifications...>>
: std::bool_constant<
(SpecificationPhysicalRieszRuntimeCoverage<Specifications>::value && ...)> { };
struct SpecificationSetPhysicalRieszRuntimeCoverage<models::detail::SpecificationSetStorage<Specifications...>>
: std::bool_constant<(SpecificationPhysicalRieszRuntimeCoverage<Specifications>::value && ...)> { };
} // namespace detail
template <typename Specification>
concept CompleteGeneratedPhysicalRieszRuntimeNormalizationFor =
detail::SpecificationPhysicalRieszRuntimeCoverage<
std::remove_cvref_t<Specification>>::value;
detail::SpecificationPhysicalRieszRuntimeCoverage<std::remove_cvref_t<Specification>>::value;
#define MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(BlockType, TopologyValue, ScaleValue) \
template <> struct PhysicalRieszBlockTraits<BlockType> { \
using Method = PhysicalRieszCoordinate<RieszTopology::TopologyValue, PhysicalScaleKind::ScaleValue>; \
static constexpr bool registered = true; \
#define MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(BlockType, TopologyValue, ScaleValue) \
template <> struct PhysicalRieszBlockTraits<BlockType> { \
using Method = PhysicalRieszCoordinate<RieszTopology::TopologyValue, PhysicalScaleKind::ScaleValue>; \
static constexpr bool registered = true; \
}
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
@@ -490,12 +463,9 @@ export namespace mean_field::normalization {
);
#undef MEAN_FIELD_PHYSICAL_RIESZ_TRAIT
template <typename Block>
[[nodiscard]] double physicalScale(
const StellarCharacteristicScales &scales
) {
template <typename Block> [[nodiscard]] double physicalScale(const StellarCharacteristicScales &scales) {
static_assert(PhysicalRieszBlockTraits<Block>::registered, "The block has no Physical Riesz normalization.");
using Method = typename PhysicalRieszBlockTraits<Block>::Method;
using Method = typename PhysicalRieszBlockTraits<Block>::Method;
constexpr PhysicalScaleKind scale = Method::scale;
if constexpr (scale == PhysicalScaleKind::dimensionless) {
return 1.0;
@@ -527,9 +497,7 @@ export namespace mean_field::normalization {
template <typename Values, typename Residuals> struct MakePhysicalRieszPlan;
template <typename... Values, typename... Residuals>
struct MakePhysicalRieszPlan<
utils::blocks::type_list<Values...>,
utils::blocks::type_list<Residuals...>> {
struct MakePhysicalRieszPlan<utils::blocks::type_list<Values...>, utils::blocks::type_list<Residuals...>> {
using Type = NormalizationPlan<
CoordinateComponent<
CoordinateKind::value,
@@ -544,9 +512,8 @@ export namespace mean_field::normalization {
template <typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
using PhysicalRieszNormalizationPlanFor = typename detail::MakePhysicalRieszPlan<
typename Form::value_blocks,
typename Form::residual_blocks>::Type;
using PhysicalRieszNormalizationPlanFor =
typename detail::MakePhysicalRieszPlan<typename Form::value_blocks, typename Form::residual_blocks>::Type;
/*
* Public compile-time extension point for a normalization prescription.
@@ -557,11 +524,10 @@ export namespace mean_field::normalization {
* actually prepare.
*/
template <typename Prescription, typename Form> struct NormalizationCompilation {
using Plan = NormalizationPlan<>;
using Plan = NormalizationPlan<>;
static constexpr bool registered = false;
template <typename PhysicalCore, typename SpecificationTypes>
static constexpr bool runtimeAvailableFor = false;
template <typename PhysicalCore, typename SpecificationTypes> static constexpr bool runtimeAvailableFor = false;
};
/* Astronomy/numerics-facing package for a policy which prepares one
@@ -571,7 +537,7 @@ export namespace mean_field::normalization {
template <NormalizationPrescription Prescription, typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
struct RuntimePreparedNormalizationCompilation {
using Plan = RuntimePreparedNormalizationPlanFor<Prescription, Form>;
using Plan = RuntimePreparedNormalizationPlanFor<Prescription, Form>;
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
template <typename PhysicalCore, typename SpecificationTypes>
@@ -581,7 +547,7 @@ export namespace mean_field::normalization {
template <typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
struct NormalizationCompilation<Unnormalized, Form> {
using Plan = IdentityNormalizationPlanFor<Form>;
using Plan = IdentityNormalizationPlanFor<Form>;
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
template <typename PhysicalCore, typename SpecificationTypes>
@@ -591,21 +557,18 @@ export namespace mean_field::normalization {
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource, typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
struct NormalizationCompilation<PhysicalRieszDiagonal<Geometry, ScaleSource>, Form> {
using Plan = PhysicalRieszNormalizationPlanFor<Form>;
using Plan = PhysicalRieszNormalizationPlanFor<Form>;
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
template <typename PhysicalCore, typename SpecificationTypes>
static constexpr bool runtimeAvailableFor =
registered &&
PhysicalRieszCoreRuntime<std::remove_cvref_t<PhysicalCore>> &&
detail::SpecificationSetPhysicalRieszRuntimeCoverage<
std::remove_cvref_t<SpecificationTypes>>::value;
registered && PhysicalRieszCoreRuntime<std::remove_cvref_t<PhysicalCore>> &&
detail::SpecificationSetPhysicalRieszRuntimeCoverage<std::remove_cvref_t<SpecificationTypes>>::value;
};
namespace detail {
template <typename Prescription, typename Form, typename = void>
struct NormalizationCompilationAudit {
using Plan = NormalizationPlan<>;
template <typename Prescription, typename Form, typename = void> struct NormalizationCompilationAudit {
using Plan = NormalizationPlan<>;
static constexpr bool registered = false;
};
@@ -616,34 +579,25 @@ export namespace mean_field::normalization {
Prescription,
Form,
std::void_t<
typename NormalizationCompilation<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>::Plan,
decltype(std::bool_constant<static_cast<bool>(
NormalizationCompilation<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>::registered)>{})>> {
using Compilation = NormalizationCompilation<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>;
using Plan = typename Compilation::Plan;
typename NormalizationCompilation<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>>::Plan,
decltype(std::bool_constant<static_cast<bool>(NormalizationCompilation<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>::registered)>{})>> {
using Compilation = NormalizationCompilation<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>>;
using Plan = typename Compilation::Plan;
static constexpr bool registered =
static_cast<bool>(Compilation::registered) &&
CompleteNormalizationFor<Plan, std::remove_cvref_t<Form>>;
static_cast<bool>(Compilation::registered) && CompleteNormalizationFor<Plan, std::remove_cvref_t<Form>>;
};
} // namespace detail
template <NormalizationPrescription Prescription, typename Form>
using NormalizationPlanFor = typename detail::NormalizationCompilationAudit<
std::remove_cvref_t<Prescription>,
Form>::Plan;
using NormalizationPlanFor =
typename detail::NormalizationCompilationAudit<std::remove_cvref_t<Prescription>, Form>::Plan;
template <typename Prescription, typename Form>
concept CompilableNormalizationFor =
detail::NormalizationCompilationAudit<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>::registered;
detail::NormalizationCompilationAudit<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>>::registered;
/* The public runtime-preparation adapter is intentionally narrower than
* an arbitrary complete plan: every coordinate must name the exact policy
@@ -654,16 +608,10 @@ export namespace mean_field::normalization {
concept RuntimePreparedNormalizationFor =
NormalizationPrescription<std::remove_cvref_t<Prescription>> &&
utils::blocks::block_form_is_valid_v<std::remove_cvref_t<Form>> &&
CompilableNormalizationFor<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>> &&
CompilableNormalizationFor<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>> &&
std::same_as<
NormalizationPlanFor<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>,
RuntimePreparedNormalizationPlanFor<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>>;
NormalizationPlanFor<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>>,
RuntimePreparedNormalizationPlanFor<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>>>;
namespace detail {
template <
@@ -674,35 +622,22 @@ export namespace mean_field::normalization {
typename = void>
struct StellarNormalizationRuntimeAudit : std::false_type { };
template <
typename Prescription,
typename Form,
typename PhysicalCore,
typename SpecificationTypes>
template <typename Prescription, typename Form, typename PhysicalCore, typename SpecificationTypes>
struct StellarNormalizationRuntimeAudit<
Prescription,
Form,
PhysicalCore,
SpecificationTypes,
std::void_t<
std::enable_if_t<NormalizationCompilationAudit<
Prescription,
Form>::registered>,
decltype(std::bool_constant<static_cast<bool>(
NormalizationCompilation<
Prescription,
Form>::template runtimeAvailableFor<
PhysicalCore,
SpecificationTypes>)>{})>>
std::enable_if_t<NormalizationCompilationAudit<Prescription, Form>::registered>,
decltype(std::bool_constant<
static_cast<bool>(NormalizationCompilation<Prescription, Form>::
template runtimeAvailableFor<PhysicalCore, SpecificationTypes>)>{})>>
: std::bool_constant<
(std::same_as<Prescription, Unnormalized> ||
PhysicalRieszDiagonalPrescription<Prescription> ||
(std::same_as<Prescription, Unnormalized> || PhysicalRieszDiagonalPrescription<Prescription> ||
RuntimePreparedNormalizationFor<Prescription, Form>) &&
static_cast<bool>(NormalizationCompilation<
Prescription,
Form>::template runtimeAvailableFor<
PhysicalCore,
SpecificationTypes>)> { };
static_cast<bool>(NormalizationCompilation<Prescription, Form>::
template runtimeAvailableFor<PhysicalCore, SpecificationTypes>)> { };
} // namespace detail
/*
@@ -714,15 +649,10 @@ export namespace mean_field::normalization {
* assembly and global-scalar runtime preparation for every generated
* coordinate in the specification pack.
*/
template <
typename Prescription,
typename Form,
typename PhysicalCore,
typename SpecificationTypes>
concept StellarNormalizationRuntimeAvailableFor =
detail::StellarNormalizationRuntimeAudit<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>,
std::remove_cvref_t<PhysicalCore>,
std::remove_cvref_t<SpecificationTypes>>::value;
template <typename Prescription, typename Form, typename PhysicalCore, typename SpecificationTypes>
concept StellarNormalizationRuntimeAvailableFor = detail::StellarNormalizationRuntimeAudit<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>,
std::remove_cvref_t<PhysicalCore>,
std::remove_cvref_t<SpecificationTypes>>::value;
} // namespace mean_field::normalization

View File

@@ -11,10 +11,7 @@ export namespace mean_field::normalization {
struct NormalizationPrescriptionTag { };
template <typename Candidate>
concept NormalizationPrescription =
std::derived_from<
std::remove_cvref_t<Candidate>,
NormalizationPrescriptionTag>;
concept NormalizationPrescription = std::derived_from<std::remove_cvref_t<Candidate>, NormalizationPrescriptionTag>;
enum class CoordinateKind { value, residual };
@@ -50,44 +47,36 @@ export namespace mean_field::normalization {
* numerical value of that factor. The owner type prevents one policy from
* silently presenting another policy's runtime map as its own plan.
*/
template <NormalizationPrescription Prescription>
struct RuntimePreparedCoordinate final {
template <NormalizationPrescription Prescription> struct RuntimePreparedCoordinate final {
using PrescriptionType = std::remove_cvref_t<Prescription>;
};
template <RieszTopology Topology, PhysicalScaleKind Scale> struct PhysicalRieszCoordinate final {
static constexpr RieszTopology topology = Topology;
static constexpr PhysicalScaleKind scale = Scale;
static constexpr RieszTopology topology = Topology;
static constexpr PhysicalScaleKind scale = Scale;
};
struct UnsupportedPhysicalRieszCoordinate final { };
template <typename Block> struct PhysicalRieszBlockTraits {
using Method = UnsupportedPhysicalRieszCoordinate;
static constexpr bool registered = false;
using Method = UnsupportedPhysicalRieszCoordinate;
static constexpr bool registered = false;
};
template <CoordinateKind Kind, typename BlockList, typename MethodType>
struct CoordinateComponent final {
template <CoordinateKind Kind, typename BlockList, typename MethodType> struct CoordinateComponent final {
using Blocks = BlockList;
using Method = MethodType;
static constexpr CoordinateKind kind = Kind;
using ValueBlocks = std::conditional_t<
Kind == CoordinateKind::value,
BlockList,
utils::blocks::type_list<>>;
using ResidualBlocks = std::conditional_t<
Kind == CoordinateKind::residual,
BlockList,
utils::blocks::type_list<>>;
using ValueBlocks = std::conditional_t<Kind == CoordinateKind::value, BlockList, utils::blocks::type_list<>>;
using ResidualBlocks =
std::conditional_t<Kind == CoordinateKind::residual, BlockList, utils::blocks::type_list<>>;
};
namespace detail {
template <typename Candidate> struct IsTypeList : std::false_type { };
template <typename... Types>
struct IsTypeList<utils::blocks::type_list<Types...>> : std::true_type { };
template <typename... Types> struct IsTypeList<utils::blocks::type_list<Types...>> : std::true_type { };
template <typename List, typename Base> struct IsUniqueDerivedBlockList : std::false_type { };
@@ -102,8 +91,7 @@ export namespace mean_field::normalization {
template <> struct IsCoordinateMethod<IdentityCoordinate> : std::true_type { };
template <NormalizationPrescription Prescription>
struct IsCoordinateMethod<RuntimePreparedCoordinate<Prescription>>
: std::true_type { };
struct IsCoordinateMethod<RuntimePreparedCoordinate<Prescription>> : std::true_type { };
template <RieszTopology Topology, PhysicalScaleKind Scale>
struct IsCoordinateMethod<PhysicalRieszCoordinate<Topology, Scale>> : std::true_type { };
@@ -121,10 +109,9 @@ export namespace mean_field::normalization {
template <RieszTopology Topology, PhysicalScaleKind Scale, typename Block>
struct MethodSupportsBlock<PhysicalRieszCoordinate<Topology, Scale>, Block>
: std::bool_constant<
PhysicalRieszBlockTraits<Block>::registered &&
std::same_as<
typename PhysicalRieszBlockTraits<Block>::Method,
PhysicalRieszCoordinate<Topology, Scale>>> { };
PhysicalRieszBlockTraits<Block>::registered && std::same_as<
typename PhysicalRieszBlockTraits<Block>::Method,
PhysicalRieszCoordinate<Topology, Scale>>> { };
template <typename Method, typename List> struct MethodSupportsEveryBlock : std::false_type { };
@@ -166,8 +153,7 @@ export namespace mean_field::normalization {
}();
static constexpr bool hasCoherentCoordinateLists = [] {
if constexpr (!hasValidKind || !IsTypeList<ValueBlocks>::value ||
!IsTypeList<ResidualBlocks>::value) {
if constexpr (!hasValidKind || !IsTypeList<ValueBlocks>::value || !IsTypeList<ResidualBlocks>::value) {
return false;
} else if constexpr (Candidate::kind == CoordinateKind::value) {
return std::same_as<ValueBlocks, Blocks> &&
@@ -182,8 +168,7 @@ export namespace mean_field::normalization {
static constexpr bool valid = hasValidKind && IsTypeList<Blocks>::value &&
IsCoordinateMethod<Method>::value && hasValidBlockList &&
hasCoherentCoordinateLists &&
MethodSupportsEveryBlock<Method, Blocks>::value;
hasCoherentCoordinateLists && MethodSupportsEveryBlock<Method, Blocks>::value;
};
template <typename... Lists> struct Concatenate;
@@ -205,23 +190,18 @@ export namespace mean_field::normalization {
template <typename List, typename Type> struct Append;
template <typename... Types, typename Appended>
struct Append<utils::blocks::type_list<Types...>, Appended> {
template <typename... Types, typename Appended> struct Append<utils::blocks::type_list<Types...>, Appended> {
using Type = utils::blocks::type_list<Types..., Appended>;
};
template <typename List, typename Type> using AppendT = typename Append<List, Type>::Type;
template <typename List, typename Type>
using AppendUniqueT = std::conditional_t<
utils::blocks::contains_type_v<Type, List>,
List,
AppendT<List, Type>>;
using AppendUniqueT = std::conditional_t<utils::blocks::contains_type_v<Type, List>, List, AppendT<List, Type>>;
template <typename Source, typename Excluded> struct ListDifference;
template <typename Excluded>
struct ListDifference<utils::blocks::type_list<>, Excluded> {
template <typename Excluded> struct ListDifference<utils::blocks::type_list<>, Excluded> {
using Type = utils::blocks::type_list<>;
};
@@ -260,10 +240,7 @@ export namespace mean_field::normalization {
};
template <typename List>
using RepeatedTypesT = typename CollectRepeatedTypes<
List,
List,
utils::blocks::type_list<>>::Type;
using RepeatedTypesT = typename CollectRepeatedTypes<List, List, utils::blocks::type_list<>>::Type;
template <typename Candidate, typename = void> struct PlanTraits {
static constexpr bool valid = false;
@@ -274,23 +251,20 @@ export namespace mean_field::normalization {
concept NormalizationComponent = detail::ComponentTraits<std::remove_cvref_t<Candidate>>::valid;
template <typename... Components> struct NormalizationPlan final {
using ComponentTypes = utils::blocks::type_list<Components...>;
using ValueBlocks = detail::ConcatenateT<typename Components::ValueBlocks...>;
using ResidualBlocks = detail::ConcatenateT<typename Components::ResidualBlocks...>;
using ComponentTypes = utils::blocks::type_list<Components...>;
using ValueBlocks = detail::ConcatenateT<typename Components::ValueBlocks...>;
using ResidualBlocks = detail::ConcatenateT<typename Components::ResidualBlocks...>;
};
namespace detail {
template <typename... Components>
struct PlanTraits<NormalizationPlan<Components...>> {
template <typename... Components> struct PlanTraits<NormalizationPlan<Components...>> {
static constexpr bool valid = (ComponentTraits<Components>::valid && ...);
};
template <typename Values, typename Residuals> struct MakeIdentityPlan;
template <typename... Values, typename... Residuals>
struct MakeIdentityPlan<
utils::blocks::type_list<Values...>,
utils::blocks::type_list<Residuals...>> {
struct MakeIdentityPlan<utils::blocks::type_list<Values...>, utils::blocks::type_list<Residuals...>> {
using Type = NormalizationPlan<
CoordinateComponent<CoordinateKind::value, utils::blocks::type_list<Values>, IdentityCoordinate>...,
CoordinateComponent<
@@ -299,30 +273,18 @@ export namespace mean_field::normalization {
IdentityCoordinate>...>;
};
template <
NormalizationPrescription Prescription,
typename Values,
typename Residuals>
template <NormalizationPrescription Prescription, typename Values, typename Residuals>
struct MakeRuntimePreparedPlan;
template <
NormalizationPrescription Prescription,
typename... Values,
typename... Residuals>
template <NormalizationPrescription Prescription, typename... Values, typename... Residuals>
struct MakeRuntimePreparedPlan<
Prescription,
utils::blocks::type_list<Values...>,
utils::blocks::type_list<Residuals...>> {
using Method = RuntimePreparedCoordinate<Prescription>;
using Type = NormalizationPlan<
CoordinateComponent<
CoordinateKind::value,
utils::blocks::type_list<Values>,
Method>...,
CoordinateComponent<
CoordinateKind::residual,
utils::blocks::type_list<Residuals>,
Method>...>;
using Type = NormalizationPlan<
CoordinateComponent<CoordinateKind::value, utils::blocks::type_list<Values>, Method>...,
CoordinateComponent<CoordinateKind::residual, utils::blocks::type_list<Residuals>, Method>...>;
};
} // namespace detail
@@ -331,46 +293,43 @@ export namespace mean_field::normalization {
template <typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
using IdentityNormalizationPlanFor = typename detail::MakeIdentityPlan<
typename Form::value_blocks,
typename Form::residual_blocks>::Type;
using IdentityNormalizationPlanFor =
typename detail::MakeIdentityPlan<typename Form::value_blocks, typename Form::residual_blocks>::Type;
template <NormalizationPrescription Prescription, typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
using RuntimePreparedNormalizationPlanFor =
typename detail::MakeRuntimePreparedPlan<
std::remove_cvref_t<Prescription>,
typename Form::value_blocks,
typename Form::residual_blocks>::Type;
using RuntimePreparedNormalizationPlanFor = typename detail::MakeRuntimePreparedPlan<
std::remove_cvref_t<Prescription>,
typename Form::value_blocks,
typename Form::residual_blocks>::Type;
template <typename Form, typename Plan>
requires utils::blocks::block_form_is_valid_v<Form>
struct NormalizationCoverage final {
using DeclaredValueBlocks = typename Plan::ValueBlocks;
using DeclaredValueBlocks = typename Plan::ValueBlocks;
using DeclaredResidualBlocks = typename Plan::ResidualBlocks;
using MissingValueBlocks = detail::ListDifferenceT<typename Form::value_blocks, DeclaredValueBlocks>;
using UnexpectedValueBlocks = detail::ListDifferenceT<DeclaredValueBlocks, typename Form::value_blocks>;
using RepeatedValueBlocks = detail::RepeatedTypesT<DeclaredValueBlocks>;
using MissingValueBlocks = detail::ListDifferenceT<typename Form::value_blocks, DeclaredValueBlocks>;
using UnexpectedValueBlocks = detail::ListDifferenceT<DeclaredValueBlocks, typename Form::value_blocks>;
using RepeatedValueBlocks = detail::RepeatedTypesT<DeclaredValueBlocks>;
using MissingResidualBlocks = detail::ListDifferenceT<typename Form::residual_blocks, DeclaredResidualBlocks>;
using UnexpectedResidualBlocks = detail::ListDifferenceT<DeclaredResidualBlocks, typename Form::residual_blocks>;
using RepeatedResidualBlocks = detail::RepeatedTypesT<DeclaredResidualBlocks>;
using MissingResidualBlocks = detail::ListDifferenceT<typename Form::residual_blocks, DeclaredResidualBlocks>;
using UnexpectedResidualBlocks =
detail::ListDifferenceT<DeclaredResidualBlocks, typename Form::residual_blocks>;
using RepeatedResidualBlocks = detail::RepeatedTypesT<DeclaredResidualBlocks>;
static constexpr bool hasEveryValueBlock = MissingValueBlocks::size == 0;
static constexpr bool hasOnlyValueBlocks = UnexpectedValueBlocks::size == 0;
static constexpr bool hasUniqueValueOwners = RepeatedValueBlocks::size == 0;
static constexpr bool hasEveryResidualBlock = MissingResidualBlocks::size == 0;
static constexpr bool hasOnlyResidualBlocks = UnexpectedResidualBlocks::size == 0;
static constexpr bool hasEveryValueBlock = MissingValueBlocks::size == 0;
static constexpr bool hasOnlyValueBlocks = UnexpectedValueBlocks::size == 0;
static constexpr bool hasUniqueValueOwners = RepeatedValueBlocks::size == 0;
static constexpr bool hasEveryResidualBlock = MissingResidualBlocks::size == 0;
static constexpr bool hasOnlyResidualBlocks = UnexpectedResidualBlocks::size == 0;
static constexpr bool hasUniqueResidualOwners = RepeatedResidualBlocks::size == 0;
static constexpr bool complete = hasEveryValueBlock && hasOnlyValueBlocks && hasUniqueValueOwners &&
hasEveryResidualBlock && hasOnlyResidualBlocks &&
hasUniqueResidualOwners;
hasEveryResidualBlock && hasOnlyResidualBlocks && hasUniqueResidualOwners;
};
template <typename Plan, typename Form>
concept CompleteNormalizationFor = utils::blocks::block_form_is_valid_v<Form> &&
NormalizationPlanType<Plan> &&
concept CompleteNormalizationFor = utils::blocks::block_form_is_valid_v<Form> && NormalizationPlanType<Plan> &&
NormalizationCoverage<Form, std::remove_cvref_t<Plan>>::complete;
} // namespace mean_field::normalization

View File

@@ -2,6 +2,7 @@ module;
#include <concepts>
#include <cstdint>
#include <expected>
#include <memory>
#include <span>
#include <stdexcept>
@@ -61,7 +62,7 @@ namespace mean_field::normalization::detail {
const field::ScalarBoundaryDofMap &surfaceMap
) {
mfem::Array<int> marker(finiteElements.mesh->bdr_attributes.Max());
marker = 0;
marker = 0;
constexpr int attribute = DomainSchema::template boundary_attribute<utils::domain::StellarSurface>();
if (attribute <= 0 || attribute > marker.Size()) {
throw std::invalid_argument("The reference mesh does not contain the stellar-surface boundary.");
@@ -107,25 +108,19 @@ export namespace mean_field::normalization {
* this layer never names a concrete integral or phase constraint.
*/
namespace detail {
template <typename Block>
using PhysicalRieszMethodFor = typename PhysicalRieszBlockTraits<Block>::Method;
template <typename Block> using PhysicalRieszMethodFor = typename PhysicalRieszBlockTraits<Block>::Method;
template <typename Block, typename = void>
struct IsGlobalGeneratedValueNormalization : std::false_type { };
template <typename Block, typename = void> struct IsGlobalGeneratedValueNormalization : std::false_type { };
template <typename Generated>
struct IsGlobalGeneratedValueNormalization<
utils::blocks::generated_value_block<Generated>,
std::void_t<
decltype(PhysicalRieszMethodFor<
utils::blocks::generated_value_block<Generated>>::topology),
decltype(PhysicalRieszMethodFor<
utils::blocks::generated_value_block<Generated>>::scale)>>
decltype(PhysicalRieszMethodFor<utils::blocks::generated_value_block<Generated>>::topology),
decltype(PhysicalRieszMethodFor<utils::blocks::generated_value_block<Generated>>::scale)>>
: std::bool_constant<
PhysicalRieszBlockTraits<
utils::blocks::generated_value_block<Generated>>::registered &&
PhysicalRieszMethodFor<
utils::blocks::generated_value_block<Generated>>::topology ==
PhysicalRieszBlockTraits<utils::blocks::generated_value_block<Generated>>::registered &&
PhysicalRieszMethodFor<utils::blocks::generated_value_block<Generated>>::topology ==
RieszTopology::global_scalar> { };
template <typename Blocks, typename Specification>
@@ -139,32 +134,26 @@ export namespace mean_field::normalization {
Generated,
Specification,
std::void_t<typename Generated::SpecificationType>>
: std::bool_constant<
std::same_as<typename Generated::SpecificationType, Specification>> { };
: std::bool_constant<std::same_as<typename Generated::SpecificationType, Specification>> { };
template <typename Specification, typename... Generated>
struct GeneratedValueBlocksBelongToSpecification<
utils::blocks::type_list<utils::blocks::generated_value_block<Generated>...>,
Specification>
: std::bool_constant<
(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> { };
: std::bool_constant<(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> {
};
template <typename Block, typename = void>
struct IsGlobalGeneratedResidualNormalization : std::false_type { };
template <typename Block, typename = void> struct IsGlobalGeneratedResidualNormalization : std::false_type { };
template <typename Generated>
struct IsGlobalGeneratedResidualNormalization<
utils::blocks::generated_residual_block<Generated>,
std::void_t<
decltype(PhysicalRieszMethodFor<
utils::blocks::generated_residual_block<Generated>>::topology),
decltype(PhysicalRieszMethodFor<
utils::blocks::generated_residual_block<Generated>>::scale)>>
decltype(PhysicalRieszMethodFor<utils::blocks::generated_residual_block<Generated>>::topology),
decltype(PhysicalRieszMethodFor<utils::blocks::generated_residual_block<Generated>>::scale)>>
: std::bool_constant<
PhysicalRieszBlockTraits<
utils::blocks::generated_residual_block<Generated>>::registered &&
PhysicalRieszMethodFor<
utils::blocks::generated_residual_block<Generated>>::topology ==
PhysicalRieszBlockTraits<utils::blocks::generated_residual_block<Generated>>::registered &&
PhysicalRieszMethodFor<utils::blocks::generated_residual_block<Generated>>::topology ==
RieszTopology::global_scalar> { };
template <typename Blocks, typename Specification>
@@ -174,14 +163,13 @@ export namespace mean_field::normalization {
struct GeneratedResidualBlocksBelongToSpecification<
utils::blocks::type_list<utils::blocks::generated_residual_block<Generated>...>,
Specification>
: std::bool_constant<
(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> { };
: std::bool_constant<(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> {
};
template <typename Blocks> struct PrepareGeneratedValueNormalizations {
static constexpr bool registered = false;
static constexpr bool registered = false;
template <typename Form>
static constexpr bool completeFor = false;
template <typename Form> static constexpr bool completeFor = false;
template <typename Form>
static void Apply(
@@ -192,14 +180,12 @@ export namespace mean_field::normalization {
}
};
template <typename... Blocks>
struct PrepareGeneratedValueNormalizations<utils::blocks::type_list<Blocks...>> {
static constexpr bool registered =
(IsGlobalGeneratedValueNormalization<Blocks>::value && ...);
template <typename... Blocks> struct PrepareGeneratedValueNormalizations<utils::blocks::type_list<Blocks...>> {
static constexpr bool registered = (IsGlobalGeneratedValueNormalization<Blocks>::value && ...);
template <typename Form>
static constexpr bool completeFor = registered &&
utils::blocks::block_form_is_valid_v<Form> &&
static constexpr bool completeFor =
registered && utils::blocks::block_form_is_valid_v<Form> &&
(utils::blocks::contains_type_v<Blocks, typename Form::value_blocks> && ...);
template <typename Form>
@@ -220,10 +206,9 @@ export namespace mean_field::normalization {
};
template <typename Blocks> struct PrepareGeneratedResidualNormalizations {
static constexpr bool registered = false;
static constexpr bool registered = false;
template <typename Form>
static constexpr bool completeFor = false;
template <typename Form> static constexpr bool completeFor = false;
template <typename Form>
static void Apply(
@@ -236,12 +221,11 @@ export namespace mean_field::normalization {
template <typename... Blocks>
struct PrepareGeneratedResidualNormalizations<utils::blocks::type_list<Blocks...>> {
static constexpr bool registered =
(IsGlobalGeneratedResidualNormalization<Blocks>::value && ...);
static constexpr bool registered = (IsGlobalGeneratedResidualNormalization<Blocks>::value && ...);
template <typename Form>
static constexpr bool completeFor = registered &&
utils::blocks::block_form_is_valid_v<Form> &&
static constexpr bool completeFor =
registered && utils::blocks::block_form_is_valid_v<Form> &&
(utils::blocks::contains_type_v<Blocks, typename Form::residual_blocks> && ...);
template <typename Form>
@@ -261,15 +245,13 @@ export namespace mean_field::normalization {
}
};
template <typename Specification, typename = void>
struct CompileStellarSpecificationNormalization {
using ValuePreparation = PrepareGeneratedValueNormalizations<void>;
using ResidualPreparation = PrepareGeneratedResidualNormalizations<void>;
template <typename Specification, typename = void> struct CompileStellarSpecificationNormalization {
using ValuePreparation = PrepareGeneratedValueNormalizations<void>;
using ResidualPreparation = PrepareGeneratedResidualNormalizations<void>;
static constexpr bool registered = false;
static constexpr bool registered = false;
template <typename Form>
static constexpr bool completeFor = false;
template <typename Form> static constexpr bool completeFor = false;
template <typename Form>
static void Apply(
@@ -277,8 +259,7 @@ export namespace mean_field::normalization {
const StellarCharacteristicScales &
) {
static_assert(
completeFor<Form>,
"The specification has no complete generated-coordinate normalization."
completeFor<Form>, "The specification has no complete generated-coordinate normalization."
);
}
};
@@ -287,32 +268,27 @@ export namespace mean_field::normalization {
struct CompileStellarSpecificationNormalization<
Specification,
std::void_t<
typename operators::StellarEquilibriumSpecificationCompilation<
Specification>::GeneratedValueBlocks,
typename operators::StellarEquilibriumSpecificationCompilation<Specification>::GeneratedValueBlocks,
typename operators::StellarEquilibriumSpecificationCompilation<
Specification>::GeneratedResidualBlocks>> {
using OperatorCompilation =
operators::StellarEquilibriumSpecificationCompilation<Specification>;
using ValuePreparation = PrepareGeneratedValueNormalizations<
typename OperatorCompilation::GeneratedValueBlocks>;
using ResidualPreparation = PrepareGeneratedResidualNormalizations<
typename OperatorCompilation::GeneratedResidualBlocks>;
using OperatorCompilation = operators::StellarEquilibriumSpecificationCompilation<Specification>;
using ValuePreparation =
PrepareGeneratedValueNormalizations<typename OperatorCompilation::GeneratedValueBlocks>;
using ResidualPreparation =
PrepareGeneratedResidualNormalizations<typename OperatorCompilation::GeneratedResidualBlocks>;
static constexpr bool registered = OperatorCompilation::complete &&
models::CompleteGeneratedNormalizationFor<
Specification> &&
models::CompleteGeneratedNormalizationFor<Specification> &&
GeneratedValueBlocksBelongToSpecification<
typename OperatorCompilation::GeneratedValueBlocks,
Specification>::value &&
GeneratedResidualBlocksBelongToSpecification<
typename OperatorCompilation::GeneratedResidualBlocks,
Specification>::value &&
ValuePreparation::registered &&
ResidualPreparation::registered;
ValuePreparation::registered && ResidualPreparation::registered;
template <typename Form>
static constexpr bool completeFor = registered &&
ValuePreparation::template completeFor<Form> &&
static constexpr bool completeFor = registered && ValuePreparation::template completeFor<Form> &&
ResidualPreparation::template completeFor<Form>;
template <typename Form>
@@ -366,9 +342,8 @@ export namespace mean_field::normalization {
Model,
Form,
std::void_t<typename std::remove_cvref_t<Model>::SpecificationTypes>>
: std::bool_constant<
PrepareSpecificationNormalizations<
typename std::remove_cvref_t<Model>::SpecificationTypes>::template completeFor<Form>> { };
: std::bool_constant<PrepareSpecificationNormalizations<
typename std::remove_cvref_t<Model>::SpecificationTypes>::template completeFor<Form>> { };
} // namespace detail
template <typename Specification>
@@ -400,9 +375,7 @@ export namespace mean_field::normalization {
template <typename Model, typename Form>
concept CompleteStellarNormalizationFor =
detail::StellarModelNormalizationCoverage<
std::remove_cvref_t<Model>,
std::remove_cvref_t<Form>>::value;
detail::StellarModelNormalizationCoverage<std::remove_cvref_t<Model>, std::remove_cvref_t<Form>>::value;
/*
* Physical Riesz preparation is an optional capability of a physical
@@ -418,8 +391,7 @@ export namespace mean_field::normalization {
template <typename Problem>
concept PhysicalRieszStellarEquilibriumProblem =
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
requires {
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> && requires {
typename std::remove_cvref_t<Problem>::ModelType;
typename std::remove_cvref_t<Problem>::FormType;
typename std::remove_cvref_t<Problem>::PhysicalCoreType;
@@ -430,8 +402,7 @@ export namespace mean_field::normalization {
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
typename std::remove_cvref_t<Problem>::FormType>;
requires CompleteStellarNormalizationFor<
typename std::remove_cvref_t<Problem>::ModelType,
typename std::remove_cvref_t<Problem>::FormType>;
typename std::remove_cvref_t<Problem>::ModelType, typename std::remove_cvref_t<Problem>::FormType>;
requires StellarNormalizationRuntimeAvailableFor<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
typename std::remove_cvref_t<Problem>::FormType,
@@ -450,42 +421,36 @@ export namespace mean_field::normalization {
template <PhysicalRieszStellarEquilibriumProblem Problem>
[[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) {
using ProblemType = std::remove_cvref_t<Problem>;
using Form = typename ProblemType::FormType;
using Form = typename ProblemType::FormType;
const fem::FEM &finiteElements = problem.GetDiscretization().finiteElementModel();
const fem::FEM &finiteElements =
equilibrium::detail::StellarEquilibriumProblemFactory::FiniteElementModel(problem);
if (!finiteElements.okay()) {
throw std::invalid_argument("Physical Riesz preparation requires a current finite-element model.");
}
const auto &physical = detail::PhysicalOperator(problem);
const auto &physical = detail::PhysicalOperator(problem);
const auto &gravityContext = physical.GetGravityContext();
const auto &enthalpyMap = physical.GetHydrostaticOperator().GetEnthalpyMap();
const auto scales = deriveStellarCharacteristicScales(
problem.GetNormalizationPrescription(),
problem.GetStellarModel()
);
const auto &enthalpyMap = physical.GetHydrostaticOperator().GetEnthalpyMap();
const auto scales =
deriveStellarCharacteristicScales(problem.GetNormalizationPrescription(), problem.GetStellarModel());
mfem::Array<int> stellarMarker =
utils::domain::make_attribute_marker<utils::domain::Stellar, detail::DomainSchema>(*finiteElements.mesh);
const mfem::Vector densityDiagonal = detail::GatherDiagonal(
detail::AssembleScalarMassDiagonal(*finiteElements.densityFes, &stellarMarker),
gravityContext.GetDensityMap(),
"density"
gravityContext.GetDensityMap(), "density"
);
const mfem::Vector enthalpyDiagonal = detail::GatherDiagonal(
detail::AssembleScalarMassDiagonal(*finiteElements.enthalpyFes, &stellarMarker),
enthalpyMap,
"enthalpy"
detail::AssembleScalarMassDiagonal(*finiteElements.enthalpyFes, &stellarMarker), enthalpyMap, "enthalpy"
);
const mfem::Vector gravityGradientDiagonal = detail::GatherDiagonal(
detail::AssembleHDivMassDiagonal(*finiteElements.gravityFluxFes),
gravityContext.GetGravityGradientMap(),
detail::AssembleHDivMassDiagonal(*finiteElements.gravityFluxFes), gravityContext.GetGravityGradientMap(),
"gravity-gradient"
);
const mfem::Vector gravityPotentialDiagonal = detail::GatherDiagonal(
detail::AssembleScalarMassDiagonal(*finiteElements.gravityPotentialFes),
gravityContext.GetGravityPotentialMap(),
"gravity-potential"
gravityContext.GetGravityPotentialMap(), "gravity-potential"
);
const field::ScalarBoundaryDofMap surfaceMap =
field::make_stellar_surface_scalar_dof_map<detail::DomainSchema>(*finiteElements.surfaceDeformationFes);
@@ -524,13 +489,11 @@ export namespace mean_field::normalization {
);
const mfem::Array<int> &surfaceRows = problem.GetPressureSurfaceRows().reduced_dofs();
builder.template SetHybridResidualBlock<utils::blocks::enthalpy::specific::residual>(
physicalScale<utils::blocks::enthalpy::specific::residual>(scales),
enthalpyDiagonal,
physicalScale<utils::blocks::enthalpy::specific::residual>(scales), enthalpyDiagonal,
std::span<const int>{surfaceRows.GetData(), static_cast<std::size_t>(surfaceRows.Size())}
);
detail::PrepareSpecificationNormalizations<typename ProblemType::ModelType::SpecificationTypes>::Apply(
builder,
scales
builder, scales
);
return std::move(builder).Build();
@@ -548,16 +511,11 @@ export namespace mean_field::normalization {
!std::same_as<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
Unnormalized> &&
!PhysicalRieszDiagonalPrescription<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType> &&
RuntimePreparedNormalizationOperation<Problem>)
[[nodiscard]] DiagonalNormalization prepareNormalization(
const Problem &problem
) {
return prepareStellarNormalization(
problem.GetNormalizationPrescription(),
problem
);
!PhysicalRieszDiagonalPrescription<typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType> &&
RuntimePreparedNormalizationOperation<Problem>
)
[[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) {
return prepareStellarNormalization(problem.GetNormalizationPrescription(), problem);
}
/*
@@ -571,9 +529,7 @@ export namespace mean_field::normalization {
concept NormalizableStellarEquilibriumProblem =
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
requires(const std::remove_cvref_t<Problem> &problem) {
{
prepareNormalization(problem)
} -> std::same_as<DiagonalNormalization>;
{ prepareNormalization(problem) } -> std::same_as<DiagonalNormalization>;
};
struct NormalizedStellarEquilibriumStatistics final {
@@ -591,17 +547,14 @@ export namespace mean_field::normalization {
* implied.
*/
template <typename Candidate, typename Problem>
concept ProblemBoundStellarInverseFor =
NormalizableStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
std::derived_from<std::remove_cvref_t<Candidate>, mfem::Solver> &&
requires(const std::remove_cvref_t<Candidate> &inverse) {
{
inverse.GetProblem()
} -> std::same_as<const std::remove_cvref_t<Problem> &>;
{
inverse.IsCurrent()
} -> std::same_as<bool>;
};
concept ProblemBoundStellarInverseFor = NormalizableStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
std::derived_from<std::remove_cvref_t<Candidate>, mfem::Solver> &&
requires(const std::remove_cvref_t<Candidate> &inverse) {
{
inverse.GetProblem()
} -> std::same_as<const std::remove_cvref_t<Problem> &>;
{ inverse.IsCurrent() } -> std::same_as<bool>;
};
template <NormalizableStellarEquilibriumProblem Problem, typename PhysicalInverse>
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
@@ -623,11 +576,20 @@ export namespace mean_field::normalization {
using ProblemType = std::remove_cvref_t<Problem>;
public:
using Report = typename ProblemType::Report;
using PreparationResult = typename ProblemType::PreparationResult;
explicit NormalizedStellarEquilibriumOperator(ProblemType &problem)
: mfem::Operator(problem.EquationSize(), problem.StateSize()),
: mfem::Operator(
problem.EquationSize(),
problem.StateSize()
),
m_problem(&problem),
m_normalization(prepareNormalization(problem)),
m_scaledJacobian(problem.GetLinearizationOperator(), m_normalization),
m_scaledJacobian(
problem.GetLinearizationOperator(),
m_normalization
),
m_physicalState(problem.StateSize()),
m_physicalResidual(problem.EquationSize()),
m_normalizedResidual(problem.EquationSize()) {
@@ -646,7 +608,9 @@ export namespace mean_field::normalization {
const mfem::Vector &normalizedState,
const operators::StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) requires(ProblemType::generatedRotationProviderCount == 0) {
)
requires(ProblemType::generatedRotationProviderCount == 0)
{
if (normalizedState.Size() != Width()) {
throw std::invalid_argument("The normalized stellar state has the wrong size.");
}
@@ -662,10 +626,37 @@ export namespace mean_field::normalization {
return report;
}
[[nodiscard]] PreparationResult TryPrepare(
const mfem::Vector &normalizedState,
const operators::StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
)
requires(ProblemType::generatedRotationProviderCount == 0)
{
if (normalizedState.Size() != Width()) {
throw std::invalid_argument("The normalized stellar state has the wrong size.");
}
m_isPrepared = false;
m_normalization.DenormalizeState(normalizedState, m_physicalState);
auto result = m_problem->TryPrepare(m_physicalState, dependencies, rotation);
if (!result.has_value()) {
return std::unexpected(result.error());
}
m_problem->BuildResidual(m_physicalResidual);
m_normalization.NormalizeResidual(m_physicalResidual, m_normalizedResidual);
m_physicalPreparationGeneration = m_problem->GetPreparationGeneration();
m_isPrepared = true;
++m_statistics.physicalPreparations;
return result;
}
[[nodiscard]] auto Prepare(
const mfem::Vector &normalizedState,
const operators::StellarEquilibriumDependencies &dependencies
) requires(ProblemType::generatedRotationProviderCount == 1) {
)
requires(ProblemType::generatedRotationProviderCount == 1)
{
if (normalizedState.Size() != Width()) {
throw std::invalid_argument("The normalized stellar state has the wrong size.");
}
@@ -681,6 +672,30 @@ export namespace mean_field::normalization {
return report;
}
[[nodiscard]] PreparationResult TryPrepare(
const mfem::Vector &normalizedState,
const operators::StellarEquilibriumDependencies &dependencies
)
requires(ProblemType::generatedRotationProviderCount == 1)
{
if (normalizedState.Size() != Width()) {
throw std::invalid_argument("The normalized stellar state has the wrong size.");
}
m_isPrepared = false;
m_normalization.DenormalizeState(normalizedState, m_physicalState);
auto result = m_problem->TryPrepare(m_physicalState, dependencies);
if (!result.has_value()) {
return std::unexpected(result.error());
}
m_problem->BuildResidual(m_physicalResidual);
m_normalization.NormalizeResidual(m_physicalResidual, m_normalizedResidual);
m_physicalPreparationGeneration = m_problem->GetPreparationGeneration();
m_isPrepared = true;
++m_statistics.physicalPreparations;
return result;
}
void BuildResidual(mfem::Vector &normalizedResidual) const {
VerifyPrepared();
normalizedResidual = m_normalizedResidual;
@@ -701,8 +716,8 @@ export namespace mean_field::normalization {
void RefreshNormalization() {
DiagonalNormalization refreshed = prepareNormalization(*m_problem);
m_normalization = std::move(refreshed);
m_isPrepared = false;
m_normalization = std::move(refreshed);
m_isPrepared = false;
++m_statistics.normalizationPreparations;
}
@@ -735,8 +750,12 @@ export namespace mean_field::normalization {
}
template <typename PhysicalInverse>
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
[[nodiscard]] NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>
requires ProblemBoundStellarInverseFor<
PhysicalInverse,
Problem>
[[nodiscard]] NormalizedStellarPreconditioner<
Problem,
std::remove_cvref_t<PhysicalInverse>>
MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const;
[[nodiscard]] bool IsPrepared() const noexcept {
@@ -802,16 +821,15 @@ export namespace mean_field::normalization {
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
class NormalizedStellarPreconditioner final : public mfem::Solver {
private:
using ProblemType = std::remove_cvref_t<Problem>;
using NormalizedOperator = NormalizedStellarEquilibriumOperator<ProblemType>;
using ProblemType = std::remove_cvref_t<Problem>;
using NormalizedOperator = NormalizedStellarEquilibriumOperator<ProblemType>;
using PhysicalInverseType = std::remove_cvref_t<PhysicalInverse>;
[[nodiscard]] static PhysicalInverseType &RequireAssociatedPhysicalInverse(
const NormalizedOperator &normalizedOperator,
PhysicalInverseType &physicalInverse
) {
if (std::addressof(physicalInverse.GetProblem()) !=
std::addressof(normalizedOperator.GetProblem())) {
if (std::addressof(physicalInverse.GetProblem()) != std::addressof(normalizedOperator.GetProblem())) {
throw std::invalid_argument(
"A normalized stellar preconditioner and its physical inverse must belong to the same problem."
);
@@ -832,7 +850,10 @@ export namespace mean_field::normalization {
m_normalizedOperator(&normalizedOperator),
m_physicalInverse(&physicalInverse),
m_scaled(
RequireAssociatedPhysicalInverse(normalizedOperator, physicalInverse),
RequireAssociatedPhysicalInverse(
normalizedOperator,
physicalInverse
),
normalizedOperator.GetPhysicalJacobian(),
normalizedOperator,
normalizedOperator.GetNormalization()
@@ -863,8 +884,7 @@ export namespace mean_field::normalization {
}
[[nodiscard]] bool IsCurrent() const {
return m_normalizedOperator->IsPrepared() &&
m_physicalInverse->IsCurrent();
return m_normalizedOperator->IsPrepared() && m_physicalInverse->IsCurrent();
}
[[nodiscard]] PhysicalInverseType &GetPhysicalInverse() noexcept {
@@ -904,14 +924,15 @@ export namespace mean_field::normalization {
template <NormalizableStellarEquilibriumProblem Problem>
template <typename PhysicalInverse>
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>
requires ProblemBoundStellarInverseFor<
PhysicalInverse,
Problem>
NormalizedStellarPreconditioner<
Problem,
std::remove_cvref_t<PhysicalInverse>>
NormalizedStellarEquilibriumOperator<Problem>::MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const {
VerifyPrepared();
return NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>{
*this,
physicalInverse
};
return NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>{*this, physicalInverse};
}
template <NormalizableStellarEquilibriumProblem Problem>

View File

@@ -1,8 +1,10 @@
module;
#include <compare>
#include <cstdint>
#include <expected>
#include <memory>
#include <mfem.hpp>
#include <stdexcept>
export module mean_field:operators.context.gravity_field;
export import :fem;
@@ -58,6 +60,23 @@ export namespace mean_field::operators::context::gravity_field {
}
};
enum class GravityFieldPreparationRejectionReason : std::uint8_t { invalid_mapping, non_finite_arithmetic };
struct GravityFieldPreparationRejection final {
GravityFieldPreparationRejectionReason reason{GravityFieldPreparationRejectionReason::invalid_mapping};
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
};
template <typename Report>
using GravityFieldPreparationResult = std::expected<Report, GravityFieldPreparationRejection>;
[[noreturn]] inline void throwGravityFieldPreparationRejection(const GravityFieldPreparationRejection &rejection) {
if (rejection.reason == GravityFieldPreparationRejectionReason::non_finite_arithmetic) {
throw std::domain_error("Prepared gravity-field data contained non-finite arithmetic.");
}
throw std::domain_error("Prepared gravity-field data could not map the candidate geometry.");
}
class GravityFieldGeometryContext {
public:
GravityFieldGeometryContext(
@@ -76,12 +95,24 @@ export namespace mean_field::operators::context::gravity_field {
DisplacementRevision displacement_revision
);
[[nodiscard]] GravityFieldPreparationResult<GravityFieldGeometryPreparation> TryPrepare(
const mfem::Vector &displacement,
DiscretizationRevision discretization_revision,
DisplacementRevision displacement_revision
);
GravityFieldGeometryPreparation PreparePrimal(
const mfem::Vector &displacement,
DiscretizationRevision discretization_revision,
DisplacementRevision displacement_revision
);
[[nodiscard]] GravityFieldPreparationResult<GravityFieldGeometryPreparation> TryPreparePrimal(
const mfem::Vector &displacement,
DiscretizationRevision discretization_revision,
DisplacementRevision displacement_revision
);
[[nodiscard]] const PreparedMappedHDivMassOperator &GetMassOperator() const;
[[nodiscard]] const PreparedMappedGravitySourceOperator &GetSourceOperator() const;
[[nodiscard]] const mfem::Operator &GetDivergenceOperator() const;
@@ -95,7 +126,7 @@ export namespace mean_field::operators::context::gravity_field {
private:
enum class PreparationMode : std::uint8_t { primal, linearization };
GravityFieldGeometryPreparation PrepareImpl(
[[nodiscard]] GravityFieldPreparationResult<GravityFieldGeometryPreparation> TryPrepareImpl(
const mfem::Vector &displacement,
DiscretizationRevision discretization_revision,
DisplacementRevision displacement_revision,
@@ -147,6 +178,11 @@ export namespace mean_field::operators::context::gravity_field {
const GravityFieldRevisions &revisions
);
[[nodiscard]] GravityFieldPreparationResult<GravityFieldPreparationReport> TryPrepare(
const GravityFieldStateView &state,
const GravityFieldRevisions &revisions
);
[[nodiscard]] const GravityFieldGeometryContext &GetGeometryContext() const;
[[nodiscard]] const mfem::Vector &GetDensityTrue() const;
[[nodiscard]] const mfem::Vector &GetGravityGradientTrue() const;

View File

@@ -24,6 +24,13 @@ export namespace mean_field::operators {
const context::gravity_field::GravityFieldRevisions &revisions
);
[[nodiscard]] context::gravity_field::GravityFieldPreparationResult<
context::gravity_field::GravityFieldPreparationReport>
TryPrepare(
const mfem::Vector &state,
const context::gravity_field::GravityFieldRevisions &revisions
);
void Mult(
const mfem::Vector &state,
mfem::Vector &residual

View File

@@ -1,5 +1,8 @@
module;
#include <cstdint>
#include <expected>
#include <mfem.hpp>
export module mean_field:operators.kernels.gravity_displacement_force;
@@ -8,6 +11,24 @@ export import :fem;
export import :mapping.domain_mapper;
export namespace mean_field::operators::kernels {
enum class GravityDisplacementForceRejectionReason : std::uint8_t { invalid_mapping, non_finite_arithmetic };
struct GravityDisplacementForceRejection final {
GravityDisplacementForceRejectionReason reason{GravityDisplacementForceRejectionReason::invalid_mapping};
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
};
using GravityDisplacementForceResult = std::expected<void, GravityDisplacementForceRejection>;
[[nodiscard]] 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
);
void apply_gravity_displacement_force_residual(
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,

View File

@@ -1,5 +1,8 @@
module;
#include <cstdint>
#include <expected>
#include <mfem.hpp>
export module mean_field:operators.kernels.rotational_displacement_force;
@@ -9,6 +12,24 @@ export import :mapping.domain_mapper;
export import :physics.rigid_rotation;
export namespace mean_field::operators::kernels {
enum class RotationalDisplacementForceRejectionReason : std::uint8_t { invalid_mapping, non_finite_arithmetic };
struct RotationalDisplacementForceRejection final {
RotationalDisplacementForceRejectionReason reason{RotationalDisplacementForceRejectionReason::invalid_mapping};
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
};
using RotationalDisplacementForceResult = std::expected<void, RotationalDisplacementForceRejection>;
[[nodiscard]] 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
);
/*
* Rotational contribution to the displacement row:
*

View File

@@ -2,6 +2,10 @@ module;
#include <compare>
#include <cstdint>
#include <expected>
#include <limits>
#include <optional>
#include <stdexcept>
#include <vector>
#include <mfem.hpp>
@@ -45,6 +49,52 @@ export namespace mean_field::operators {
constexpr auto operator<=>(const PreparedAngularMomentumReport &) const = default;
};
enum class AngularMomentumPreparationRejectionReason : std::uint8_t {
inverted_geometry,
non_finite_geometry,
non_finite_angular_velocity,
non_finite_density,
negative_moment_of_inertia,
non_finite_moment_of_inertia,
non_finite_residual
};
/*
* A trial state can fail to define the angular-momentum invariant without
* violating the operator's structural contract. Keep that distinction in
* a fixed-size value so a line search can reject the candidate without
* constructing or transporting an exception.
*/
struct AngularMomentumPreparationRejection final {
AngularMomentumPreparationRejectionReason reason{AngularMomentumPreparationRejectionReason::inverted_geometry};
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
double momentOfInertia{std::numeric_limits<double>::quiet_NaN()};
};
using AngularMomentumPreparationResult =
std::expected<PreparedAngularMomentumReport, AngularMomentumPreparationRejection>;
[[noreturn]] inline void
throwAngularMomentumPreparationRejection(const AngularMomentumPreparationRejection &rejection) {
switch (rejection.reason) {
case AngularMomentumPreparationRejectionReason::inverted_geometry:
throw std::domain_error("The angular-momentum trial inverts mapped geometry.");
case AngularMomentumPreparationRejectionReason::non_finite_geometry:
throw std::domain_error("The angular-momentum trial produced non-finite mapped geometry.");
case AngularMomentumPreparationRejectionReason::non_finite_angular_velocity:
throw std::domain_error("The angular-momentum trial has a non-finite angular velocity.");
case AngularMomentumPreparationRejectionReason::non_finite_density:
throw std::domain_error("The angular-momentum trial produced a non-finite interpolated density.");
case AngularMomentumPreparationRejectionReason::negative_moment_of_inertia:
throw std::domain_error("The angular-momentum trial produced a negative moment of inertia.");
case AngularMomentumPreparationRejectionReason::non_finite_moment_of_inertia:
throw std::domain_error("The angular-momentum trial produced a non-finite moment of inertia.");
case AngularMomentumPreparationRejectionReason::non_finite_residual:
throw std::domain_error("The angular-momentum trial produced a non-finite residual.");
}
throw std::logic_error("An unknown angular-momentum trial rejection was reported.");
}
struct AngularMomentumConstraintReport final {
double targetAngularMomentum;
double achievedAngularMomentum;
@@ -97,6 +147,11 @@ export namespace mean_field::operators {
const AngularMomentumDependencies &dependencies
);
[[nodiscard]] AngularMomentumPreparationResult TryPrepare(
double angularVelocity,
const AngularMomentumDependencies &dependencies
);
void BuildResidual(mfem::Vector &residual) const;
void ApplyDensityJacobianAction(
@@ -156,9 +211,9 @@ export namespace mean_field::operators {
};
void BuildStaticPlan();
void RefreshGeometry(const mfem::Vector &displacement);
void RefreshDensity(const mfem::Vector &density);
void AssembleResidual();
[[nodiscard]] std::optional<mapping::MappingStatus> RefreshGeometry(const mfem::Vector &displacement);
[[nodiscard]] bool RefreshDensity(const mfem::Vector &density);
[[nodiscard]] std::optional<AngularMomentumPreparationRejection> TryAssembleResidual();
void VerifyPrepared() const;
[[nodiscard]] double EvaluateDensityMomentActionLocal(const mfem::Vector &densityVariation) const;

View File

@@ -1,6 +1,7 @@
module;
#include <cstdint>
#include <expected>
#include <mfem.hpp>
#include <vector>
@@ -22,6 +23,29 @@ export namespace mean_field::operators {
}
};
enum class BarotropicClosurePreparationRejectionReason : std::uint8_t {
mapping_failure,
invalid_quadrature_data,
equation_of_state
};
/*
* A rejected candidate is part of the nonlinear-solver control flow, not
* an exceptional API failure. Keep the payload fixed-size so it can be
* selected deterministically across ranks without allocating. Only the
* detail associated with `reason` is meaningful.
*/
struct BarotropicClosurePreparationRejection final {
BarotropicClosurePreparationRejectionReason reason{
BarotropicClosurePreparationRejectionReason::mapping_failure
};
mapping::MappingStatus mappingStatus{mapping::MappingStatus::non_finite_result};
eos::EvaluationErrorCode equationOfStateError{eos::EvaluationErrorCode::nonfinite_result};
};
using BarotropicClosurePreparationResult =
std::expected<PreparedBarotropicClosureReport, BarotropicClosurePreparationRejection>;
class PreparedBarotropicClosureOperator final : public mfem::Operator {
public:
PreparedBarotropicClosureOperator(
@@ -40,6 +64,11 @@ export namespace mean_field::operators {
const context::barotropic::BarotropicClosureDependencies &dependencies
);
[[nodiscard]] BarotropicClosurePreparationResult TryPrepare(
const context::barotropic::BarotropicClosureStateView &state,
const context::barotropic::BarotropicClosureDependencies &dependencies
);
void Mult(
const mfem::Vector &densityVariation,
const mfem::Vector &enthalpyVariation,

View File

@@ -2,6 +2,8 @@ module;
#include <compare>
#include <cstdint>
#include <expected>
#include <optional>
#include <mfem.hpp>
@@ -18,6 +20,28 @@ export import :physics.rigid_rotation;
export import :utils.blocks;
export namespace mean_field::operators {
enum class DisplacementResidualPreparationRejectionSource : std::uint8_t {
pressure,
gravity,
rotation,
composition
};
enum class DisplacementResidualPreparationRejectionReason : std::uint8_t {
equation_of_state,
invalid_mapping,
non_finite_arithmetic
};
struct DisplacementResidualPreparationRejection final {
DisplacementResidualPreparationRejectionSource source{DisplacementResidualPreparationRejectionSource::pressure};
DisplacementResidualPreparationRejectionReason reason{
DisplacementResidualPreparationRejectionReason::equation_of_state
};
eos::EvaluationErrorCode equationOfStateCode{eos::EvaluationErrorCode::nonfinite_result};
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
};
struct DisplacementResidualDependencyStamp final {
std::uint64_t identity{0};
std::uint64_t revision{0};
@@ -104,6 +128,15 @@ export namespace mean_field::operators {
const physics::RigidRotation &rotation
);
[[nodiscard]] std::expected<
PreparedDisplacementResidualReport,
DisplacementResidualPreparationRejection>
TryPrepare(
const DisplacementResidualStateView &state,
const DisplacementResidualDependencies &dependencies,
const physics::RigidRotation &rotation
);
void BuildResidual(mfem::Vector &residual) const;
void ApplyDensityJacobianAction(
@@ -154,7 +187,7 @@ export namespace mean_field::operators {
GetGravityContext() const noexcept;
private:
void AssembleResidual();
[[nodiscard]] std::optional<DisplacementResidualPreparationRejection> AssembleResidual();
void VerifyPrepared() const;
const fem::FEM &m_fem;

View File

@@ -2,6 +2,7 @@ module;
#include <compare>
#include <cstdint>
#include <expected>
#include <vector>
#include <mfem.hpp>
@@ -11,6 +12,7 @@ export module mean_field:operators.prepared_gravity_displacement_force;
export import :fem;
export import :mapping.domain_mapper;
export import :operators.context.gravity_field;
export import :operators.kernels.gravity_displacement_force;
export import :utils.blocks;
export namespace mean_field::operators {
@@ -57,6 +59,11 @@ export namespace mean_field::operators {
*/
PreparedGravityDisplacementForceReport Prepare();
[[nodiscard]] std::expected<
PreparedGravityDisplacementForceReport,
kernels::GravityDisplacementForceRejection>
TryPrepare();
void BuildResidual(mfem::Vector &residual) const;
void ApplyDensityJacobianAction(
@@ -106,7 +113,10 @@ export namespace mean_field::operators {
private:
void VerifyPrepared() const;
void PrepareElementData();
[[nodiscard]] std::expected<
void,
kernels::GravityDisplacementForceRejection>
TryPrepareElementData();
void ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,

View File

@@ -1,7 +1,9 @@
module;
#include <cstdint>
#include <expected>
#include <memory>
#include <mfem.hpp>
#include <stdexcept>
#include <vector>
export module mean_field:operators.prepared_gravity_source;
@@ -10,6 +12,23 @@ export import :field.mfem;
export import :mapping.domain_mapper;
export namespace mean_field::operators {
enum class GravitySourcePreparationRejectionReason : std::uint8_t { invalid_mapping, non_finite_arithmetic };
struct GravitySourcePreparationRejection final {
GravitySourcePreparationRejectionReason reason{GravitySourcePreparationRejectionReason::invalid_mapping};
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
};
using GravitySourcePreparationResult = std::expected<void, GravitySourcePreparationRejection>;
[[noreturn]] inline void
throwGravitySourcePreparationRejection(const GravitySourcePreparationRejection &rejection) {
if (rejection.reason == GravitySourcePreparationRejectionReason::non_finite_arithmetic) {
throw std::domain_error("Prepared gravity-source data contained non-finite arithmetic.");
}
throw std::domain_error("Prepared gravity-source data could not map the candidate geometry.");
}
class PreparedMappedGravitySourceOperator final : public mfem::Operator {
public:
PreparedMappedGravitySourceOperator(
@@ -19,6 +38,8 @@ export namespace mean_field::operators {
void Prepare(const mfem::Vector &displacement);
void PreparePrimal(const mfem::Vector &displacement);
[[nodiscard]] GravitySourcePreparationResult TryPrepare(const mfem::Vector &displacement);
[[nodiscard]] GravitySourcePreparationResult TryPreparePrimal(const mfem::Vector &displacement);
void Mult(
const mfem::Vector &density,
mfem::Vector &action
@@ -68,7 +89,7 @@ export namespace mean_field::operators {
mfem::Vector quadrature_data;
};
void PrepareImpl(
[[nodiscard]] GravitySourcePreparationResult TryPrepareImpl(
const mfem::Vector &displacement,
PreparationMode mode
);

View File

@@ -1,7 +1,9 @@
module;
#include <cstdint>
#include <expected>
#include <memory>
#include <mfem.hpp>
#include <stdexcept>
#include <vector>
export module mean_field:operators.prepared_hdiv_mass;
@@ -10,6 +12,22 @@ export import :field.mfem;
export import :mapping.domain_mapper;
export namespace mean_field::operators {
enum class HDivMassPreparationRejectionReason : std::uint8_t { invalid_mapping, non_finite_arithmetic };
struct HDivMassPreparationRejection final {
HDivMassPreparationRejectionReason reason{HDivMassPreparationRejectionReason::invalid_mapping};
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
};
using HDivMassPreparationResult = std::expected<void, HDivMassPreparationRejection>;
[[noreturn]] inline void throwHDivMassPreparationRejection(const HDivMassPreparationRejection &rejection) {
if (rejection.reason == HDivMassPreparationRejectionReason::non_finite_arithmetic) {
throw std::domain_error("Prepared H(div) mass data contained non-finite arithmetic.");
}
throw std::domain_error("Prepared H(div) mass data could not map the candidate geometry.");
}
class PreparedMappedHDivMassOperator final : public mfem::Operator {
public:
PreparedMappedHDivMassOperator(
@@ -19,6 +37,8 @@ export namespace mean_field::operators {
void Prepare(const mfem::Vector &displacement);
void PreparePrimal(const mfem::Vector &displacement);
[[nodiscard]] HDivMassPreparationResult TryPrepare(const mfem::Vector &displacement);
[[nodiscard]] HDivMassPreparationResult TryPreparePrimal(const mfem::Vector &displacement);
void Mult(
const mfem::Vector &gravity_gradient,
mfem::Vector &action
@@ -54,8 +74,8 @@ export namespace mean_field::operators {
mfem::DenseMatrix frozenMappingData;
};
void PrepareVariationData();
void PrepareImpl(
[[nodiscard]] mapping::MappingStatus PrepareVariationData();
[[nodiscard]] HDivMassPreparationResult TryPrepareImpl(
const mfem::Vector &displacement,
PreparationMode mode
);

View File

@@ -3,7 +3,9 @@ module;
#include <compare>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <optional>
#include <stdexcept>
#include <vector>
#include <mfem.hpp>
@@ -30,6 +32,35 @@ export namespace mean_field::operators {
}
};
enum class HydrostaticEquilibriumPreparationRejectionReason : std::uint8_t {
inverted_geometry,
non_finite_geometry,
non_finite_residual
};
struct HydrostaticEquilibriumPreparationRejection final {
HydrostaticEquilibriumPreparationRejectionReason reason{
HydrostaticEquilibriumPreparationRejectionReason::inverted_geometry
};
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
};
using HydrostaticEquilibriumPreparationResult =
std::expected<PreparedHydrostaticEquilibriumReport, HydrostaticEquilibriumPreparationRejection>;
[[noreturn]] inline void
throwHydrostaticEquilibriumPreparationRejection(const HydrostaticEquilibriumPreparationRejection &rejection) {
switch (rejection.reason) {
case HydrostaticEquilibriumPreparationRejectionReason::non_finite_geometry:
throw std::domain_error("Prepared hydrostatic equilibrium encountered non-finite mapped geometry.");
case HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual:
throw std::domain_error("Prepared hydrostatic equilibrium produced a non-finite residual.");
case HydrostaticEquilibriumPreparationRejectionReason::inverted_geometry:
default:
throw std::domain_error("Prepared hydrostatic equilibrium encountered inverted mapped geometry.");
}
}
struct PreparedHydrostaticAlgebraicJacobianStatistics {
std::uint64_t preparations{0};
std::uint64_t enthalpyApplications{0};
@@ -102,6 +133,12 @@ export namespace mean_field::operators {
const physics::RigidRotation &rotation
);
[[nodiscard]] HydrostaticEquilibriumPreparationResult TryPrepare(
const context::hydrostatic::HydrostaticEquilibriumStateView &state,
const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
);
void BuildResidual(mfem::Vector &residual) const;
void ApplyEnthalpyJacobianAction(
@@ -221,12 +258,12 @@ export namespace mean_field::operators {
};
void PrepareStaticPlan();
void PrepareGeometry();
void PrepareAlgebraicJacobianBlocks();
void PrepareRotation();
void PrepareBaseState();
[[nodiscard]] std::optional<mapping::MappingStatus> PrepareGeometry();
[[nodiscard]] bool PrepareAlgebraicJacobianBlocks();
[[nodiscard]] bool PrepareRotation();
[[nodiscard]] bool PrepareBaseState();
void FinalizeDisplacementJacobianPreparation();
void AssembleCachedResidual();
[[nodiscard]] bool AssembleCachedResidual();
void VerifyPrepared() const;
const fem::FEM &m_fem;

View File

@@ -2,7 +2,9 @@ module;
#include <compare>
#include <cstdint>
#include <expected>
#include <mfem.hpp>
#include <optional>
#include <vector>
export module mean_field:operators.prepared_mass_normalization;
@@ -59,6 +61,28 @@ export namespace mean_field::operators {
constexpr auto operator<=>(const PreparedMassNormalizationReport &) const = default;
};
enum class MassNormalizationPreparationRejectionReason : std::uint8_t {
mapping_failure,
non_finite_density_interpolation,
non_finite_assembled_mass
};
/*
* Candidate rejection is deliberately represented without text or owned
* storage. That makes the result cheap to propagate through a line search
* and gives the MPI implementation a deterministic, allocation-free value
* to select on every rank.
*/
struct MassNormalizationPreparationRejection final {
MassNormalizationPreparationRejectionReason reason{
MassNormalizationPreparationRejectionReason::mapping_failure
};
mapping::MappingStatus mappingStatus{mapping::MappingStatus::non_finite_result};
};
using MassNormalizationPreparationResult =
std::expected<PreparedMassNormalizationReport, MassNormalizationPreparationRejection>;
struct PreparedMassNormalizationActionStatistics final {
std::uint64_t densityApplications{0};
std::uint64_t displacementApplications{0};
@@ -109,6 +133,16 @@ export namespace mean_field::operators {
const MassNormalizationDependencies &dependencies
);
[[nodiscard]] MassNormalizationPreparationResult TryPrepare(
const MassNormalizationStateView &state,
const MassNormalizationDependencies &dependencies
);
[[nodiscard]] MassNormalizationPreparationResult TryPrepare(
const models::CompiledFixedMass &constraint,
const MassNormalizationDependencies &dependencies
);
void BuildResidual(mfem::Vector &residual) const;
void ApplyDensityJacobianAction(
@@ -179,9 +213,11 @@ export namespace mean_field::operators {
};
void BuildStaticPlan();
void RefreshGeometry(const mfem::Vector &displacement);
void RefreshDensity(const mfem::Vector &density);
void AssembleResidual();
[[nodiscard]] std::optional<MassNormalizationPreparationRejection>
RefreshGeometry(const mfem::Vector &displacement);
[[nodiscard]] std::optional<MassNormalizationPreparationRejection> RefreshDensity(const mfem::Vector &density);
[[nodiscard]] std::optional<MassNormalizationPreparationRejection> AssembleResidual();
[[nodiscard]] std::optional<MassNormalizationPreparationRejection> UpdateResidualForTargetMass();
void VerifyPrepared() const;
[[nodiscard]] double EvaluateDensityActionLocal(const mfem::Vector &densityVariation) const;

View File

@@ -3,6 +3,7 @@ module;
#include <compare>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <optional>
#include <vector>
@@ -18,6 +19,18 @@ export import :operators.context.pressure_force;
export import :utils.blocks;
export namespace mean_field::operators {
enum class PressureForcePreparationRejectionReason : std::uint8_t {
equation_of_state,
invalid_mapping,
non_finite_arithmetic
};
struct PressureForcePreparationRejection final {
PressureForcePreparationRejectionReason reason{PressureForcePreparationRejectionReason::equation_of_state};
eos::EvaluationErrorCode equationOfStateCode{eos::EvaluationErrorCode::nonfinite_result};
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
};
struct PreparedPressureForceReport final {
context::pressure_force::PressureForcePreparationReport contextReport;
@@ -92,6 +105,14 @@ export namespace mean_field::operators {
const context::pressure_force::PressureForceDependencies &dependencies
);
[[nodiscard]] std::expected<
PreparedPressureForceReport,
PressureForcePreparationRejection>
TryPrepare(
const context::pressure_force::PressureForceStateView &state,
const context::pressure_force::PressureForceDependencies &dependencies
);
void BuildResidual(mfem::Vector &residual) const;
void ApplyEnthalpyJacobianAction(
@@ -206,12 +227,12 @@ export namespace mean_field::operators {
};
void PrepareStaticPlan();
void PrepareGeometry();
void PrepareMaterialState();
[[nodiscard]] std::optional<PressureForcePreparationRejection> PrepareGeometry();
[[nodiscard]] std::optional<PressureForcePreparationRejection> PrepareMaterialState();
void FinalizeDisplacementJacobianPreparation();
void AssembleCachedResidual();
[[nodiscard]] std::optional<PressureForcePreparationRejection> AssembleCachedResidual();
void VerifyPrepared() const;
@@ -301,4 +322,4 @@ export namespace mean_field::operators {
const PreparedPressureForceOperator &m_preparedOperator;
};
} // namespace mean_field::operators
} // namespace mean_field::operators

View File

@@ -2,6 +2,7 @@ module;
#include <compare>
#include <cstdint>
#include <expected>
#include <optional>
#include <vector>
@@ -12,6 +13,7 @@ export module mean_field:operators.prepared_rotational_displacement_force;
export import :fem;
export import :mapping.domain_mapper;
export import :operators.context.rotational_displacement_force;
export import :operators.kernels.rotational_displacement_force;
export import :physics.rigid_rotation;
export import :utils.blocks;
@@ -61,6 +63,15 @@ export namespace mean_field::operators {
const physics::RigidRotation &rotation
);
[[nodiscard]] std::expected<
PreparedRotationalDisplacementForceReport,
kernels::RotationalDisplacementForceRejection>
TryPrepare(
const context::rotational_displacement_force::RotationalDisplacementForceStateView &state,
const context::rotational_displacement_force::RotationalDisplacementForceDependencies &dependencies,
const physics::RigidRotation &rotation
);
void BuildResidual(mfem::Vector &residual) const;
void ApplyDensityJacobianAction(
@@ -104,7 +115,10 @@ export namespace mean_field::operators {
private:
void VerifyPrepared() const;
void PrepareElementData();
[[nodiscard]] std::expected<
void,
kernels::RotationalDisplacementForceRejection>
TryPrepareElementData();
void ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,

View File

@@ -3,10 +3,14 @@ module;
#include <compare>
#include <concepts>
#include <cstdint>
#include <expected>
#include <limits>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
#include <mpi.h>
export module mean_field:operators.prepared_stellar_equilibrium;
@@ -72,6 +76,70 @@ export namespace mean_field::operators {
}
};
enum class StellarEquilibriumPreparationRejectionReason : std::uint8_t {
inverted_geometry,
non_finite_geometry,
thermodynamic_domain,
non_finite_thermodynamics,
inadmissible_physics,
non_finite_physics
};
enum class StellarEquilibriumPreparationStage : std::uint8_t {
unspecified,
generated_geometry,
gravity,
barotropic_closure,
hydrostatic_equilibrium,
displacement_residual,
pressure_force,
gravity_displacement_force,
rotational_displacement_force,
displacement_composition,
mass_normalization,
model_specification
};
/*
* A candidate state that cannot define a physical mapped domain is an
* expected line-search outcome, not an exceptional program failure. Keep
* this payload fixed-size so every trial can report it without allocating.
*/
struct StellarEquilibriumPreparationRejection final {
StellarEquilibriumPreparationRejectionReason reason{
StellarEquilibriumPreparationRejectionReason::inverted_geometry
};
StellarEquilibriumPreparationStage stage{StellarEquilibriumPreparationStage::unspecified};
double minimumJacobianDeterminant{std::numeric_limits<double>::quiet_NaN()};
eos::EvaluationErrorCode thermodynamicErrorCode{eos::EvaluationErrorCode::nonfinite_result};
};
template <typename Report>
using StellarEquilibriumPreparationResult = std::expected<Report, StellarEquilibriumPreparationRejection>;
[[noreturn]] inline void
throwStellarEquilibriumPreparationRejection(const StellarEquilibriumPreparationRejection &rejection) {
switch (rejection.reason) {
case StellarEquilibriumPreparationRejectionReason::non_finite_geometry:
throw std::domain_error("The prepared domain deformation has non-finite mapped geometry.");
case StellarEquilibriumPreparationRejectionReason::thermodynamic_domain:
throw eos::EvaluationError(
rejection.thermodynamicErrorCode, "The stellar state lies outside the equation-of-state domain."
);
case StellarEquilibriumPreparationRejectionReason::non_finite_thermodynamics:
throw eos::EvaluationError(
rejection.thermodynamicErrorCode, "The stellar state produced non-finite thermodynamic data."
);
case StellarEquilibriumPreparationRejectionReason::inadmissible_physics:
throw std::domain_error("The stellar state is physically inadmissible.");
case StellarEquilibriumPreparationRejectionReason::non_finite_physics:
throw std::domain_error("The stellar state produced non-finite physical data.");
case StellarEquilibriumPreparationRejectionReason::inverted_geometry:
throw std::domain_error("The prepared domain deformation inverts at least one volume element.");
}
throw std::logic_error("Unknown stellar-equilibrium candidate-rejection reason.");
}
struct PreparedStellarEquilibriumStatistics final {
std::uint64_t residualAssemblies{0};
std::uint64_t residualApplications{0};
@@ -159,6 +227,12 @@ export namespace mean_field::operators {
const physics::RigidRotation &rotation
);
[[nodiscard]] StellarEquilibriumPreparationResult<PreparedStellarEquilibriumReport> TryPrepare(
const mfem::Vector &state,
const StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
);
void BuildResidual(mfem::Vector &residual) const;
void Mult(
@@ -188,12 +262,9 @@ export namespace mean_field::operators {
[[nodiscard]] const PreparedHydrostaticEquilibriumOperator &GetHydrostaticOperator() const noexcept;
[[nodiscard]] const PreparedDisplacementResidualOperator &GetDisplacementOperator() const noexcept;
[[nodiscard]] const PreparedMassNormalizationOperator &GetMassNormalizationOperator() const noexcept;
[[nodiscard]] double ApplyDensityVolumeIntegralDensityAction(
const mfem::Vector &densityDirection
) const;
[[nodiscard]] double ApplyDensityVolumeIntegralSurfaceShapeAction(
const mfem::Vector &surfaceShapeDirection
) const;
[[nodiscard]] double ApplyDensityVolumeIntegralDensityAction(const mfem::Vector &densityDirection) const;
[[nodiscard]] double
ApplyDensityVolumeIntegralSurfaceShapeAction(const mfem::Vector &surfaceShapeDirection) const;
[[nodiscard]] const PreparedPressureSurfaceConstraint &GetSurfaceConstraintOperator() const noexcept;
[[nodiscard]] const deformation::PreparedDomainDeformationRuntime &GetDomainDeformation() const noexcept;
[[nodiscard]] const mfem::Vector &GetSurfaceDeformationParameters() const;
@@ -222,6 +293,7 @@ export namespace mean_field::operators {
void VerifyPrepared() const;
StellarEquilibriumRootManifest m_rootManifest;
MPI_Comm m_communicator{MPI_COMM_NULL};
mfem::Array<int> m_gravityStateOffsets;
context::gravity_field::GravityFieldLinearizationContext m_gravityContext;

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ module;
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <memory>
#include <type_traits>
#include <utility>
@@ -28,12 +29,11 @@ export namespace mean_field::equilibrium {
static constexpr bool complete = false;
};
template <model::StellarModelType Model>
struct StellarSurfaceCompilationAudit<Model, true> {
template <model::StellarModelType Model> struct StellarSurfaceCompilationAudit<Model, true> {
private:
using ModelType = std::remove_cvref_t<Model>;
using EquationOfState = typename ModelType::EquationOfStateType;
using Form = operators::CompiledStellarEquilibriumForm<ModelType>;
using ModelType = std::remove_cvref_t<Model>;
using EquationOfState = typename ModelType::EquationOfStateType;
using Form = operators::CompiledStellarEquilibriumForm<ModelType>;
using AvailableEquations = material::StellarEquilibriumThermodynamicEquations;
static constexpr bool thermodynamicsCompilable =
@@ -46,14 +46,12 @@ export namespace mean_field::equilibrium {
} else {
using ThermodynamicEquations =
material::CompiledThermodynamicEquationsT<EquationOfState, Form, AvailableEquations>;
using Formulation = typename ThermodynamicEquations::PressureSurfaceFormulation;
using CompiledSurface =
surface::CompiledPressureSurfaceConstraintT<Formulation, EquationOfState>;
using Formulation = typename ThermodynamicEquations::PressureSurfaceFormulation;
using CompiledSurface = surface::CompiledPressureSurfaceConstraintT<Formulation, EquationOfState>;
return requires(const ModelType &model) {
{
surface::compilePressureSurfaceConstraint<Formulation>(
model.surfaceCondition(),
model.equationOfState()
model.surfaceCondition(), model.equationOfState()
)
} -> std::same_as<CompiledSurface>;
};
@@ -77,8 +75,7 @@ export namespace mean_field::equilibrium {
requires operators::StellarEquilibriumSystemCompilable<std::remove_cvref_t<Candidate>>;
requires hasStellarEquilibriumSurfaceCompilation<std::remove_cvref_t<Candidate>>;
requires operators::CompilableRootManifestFor<
std::remove_cvref_t<Candidate>,
operators::CompiledStellarEquilibriumForm<std::remove_cvref_t<Candidate>>>;
std::remove_cvref_t<Candidate>, operators::CompiledStellarEquilibriumForm<std::remove_cvref_t<Candidate>>>;
requires operators::hasStellarEquilibriumCoreRuntime<std::remove_cvref_t<Candidate>>;
requires operators::hasCompleteStellarEquilibriumRuntime<std::remove_cvref_t<Candidate>>;
requires operators::stellarEquilibriumRotationProviderCount<std::remove_cvref_t<Candidate>> <= 1;
@@ -105,19 +102,16 @@ export namespace mean_field::equilibrium {
operators::StellarEquilibriumPhysicalCoreType<std::remove_cvref_t<Model>>,
typename std::remove_cvref_t<Model>::SpecificationTypes>> { };
struct StellarEquilibriumProblemFactory;
} // namespace detail
template <
StellarEquilibriumModel Model,
StellarDiscretizationType Discretization = StellarDiscretization>
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization = StellarDiscretization>
requires detail::StellarEquilibriumModelDiscretizationStructureAudit<
std::remove_cvref_t<Model>,
std::remove_cvref_t<Discretization>>::value
class StellarEquilibriumProblem final {
public:
using ModelType = std::remove_cvref_t<Model>;
using DiscretizationType = std::remove_cvref_t<Discretization>;
using ModelType = std::remove_cvref_t<Model>;
using DiscretizationType = std::remove_cvref_t<Discretization>;
using NormalizationPrescriptionType = typename DiscretizationType::NormalizationPrescriptionType;
static constexpr bool hasFixedCentralDensity =
@@ -128,13 +122,15 @@ export namespace mean_field::equilibrium {
operators::stellarEquilibriumRotationProviderCount<ModelType>;
static constexpr bool symbolicallySquare = ModelType::symbolicallySquare;
using PreparedOperatorType = operators::PreparedVariadicStellarEquilibriumOperator<ModelType>;
using PhysicalCoreType = typename PreparedOperatorType::PhysicalCoreType;
using FormType = operators::CompiledStellarEquilibriumForm<ModelType>;
using JacobianFormType = operators::CompiledStellarEquilibriumJacobianForm<ModelType>;
using ManifestType = operators::EquilibriumSystemManifest<ModelType, FormType, JacobianFormType>;
using EquationOfStateType = model::EquationOfStateType<ModelType>;
using SurfaceConditionType = model::SurfaceConditionType<ModelType>;
using PreparedOperatorType = operators::PreparedVariadicStellarEquilibriumOperator<ModelType>;
using Report = typename PreparedOperatorType::Report;
using PreparationResult = typename PreparedOperatorType::PreparationResult;
using PhysicalCoreType = typename PreparedOperatorType::PhysicalCoreType;
using FormType = operators::CompiledStellarEquilibriumForm<ModelType>;
using JacobianFormType = operators::CompiledStellarEquilibriumJacobianForm<ModelType>;
using ManifestType = operators::EquilibriumSystemManifest<ModelType, FormType, JacobianFormType>;
using EquationOfStateType = model::EquationOfStateType<ModelType>;
using SurfaceConditionType = model::SurfaceConditionType<ModelType>;
using AvailableThermodynamicEquations = material::StellarEquilibriumThermodynamicEquations;
using ThermodynamicEquationsType =
material::CompiledThermodynamicEquationsT<EquationOfStateType, FormType, AvailableThermodynamicEquations>;
@@ -147,17 +143,18 @@ export namespace mean_field::equilibrium {
StellarEquilibriumProblem(
ModelType stellarModel,
DiscretizationType discretization
DiscretizationType discretization,
fem::FEM &finiteElementModel
)
: m_stellarModel(std::make_shared<ModelType>(std::move(stellarModel))),
m_discretization(std::move(discretization)),
m_compiledSurfaceConstraint(CompileSurfaceConstraint(*m_stellarModel)),
m_preparedOperator(
m_discretization.finiteElementModel(),
finiteElementModel,
m_discretization.domainMapper(),
m_stellarModel,
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
CompileDefaultDomainDeformation(m_discretization.finiteElementModel())
CompileDefaultDomainDeformation(finiteElementModel)
) {
VerifyProblem();
}
@@ -176,6 +173,12 @@ export namespace mean_field::equilibrium {
return m_discretization;
}
[[nodiscard]] MPI_Comm GetCommunicator() const & {
return m_discretization.communicator();
}
[[nodiscard]] MPI_Comm GetCommunicator() const && = delete;
[[nodiscard]] const NormalizationPrescriptionType &GetNormalizationPrescription() const noexcept {
return m_discretization.normalizationPrescription();
}
@@ -236,21 +239,54 @@ export namespace mean_field::equilibrium {
const mfem::Vector &state,
const operators::StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) requires(generatedRotationProviderCount == 0) {
)
requires(generatedRotationProviderCount == 0)
{
auto report = m_preparedOperator.Prepare(state, dependencies, rotation);
++m_preparationGeneration;
return report;
}
[[nodiscard]] PreparationResult TryPrepare(
const mfem::Vector &state,
const operators::StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
)
requires(generatedRotationProviderCount == 0)
{
auto result = m_preparedOperator.TryPrepare(state, dependencies, rotation);
if (!result.has_value()) {
return std::unexpected(result.error());
}
++m_preparationGeneration;
return result;
}
[[nodiscard]] auto Prepare(
const mfem::Vector &state,
const operators::StellarEquilibriumDependencies &dependencies
) requires(generatedRotationProviderCount == 1) {
)
requires(generatedRotationProviderCount == 1)
{
auto report = m_preparedOperator.Prepare(state, dependencies);
++m_preparationGeneration;
return report;
}
[[nodiscard]] PreparationResult TryPrepare(
const mfem::Vector &state,
const operators::StellarEquilibriumDependencies &dependencies
)
requires(generatedRotationProviderCount == 1)
{
auto result = m_preparedOperator.TryPrepare(state, dependencies);
if (!result.has_value()) {
return std::unexpected(result.error());
}
++m_preparationGeneration;
return result;
}
void BuildResidual(mfem::Vector &residual) const {
m_preparedOperator.BuildResidual(residual);
}
@@ -266,8 +302,7 @@ export namespace mean_field::equilibrium {
[[nodiscard]] static CompiledSurfaceConstraintType CompileSurfaceConstraint(const ModelType &stellarModel) {
return surface::compilePressureSurfaceConstraint<
typename ThermodynamicEquationsType::PressureSurfaceFormulation>(
stellarModel.surfaceCondition(),
stellarModel.equationOfState()
stellarModel.surfaceCondition(), stellarModel.equationOfState()
);
}
@@ -318,28 +353,25 @@ export namespace mean_field::equilibrium {
template <
typename Model,
typename Discretization,
bool StructurallyCompatible =
StellarEquilibriumModelDiscretizationStructureAudit<
std::remove_cvref_t<Model>,
std::remove_cvref_t<Discretization>>::value>
bool StructurallyCompatible = StellarEquilibriumModelDiscretizationStructureAudit<
std::remove_cvref_t<Model>,
std::remove_cvref_t<Discretization>>::value>
struct StellarEquilibriumModelDiscretizationOperationAudit : std::false_type { };
template <typename Model, typename Discretization>
struct StellarEquilibriumModelDiscretizationOperationAudit<
Model,
Discretization,
true> {
struct StellarEquilibriumModelDiscretizationOperationAudit<Model, Discretization, true> {
private:
using ModelType = std::remove_cvref_t<Model>;
using ModelType = std::remove_cvref_t<Model>;
using DiscretizationType = std::remove_cvref_t<Discretization>;
using Problem = StellarEquilibriumProblem<ModelType, DiscretizationType>;
using Prescription = typename DiscretizationType::NormalizationPrescriptionType;
using Problem = StellarEquilibriumProblem<ModelType, DiscretizationType>;
using Prescription = typename DiscretizationType::NormalizationPrescriptionType;
public:
static constexpr bool value = [] {
if constexpr (
std::same_as<Prescription, normalization::Unnormalized> ||
normalization::PhysicalRieszDiagonalPrescription<Prescription>) {
normalization::PhysicalRieszDiagonalPrescription<Prescription>
) {
return true;
} else {
return normalization::RuntimePreparedNormalizationOperation<Problem>;
@@ -362,46 +394,99 @@ export namespace mean_field::equilibrium {
std::remove_cvref_t<Model>,
std::remove_cvref_t<Discretization>>::value;
namespace detail {
/* The structurally formed problem type is needed to probe the ADL
* operation without a recursive concept. Its constructor remains
* private, and this factory is the single construction authority after
* the complete public compatibility contract has succeeded. */
struct StellarEquilibriumProblemFactory final {
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization>
requires StellarEquilibriumModelDiscretizationCompatible<Model, Discretization>
[[nodiscard]] static auto Create(
Model &&stellarModel,
Discretization discretization
) {
using ModelType = std::remove_cvref_t<Model>;
using DiscretizationType = std::remove_cvref_t<Discretization>;
return StellarEquilibriumProblem<ModelType, DiscretizationType>{
std::forward<Model>(stellarModel),
std::move(discretization)
};
}
};
} // namespace detail
} // namespace mean_field::equilibrium
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization>
requires StellarEquilibriumModelDiscretizationCompatible<Model, Discretization>
namespace mean_field::equilibrium::detail {
/* The structurally formed problem type is needed to probe the ADL
* operation without a recursive concept. Its constructor remains
* private, and this factory is the single construction authority after
* the complete public compatibility contract has succeeded. */
struct StellarEquilibriumProblemFactory final {
template <
StellarEquilibriumModel Model,
StellarDiscretizationType Discretization>
requires StellarEquilibriumModelDiscretizationCompatible<
Model,
Discretization>
[[nodiscard]] static auto Create(
Model &&stellarModel,
Discretization discretization
) {
using ModelType = std::remove_cvref_t<Model>;
using DiscretizationType = std::remove_cvref_t<Discretization>;
fem::FEM &finiteElementModel = discretization.MutableFiniteElementModelForAssembly();
return StellarEquilibriumProblem<ModelType, DiscretizationType>{
std::forward<Model>(stellarModel), std::move(discretization), finiteElementModel
};
}
template <
StellarEquilibriumModel Model,
StellarDiscretizationType Discretization>
requires StellarEquilibriumModelDiscretizationCompatible<
Model,
Discretization>
[[nodiscard]] static auto CreateOwned(
Model &&stellarModel,
Discretization discretization
) {
using ModelType = std::remove_cvref_t<Model>;
using DiscretizationType = std::remove_cvref_t<Discretization>;
using ProblemType = StellarEquilibriumProblem<ModelType, DiscretizationType>;
fem::FEM &finiteElementModel = discretization.MutableFiniteElementModelForAssembly();
return std::unique_ptr<ProblemType>{
new ProblemType{std::forward<Model>(stellarModel), std::move(discretization), finiteElementModel}
};
}
template <DiscretizedStellarEquilibriumProblem Problem>
[[nodiscard]] static fem::FEM &MutableFiniteElementModelForProjection(Problem &problem) {
return problem.m_discretization.MutableFiniteElementModelForAssembly();
}
template <DiscretizedStellarEquilibriumProblem Problem>
[[nodiscard]] static const fem::FEM &FiniteElementModel(const Problem &problem) {
return problem.m_discretization.RequireFiniteElementModel();
}
};
} // namespace mean_field::equilibrium::detail
export namespace mean_field::equilibrium {
template <
StellarEquilibriumModel Model,
StellarDiscretizationType Discretization>
requires StellarEquilibriumModelDiscretizationCompatible<
Model,
Discretization>
[[nodiscard]] auto discretize(
Model &&stellarModel,
Discretization discretization
) {
return detail::StellarEquilibriumProblemFactory::Create(
std::forward<Model>(stellarModel),
std::move(discretization)
std::forward<Model>(stellarModel), std::move(discretization)
);
}
template <StellarEquilibriumModel Model>
requires StellarEquilibriumModelDiscretizationCompatible<Model, StellarDiscretization>
requires StellarEquilibriumModelDiscretizationCompatible<
Model,
StellarDiscretization>
[[nodiscard]] auto discretize(
Model &&stellarModel,
fem::FEM &finiteElementModel
fem::FEM &&finiteElementModel
) {
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel});
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{std::move(finiteElementModel)});
}
template <StellarEquilibriumModel Model>
requires StellarEquilibriumModelDiscretizationCompatible<
Model,
StellarDiscretization>
StellarEquilibriumProblem<
std::remove_cvref_t<Model>,
StellarDiscretization>
discretize(
Model &&,
fem::FEM &
) = delete;
} // namespace mean_field::equilibrium

View File

@@ -17,10 +17,9 @@ export namespace mean_field::equilibrium {
template <StellarEquilibriumModel Model>
[[nodiscard]] auto makeStellarEquilibriumSystem(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper,
fem::FEM &&finiteElementModel,
Model &&stellarModel
) {
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel, domainMapper});
return discretize(std::forward<Model>(stellarModel), std::move(finiteElementModel));
}
} // namespace mean_field::equilibrium

View File

@@ -325,8 +325,14 @@ export namespace mean_field::preconditioning {
}
}
PreparedStellarPreconditioner(ProblemType &&, BlockType) = delete;
PreparedStellarPreconditioner(const ProblemType &&, BlockType) = delete;
PreparedStellarPreconditioner(
ProblemType &&,
BlockType
) = delete;
PreparedStellarPreconditioner(
const ProblemType &&,
BlockType
) = delete;
PreparedStellarPreconditioner(const PreparedStellarPreconditioner &) = delete;
PreparedStellarPreconditioner &operator=(const PreparedStellarPreconditioner &) = delete;
@@ -399,9 +405,11 @@ export namespace mean_field::preconditioning {
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
SpecificationBorderBlockType Block>
requires EquilibriumCoordinateComponentFor<
Block,
typename std::remove_cvref_t<Problem>::FormType> &&
SpecificationBorderPreparableFor<Problem, Block>
Block,
typename std::remove_cvref_t<Problem>::FormType> &&
SpecificationBorderPreparableFor<
Problem,
Block>
[[nodiscard]] auto prepare(
const Problem &problem,
Block block
@@ -409,17 +417,22 @@ export namespace mean_field::preconditioning {
return PreparedStellarPreconditioner<Problem, Block>{problem, std::move(block)};
}
template <typename Problem, SpecificationBorderBlockType Block>
requires (!std::is_lvalue_reference_v<Problem>) &&
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
EquilibriumCoordinateComponentFor<
Block,
typename std::remove_cvref_t<Problem>::FormType> &&
SpecificationBorderPreparableFor<std::remove_cvref_t<Problem>, Block>
template <
typename Problem,
SpecificationBorderBlockType Block>
requires(!std::is_lvalue_reference_v<Problem>) &&
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
EquilibriumCoordinateComponentFor<
Block,
typename std::remove_cvref_t<Problem>::FormType> &&
SpecificationBorderPreparableFor<
std::remove_cvref_t<Problem>,
Block>
[[nodiscard]] auto prepare(
Problem &&,
Block
) -> PreparedStellarPreconditioner<
std::remove_cvref_t<Problem>,
std::remove_cvref_t<Block>> = delete;
)
-> PreparedStellarPreconditioner<
std::remove_cvref_t<Problem>,
std::remove_cvref_t<Block>> = delete;
} // namespace mean_field::preconditioning

View File

@@ -236,15 +236,13 @@ export namespace mean_field::preconditioning {
* assembly. The current kernels remain polytropic, but selection no
* longer embeds that closed-world type test in the descriptor concept.
*/
template <typename EquationOfState>
struct MaterialSurfaceEquationOfStateBackend {
template <typename EquationOfState> struct MaterialSurfaceEquationOfStateBackend {
static constexpr bool registered = false;
};
template <>
struct MaterialSurfaceEquationOfStateBackend<eos::Polytrope> {
template <> struct MaterialSurfaceEquationOfStateBackend<eos::Polytrope> {
static constexpr bool registered = true;
using CoreType = operators::PreparedStellarEquilibriumOperator;
using CoreType = operators::PreparedStellarEquilibriumOperator;
};
template <typename EquationOfState>
@@ -252,8 +250,7 @@ export namespace mean_field::preconditioning {
{
MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::registered
} -> std::convertible_to<bool>;
requires MaterialSurfaceEquationOfStateBackend<
std::remove_cvref_t<EquationOfState>>::registered;
requires MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::registered;
typename MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::CoreType;
};
@@ -266,13 +263,11 @@ export namespace mean_field::preconditioning {
* truthful while the current kernels still consume the legacy physical
* core directly.
*/
template <typename EquationOfState, typename PhysicalCore>
struct MaterialSurfaceExecutableRuntime {
template <typename EquationOfState, typename PhysicalCore> struct MaterialSurfaceExecutableRuntime {
static constexpr bool available = false;
};
template <>
struct MaterialSurfaceExecutableRuntime<eos::Polytrope, operators::PreparedStellarEquilibriumOperator> {
template <> struct MaterialSurfaceExecutableRuntime<eos::Polytrope, operators::PreparedStellarEquilibriumOperator> {
static constexpr bool available = true;
};
@@ -280,33 +275,28 @@ export namespace mean_field::preconditioning {
concept ExecutableMaterialSurfaceRuntimeFor = requires {
{
MaterialSurfaceExecutableRuntime<
std::remove_cvref_t<EquationOfState>,
std::remove_cvref_t<PhysicalCore>>::available
std::remove_cvref_t<EquationOfState>, std::remove_cvref_t<PhysicalCore>>::available
} -> std::convertible_to<bool>;
requires MaterialSurfaceExecutableRuntime<
std::remove_cvref_t<EquationOfState>,
std::remove_cvref_t<PhysicalCore>>::available;
std::remove_cvref_t<EquationOfState>, std::remove_cvref_t<PhysicalCore>>::available;
};
template <typename Descriptor>
concept ImplementedMaterialSurfaceDescriptor =
MaterialSurfaceDescriptor<Descriptor> &&
ImplementedMaterialSurfaceEquationOfState<
typename Descriptor::ThermodynamicEquations::EquationOfStateType>;
ImplementedMaterialSurfaceEquationOfState<typename Descriptor::ThermodynamicEquations::EquationOfStateType>;
template <typename Descriptor, typename PhysicalCore>
concept MaterialSurfaceRuntimeFor =
ImplementedMaterialSurfaceDescriptor<Descriptor> && requires {
concept MaterialSurfaceRuntimeFor = ImplementedMaterialSurfaceDescriptor<Descriptor> && requires {
typename MaterialSurfaceEquationOfStateBackend<
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType;
requires std::same_as<
std::remove_cvref_t<PhysicalCore>,
typename MaterialSurfaceEquationOfStateBackend<
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType;
requires std::same_as<
std::remove_cvref_t<PhysicalCore>,
typename MaterialSurfaceEquationOfStateBackend<
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType>;
requires ExecutableMaterialSurfaceRuntimeFor<
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType,
PhysicalCore>;
};
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType>;
requires ExecutableMaterialSurfaceRuntimeFor<
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType, PhysicalCore>;
};
template <typename Candidate>
concept MaterialSurfacePreconditionerProblem =
@@ -778,14 +768,14 @@ export namespace mean_field::preconditioning {
fullEnthalpyDirection = enthalpyDirection;
m_operation->Mult(m_fullDirection, m_fullAction);
const auto fullActionView = m_operation->GetRootManifest().residualView(m_fullAction);
const auto fullActionView = m_operation->GetRootManifest().residualView(m_fullAction);
const auto fullDensityAction = fullActionView.block(utils::blocks::density_field.mass_term);
const auto fullSurfaceAction =
fullActionView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term);
const auto fullEnthalpyAction = fullActionView.block(utils::blocks::enthalpy_field.specific_term);
densityAction = fullDensityAction;
surfaceAction = fullSurfaceAction;
enthalpyAction = fullEnthalpyAction;
densityAction = fullDensityAction;
surfaceAction = fullSurfaceAction;
enthalpyAction = fullEnthalpyAction;
}
void ApplyEnthalpyToDensity(
@@ -1121,6 +1111,79 @@ export namespace mean_field::preconditioning {
std::uint64_t regularizedEntries{0};
};
namespace detail {
[[nodiscard]] inline DiagonalPreparationQuality regularizeMaterialSurfaceDiagonal(
mfem::Vector &diagonal,
const MaterialSurfaceDiagonalOptions options,
const MPI_Comm communicator
) {
const int localOptionsAreValid = std::isfinite(options.relativeFloor) && options.relativeFloor >= 0.0 &&
std::isfinite(options.absoluteFloor) && options.absoluteFloor > 0.0
? 1
: 0;
int globalOptionsAreValid = 0;
if (MPI_Allreduce(&localOptionsAreValid, &globalOptionsAreValid, 1, MPI_INT, MPI_MIN, communicator) !=
MPI_SUCCESS) {
throw std::runtime_error("Material-surface regularization could not validate its options.");
}
if (globalOptionsAreValid == 0) {
throw std::invalid_argument("Material-surface diagonal floors must be finite and nonnegative.");
}
double localMaximum = 0.0;
double localMinimum = std::numeric_limits<double>::infinity();
int localEntriesAreFinite = 1;
for (int index = 0; index < diagonal.Size(); ++index) {
if (!std::isfinite(diagonal(index))) {
localEntriesAreFinite = 0;
continue;
}
const double magnitude = std::abs(diagonal(index));
localMaximum = std::max(localMaximum, magnitude);
localMinimum = std::min(localMinimum, magnitude);
}
int globalEntriesAreFinite = 0;
if (MPI_Allreduce(&localEntriesAreFinite, &globalEntriesAreFinite, 1, MPI_INT, MPI_MIN, communicator) !=
MPI_SUCCESS) {
throw std::runtime_error("Material-surface regularization could not validate its diagonal.");
}
if (globalEntriesAreFinite == 0) {
throw std::invalid_argument("A material-surface diagonal contains a non-finite entry.");
}
double globalMaximum = 0.0;
double globalMinimum = 0.0;
const int maximumStatus =
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
const int minimumStatus =
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
if (maximumStatus != MPI_SUCCESS || minimumStatus != MPI_SUCCESS) {
throw std::runtime_error("Material-surface regularization could not reduce diagonal magnitudes.");
}
const double floor = std::max(options.absoluteFloor, options.relativeFloor * globalMaximum);
std::uint64_t localRegularized = 0;
for (int index = 0; index < diagonal.Size(); ++index) {
if (std::abs(diagonal(index)) < floor) {
diagonal(index) = std::copysign(floor, diagonal(index) == 0.0 ? 1.0 : diagonal(index));
++localRegularized;
}
}
std::uint64_t globalRegularized = 0;
if (MPI_Allreduce(&localRegularized, &globalRegularized, 1, MPI_UINT64_T, MPI_SUM, communicator) !=
MPI_SUCCESS) {
throw std::runtime_error("Material-surface regularization could not count regularized entries.");
}
return {
.minimumAbsoluteEntryBeforeRegularization = globalMinimum,
.maximumAbsoluteEntryBeforeRegularization = globalMaximum,
.appliedFloor = floor,
.regularizedEntries = globalRegularized
};
}
} // namespace detail
struct SurfaceRieszCalibrationReport final {
SurfaceRieszCalibrationTarget target{SurfaceRieszCalibrationTarget::none};
int probeCount{0};
@@ -1522,40 +1585,7 @@ export namespace mean_field::preconditioning {
const MaterialSurfaceDiagonalOptions options,
const MPI_Comm communicator
) {
if (!std::isfinite(options.relativeFloor) || options.relativeFloor < 0.0 ||
!std::isfinite(options.absoluteFloor) || options.absoluteFloor <= 0.0) {
throw std::invalid_argument("Material-surface diagonal floors must be finite and nonnegative.");
}
double localMaximum = 0.0;
double localMinimum = std::numeric_limits<double>::infinity();
for (int index = 0; index < diagonal.Size(); ++index) {
if (!std::isfinite(diagonal(index))) {
throw std::invalid_argument("A material-surface diagonal contains a non-finite entry.");
}
const double magnitude = std::abs(diagonal(index));
localMaximum = std::max(localMaximum, magnitude);
localMinimum = std::min(localMinimum, magnitude);
}
double globalMaximum = 0.0;
double globalMinimum = 0.0;
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
const double floor = std::max(options.absoluteFloor, options.relativeFloor * globalMaximum);
std::uint64_t localRegularized = 0;
for (int index = 0; index < diagonal.Size(); ++index) {
if (std::abs(diagonal(index)) < floor) {
diagonal(index) = std::copysign(floor, diagonal(index) == 0.0 ? 1.0 : diagonal(index));
++localRegularized;
}
}
std::uint64_t globalRegularized = 0;
MPI_Allreduce(&localRegularized, &globalRegularized, 1, MPI_UINT64_T, MPI_SUM, communicator);
return {
.minimumAbsoluteEntryBeforeRegularization = globalMinimum,
.maximumAbsoluteEntryBeforeRegularization = globalMaximum,
.appliedFloor = floor,
.regularizedEntries = globalRegularized
};
return detail::regularizeMaterialSurfaceDiagonal(diagonal, options, communicator);
}
Block m_block;
@@ -2051,40 +2081,7 @@ export namespace mean_field::preconditioning {
const MaterialSurfaceDiagonalOptions options,
const MPI_Comm communicator
) {
if (!std::isfinite(options.relativeFloor) || options.relativeFloor < 0.0 ||
!std::isfinite(options.absoluteFloor) || options.absoluteFloor <= 0.0) {
throw std::invalid_argument("Material-surface diagonal floors must be finite and nonnegative.");
}
double localMaximum = 0.0;
double localMinimum = std::numeric_limits<double>::infinity();
for (int index = 0; index < diagonal.Size(); ++index) {
if (!std::isfinite(diagonal(index))) {
throw std::invalid_argument("A material-surface diagonal contains a non-finite entry.");
}
const double magnitude = std::abs(diagonal(index));
localMaximum = std::max(localMaximum, magnitude);
localMinimum = std::min(localMinimum, magnitude);
}
double globalMaximum = 0.0;
double globalMinimum = 0.0;
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
const double floor = std::max(options.absoluteFloor, options.relativeFloor * globalMaximum);
std::uint64_t localRegularized = 0;
for (int index = 0; index < diagonal.Size(); ++index) {
if (std::abs(diagonal(index)) < floor) {
diagonal(index) = std::copysign(floor, diagonal(index) == 0.0 ? 1.0 : diagonal(index));
++localRegularized;
}
}
std::uint64_t globalRegularized = 0;
MPI_Allreduce(&localRegularized, &globalRegularized, 1, MPI_UINT64_T, MPI_SUM, communicator);
return {
.minimumAbsoluteEntryBeforeRegularization = globalMinimum,
.maximumAbsoluteEntryBeforeRegularization = globalMaximum,
.appliedFloor = floor,
.regularizedEntries = globalRegularized
};
return detail::regularizeMaterialSurfaceDiagonal(diagonal, options, communicator);
}
Block m_block;
@@ -2111,7 +2108,9 @@ export namespace mean_field::preconditioning {
template <
MaterialSurfaceDescriptor Descriptor,
MaterialSurfaceFactorizationPolicy Policy>
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
requires MaterialSurfaceRuntimeFor<
Descriptor,
operators::PreparedStellarEquilibriumOperator>
[[nodiscard]] auto prepare(
const operators::PreparedStellarEquilibriumOperator &operation,
MaterialSurfaceBlock<
@@ -2127,12 +2126,17 @@ export namespace mean_field::preconditioning {
equilibrium::StellarEquilibriumModel Model,
equilibrium::StellarDiscretizationType Discretization,
MaterialSurfaceFactorizationPolicy Policy>
requires MaterialSurfacePreconditionerProblem<
equilibrium::StellarEquilibriumProblem<Model, Discretization>>
requires MaterialSurfacePreconditionerProblem<equilibrium::StellarEquilibriumProblem<
Model,
Discretization>>
[[nodiscard]] auto prepare(
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
const equilibrium::StellarEquilibriumProblem<
Model,
Discretization> &problem,
MaterialSurfaceBlock<
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model, Discretization>>,
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<
Model,
Discretization>>,
backend::Diagonal,
backend::Diagonal,
Policy> block
@@ -2144,7 +2148,9 @@ export namespace mean_field::preconditioning {
MaterialSurfaceDescriptor Descriptor,
MaterialSurfaceFactorizationPolicy Policy,
backend::ApplicationMode Mode>
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
requires MaterialSurfaceRuntimeFor<
Descriptor,
operators::PreparedStellarEquilibriumOperator>
[[nodiscard]] auto prepare(
const operators::PreparedStellarEquilibriumOperator &operation,
MaterialSurfaceBlock<
@@ -2162,12 +2168,17 @@ export namespace mean_field::preconditioning {
equilibrium::StellarDiscretizationType Discretization,
MaterialSurfaceFactorizationPolicy Policy,
backend::ApplicationMode Mode>
requires MaterialSurfacePreconditionerProblem<
equilibrium::StellarEquilibriumProblem<Model, Discretization>>
requires MaterialSurfacePreconditionerProblem<equilibrium::StellarEquilibriumProblem<
Model,
Discretization>>
[[nodiscard]] auto prepare(
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
const equilibrium::StellarEquilibriumProblem<
Model,
Discretization> &problem,
MaterialSurfaceBlock<
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model, Discretization>>,
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<
Model,
Discretization>>,
backend::Diagonal,
backend::HypreBoomerAMG<Mode>,
Policy,

View File

@@ -9,3 +9,4 @@ export import :preconditioning.stellar_equilibrium;
export import :preconditioning.stellar_structure;
export import :preconditioning.specification_border;
export import :preconditioning.equilibrium_coordinates;
export import :preconditioning.stellar_recipe;

View File

@@ -63,7 +63,7 @@ export namespace mean_field::preconditioning {
.geometry = prepared.geometry != current.geometry,
.equationOfState = prepared.equationOfStateIdentity != current.equationOfStateIdentity,
.linearization = prepared.linearization != current.linearization ||
prepared.preparedOperatorGeneration != current.preparedOperatorGeneration
prepared.preparedOperatorGeneration != current.preparedOperatorGeneration
};
}
@@ -97,9 +97,7 @@ export namespace mean_field::preconditioning {
static constexpr bool registered = false;
};
template <
equilibrium::StellarEquilibriumModel Model,
equilibrium::StellarDiscretizationType Discretization>
template <equilibrium::StellarEquilibriumModel Model, equilibrium::StellarDiscretizationType Discretization>
struct StellarEquilibriumProblemTraits<equilibrium::StellarEquilibriumProblem<Model, Discretization>> {
using Problem = equilibrium::StellarEquilibriumProblem<Model, Discretization>;
using Form = typename Problem::FormType;
@@ -131,10 +129,9 @@ export namespace mean_field::preconditioning {
[[nodiscard]] static StellarPreconditionerLifecycleSnapshot Snapshot(const Problem &problem) {
const operators::StellarEquilibriumDependencies &dependencies = problem.GetLinearizationDependencies();
return {
.discretization = dependencies.discretization,
.geometry = problem.GetGeometryDependency(),
.equationOfStateIdentity =
std::addressof(problem.GetStellarModel().equationOfState()),
.discretization = dependencies.discretization,
.geometry = problem.GetGeometryDependency(),
.equationOfStateIdentity = std::addressof(problem.GetStellarModel().equationOfState()),
.linearization = dependencies,
.preparedOperatorGeneration = problem.GetPreparationGeneration()
};
@@ -145,26 +142,20 @@ export namespace mean_field::preconditioning {
concept StellarPreconditionerProblem = StellarEquilibriumProblemTraits<std::remove_cvref_t<Candidate>>::registered;
namespace detail {
template <typename Block>
struct IsGeneratedStellarValueBlock : std::false_type { };
template <typename Block> struct IsGeneratedStellarValueBlock : std::false_type { };
template <typename Generated>
struct IsGeneratedStellarValueBlock<utils::blocks::generated_value_block<Generated>>
: std::true_type { };
struct IsGeneratedStellarValueBlock<utils::blocks::generated_value_block<Generated>> : std::true_type { };
template <typename Block>
struct IsGeneratedStellarResidualBlock : std::false_type { };
template <typename Block> struct IsGeneratedStellarResidualBlock : std::false_type { };
template <typename Generated>
struct IsGeneratedStellarResidualBlock<utils::blocks::generated_residual_block<Generated>>
: std::true_type { };
struct IsGeneratedStellarResidualBlock<utils::blocks::generated_residual_block<Generated>> : std::true_type { };
template <typename Coupling>
inline constexpr bool isPurePhysicalStellarCoupling =
!IsGeneratedStellarValueBlock<
std::remove_cvref_t<typename Coupling::Value>>::value &&
!IsGeneratedStellarResidualBlock<
std::remove_cvref_t<typename Coupling::Residual>>::value;
!IsGeneratedStellarValueBlock<std::remove_cvref_t<typename Coupling::Value>>::value &&
!IsGeneratedStellarResidualBlock<std::remove_cvref_t<typename Coupling::Residual>>::value;
/* Pure structure contributions owned by a trusted backend are exact
* (core, specification, coupling) capabilities. Future cores and new
@@ -172,8 +163,7 @@ export namespace mean_field::preconditioning {
* be accompanied by an explicit preconditioner decision. Generated-
* border terms remain the responsibility of specification-border
* machinery. */
template <typename PhysicalCore, typename Specification>
struct StellarStructureBackendHandledCouplings {
template <typename PhysicalCore, typename Specification> struct StellarStructureBackendHandledCouplings {
using Type = utils::blocks::type_list<>;
};
@@ -213,17 +203,12 @@ export namespace mean_field::preconditioning {
struct StellarStructureBackendHandledCouplings<
operators::PreparedStellarEquilibriumOperator,
models::FixedCentralDensity> {
using Type = utils::blocks::type_list<
operators::StellarEquilibriumJacobianCoupling<
utils::blocks::enthalpy::specific::residual,
utils::blocks::enthalpy::specific::value>>;
using Type = utils::blocks::type_list<operators::StellarEquilibriumJacobianCoupling<
utils::blocks::enthalpy::specific::residual,
utils::blocks::enthalpy::specific::value>>;
};
template <
typename Coupling,
typename Model,
typename PhysicalCore,
typename ModelSpecifications>
template <typename Coupling, typename Model, typename PhysicalCore, typename ModelSpecifications>
struct EveryCouplingContributionHandled;
template <
@@ -235,30 +220,22 @@ export namespace mean_field::preconditioning {
Coupling,
Model,
PhysicalCore,
models::detail::SpecificationSetStorage<Specifications...>> final {
models::detail::SpecificationSetStorage<Specifications...>>
final {
private:
template <typename Specification>
static constexpr bool handled =
!utils::blocks::contains_type_v<
Coupling,
typename operators::StellarEquilibriumSpecificationCompilation<
Specification>::JacobianCouplings> ||
(operators::stellarEquilibriumBackendRuntimeAuthorized<
Specification,
Model> &&
typename operators::StellarEquilibriumSpecificationCompilation<Specification>::JacobianCouplings> ||
(operators::stellarEquilibriumBackendRuntimeAuthorized<Specification, Model> &&
utils::blocks::contains_type_v<
Coupling,
typename StellarStructureBackendHandledCouplings<
PhysicalCore,
Specification>::Type>) ||
operators::stellarEquilibriumSpecificationCouplingIsStructuralZero<
Specification,
Model,
Coupling>;
typename StellarStructureBackendHandledCouplings<PhysicalCore, Specification>::Type>) ||
operators::stellarEquilibriumSpecificationCouplingIsStructuralZero<Specification, Model, Coupling>;
public:
static constexpr bool value =
(handled<Specifications> && ...);
static constexpr bool value = (handled<Specifications> && ...);
};
template <
@@ -269,11 +246,7 @@ export namespace mean_field::preconditioning {
typename Unsupported>
struct CollectUnsupportedStellarStructureCouplings;
template <
typename Model,
typename PhysicalCore,
typename ModelSpecifications,
typename Unsupported>
template <typename Model, typename PhysicalCore, typename ModelSpecifications, typename Unsupported>
struct CollectUnsupportedStellarStructureCouplings<
utils::blocks::type_list<>,
Model,
@@ -299,11 +272,7 @@ export namespace mean_field::preconditioning {
private:
static constexpr bool supported =
!isPurePhysicalStellarCoupling<Head> ||
EveryCouplingContributionHandled<
Head,
Model,
PhysicalCore,
ModelSpecifications>::value;
EveryCouplingContributionHandled<Head, Model, PhysicalCore, ModelSpecifications>::value;
using Next = std::conditional_t<
supported,
utils::blocks::type_list<Unsupported...>,
@@ -318,42 +287,36 @@ export namespace mean_field::preconditioning {
Next>::Type;
};
template <typename Candidate, typename = void>
struct DefaultStellarStructurePhysicalTopologyAudit {
using ContributionCouplings = utils::blocks::type_list<>;
using UnsupportedCouplings = utils::blocks::type_list<>;
template <typename Candidate, typename = void> struct DefaultStellarStructurePhysicalTopologyAudit {
using ContributionCouplings = utils::blocks::type_list<>;
using UnsupportedCouplings = utils::blocks::type_list<>;
static constexpr bool supported = false;
};
template <model::StellarModelType Model>
requires(
operators::StellarEquilibriumSystemCompilable<
std::remove_cvref_t<Model>> &&
operators::hasStellarEquilibriumCoreRuntime<
std::remove_cvref_t<Model>>)
operators::StellarEquilibriumSystemCompilable<std::remove_cvref_t<Model>> &&
operators::hasStellarEquilibriumCoreRuntime<std::remove_cvref_t<Model>>
)
struct DefaultStellarStructurePhysicalTopologyAudit<
Model,
std::void_t<
typename operators::CompiledStellarEquilibriumSystem<
std::remove_cvref_t<Model>>::ContributionJacobianCouplings,
operators::StellarEquilibriumPhysicalCoreType<
std::remove_cvref_t<Model>>>> {
operators::StellarEquilibriumPhysicalCoreType<std::remove_cvref_t<Model>>>> {
private:
using Compilation = operators::CompiledStellarEquilibriumSystem<
std::remove_cvref_t<Model>>;
using PhysicalCore = operators::StellarEquilibriumPhysicalCoreType<
std::remove_cvref_t<Model>>;
using Compilation = operators::CompiledStellarEquilibriumSystem<std::remove_cvref_t<Model>>;
using PhysicalCore = operators::StellarEquilibriumPhysicalCoreType<std::remove_cvref_t<Model>>;
public:
using ContributionCouplings =
typename Compilation::ContributionJacobianCouplings;
using UnsupportedCouplings =
typename CollectUnsupportedStellarStructureCouplings<
ContributionCouplings,
std::remove_cvref_t<Model>,
PhysicalCore,
typename std::remove_cvref_t<Model>::SpecificationTypes,
utils::blocks::type_list<>>::Type;
using ContributionCouplings = typename Compilation::ContributionJacobianCouplings;
using UnsupportedCouplings = typename CollectUnsupportedStellarStructureCouplings<
ContributionCouplings,
std::remove_cvref_t<Model>,
PhysicalCore,
typename std::remove_cvref_t<Model>::SpecificationTypes,
utils::blocks::type_list<>>::Type;
static constexpr bool supported = UnsupportedCouplings::size == 0;
};
@@ -369,13 +332,11 @@ export namespace mean_field::preconditioning {
* detection-safe and therefore suitable for constraining factories. */
template <typename Candidate>
struct DefaultStellarStructurePhysicalTopologySupport
: detail::DefaultStellarStructurePhysicalTopologyAudit<
std::remove_cvref_t<Candidate>> { };
: detail::DefaultStellarStructurePhysicalTopologyAudit<std::remove_cvref_t<Candidate>> { };
template <typename Candidate>
inline constexpr bool defaultStellarStructurePhysicalTopologySupported =
DefaultStellarStructurePhysicalTopologySupport<
std::remove_cvref_t<Candidate>>::supported;
DefaultStellarStructurePhysicalTopologySupport<std::remove_cvref_t<Candidate>>::supported;
template <typename Candidate>
concept DefaultStellarStructurePhysicalTopologySupportedFor =
@@ -419,9 +380,8 @@ export namespace mean_field::preconditioning {
template <typename Form> struct IdentityPlanForForm;
template <typename... Values, typename... Residuals>
struct IdentityPlanForForm<utils::blocks::block_form<
utils::blocks::type_list<Values...>,
utils::blocks::type_list<Residuals...>>> {
struct IdentityPlanForForm<
utils::blocks::block_form<utils::blocks::type_list<Values...>, utils::blocks::type_list<Residuals...>>> {
static_assert(sizeof...(Values) == sizeof...(Residuals));
using Type = PreconditionerPlan<IdentityBlock<Values, Residuals>...>;

View File

@@ -0,0 +1,83 @@
module;
#include <concepts>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:preconditioning.stellar_recipe;
export import :preconditioning.equilibrium_coordinates;
export namespace mean_field::preconditioning {
/*
* A stellar-preconditioner prescription is an unbound, owning value. It
* may therefore be created before a problem exists and safely moved into
* the eventual user-owned solve context. A prepared inverse is deliberately a
* separate, problem-bound object with a stable address.
*/
struct StellarPreconditionerPrescriptionTag { };
template <typename Candidate>
concept StellarPreconditionerPrescription =
std::derived_from<std::remove_cvref_t<Candidate>, StellarPreconditionerPrescriptionTag> &&
std::move_constructible<std::remove_cvref_t<Candidate>>;
struct DefaultStellarPreconditioner final : StellarPreconditionerPrescriptionTag { };
/*
* This overload is the user-facing, problem-independent factory. The
* existing makePreconditioner(problem) overload remains the low-level
* factory for the typed, unprepared block assembled below.
*/
[[nodiscard]] constexpr DefaultStellarPreconditioner makePreconditioner() noexcept {
return {};
}
template <typename Candidate, typename Problem>
concept PreparedStellarInverseFor =
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
std::derived_from<std::remove_cvref_t<Candidate>, mfem::Solver> &&
std::destructible<std::remove_cvref_t<Candidate>> &&
requires(std::remove_cvref_t<Candidate> &prepared, const std::remove_cvref_t<Candidate> &constantPrepared) {
{ constantPrepared.GetProblem() } -> std::same_as<const std::remove_cvref_t<Problem> &>;
{ constantPrepared.IsCurrent() } -> std::same_as<bool>;
prepared.Refresh();
};
/*
* Built-in preparation is intentionally policy-first. The same spelling
* can be supplied beside a third-party prescription and found by ADL,
* without adding that prescription to a central registry or switch.
* Preparation requires an already-prepared problem because the current
* physical inverse assembles state-dependent numerical data.
*/
template <DefaultStellarPreconditionerAvailableFor Problem>
[[nodiscard]] auto prepareStellarPreconditioner(
DefaultStellarPreconditioner,
const Problem &problem
) {
return preconditioning::prepare(problem, preconditioning::makePreconditioner(problem));
}
template <typename Prescription, typename Problem>
concept StellarPreconditionerRuntimeAvailableFor =
StellarPreconditionerPrescription<Prescription> &&
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
requires(std::remove_cvref_t<Prescription> prescription, const std::remove_cvref_t<Problem> &problem) {
requires std::same_as<
decltype(prepareStellarPreconditioner(std::move(prescription), problem)),
std::remove_cvref_t<decltype(prepareStellarPreconditioner(std::move(prescription), problem))>>;
{
prepareStellarPreconditioner(std::move(prescription), problem)
} -> PreparedStellarInverseFor<std::remove_cvref_t<Problem>>;
};
template <StellarPreconditionerPrescription Prescription, typename Problem>
requires StellarPreconditionerRuntimeAvailableFor<Prescription, Problem>
using PreparedStellarInverseType = std::remove_cvref_t<decltype(prepareStellarPreconditioner(
std::declval<std::remove_cvref_t<Prescription> &&>(),
std::declval<const std::remove_cvref_t<Problem> &>()
))>;
} // namespace mean_field::preconditioning

View File

@@ -634,13 +634,11 @@ export namespace mean_field::preconditioning {
* Add future cores here only together with matching cross-coupling and
* preparation implementations.
*/
template <typename PhysicalCore>
struct StellarStructureExecutableRuntime {
template <typename PhysicalCore> struct StellarStructureExecutableRuntime {
static constexpr bool available = false;
};
template <>
struct StellarStructureExecutableRuntime<operators::PreparedStellarEquilibriumOperator> {
template <> struct StellarStructureExecutableRuntime<operators::PreparedStellarEquilibriumOperator> {
static constexpr bool available = true;
};
@@ -654,14 +652,12 @@ export namespace mean_field::preconditioning {
template <typename Descriptor, typename PhysicalCore>
concept StellarStructureRuntimeFor =
MaterialSurfaceRuntimeFor<Descriptor, PhysicalCore> &&
ExecutableStellarStructureRuntimeFor<PhysicalCore>;
MaterialSurfaceRuntimeFor<Descriptor, PhysicalCore> && ExecutableStellarStructureRuntimeFor<PhysicalCore>;
template <typename Candidate>
concept StellarStructurePreconditionerProblem =
equilibrium::DiscretizedStellarEquilibriumProblem<Candidate> &&
DefaultStellarStructurePhysicalTopologySupportedFor<
typename std::remove_cvref_t<Candidate>::ModelType> &&
DefaultStellarStructurePhysicalTopologySupportedFor<typename std::remove_cvref_t<Candidate>::ModelType> &&
requires {
requires StellarStructureRuntimeFor<
MaterialSurfaceDescriptorFor<std::remove_cvref_t<Candidate>>,
@@ -688,8 +684,7 @@ export namespace mean_field::preconditioning {
preconditioning::prepare(problem, std::move(materialComponent));
preconditioning::prepare(
problem.GetPhysicalOperator().GetHydrostaticOperator().GetFEM(),
problem.GetPhysicalOperator().GetGravityContext().GetGeometryContext(),
std::move(gravityComponent)
problem.GetPhysicalOperator().GetGravityContext().GetGeometryContext(), std::move(gravityComponent)
);
StellarStructureCrossJacobianOperator{problem.GetPhysicalOperator()};
};
@@ -873,19 +868,30 @@ export namespace mean_field::preconditioning {
GravityFactorizationPolicy GravityPolicy,
StellarStructureFactorizationPolicy StructurePolicy>
requires StellarStructurePreparableFor<
equilibrium::StellarEquilibriumProblem<Model, Discretization>,
equilibrium::StellarEquilibriumProblem<
Model,
Discretization>,
MaterialComponent,
GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>>
GravityFieldBlock<
GravityMassBackend,
backend::HypreBoomerAMG<Mode>,
GravityPolicy>>
[[nodiscard]] auto prepare(
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
const equilibrium::StellarEquilibriumProblem<
Model,
Discretization> &problem,
StellarStructureBlock<
MaterialComponent,
GravityFieldBlock<
GravityMassBackend,
backend::HypreBoomerAMG<Mode>,
GravityPolicy>,
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::FormType,
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::JacobianFormType,
typename equilibrium::StellarEquilibriumProblem<
Model,
Discretization>::FormType,
typename equilibrium::StellarEquilibriumProblem<
Model,
Discretization>::JacobianFormType,
StructurePolicy> structure
) {
return PreparedStellarStructureBlock<

View File

@@ -1,8 +1,8 @@
module;
#include <cmath>
#include <cstddef>
#include <concepts>
#include <cstddef>
#include <stdexcept>
#include <type_traits>
#include <utility>
@@ -59,13 +59,14 @@ export namespace mean_field::seed {
/* An explicit opt-in for a specification that leaves a radial seed unchanged. */
struct NoStateChange {
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
template <typename Model>
static constexpr bool supports = true;
template <typename Model> static constexpr bool supports = true;
template <typename Specification, typename Model>
template <
typename Specification,
typename Model>
static void validate(
const Specification &,
const Model &,
@@ -74,7 +75,9 @@ export namespace mean_field::seed {
) noexcept {
}
template <typename Specification, typename Model>
template <
typename Specification,
typename Model>
static void initialize(
const Specification &,
const Model &,
@@ -115,19 +118,17 @@ export namespace mean_field::seed {
}
struct UnavailableRadialProjectionPhysics final {
static constexpr bool registered = false;
static constexpr bool providesRadialMass = false;
static constexpr bool registered = false;
static constexpr bool providesRadialMass = false;
template <typename Model>
static constexpr bool supports = false;
template <typename Model> static constexpr bool supports = false;
};
struct FixedTotalMassRadialProjectionPhysics final {
static constexpr bool registered = true;
static constexpr bool providesRadialMass = true;
static constexpr bool registered = true;
static constexpr bool providesRadialMass = true;
template <typename Model>
static constexpr bool supports = true;
template <typename Model> static constexpr bool supports = true;
[[nodiscard]] static dimensions::MassValue targetMass(const models::FixedTotalMass &specification) {
return specification.targetMass();
@@ -164,10 +165,7 @@ export namespace mean_field::seed {
static constexpr bool providesRadialMass = false;
template <typename Model>
static constexpr bool supports = requires(
const Model &model,
const surface::Isobaric &condition
) {
static constexpr bool supports = requires(const Model &model, const surface::Isobaric &condition) {
{
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
model.equationOfState(), condition.targetPressure()
@@ -213,11 +211,10 @@ export namespace mean_field::seed {
};
struct FixedCentralDensityRadialProjectionPhysics final {
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
template <typename Model>
static constexpr bool supports = true;
template <typename Model> static constexpr bool supports = true;
template <typename Model>
static void validate(
@@ -246,11 +243,10 @@ export namespace mean_field::seed {
};
struct FixedAngularMomentumRadialProjectionPhysics final {
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
template <typename Model>
static constexpr bool supports = true;
template <typename Model> static constexpr bool supports = true;
template <typename Model>
static void validate(
@@ -316,12 +312,12 @@ export namespace mean_field::seed {
};
template <typename Candidate> struct UnwrapRadialProjectionPhysics {
using Type = UnavailableRadialProjectionPhysics;
using Type = UnavailableRadialProjectionPhysics;
static constexpr bool valid = false;
};
template <typename Physics> struct UnwrapRadialProjectionPhysics<projection::Use<Physics>> {
using Type = Physics;
using Type = Physics;
static constexpr bool valid = true;
};
@@ -358,7 +354,9 @@ export namespace mean_field::seed {
}
}
template <typename Specification, typename Model>
template <
typename Specification,
typename Model>
[[nodiscard]] consteval bool radialProjectionPhysicsIsComplete() {
using Physics = typename SelectRadialProjectionPhysics<Specification>::Type;
if constexpr (!radialProjectionPhysicsRegistered<Physics>()) {
@@ -370,12 +368,9 @@ export namespace mean_field::seed {
} else if constexpr (!static_cast<bool>(Physics::template supports<Model>)) {
return false;
} else if constexpr (!requires(
const Specification &specification,
const Model &model,
const RadialProfile &profile,
const StellarEquilibriumProjectionOptions &options,
const RadialProjectionScales &scales,
RadialProjectionState &state,
const Specification &specification, const Model &model,
const RadialProfile &profile, const StellarEquilibriumProjectionOptions &options,
const RadialProjectionScales &scales, RadialProjectionState &state,
mfem::Vector coordinate
) {
Physics::validate(specification, model, profile, options);
@@ -417,21 +412,24 @@ export namespace mean_field::seed {
static constexpr std::size_t radialMassProviderCount =
(std::size_t{0} + ... +
(radialProjectionPhysicsProvidesMass<
typename SelectRadialProjectionPhysics<Specifications>::Type>()
(radialProjectionPhysicsProvidesMass<typename SelectRadialProjectionPhysics<Specifications>::Type>()
? std::size_t{1}
: std::size_t{0}));
static constexpr bool complete = radialMassProviderCount == 1 &&
(radialProjectionPhysicsIsComplete<Specifications, ModelType>() && ...);
static constexpr bool complete =
radialMassProviderCount == 1 && (radialProjectionPhysicsIsComplete<Specifications, ModelType>() && ...);
[[nodiscard]] static dimensions::MassValue targetMass(const ModelType &model) requires complete {
[[nodiscard]] static dimensions::MassValue targetMass(const ModelType &model)
requires complete
{
dimensions::MassValue result{0.0};
([&] {
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
if constexpr (radialProjectionPhysicsProvidesMass<Physics>()) {
result = Physics::targetMass(model.template specification<Specifications>());
}
}(), ...);
(
[&] {
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
if constexpr (radialProjectionPhysicsProvidesMass<Physics>()) {
result = Physics::targetMass(model.template specification<Specifications>());
}
}(),
...);
return result;
}
@@ -439,11 +437,15 @@ export namespace mean_field::seed {
const ModelType &model,
const RadialProfile &profile,
const StellarEquilibriumProjectionOptions &options
) requires complete {
([&] {
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
Physics::validate(model.template specification<Specifications>(), model, profile, options);
}(), ...);
)
requires complete
{
(
[&] {
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
Physics::validate(model.template specification<Specifications>(), model, profile, options);
}(),
...);
}
template <typename StateView>
@@ -452,27 +454,32 @@ export namespace mean_field::seed {
const RadialProjectionScales &scales,
RadialProjectionState &state,
const StateView &stateView
) requires complete {
([&] {
using Contribution = models::SpecificationContribution<Specifications>;
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
if constexpr (Contribution::generatedValueArity == 0) {
Physics::initialize(
model.template specification<Specifications>(), model, scales, state, mfem::Vector{}
);
} else {
static_assert(
Contribution::generatedValueArity == 1,
"Radial projection currently requires each specification contribution to generate at "
"most one scalar coordinate."
);
using Term = RadialProjectionCoordinateTerm<Specifications, Contribution::generatedStateKind>;
Physics::initialize(
model.template specification<Specifications>(), model, scales, state,
stateView.block(Term{})
);
}
}(), ...);
)
requires complete
{
(
[&] {
using Contribution = models::SpecificationContribution<Specifications>;
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
if constexpr (Contribution::generatedValueArity == 0) {
Physics::initialize(
model.template specification<Specifications>(), model, scales, state, mfem::Vector{}
);
} else {
static_assert(
Contribution::generatedValueArity == 1,
"Radial projection currently requires each specification contribution to generate at "
"most one scalar coordinate."
);
using Term =
RadialProjectionCoordinateTerm<Specifications, Contribution::generatedStateKind>;
Physics::initialize(
model.template specification<Specifications>(), model, scales, state,
stateView.block(Term{})
);
}
}(),
...);
}
};
@@ -494,21 +501,25 @@ export namespace mean_field::seed {
concept RadialProfileProjectableModel =
model::StellarModelType<Candidate> && radialProjectionIsCompilable<std::remove_cvref_t<Candidate>>;
template <equilibrium::StellarEquilibriumModel Model, equilibrium::StellarDiscretizationType Discretization>
template <
equilibrium::StellarEquilibriumModel Model,
equilibrium::StellarDiscretizationType Discretization>
requires RadialProfileProjectableModel<Model>
[[nodiscard]] ProjectedEquilibriumState<Model> projectRadialProfile(
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
equilibrium::StellarEquilibriumProblem<
Model,
Discretization> &problem,
const RadialProfile &profile,
const StellarEquilibriumProjectionOptions &options = {}
) {
using Projection = detail::CompileRadialProjection<
std::remove_cvref_t<Model>,
typename std::remove_cvref_t<Model>::SpecificationTypes>;
std::remove_cvref_t<Model>, typename std::remove_cvref_t<Model>::SpecificationTypes>;
const auto &stellarModel = problem.GetStellarModel();
Projection::validate(stellarModel, profile, options);
const dimensions::MassValue targetMass = Projection::targetMass(stellarModel);
const dimensions::MassValue targetMass = Projection::targetMass(stellarModel);
const detail::ProjectedRadialFields fields = detail::projectRadialFields(
problem.GetDiscretization().finiteElementModel(), profile, targetMass, options
equilibrium::detail::StellarEquilibriumProblemFactory::MutableFiniteElementModelForProjection(problem),
profile, targetMass, options
);
mfem::Vector values(problem.StateSize());
@@ -563,11 +574,15 @@ export namespace mean_field::seed {
equilibrium::StellarDiscretizationType Discretization,
typename Strategy>
requires RadialSeedStrategyFor<
Strategy,
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::ModelType> &&
Strategy,
typename equilibrium::StellarEquilibriumProblem<
Model,
Discretization>::ModelType> &&
RadialProfileProjectableModel<Model>
[[nodiscard]] ProjectedEquilibriumState<Model> makeProjectedEquilibriumState(
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
equilibrium::StellarEquilibriumProblem<
Model,
Discretization> &problem,
const Strategy &strategy,
const StellarEquilibriumProjectionOptions &options = {}
) {

View File

@@ -0,0 +1,868 @@
module;
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <concepts>
#include <cstdint>
#include <limits>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
#include <mpi.h>
export module mean_field:solver.linear_backend;
export import :preconditioning.backend;
export namespace mean_field::solver {
enum class LinearSolveStatus : std::uint8_t {
converged,
maximum_iterations,
breakdown,
non_finite,
backend_failure
};
struct LinearSolveControl final {
double relativeTolerance{1.0e-8};
double absoluteTolerance{0.0};
int maximumIterations{100};
void Validate() const {
if (!std::isfinite(relativeTolerance) || relativeTolerance < 0.0) {
throw std::invalid_argument("A linear solve requires a finite, non-negative relative tolerance.");
}
if (!std::isfinite(absoluteTolerance) || absoluteTolerance < 0.0) {
throw std::invalid_argument("A linear solve requires a finite, non-negative absolute tolerance.");
}
if (maximumIterations <= 0) {
throw std::invalid_argument("A linear solve requires at least one permitted iteration.");
}
}
[[nodiscard]] double ConvergenceThreshold(const double globalRightHandSideNorm) const {
Validate();
if (!std::isfinite(globalRightHandSideNorm) || globalRightHandSideNorm < 0.0) {
throw std::invalid_argument("A linear solve requires a finite, non-negative right-hand-side norm.");
}
const double relativeThreshold = relativeTolerance * globalRightHandSideNorm;
if (!std::isfinite(relativeThreshold)) {
throw std::invalid_argument("The linear relative convergence threshold must be finite.");
}
return absoluteTolerance > relativeThreshold ? absoluteTolerance : relativeThreshold;
}
};
/*
* Numerical termination is data, not an exception. Implementations throw
* for invalid controls, configuration, dimensions, or violated lifetime
* contracts. All reported norms are communicator-global Euclidean norms.
* Solve uses the incoming correction as its initial guess and overwrites
* it with the final correction, so initialResidualNorm is ||b - A x_0||.
* Convergence remains relative to the right-hand side rather than the
* quality of a particular initial guess:
*
* ||b - A x|| <= max(absoluteTolerance,
* relativeTolerance * ||b||).
*
* For a zero right-hand side, relativeTrueResidualNorm is zero exactly
* when the true residual is zero and positive infinity otherwise. The
* true-residual fields are distinct from the backend's recurrence so
* callers never have to infer one from the other. This is deliberately
* fixed-size: recording a history is an optional backend concern whose
* storage must be owned and reserved by the prepared runtime, not allocated
* while Solve is active.
*/
struct LinearSolveReport final {
LinearSolveStatus status{LinearSolveStatus::backend_failure};
LinearSolveControl control{};
int iterations{0};
int restarts{0};
double rightHandSideNorm{0.0};
double initialResidualNorm{0.0};
double reportedResidualNorm{0.0};
double trueResidualNorm{0.0};
double relativeTrueResidualNorm{0.0};
// Includes MFEM's initial-guess residual application and the
// post-solve application used to verify the true residual.
std::uint64_t operatorApplications{0};
std::uint64_t inversePreconditionerApplications{0};
double solveSeconds{0.0};
// These totals include only completed applications. Operator time also
// includes the post-solve true-residual verification application.
double operatorSeconds{0.0};
double inversePreconditionerSeconds{0.0};
[[nodiscard]] bool Converged() const noexcept {
return status == LinearSolveStatus::converged;
}
};
struct LinearBackendConfigurationTag { };
template <typename Candidate>
concept LinearBackendConfiguration =
std::derived_from<std::remove_cvref_t<Candidate>, LinearBackendConfigurationTag> &&
std::move_constructible<std::remove_cvref_t<Candidate>> && requires {
requires std::same_as<
std::remove_cv_t<decltype(std::remove_cvref_t<Candidate>::supportedPreconditionerContract)>,
preconditioning::ApplicationContract>;
typename std::integral_constant<
preconditioning::ApplicationContract, std::remove_cvref_t<Candidate>::supportedPreconditionerContract>;
requires(
std::remove_cvref_t<Candidate>::supportedPreconditionerContract ==
preconditioning::ApplicationContract::stationary_linear ||
std::remove_cvref_t<Candidate>::supportedPreconditionerContract ==
preconditioning::ApplicationContract::flexible
);
};
} // namespace mean_field::solver
namespace mean_field::solver::detail {
template <typename Candidate>
concept StaticPreconditionerContractDeclared = requires { &std::remove_cvref_t<Candidate>::applicationContract; };
template <typename Candidate>
concept ExactStaticPreconditionerContract = requires {
requires std::same_as<
std::remove_cv_t<decltype(std::remove_cvref_t<Candidate>::applicationContract)>,
preconditioning::ApplicationContract>;
typename std::integral_constant<
preconditioning::ApplicationContract, std::remove_cvref_t<Candidate>::applicationContract>;
requires(
std::remove_cvref_t<Candidate>::applicationContract ==
preconditioning::ApplicationContract::stationary_linear ||
std::remove_cvref_t<Candidate>::applicationContract == preconditioning::ApplicationContract::flexible
);
};
template <typename Candidate, typename = void> struct StaticPreconditionerContract {
static constexpr bool declared = StaticPreconditionerContractDeclared<Candidate>;
static constexpr bool registered = false;
static constexpr preconditioning::ApplicationContract value =
preconditioning::ApplicationContract::stationary_linear;
};
template <typename Candidate>
struct StaticPreconditionerContract<Candidate, std::enable_if_t<ExactStaticPreconditionerContract<Candidate>>> {
static constexpr bool declared = true;
static constexpr auto value = std::remove_cvref_t<Candidate>::applicationContract;
static constexpr bool registered = true;
};
template <typename Candidate>
concept BackendPreconditionerContractDeclared = requires { typename std::remove_cvref_t<Candidate>::BackendType; };
template <typename Backend>
concept ExactRegisteredBackendPreconditionerContract = requires {
requires preconditioning::backend::Registered<std::remove_cvref_t<Backend>>;
requires std::same_as<
std::remove_cv_t<
decltype(preconditioning::backend::Traits<std::remove_cvref_t<Backend>>::applicationContract)>,
preconditioning::ApplicationContract>;
typename std::integral_constant<
preconditioning::ApplicationContract,
preconditioning::backend::Traits<std::remove_cvref_t<Backend>>::applicationContract>;
requires(
preconditioning::backend::Traits<std::remove_cvref_t<Backend>>::applicationContract ==
preconditioning::ApplicationContract::stationary_linear ||
preconditioning::backend::Traits<std::remove_cvref_t<Backend>>::applicationContract ==
preconditioning::ApplicationContract::flexible
);
};
template <typename Candidate>
concept ExactBackendPreconditionerContract =
BackendPreconditionerContractDeclared<Candidate> &&
ExactRegisteredBackendPreconditionerContract<typename std::remove_cvref_t<Candidate>::BackendType>;
template <typename Candidate, typename = void> struct BackendPreconditionerContract {
static constexpr bool declared = BackendPreconditionerContractDeclared<Candidate>;
static constexpr bool registered = false;
static constexpr preconditioning::ApplicationContract value =
preconditioning::ApplicationContract::stationary_linear;
};
template <typename Candidate>
struct BackendPreconditionerContract<Candidate, std::enable_if_t<ExactBackendPreconditionerContract<Candidate>>> {
private:
using Backend = typename std::remove_cvref_t<Candidate>::BackendType;
public:
static constexpr bool declared = true;
static constexpr bool registered = true;
static constexpr preconditioning::ApplicationContract value =
preconditioning::backend::Traits<std::remove_cvref_t<Backend>>::applicationContract;
};
template <typename Candidate> struct DirectPreconditionerContractAudit final {
private:
using StaticContract = StaticPreconditionerContract<Candidate>;
using BackendContract = BackendPreconditionerContract<Candidate>;
public:
static constexpr bool declarationsValid = (!StaticContract::declared || StaticContract::registered) &&
(!BackendContract::declared || BackendContract::registered);
static constexpr bool sourcesAgree = !StaticContract::registered || !BackendContract::registered ||
StaticContract::value == BackendContract::value;
static constexpr bool registered =
declarationsValid && sourcesAgree && (StaticContract::registered || BackendContract::registered);
static constexpr preconditioning::ApplicationContract value = [] {
if constexpr (StaticContract::registered) {
return StaticContract::value;
} else if constexpr (BackendContract::registered) {
return BackendContract::value;
} else {
return preconditioning::ApplicationContract::stationary_linear;
}
}();
};
template <typename Candidate>
concept PhysicalInversePreconditionerContractDeclared =
requires(const std::remove_cvref_t<Candidate> &candidate) { candidate.GetPhysicalInverse(); };
template <typename Candidate>
using PhysicalInverseType =
std::remove_cvref_t<decltype(std::declval<const std::remove_cvref_t<Candidate> &>().GetPhysicalInverse())>;
template <typename Candidate>
concept ExactPhysicalInversePreconditionerContract =
PhysicalInversePreconditionerContractDeclared<Candidate> &&
DirectPreconditionerContractAudit<PhysicalInverseType<Candidate>>::registered;
template <typename Candidate, typename = void> struct PhysicalInversePreconditionerContract {
static constexpr bool declared = PhysicalInversePreconditionerContractDeclared<Candidate>;
static constexpr bool registered = false;
static constexpr preconditioning::ApplicationContract value =
preconditioning::ApplicationContract::stationary_linear;
};
template <typename Candidate>
struct PhysicalInversePreconditionerContract<
Candidate,
std::enable_if_t<ExactPhysicalInversePreconditionerContract<Candidate>>> {
using PhysicalInverse = PhysicalInverseType<Candidate>;
using ContractAudit = DirectPreconditionerContractAudit<PhysicalInverse>;
static constexpr bool declared = true;
static constexpr bool registered = true;
static constexpr preconditioning::ApplicationContract value = ContractAudit::value;
};
template <typename Candidate> struct LinearPreconditionerContractAudit final {
private:
using StaticContract = StaticPreconditionerContract<Candidate>;
using BackendContract = BackendPreconditionerContract<Candidate>;
using PhysicalContract = PhysicalInversePreconditionerContract<Candidate>;
public:
static constexpr bool declarationsValid = (!StaticContract::declared || StaticContract::registered) &&
(!BackendContract::declared || BackendContract::registered) &&
(!PhysicalContract::declared || PhysicalContract::registered);
static constexpr bool sourcesAgree = (!StaticContract::registered || !BackendContract::registered ||
StaticContract::value == BackendContract::value) &&
(!StaticContract::registered || !PhysicalContract::registered ||
StaticContract::value == PhysicalContract::value) &&
(!BackendContract::registered || !PhysicalContract::registered ||
BackendContract::value == PhysicalContract::value);
static constexpr bool registered =
declarationsValid && sourcesAgree &&
(StaticContract::registered || BackendContract::registered || PhysicalContract::registered);
static constexpr preconditioning::ApplicationContract value = [] {
if constexpr (StaticContract::registered) {
return StaticContract::value;
} else if constexpr (BackendContract::registered) {
return BackendContract::value;
} else if constexpr (PhysicalContract::registered) {
return PhysicalContract::value;
} else {
return preconditioning::ApplicationContract::stationary_linear;
}
}();
};
} // namespace mean_field::solver::detail
export namespace mean_field::solver {
template <typename Candidate>
concept LinearPreconditionerApplicationContractAvailable =
detail::LinearPreconditionerContractAudit<std::remove_cvref_t<Candidate>>::registered;
template <LinearPreconditionerApplicationContractAvailable Candidate>
inline constexpr preconditioning::ApplicationContract linearPreconditionerApplicationContract =
detail::LinearPreconditionerContractAudit<std::remove_cvref_t<Candidate>>::value;
template <typename Configuration, typename Preconditioner>
concept LinearBackendPreconditionerCompatible =
LinearBackendConfiguration<Configuration> && LinearPreconditionerApplicationContractAvailable<Preconditioner> &&
(std::remove_cvref_t<Configuration>::supportedPreconditionerContract ==
preconditioning::ApplicationContract::flexible ||
linearPreconditionerApplicationContract<std::remove_cvref_t<Preconditioner>> ==
preconditioning::ApplicationContract::stationary_linear);
/*
* A prepared backend is bound once to the exact operator and inverse that
* its owner keeps at stable addresses and identifies the communicator on
* which it operates. The communicator supplied to preparation is borrowed;
* a backend may retain it or own a congruent duplicate. The handle returned
* by GetCommunicator is borrowed from the backend and must not be freed by
* the caller. Every prepared backend must be destroyed before MPI_Finalize.
* It owns its numerical workspaces; neither copyability nor movability is
* required. Solve treats a caller-provided, correctly sized correction
* vector as its initial guess and overwrites it with the final correction.
*/
template <typename Candidate, typename Operator, typename Preconditioner>
concept PreparedLinearBackendFor = std::derived_from<std::remove_cvref_t<Operator>, mfem::Operator> &&
std::derived_from<std::remove_cvref_t<Preconditioner>, mfem::Solver> &&
std::destructible<std::remove_cvref_t<Candidate>> &&
requires(
std::remove_cvref_t<Candidate> &prepared,
const std::remove_cvref_t<Candidate> &constantPrepared,
const mfem::Vector &rightHandSide,
mfem::Vector &correction,
const LinearSolveControl &control
) {
{
constantPrepared.GetOperator()
} -> std::same_as<const std::remove_cvref_t<Operator> &>;
{
constantPrepared.GetPreconditioner()
} -> std::same_as<const std::remove_cvref_t<Preconditioner> &>;
{ constantPrepared.GetCommunicator() } -> std::same_as<MPI_Comm>;
{ constantPrepared.IsReady() } -> std::same_as<bool>;
{ constantPrepared.RightHandSideSize() } -> std::same_as<int>;
{ constantPrepared.CorrectionSize() } -> std::same_as<int>;
{
prepared.Solve(rightHandSide, correction, control)
} -> std::same_as<LinearSolveReport>;
};
/*
* `prepareLinearBackend` is intentionally unqualified in this detection
* boundary. A third-party configuration supplies its overload beside the
* configuration type and ADL discovers it without a library registry.
*/
template <typename Configuration, typename Operator, typename Preconditioner>
concept LinearBackendRuntimeAvailableFor =
LinearBackendPreconditionerCompatible<Configuration, Preconditioner> &&
std::derived_from<std::remove_cvref_t<Operator>, mfem::Operator> &&
std::derived_from<std::remove_cvref_t<Preconditioner>, mfem::Solver> &&
requires(
std::remove_cvref_t<Configuration> configuration,
const std::remove_cvref_t<Operator> &operation,
std::remove_cvref_t<Preconditioner> &preconditioner,
MPI_Comm communicator
) {
requires std::same_as<
decltype(prepareLinearBackend(std::move(configuration), operation, preconditioner, communicator)),
std::remove_cvref_t<
decltype(prepareLinearBackend(std::move(configuration), operation, preconditioner, communicator))>>;
{
prepareLinearBackend(std::move(configuration), operation, preconditioner, communicator)
} -> PreparedLinearBackendFor<std::remove_cvref_t<Operator>, std::remove_cvref_t<Preconditioner>>;
};
template <LinearBackendConfiguration Configuration, typename Operator, typename Preconditioner>
requires LinearBackendRuntimeAvailableFor<Configuration, Operator, Preconditioner>
using PreparedLinearBackendType = std::remove_cvref_t<decltype(prepareLinearBackend(
std::declval<std::remove_cvref_t<Configuration> &&>(),
std::declval<const std::remove_cvref_t<Operator> &>(),
std::declval<std::remove_cvref_t<Preconditioner> &>(),
std::declval<MPI_Comm>()
))>;
} // namespace mean_field::solver
export namespace mean_field::solver::linear {
struct FGMRESOptions final {
int restartLength{50};
int printLevel{-1};
void Validate() const {
if (restartLength <= 0) {
throw std::invalid_argument("MFEM FGMRES requires a positive restart length.");
}
if (printLevel < -1 || printLevel > 3) {
throw std::invalid_argument("MFEM FGMRES print level must be between -1 and 3.");
}
}
};
class FGMRES final : public LinearBackendConfigurationTag {
public:
static constexpr preconditioning::ApplicationContract supportedPreconditionerContract =
preconditioning::ApplicationContract::flexible;
FGMRES() = default;
explicit FGMRES(FGMRESOptions options) : m_options(std::move(options)) {
m_options.Validate();
}
[[nodiscard]] const FGMRESOptions &GetOptions() const noexcept {
return m_options;
}
private:
FGMRESOptions m_options{};
};
namespace detail {
template <typename Operation>
requires std::derived_from<std::remove_cvref_t<Operation>, mfem::Operator>
class CountedOperator final : public mfem::Operator {
public:
explicit CountedOperator(const Operation &operation)
: mfem::Operator(
operation.Height(),
operation.Width()
),
m_operation(std::addressof(operation)) {
}
void Mult(
const mfem::Vector &input,
mfem::Vector &output
) const override {
const auto start = std::chrono::steady_clock::now();
m_operation->Mult(input, output);
m_seconds += std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
++m_applications;
}
void Reset() const noexcept {
m_applications = 0;
m_seconds = 0.0;
}
[[nodiscard]] std::uint64_t Applications() const noexcept {
return m_applications;
}
[[nodiscard]] double Seconds() const noexcept {
return m_seconds;
}
private:
const Operation *m_operation;
mutable std::uint64_t m_applications{0};
mutable double m_seconds{0.0};
};
template <typename Operation, typename Preconditioner>
requires std::derived_from<std::remove_cvref_t<Operation>, mfem::Operator> &&
std::derived_from<std::remove_cvref_t<Preconditioner>, mfem::Solver>
class CountedPreconditioner final : public mfem::Solver {
public:
CountedPreconditioner(
const Operation &operation,
Preconditioner &preconditioner,
const CountedOperator<Operation> &countedOperation
)
: mfem::Solver(
preconditioner.Height(),
preconditioner.Width(),
false
),
m_operation(std::addressof(operation)),
m_preconditioner(std::addressof(preconditioner)),
m_countedOperation(std::addressof(countedOperation)) {
m_preconditioner->iterative_mode = false;
}
void SetOperator(const mfem::Operator &operation) override {
if (std::addressof(operation) != m_countedOperation) {
throw std::invalid_argument("The MFEM FGMRES preconditioner received an unexpected operator.");
}
m_preconditioner->SetOperator(*m_operation);
if (m_preconditioner->Height() != Height() || m_preconditioner->Width() != Width()) {
throw std::invalid_argument(
"The MFEM FGMRES preconditioner changed dimensions while binding its operator."
);
}
}
void Mult(
const mfem::Vector &input,
mfem::Vector &output
) const override {
const auto start = std::chrono::steady_clock::now();
m_preconditioner->Mult(input, output);
m_seconds += std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
++m_applications;
}
void Reset() const noexcept {
m_applications = 0;
m_seconds = 0.0;
}
[[nodiscard]] std::uint64_t Applications() const noexcept {
return m_applications;
}
[[nodiscard]] double Seconds() const noexcept {
return m_seconds;
}
private:
const Operation *m_operation;
Preconditioner *m_preconditioner;
const CountedOperator<Operation> *m_countedOperation;
mutable std::uint64_t m_applications{0};
mutable double m_seconds{0.0};
};
[[nodiscard]] inline bool MpiIsUsable() noexcept {
int initialized = 0;
int finalized = 0;
return MPI_Initialized(&initialized) == MPI_SUCCESS && initialized != 0 &&
MPI_Finalized(&finalized) == MPI_SUCCESS && finalized == 0;
}
[[nodiscard]] inline bool AllRanksAgree(
const bool localValue,
const MPI_Comm communicator
) {
int local = localValue ? 1 : 0;
int global = 0;
if (MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, communicator) != MPI_SUCCESS) {
throw std::runtime_error("MFEM FGMRES could not perform a communicator-wide validity check.");
}
return global != 0;
}
inline void RequireCollectivelyIdenticalConfiguration(
const FGMRESOptions &options,
const LinearSolveControl &control,
const MPI_Comm communicator
) {
const std::array<double, 2> localRealValues{control.relativeTolerance, control.absoluteTolerance};
std::array<double, 2> minimumRealValues{};
std::array<double, 2> maximumRealValues{};
const std::array<int, 3> localIntegerValues{
control.maximumIterations, options.restartLength, options.printLevel
};
std::array<int, 3> minimumIntegerValues{};
std::array<int, 3> maximumIntegerValues{};
if (MPI_Allreduce(
localRealValues.data(), minimumRealValues.data(), static_cast<int>(localRealValues.size()),
MPI_DOUBLE, MPI_MIN, communicator
) != MPI_SUCCESS ||
MPI_Allreduce(
localRealValues.data(), maximumRealValues.data(), static_cast<int>(localRealValues.size()),
MPI_DOUBLE, MPI_MAX, communicator
) != MPI_SUCCESS ||
MPI_Allreduce(
localIntegerValues.data(), minimumIntegerValues.data(), static_cast<int>(localIntegerValues.size()),
MPI_INT, MPI_MIN, communicator
) != MPI_SUCCESS ||
MPI_Allreduce(
localIntegerValues.data(), maximumIntegerValues.data(), static_cast<int>(localIntegerValues.size()),
MPI_INT, MPI_MAX, communicator
) != MPI_SUCCESS) {
throw std::runtime_error("MFEM FGMRES could not validate its distributed configuration.");
}
if (minimumRealValues != maximumRealValues || minimumIntegerValues != maximumIntegerValues) {
throw std::invalid_argument(
"MFEM FGMRES requires identical options and solve controls on every communicator rank."
);
}
}
[[nodiscard]] inline bool LocallyFinite(const mfem::Vector &values) {
for (int index = 0; index < values.Size(); ++index) {
if (!std::isfinite(values(index))) {
return false;
}
}
return true;
}
[[nodiscard]] inline double GlobalNorm(
const mfem::Vector &values,
const MPI_Comm communicator
) {
const double localNorm = values.Norml2();
const double localNormSquared = localNorm * localNorm;
double globalNormSquared = 0.0;
if (MPI_Allreduce(&localNormSquared, &globalNormSquared, 1, MPI_DOUBLE, MPI_SUM, communicator) !=
MPI_SUCCESS) {
throw std::runtime_error("MFEM FGMRES could not reduce a global vector norm.");
}
if (!std::isfinite(globalNormSquared) || globalNormSquared < 0.0) {
return std::numeric_limits<double>::quiet_NaN();
}
return std::sqrt(globalNormSquared);
}
[[nodiscard]] inline double MaximumRankValue(
const double localValue,
const MPI_Comm communicator
) {
double maximumValue = 0.0;
if (MPI_Allreduce(&localValue, &maximumValue, 1, MPI_DOUBLE, MPI_MAX, communicator) != MPI_SUCCESS) {
throw std::runtime_error("MFEM FGMRES could not reduce a communicator-wide timing measurement.");
}
return maximumValue;
}
template <typename Candidate> [[nodiscard]] bool RuntimeDependencyIsCurrent(const Candidate &candidate) {
if constexpr (requires {
{ candidate.IsCurrent() } -> std::same_as<bool>;
}) {
return candidate.IsCurrent();
} else if constexpr (requires {
{ candidate.IsPrepared() } -> std::same_as<bool>;
}) {
return candidate.IsPrepared();
} else {
return true;
}
}
template <typename Operation, typename Preconditioner>
requires std::derived_from<std::remove_cvref_t<Operation>, mfem::Operator> &&
std::derived_from<std::remove_cvref_t<Preconditioner>, mfem::Solver>
class PreparedFGMRES final {
private:
using Clock = std::chrono::steady_clock;
public:
PreparedFGMRES(
FGMRESOptions options,
const Operation &operation,
Preconditioner &preconditioner,
const MPI_Comm communicator
)
: m_options(std::move(options)),
m_operation(std::addressof(operation)),
m_preconditioner(std::addressof(preconditioner)),
m_communicator(communicator),
m_countedOperation(operation),
m_countedPreconditioner(
operation,
preconditioner,
m_countedOperation
),
m_solver(communicator),
m_rightHandSide(operation.Height()),
m_operationAction(operation.Height()),
m_trueResidual(operation.Height()) {
m_options.Validate();
if (!MpiIsUsable()) {
throw std::logic_error("MFEM FGMRES requires initialized MPI that has not been finalized.");
}
if (m_communicator == MPI_COMM_NULL) {
throw std::invalid_argument("MFEM FGMRES requires a non-null MPI communicator.");
}
if (operation.Height() <= 0 || operation.Width() <= 0 || operation.Height() != operation.Width() ||
preconditioner.Height() != operation.Width() || preconditioner.Width() != operation.Height()) {
throw std::invalid_argument(
"MFEM FGMRES requires compatible square operator and preconditioner dimensions."
);
}
m_rightHandSide = 0.0;
m_operationAction = 0.0;
m_trueResidual = 0.0;
m_solver.SetPreconditioner(m_countedPreconditioner);
m_solver.SetOperator(m_countedOperation);
m_solver.SetKDim(m_options.restartLength);
m_solver.SetPrintLevel(m_options.printLevel);
m_solver.iterative_mode = true;
}
PreparedFGMRES(const PreparedFGMRES &) = delete;
PreparedFGMRES &operator=(const PreparedFGMRES &) = delete;
PreparedFGMRES(PreparedFGMRES &&) = delete;
PreparedFGMRES &operator=(PreparedFGMRES &&) = delete;
[[nodiscard]] const Operation &GetOperator() const noexcept {
return *m_operation;
}
[[nodiscard]] const Preconditioner &GetPreconditioner() const noexcept {
return *m_preconditioner;
}
[[nodiscard]] MPI_Comm GetCommunicator() const noexcept {
return m_communicator;
}
[[nodiscard]] bool IsReady() const {
return m_operation != nullptr && m_preconditioner != nullptr && m_communicator != MPI_COMM_NULL &&
m_operation->Height() == m_operation->Width() &&
m_preconditioner->Height() == m_operation->Width() &&
m_preconditioner->Width() == m_operation->Height() &&
m_rightHandSide.Size() == m_operation->Height() &&
m_operationAction.Size() == m_operation->Height() &&
m_trueResidual.Size() == m_operation->Height() && RuntimeDependencyIsCurrent(*m_operation) &&
RuntimeDependencyIsCurrent(*m_preconditioner);
}
[[nodiscard]] int RightHandSideSize() const noexcept {
return m_operation->Height();
}
[[nodiscard]] int CorrectionSize() const noexcept {
return m_operation->Width();
}
[[nodiscard]] LinearSolveReport Solve(
const mfem::Vector &rightHandSide,
mfem::Vector &correction,
const LinearSolveControl &control
) {
bool localConfigurationIsValid = true;
try {
m_options.Validate();
control.Validate();
} catch (const std::invalid_argument &) {
localConfigurationIsValid = false;
}
if (!AllRanksAgree(localConfigurationIsValid, m_communicator)) {
throw std::invalid_argument(
"MFEM FGMRES requires valid options and solve controls on every communicator rank."
);
}
if (!localConfigurationIsValid) {
throw std::invalid_argument("MFEM FGMRES received invalid options or solve controls.");
}
RequireCollectivelyIdenticalConfiguration(m_options, control, m_communicator);
if (!AllRanksAgree(IsReady(), m_communicator)) {
throw std::logic_error(
"MFEM FGMRES requires a complete, current prepared runtime on every communicator rank."
);
}
if (!AllRanksAgree(
rightHandSide.Size() == RightHandSideSize() && correction.Size() == CorrectionSize(),
m_communicator
)) {
throw std::invalid_argument(
"MFEM FGMRES received incompatible linear-system vectors on at least one rank."
);
}
if (!AllRanksAgree(LocallyFinite(rightHandSide), m_communicator) ||
!AllRanksAgree(LocallyFinite(correction), m_communicator)) {
throw std::invalid_argument(
"MFEM FGMRES requires finite right-hand side and initial-guess values."
);
}
m_rightHandSide = rightHandSide;
const double rightHandSideNorm = GlobalNorm(m_rightHandSide, m_communicator);
const double threshold = control.ConvergenceThreshold(rightHandSideNorm);
m_countedOperation.Reset();
m_countedPreconditioner.Reset();
m_solver.SetRelTol(0.0);
m_solver.SetAbsTol(threshold);
m_solver.SetMaxIter(control.maximumIterations);
const Clock::time_point start = Clock::now();
m_solver.Mult(m_rightHandSide, correction);
const double solveSeconds =
MaximumRankValue(std::chrono::duration<double>(Clock::now() - start).count(), m_communicator);
const bool correctionIsFinite = AllRanksAgree(LocallyFinite(correction), m_communicator);
double trueResidualNorm = std::numeric_limits<double>::quiet_NaN();
if (correctionIsFinite) {
m_countedOperation.Mult(correction, m_operationAction);
m_trueResidual = m_rightHandSide;
m_trueResidual -= m_operationAction;
if (AllRanksAgree(LocallyFinite(m_trueResidual), m_communicator)) {
trueResidualNorm = GlobalNorm(m_trueResidual, m_communicator);
}
}
const double initialResidualNorm = m_solver.GetInitialNorm();
const double reportedResidualNorm = m_solver.GetFinalNorm();
const std::uint64_t krylovIterationCount = m_countedPreconditioner.Applications();
if (krylovIterationCount > static_cast<std::uint64_t>(std::numeric_limits<int>::max())) {
throw std::overflow_error("MFEM FGMRES reported more Krylov iterations than can be represented.");
}
const int iterations = static_cast<int>(krylovIterationCount);
// A restart is an additional Krylov cycle entered after the
// initial cycle, not the residual check at a cycle boundary.
const int restarts = iterations > 0 ? (iterations - 1) / m_options.restartLength : 0;
const bool numericalValuesAreFinite = correctionIsFinite && std::isfinite(initialResidualNorm) &&
std::isfinite(reportedResidualNorm) &&
std::isfinite(trueResidualNorm);
LinearSolveStatus status = LinearSolveStatus::backend_failure;
if (!numericalValuesAreFinite) {
status = LinearSolveStatus::non_finite;
} else if (trueResidualNorm <= threshold) {
status = LinearSolveStatus::converged;
} else if (!m_solver.GetConverged() && iterations >= control.maximumIterations) {
status = LinearSolveStatus::maximum_iterations;
}
const double relativeTrueResidualNorm =
rightHandSideNorm > 0.0 ? trueResidualNorm / rightHandSideNorm
: (trueResidualNorm == 0.0 ? 0.0 : std::numeric_limits<double>::infinity());
const double operatorSeconds = MaximumRankValue(m_countedOperation.Seconds(), m_communicator);
const double inversePreconditionerSeconds =
MaximumRankValue(m_countedPreconditioner.Seconds(), m_communicator);
return {
.status = status,
.control = control,
.iterations = iterations,
.restarts = restarts,
.rightHandSideNorm = rightHandSideNorm,
.initialResidualNorm = initialResidualNorm,
.reportedResidualNorm = reportedResidualNorm,
.trueResidualNorm = trueResidualNorm,
.relativeTrueResidualNorm = relativeTrueResidualNorm,
.operatorApplications = m_countedOperation.Applications(),
.inversePreconditionerApplications = m_countedPreconditioner.Applications(),
.solveSeconds = solveSeconds,
.operatorSeconds = operatorSeconds,
.inversePreconditionerSeconds = inversePreconditionerSeconds
};
}
private:
FGMRESOptions m_options;
const Operation *m_operation;
Preconditioner *m_preconditioner;
MPI_Comm m_communicator;
CountedOperator<Operation> m_countedOperation;
CountedPreconditioner<Operation, Preconditioner> m_countedPreconditioner;
mfem::FGMRESSolver m_solver;
mfem::Vector m_rightHandSide;
mfem::Vector m_operationAction;
mfem::Vector m_trueResidual;
};
} // namespace detail
template <
typename Operation,
typename Preconditioner>
requires std::derived_from<
std::remove_cvref_t<Operation>,
mfem::Operator> &&
std::derived_from<
std::remove_cvref_t<Preconditioner>,
mfem::Solver>
[[nodiscard]] auto prepareLinearBackend(
FGMRES configuration,
const Operation &operation,
Preconditioner &preconditioner,
const MPI_Comm communicator
) {
return detail::PreparedFGMRES<Operation, Preconditioner>{
configuration.GetOptions(), operation, preconditioner, communicator
};
}
} // namespace mean_field::solver::linear

View File

@@ -0,0 +1,579 @@
module;
#include <cmath>
#include <concepts>
#include <cstdint>
#include <exception>
#include <functional>
#include <optional>
#include <span>
#include <stdexcept>
#include <string_view>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
#include <mpi.h>
export module mean_field:solver.newton;
export import :solver.linear_backend;
export namespace mean_field::solver::nonlinear {
/*
* Backtracking counts the full Newton trial as its first trial. A
* contraction is applied only after that candidate has been rejected.
*/
struct BacktrackingOptions final {
double initialStepLength{1.0};
double contractionFactor{0.5};
double fractionToBoundarySafety{0.9};
double sufficientDecrease{1.0e-4};
double minimumStepLength{1.0e-8};
int maximumTrials{20};
void Validate() const {
if (!std::isfinite(initialStepLength) || initialStepLength <= 0.0) {
throw std::invalid_argument("Newton backtracking requires a finite, positive initial step length.");
}
if (!std::isfinite(contractionFactor) || contractionFactor <= 0.0 || contractionFactor >= 1.0) {
throw std::invalid_argument(
"Newton backtracking requires a finite contraction factor strictly between zero and one."
);
}
if (!std::isfinite(fractionToBoundarySafety) || fractionToBoundarySafety <= 0.0 ||
fractionToBoundarySafety >= 1.0) {
throw std::invalid_argument(
"Newton backtracking requires a finite fraction-to-boundary safety factor strictly between zero "
"and one."
);
}
if (!std::isfinite(sufficientDecrease) || sufficientDecrease <= 0.0 || sufficientDecrease >= 1.0) {
throw std::invalid_argument(
"Newton backtracking requires a finite sufficient-decrease factor strictly between zero and one."
);
}
if (!std::isfinite(minimumStepLength) || minimumStepLength <= 0.0 ||
minimumStepLength > initialStepLength) {
throw std::invalid_argument(
"Newton backtracking requires a finite, positive minimum step no larger than the initial step."
);
}
if (maximumTrials <= 0) {
throw std::invalid_argument("Newton backtracking requires at least one permitted trial.");
}
}
};
struct NewtonOptions final {
double relativeTolerance{1.0e-8};
double absoluteTolerance{0.0};
int maximumIterations{50};
LinearSolveControl linearSolve{};
BacktrackingOptions backtracking{};
void Validate() const {
if (!std::isfinite(relativeTolerance) || relativeTolerance < 0.0) {
throw std::invalid_argument("A Newton solve requires a finite, non-negative relative tolerance.");
}
if (!std::isfinite(absoluteTolerance) || absoluteTolerance < 0.0) {
throw std::invalid_argument("A Newton solve requires a finite, non-negative absolute tolerance.");
}
if (maximumIterations <= 0) {
throw std::invalid_argument("A Newton solve requires at least one permitted iteration.");
}
linearSolve.Validate();
backtracking.Validate();
}
[[nodiscard]] double ConvergenceThreshold(const double initialResidualNorm) const {
Validate();
if (!std::isfinite(initialResidualNorm) || initialResidualNorm < 0.0) {
throw std::invalid_argument(
"A Newton convergence threshold requires a finite, non-negative initial residual norm."
);
}
const double relativeThreshold = relativeTolerance * initialResidualNorm;
if (!std::isfinite(relativeThreshold)) {
throw std::invalid_argument("The Newton relative convergence threshold must be finite.");
}
return absoluteTolerance > relativeThreshold ? absoluteTolerance : relativeThreshold;
}
};
/*
* The MVP globalization merit is phi(x) = 0.5 ||F_normalized(x)||^2.
* A metric customization receives the solve communicator and must return
* communicator-consistent values or throw collectively. The Newton engine
* reduces every predicate that drives control flow, but it cannot make a
* rank-local exception inside an arbitrary callback collective-safe.
*/
struct NormalizedResidualMetric final { };
struct MetricEvaluation final {
double residualNorm{0.0};
double merit{0.0};
};
[[nodiscard]] inline MetricEvaluation getMetric(
const NormalizedResidualMetric &,
const mfem::Vector &normalizedResidual,
const MPI_Comm communicator
) {
if (communicator == MPI_COMM_NULL) {
throw std::invalid_argument("A nonlinear metric requires a valid communicator.");
}
double localSquaredNorm = 0.0;
for (int index = 0; index < normalizedResidual.Size(); ++index) {
const double value = static_cast<double>(normalizedResidual(index));
localSquaredNorm += value * value;
}
double globalSquaredNorm = 0.0;
if (MPI_Allreduce(&localSquaredNorm, &globalSquaredNorm, 1, MPI_DOUBLE, MPI_SUM, communicator) != MPI_SUCCESS) {
throw std::runtime_error("The nonlinear metric could not reduce the normalized residual norm.");
}
const double residualNorm = std::sqrt(globalSquaredNorm);
return {.residualNorm = residualNorm, .merit = 0.5 * residualNorm * residualNorm};
}
template <typename Metric = NormalizedResidualMetric>
requires std::move_constructible<std::remove_cvref_t<Metric>>
class Newton final {
public:
using MetricType = std::remove_cvref_t<Metric>;
Newton()
requires std::default_initializable<MetricType>
: Newton(
NewtonOptions{},
MetricType{}
) {
}
explicit Newton(NewtonOptions options)
requires std::default_initializable<MetricType>
: Newton(
std::move(options),
MetricType{}
) {
}
Newton(
NewtonOptions options,
MetricType metric
)
: m_options(std::move(options)),
m_metric(std::move(metric)) {
m_options.Validate();
}
[[nodiscard]] const NewtonOptions &options() const noexcept {
return m_options;
}
[[nodiscard]] const MetricType &metric() const noexcept {
return m_metric;
}
private:
NewtonOptions m_options;
[[no_unique_address]] MetricType m_metric;
};
Newton() -> Newton<NormalizedResidualMetric>;
Newton(NewtonOptions) -> Newton<NormalizedResidualMetric>;
template <typename Metric>
Newton(
NewtonOptions,
Metric
) -> Newton<std::remove_cvref_t<Metric>>;
template <typename Candidate> struct IsNewtonConfiguration : std::false_type { };
template <typename Metric> struct IsNewtonConfiguration<Newton<Metric>> : std::true_type { };
template <typename Candidate>
concept NewtonConfiguration = IsNewtonConfiguration<std::remove_cvref_t<Candidate>>::value;
enum class IterationDisposition : std::uint8_t {
unspecified,
accepted,
converged,
inadmissible_state,
non_finite_state,
non_finite_residual,
linear_solve_failure,
globalization_failure,
stagnation,
iteration_limit
};
/*
* Event spans borrow solver workspaces and are valid only for the duration
* of the callback. Copy values that must outlive the callback.
*/
struct BeforeIteration final {
int iteration{0};
double initialResidualNorm{0.0};
double residualNorm{0.0};
double relativeResidualNorm{0.0};
double merit{0.0};
MPI_Comm communicator{MPI_COMM_NULL};
std::span<const mfem::real_t> physicalState{};
std::span<const mfem::real_t> normalizedState{};
std::span<const mfem::real_t> normalizedResidual{};
};
enum class LineSearchTrialDisposition : std::uint8_t {
accepted,
inadmissible_state,
non_finite_state,
non_finite_residual,
insufficient_decrease
};
struct AfterLineSearchTrial final {
int iteration{0};
int trial{0};
double stepLength{0.0};
LineSearchTrialDisposition disposition{LineSearchTrialDisposition::insufficient_decrease};
std::string_view rejectionSource{};
std::optional<MetricEvaluation> metric{};
std::optional<double> minimumJacobianDeterminant{};
double preparationSeconds{0.0};
double metricSeconds{0.0};
MPI_Comm communicator{MPI_COMM_NULL};
std::span<const mfem::real_t> candidatePhysicalState{};
std::span<const mfem::real_t> candidateNormalizedState{};
std::span<const mfem::real_t> candidateNormalizedResidual{};
};
struct AfterIteration final {
int iteration{0};
IterationDisposition disposition{IterationDisposition::unspecified};
bool stepAccepted{false};
double acceptedStepLength{0.0};
int lineSearchTrials{0};
double initialResidualNorm{0.0};
double previousResidualNorm{0.0};
double residualNorm{0.0};
double relativeResidualNorm{0.0};
double merit{0.0};
double iterationSeconds{0.0};
double lineSearchSeconds{0.0};
double trialPreparationSeconds{0.0};
double metricEvaluationSeconds{0.0};
double preconditionerRefreshSeconds{0.0};
double rollbackSeconds{0.0};
std::optional<LinearSolveReport> linearSolve{};
MPI_Comm communicator{MPI_COMM_NULL};
std::span<const mfem::real_t> physicalState{};
std::span<const mfem::real_t> normalizedState{};
std::span<const mfem::real_t> normalizedResidual{};
};
struct NoObserver final { };
template <typename Candidate>
concept BeforeIterationCallback = std::invocable<Candidate &, const BeforeIteration &> &&
std::same_as<std::invoke_result_t<Candidate &, const BeforeIteration &>, void>;
template <typename Candidate>
concept AfterIterationCallback = std::invocable<Candidate &, const AfterIteration &> &&
std::same_as<std::invoke_result_t<Candidate &, const AfterIteration &>, void>;
template <typename Candidate>
concept LineSearchTrialCallback =
std::invocable<Candidate &, const AfterLineSearchTrial &> &&
std::same_as<std::invoke_result_t<Candidate &, const AfterLineSearchTrial &>, void>;
template <BeforeIterationCallback BeforeCallback, AfterIterationCallback AfterCallback>
class CallbackObserver final {
public:
CallbackObserver(
BeforeCallback before,
AfterCallback after
)
: m_before(std::move(before)),
m_after(std::move(after)) {
}
void beforeIteration(const BeforeIteration &event) noexcept(std::is_nothrow_invocable_v<
BeforeCallback &,
const BeforeIteration &>) {
std::invoke(m_before, event);
}
void afterIteration(const AfterIteration &event) noexcept(std::is_nothrow_invocable_v<
AfterCallback &,
const AfterIteration &>) {
std::invoke(m_after, event);
}
private:
[[no_unique_address]] BeforeCallback m_before;
[[no_unique_address]] AfterCallback m_after;
};
template <
typename BeforeCallback,
typename AfterCallback>
requires BeforeIterationCallback<std::decay_t<BeforeCallback>> &&
AfterIterationCallback<std::decay_t<AfterCallback>> &&
std::constructible_from<
std::decay_t<BeforeCallback>,
BeforeCallback> &&
std::constructible_from<
std::decay_t<AfterCallback>,
AfterCallback>
[[nodiscard]] auto makeObserver(
BeforeCallback &&before,
AfterCallback &&after
) {
return CallbackObserver<std::decay_t<BeforeCallback>, std::decay_t<AfterCallback>>{
std::forward<BeforeCallback>(before), std::forward<AfterCallback>(after)
};
}
template <
BeforeIterationCallback BeforeCallback,
LineSearchTrialCallback TrialCallback,
AfterIterationCallback AfterCallback>
class DetailedCallbackObserver final {
public:
DetailedCallbackObserver(
BeforeCallback before,
TrialCallback trial,
AfterCallback after
)
: m_before(std::move(before)),
m_trial(std::move(trial)),
m_after(std::move(after)) {
}
void beforeIteration(const BeforeIteration &event) noexcept(std::is_nothrow_invocable_v<
BeforeCallback &,
const BeforeIteration &>) {
std::invoke(m_before, event);
}
void afterLineSearchTrial(const AfterLineSearchTrial &event) noexcept(std::is_nothrow_invocable_v<
TrialCallback &,
const AfterLineSearchTrial &>) {
std::invoke(m_trial, event);
}
void afterIteration(const AfterIteration &event) noexcept(std::is_nothrow_invocable_v<
AfterCallback &,
const AfterIteration &>) {
std::invoke(m_after, event);
}
private:
[[no_unique_address]] BeforeCallback m_before;
[[no_unique_address]] TrialCallback m_trial;
[[no_unique_address]] AfterCallback m_after;
};
template <
typename BeforeCallback,
typename TrialCallback,
typename AfterCallback>
requires BeforeIterationCallback<std::decay_t<BeforeCallback>> &&
LineSearchTrialCallback<std::decay_t<TrialCallback>> &&
AfterIterationCallback<std::decay_t<AfterCallback>> &&
std::constructible_from<
std::decay_t<BeforeCallback>,
BeforeCallback> &&
std::constructible_from<
std::decay_t<TrialCallback>,
TrialCallback> &&
std::constructible_from<
std::decay_t<AfterCallback>,
AfterCallback>
[[nodiscard]] auto makeObserver(
BeforeCallback &&before,
TrialCallback &&trial,
AfterCallback &&after
) {
return DetailedCallbackObserver<
std::decay_t<BeforeCallback>, std::decay_t<TrialCallback>, std::decay_t<AfterCallback>>{
std::forward<BeforeCallback>(before), std::forward<TrialCallback>(trial), std::forward<AfterCallback>(after)
};
}
namespace detail {
[[nodiscard]] inline double NextBacktrackingStepLength(
const double rejectedStepLength,
const double acceptedMinimumJacobianDeterminant,
const std::optional<double> rejectedMinimumJacobianDeterminant,
const bool rejectedByInvertedGeometry,
const BacktrackingOptions &options
) noexcept {
const double contractedStepLength = rejectedStepLength * options.contractionFactor;
if (!rejectedByInvertedGeometry || !rejectedMinimumJacobianDeterminant.has_value() ||
!std::isfinite(acceptedMinimumJacobianDeterminant) || acceptedMinimumJacobianDeterminant <= 0.0 ||
!std::isfinite(*rejectedMinimumJacobianDeterminant) || *rejectedMinimumJacobianDeterminant > 0.0) {
return contractedStepLength;
}
const double determinantChange = acceptedMinimumJacobianDeterminant - *rejectedMinimumJacobianDeterminant;
if (!std::isfinite(determinantChange) || determinantChange <= 0.0) {
return contractedStepLength;
}
const double estimatedBoundaryStep =
rejectedStepLength * acceptedMinimumJacobianDeterminant / determinantChange;
const double safeguardedStep = options.fractionToBoundarySafety * estimatedBoundaryStep;
if (!std::isfinite(safeguardedStep) || safeguardedStep <= 0.0 || safeguardedStep >= rejectedStepLength) {
return contractedStepLength;
}
/*
* Keep the configured backtracking ladder intact. The geometry
* certificate is used only to skip rungs that its local boundary
* estimate says are unsafe; it does not introduce a new trial
* length between two rungs. This preserves the candidates that
* ordinary backtracking would eventually test while avoiding the
* expensive preparation of the skipped, inverted geometries.
*/
if (contractedStepLength <= safeguardedStep) {
return contractedStepLength;
}
const double rung =
std::ceil(std::log(safeguardedStep / rejectedStepLength) / std::log(options.contractionFactor));
double skippedStep = rejectedStepLength * std::pow(options.contractionFactor, rung);
if (!std::isfinite(skippedStep) || skippedStep <= 0.0 || skippedStep >= rejectedStepLength) {
return contractedStepLength;
}
if (skippedStep > safeguardedStep) {
skippedStep *= options.contractionFactor;
}
return skippedStep;
}
template <typename Observer>
inline constexpr bool isNoObserver = std::same_as<std::remove_cvref_t<Observer>, NoObserver>;
template <typename Observer>
concept ObservesBeforeIteration =
!isNoObserver<Observer> &&
requires(std::remove_reference_t<Observer> &observer, const BeforeIteration &event) {
{ observer.beforeIteration(event) } -> std::same_as<void>;
};
template <typename Observer>
concept ObservesAfterIteration =
!isNoObserver<Observer> &&
requires(std::remove_reference_t<Observer> &observer, const AfterIteration &event) {
{ observer.afterIteration(event) } -> std::same_as<void>;
};
template <typename Observer>
concept ObservesLineSearchTrial =
!isNoObserver<Observer> &&
requires(std::remove_reference_t<Observer> &observer, const AfterLineSearchTrial &event) {
{ observer.afterLineSearchTrial(event) } -> std::same_as<void>;
};
template <typename Callback>
void InvokeObserverHookCollectively(
const MPI_Comm communicator,
const char *remoteFailureMessage,
Callback &&callback
) {
std::exception_ptr localFailure;
try {
std::invoke(std::forward<Callback>(callback));
} catch (...) {
localFailure = std::current_exception();
}
const int localFailureFlag = localFailure != nullptr ? 1 : 0;
int globalFailureFlag = 0;
if (MPI_Allreduce(&localFailureFlag, &globalFailureFlag, 1, MPI_INT, MPI_MAX, communicator) !=
MPI_SUCCESS) {
if (localFailure != nullptr) {
std::rethrow_exception(localFailure);
}
throw std::runtime_error("The nonlinear solver could not synchronize an observer callback.");
}
if (globalFailureFlag != 0) {
if (localFailure != nullptr) {
std::rethrow_exception(localFailure);
}
throw std::runtime_error(remoteFailureMessage);
}
}
template <typename Observer>
void InvokeBeforeIteration(
Observer &observer,
const BeforeIteration &event
) {
if constexpr (ObservesBeforeIteration<Observer>) {
if constexpr (noexcept(observer.beforeIteration(event))) {
observer.beforeIteration(event);
} else {
InvokeObserverHookCollectively(
event.communicator, "An observer before-iteration callback failed on another rank.",
[&observer, &event] { observer.beforeIteration(event); }
);
}
}
}
template <typename Observer>
void InvokeAfterIteration(
Observer &observer,
const AfterIteration &event
) {
if constexpr (ObservesAfterIteration<Observer>) {
if constexpr (noexcept(observer.afterIteration(event))) {
observer.afterIteration(event);
} else {
InvokeObserverHookCollectively(
event.communicator, "An observer after-iteration callback failed on another rank.",
[&observer, &event] { observer.afterIteration(event); }
);
}
}
}
template <typename Observer>
void InvokeAfterLineSearchTrial(
Observer &observer,
const AfterLineSearchTrial &event
) {
if constexpr (ObservesLineSearchTrial<Observer>) {
if constexpr (noexcept(observer.afterLineSearchTrial(event))) {
observer.afterLineSearchTrial(event);
} else {
InvokeObserverHookCollectively(
event.communicator, "An observer line-search callback failed on another rank.",
[&observer, &event] { observer.afterLineSearchTrial(event); }
);
}
}
}
} // namespace detail
/*
* Observers run synchronously on every solve rank. Ordinary callback
* exceptions are synchronized before the solver proceeds, so all ranks can
* unwind together; explicitly noexcept callbacks bypass that synchronization.
* A callback must still not enter an MPI collective on only a subset of
* ranks. A before/after pair is guaranteed for iterations that finish by
* returning an evaluation report. Infrastructure exceptions unwind
* immediately and do not promise an after callback.
*/
template <typename Candidate>
concept NewtonObserver = detail::isNoObserver<Candidate> || detail::ObservesBeforeIteration<Candidate> ||
detail::ObservesLineSearchTrial<Candidate> || detail::ObservesAfterIteration<Candidate>;
} // namespace mean_field::solver::nonlinear

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
export module mean_field:solver.stellar_equilibrium;
export import :solver.stellar_structure;
export import :solver.stellar_context;

View File

@@ -0,0 +1,60 @@
module;
#include <cstdint>
#include <optional>
#include <string>
export module mean_field:solver.stellar_equilibrium_types;
export import :solver.linear_backend;
export namespace mean_field::solver {
enum class StellarEquilibriumFailureReason : std::uint8_t {
unspecified,
inadmissible_state,
non_finite_state,
non_finite_residual,
linear_solve_failure,
globalization_failure,
stagnation,
iteration_limit
};
/*
* An owning, backend-neutral account of an expected numerical failure.
* Backend-specific measurements may be translated into the message or
* future common diagnostics, but are deliberately not part of this stable
* result boundary.
*/
struct StellarEquilibriumFailureReport final {
StellarEquilibriumFailureReason reason{StellarEquilibriumFailureReason::unspecified};
std::string message;
int completedNonlinearIterations{0};
std::optional<double> initialResidualNorm;
std::optional<double> finalResidualNorm;
};
/*
* Fixed-size diagnostics retained by every evaluation report. Detailed
* iteration histories belong in an observer so the default solve does not
* allocate storage proportional to the iteration count.
*/
struct StellarEquilibriumEvaluationDiagnostics final {
int attemptedNonlinearIterations{0};
int acceptedNonlinearIterations{0};
int totalLineSearchTrials{0};
int inadmissibleLineSearchTrials{0};
int nonFiniteLineSearchTrials{0};
int insufficientDecreaseTrials{0};
double initialResidualNorm{0.0};
double finalResidualNorm{0.0};
double lastAcceptedStepLength{0.0};
double totalLinearSolveSeconds{0.0};
double totalLineSearchSeconds{0.0};
double totalTrialPreparationSeconds{0.0};
double totalMetricEvaluationSeconds{0.0};
double totalPreconditionerRefreshSeconds{0.0};
double totalRollbackSeconds{0.0};
std::optional<LinearSolveReport> lastLinearSolve;
};
} // namespace mean_field::solver

View File

@@ -0,0 +1,486 @@
module;
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
#include <mpi.h>
export module mean_field:solver.stellar_structure;
export import :operators.stellar_equilibrium_problem;
export import :solver.stellar_equilibrium_types;
export namespace mean_field::solver {
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem> class StellarEquilibriumEvaluationReport;
}
namespace mean_field::solver::detail {
enum class StellarViewCertification : std::uint8_t { unavailable, checkpoint, structure };
template <typename Problem> struct StellarStructureStorage final {
using ProblemType = std::remove_cvref_t<Problem>;
StellarStructureStorage(
std::unique_ptr<ProblemType> ownedProblem,
std::unique_ptr<mfem::Vector> acceptedPhysicalState,
std::unique_ptr<physics::RigidRotation> prescribedRotation
)
: problem(std::move(ownedProblem)),
physicalState(std::move(acceptedPhysicalState)),
rotation(std::move(prescribedRotation)) {
}
std::unique_ptr<ProblemType> problem;
std::unique_ptr<mfem::Vector> physicalState;
std::unique_ptr<physics::RigidRotation> rotation;
std::uint64_t viewGeneration{0};
StellarViewCertification certification{StellarViewCertification::unavailable};
};
template <typename Problem>
[[nodiscard]] bool IsCurrentView(
const std::weak_ptr<const StellarStructureStorage<Problem>> &candidate,
const std::uint64_t generation,
const bool requireConverged
) noexcept {
const auto storage = candidate.lock();
if (storage == nullptr || storage->problem == nullptr || storage->physicalState == nullptr ||
storage->viewGeneration != generation) {
return false;
}
if (requireConverged) {
return storage->certification == StellarViewCertification::structure;
}
return storage->certification == StellarViewCertification::checkpoint ||
storage->certification == StellarViewCertification::structure;
}
template <typename Problem>
[[nodiscard]] std::shared_ptr<const StellarStructureStorage<Problem>> RequireCurrentView(
const std::weak_ptr<const StellarStructureStorage<Problem>> &candidate,
const std::uint64_t generation,
const bool requireConverged
) {
auto storage = candidate.lock();
if (storage == nullptr || storage->problem == nullptr || storage->physicalState == nullptr ||
storage->viewGeneration != generation ||
(requireConverged && storage->certification != StellarViewCertification::structure) ||
(!requireConverged && storage->certification != StellarViewCertification::checkpoint &&
storage->certification != StellarViewCertification::structure)) {
throw std::logic_error("The stellar structure view is stale or is not certified for this result.");
}
return storage;
}
template <typename Vector> [[nodiscard]] std::span<const mfem::real_t> ReadOnlySpan(const Vector &values) noexcept {
return {values.GetData(), static_cast<std::size_t>(values.Size())};
}
template <typename Problem> struct StellarEvaluationReportAccess;
} // namespace mean_field::solver::detail
export namespace mean_field::equilibrium {
/*
* These are the future owning, self-contained values. There is no public
* construction path until deep capture and its MPI-independent storage
* schema are implemented.
*/
template <DiscretizedStellarEquilibriumProblem Problem> class StellarStructure final {
public:
using ProblemType = std::remove_cvref_t<Problem>;
StellarStructure(const StellarStructure &) = delete;
StellarStructure &operator=(const StellarStructure &) = delete;
StellarStructure(StellarStructure &&) noexcept = default;
StellarStructure &operator=(StellarStructure &&) = delete;
~StellarStructure() = default;
private:
StellarStructure() = default;
};
template <DiscretizedStellarEquilibriumProblem Problem> class StellarCheckpoint final {
public:
using ProblemType = std::remove_cvref_t<Problem>;
StellarCheckpoint(const StellarCheckpoint &) = delete;
StellarCheckpoint &operator=(const StellarCheckpoint &) = delete;
StellarCheckpoint(StellarCheckpoint &&) noexcept = default;
StellarCheckpoint &operator=(StellarCheckpoint &&) = delete;
~StellarCheckpoint() = default;
private:
StellarCheckpoint() = default;
};
/*
* Result views weakly observe context-owned storage. valid() remains safe
* after that context is destroyed. References and spans extracted from a
* valid view remain borrowed: the context must outlive their use, and the
* next evaluate() call invalidates them along with their originating view.
*/
template <DiscretizedStellarEquilibriumProblem Problem> class StellarStructureView final {
public:
using ProblemType = std::remove_cvref_t<Problem>;
using ModelType = typename ProblemType::ModelType;
[[nodiscard]] bool valid() const noexcept {
return solver::detail::IsCurrentView(m_storage, m_generation, true);
}
[[nodiscard]] const ModelType &model() const & {
const auto storage = RequireStorage();
return storage->problem->GetStellarModel();
}
[[nodiscard]] const ModelType &model() const && = delete;
[[nodiscard]] MPI_Comm communicator() const & {
const auto storage = RequireStorage();
return storage->problem->GetCommunicator();
}
[[nodiscard]] MPI_Comm communicator() const && = delete;
[[nodiscard]] std::span<const mfem::real_t> state() const & {
const auto storage = RequireStorage();
return solver::detail::ReadOnlySpan(*storage->physicalState);
}
[[nodiscard]] std::span<const mfem::real_t> state() const && = delete;
[[nodiscard]] std::span<const operators::RootBlockDescriptor> stateDescriptors() const & {
const auto storage = RequireStorage();
return storage->problem->GetManifest().valueBlocks();
}
[[nodiscard]] std::span<const operators::RootBlockDescriptor> stateDescriptors() const && = delete;
template <typename Term>
requires requires(
const typename ProblemType::ManifestType &manifest,
const mfem::Vector &physicalState,
const Term &term
) { manifest.stateView(physicalState).block(term); }
[[nodiscard]] std::span<const mfem::real_t> stateBlock(const Term &term) const & {
const auto storage = RequireStorage();
const auto block = storage->problem->GetManifest().stateView(*storage->physicalState).block(term);
return solver::detail::ReadOnlySpan(block);
}
template <typename Term> [[nodiscard]] std::span<const mfem::real_t> stateBlock(const Term &) const && = delete;
[[nodiscard]] std::optional<physics::RigidRotation> prescribedRotation() const & {
const auto storage = RequireStorage();
if (storage->rotation == nullptr) {
return std::nullopt;
}
return *storage->rotation;
}
[[nodiscard]] std::optional<physics::RigidRotation> prescribedRotation() const && = delete;
[[nodiscard]] physics::RigidRotation rotation() const & {
const auto storage = RequireStorage();
return storage->problem->GetPreparedOperator().GetRotation();
}
[[nodiscard]] physics::RigidRotation rotation() const && = delete;
[[nodiscard]] StellarStructure<ProblemType> capture() const {
(void)RequireStorage();
throw std::logic_error("Capturing a self-contained StellarStructure is not implemented.");
}
private:
template <DiscretizedStellarEquilibriumProblem> friend class solver::StellarEquilibriumEvaluationReport;
using Storage = solver::detail::StellarStructureStorage<ProblemType>;
StellarStructureView(
std::weak_ptr<const Storage> storage,
const std::uint64_t generation
) noexcept
: m_storage(std::move(storage)),
m_generation(generation) {
}
[[nodiscard]] std::shared_ptr<const Storage> RequireStorage() const {
return solver::detail::RequireCurrentView(m_storage, m_generation, true);
}
std::weak_ptr<const Storage> m_storage;
std::uint64_t m_generation;
};
template <DiscretizedStellarEquilibriumProblem Problem> class StellarCheckpointView final {
public:
using ProblemType = std::remove_cvref_t<Problem>;
using ModelType = typename ProblemType::ModelType;
[[nodiscard]] bool valid() const noexcept {
return solver::detail::IsCurrentView(m_storage, m_generation, false);
}
[[nodiscard]] const ModelType &model() const & {
const auto storage = RequireStorage();
return storage->problem->GetStellarModel();
}
[[nodiscard]] const ModelType &model() const && = delete;
[[nodiscard]] MPI_Comm communicator() const & {
const auto storage = RequireStorage();
return storage->problem->GetCommunicator();
}
[[nodiscard]] MPI_Comm communicator() const && = delete;
[[nodiscard]] std::span<const mfem::real_t> state() const & {
const auto storage = RequireStorage();
return solver::detail::ReadOnlySpan(*storage->physicalState);
}
[[nodiscard]] std::span<const mfem::real_t> state() const && = delete;
[[nodiscard]] std::span<const operators::RootBlockDescriptor> stateDescriptors() const & {
const auto storage = RequireStorage();
return storage->problem->GetManifest().valueBlocks();
}
[[nodiscard]] std::span<const operators::RootBlockDescriptor> stateDescriptors() const && = delete;
template <typename Term>
requires requires(
const typename ProblemType::ManifestType &manifest,
const mfem::Vector &physicalState,
const Term &term
) { manifest.stateView(physicalState).block(term); }
[[nodiscard]] std::span<const mfem::real_t> stateBlock(const Term &term) const & {
const auto storage = RequireStorage();
const auto block = storage->problem->GetManifest().stateView(*storage->physicalState).block(term);
return solver::detail::ReadOnlySpan(block);
}
template <typename Term> [[nodiscard]] std::span<const mfem::real_t> stateBlock(const Term &) const && = delete;
[[nodiscard]] std::optional<physics::RigidRotation> prescribedRotation() const & {
const auto storage = RequireStorage();
if (storage->rotation == nullptr) {
return std::nullopt;
}
return *storage->rotation;
}
[[nodiscard]] std::optional<physics::RigidRotation> prescribedRotation() const && = delete;
[[nodiscard]] physics::RigidRotation rotation() const & {
const auto storage = RequireStorage();
return storage->problem->GetPreparedOperator().GetRotation();
}
[[nodiscard]] physics::RigidRotation rotation() const && = delete;
[[nodiscard]] StellarCheckpoint<ProblemType> capture() const {
(void)RequireStorage();
throw std::logic_error("Capturing a self-contained StellarCheckpoint is not implemented.");
}
private:
template <DiscretizedStellarEquilibriumProblem> friend class solver::StellarEquilibriumEvaluationReport;
using Storage = solver::detail::StellarStructureStorage<ProblemType>;
StellarCheckpointView(
std::weak_ptr<const Storage> storage,
const std::uint64_t generation
) noexcept
: m_storage(std::move(storage)),
m_generation(generation) {
}
[[nodiscard]] std::shared_ptr<const Storage> RequireStorage() const {
return solver::detail::RequireCurrentView(m_storage, m_generation, false);
}
std::weak_ptr<const Storage> m_storage;
std::uint64_t m_generation;
};
template <DiscretizedStellarEquilibriumProblem Problem>
[[noreturn]] void serialize(
const StellarStructure<Problem> &,
const std::filesystem::path &
) {
throw std::logic_error("Serializing a StellarStructure is not implemented.");
}
template <DiscretizedStellarEquilibriumProblem Problem>
[[noreturn]] void serialize(
const StellarStructureView<Problem> &view,
const std::filesystem::path &
) {
(void)view.state();
throw std::logic_error("Serializing a StellarStructureView is not implemented.");
}
template <DiscretizedStellarEquilibriumProblem Problem>
[[noreturn]] void serialize(
const StellarCheckpoint<Problem> &,
const std::filesystem::path &
) {
throw std::logic_error("Serializing a StellarCheckpoint is not implemented.");
}
template <DiscretizedStellarEquilibriumProblem Problem>
[[noreturn]] void serialize(
const StellarCheckpointView<Problem> &view,
const std::filesystem::path &
) {
(void)view.state();
throw std::logic_error("Serializing a StellarCheckpointView is not implemented.");
}
} // namespace mean_field::equilibrium
export namespace mean_field::solver {
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
class StellarEquilibriumEvaluationReport final {
public:
using ProblemType = std::remove_cvref_t<Problem>;
using StructureView = equilibrium::StellarStructureView<ProblemType>;
using CheckpointView = equilibrium::StellarCheckpointView<ProblemType>;
StellarEquilibriumEvaluationReport(const StellarEquilibriumEvaluationReport &) = default;
StellarEquilibriumEvaluationReport &operator=(const StellarEquilibriumEvaluationReport &) = default;
StellarEquilibriumEvaluationReport(StellarEquilibriumEvaluationReport &&) noexcept = default;
StellarEquilibriumEvaluationReport &operator=(StellarEquilibriumEvaluationReport &&) noexcept = default;
~StellarEquilibriumEvaluationReport() = default;
[[nodiscard]] bool converged() const noexcept {
return m_converged;
}
[[nodiscard]] const StellarEquilibriumEvaluationDiagnostics &diagnostics() const & noexcept {
return m_diagnostics;
}
[[nodiscard]] const StellarEquilibriumEvaluationDiagnostics &diagnostics() const && = delete;
[[nodiscard]] int completedNonlinearIterations() const noexcept {
return m_diagnostics.acceptedNonlinearIterations;
}
[[nodiscard]] double initialResidualNorm() const noexcept {
return m_diagnostics.initialResidualNorm;
}
[[nodiscard]] double finalResidualNorm() const noexcept {
return m_diagnostics.finalResidualNorm;
}
[[nodiscard]] const StellarEquilibriumFailureReport &failure() const & {
if (!m_failure.has_value()) {
throw std::logic_error("A converged stellar-equilibrium report has no failure record.");
}
return *m_failure;
}
[[nodiscard]] const StellarEquilibriumFailureReport &failure() const && = delete;
[[nodiscard]] StructureView structureView() const {
if (!m_converged) {
throw std::logic_error("A failed stellar-equilibrium report cannot certify a structure view.");
}
StructureView view{m_storage, m_generation};
if (!view.valid()) {
throw std::logic_error("The stellar-equilibrium structure view has been invalidated.");
}
return view;
}
[[nodiscard]] CheckpointView checkpointView() const {
CheckpointView view{m_storage, m_generation};
if (!view.valid()) {
throw std::logic_error("The stellar-equilibrium checkpoint view has been invalidated.");
}
return view;
}
[[nodiscard]] CheckpointView lastAcceptedCheckpointView() const {
return checkpointView();
}
private:
friend struct detail::StellarEvaluationReportAccess<ProblemType>;
using Storage = detail::StellarStructureStorage<ProblemType>;
StellarEquilibriumEvaluationReport(
const bool converged,
StellarEquilibriumEvaluationDiagnostics diagnostics,
std::optional<StellarEquilibriumFailureReport> failure,
std::weak_ptr<const Storage> storage,
const std::uint64_t generation
)
: m_converged(converged),
m_diagnostics(std::move(diagnostics)),
m_failure(std::move(failure)),
m_storage(std::move(storage)),
m_generation(generation) {
}
bool m_converged;
StellarEquilibriumEvaluationDiagnostics m_diagnostics;
std::optional<StellarEquilibriumFailureReport> m_failure;
std::weak_ptr<const Storage> m_storage;
std::uint64_t m_generation;
};
} // namespace mean_field::solver
namespace mean_field::solver::detail {
template <typename Problem> struct StellarEvaluationReportAccess final {
using ProblemType = std::remove_cvref_t<Problem>;
using Report = StellarEquilibriumEvaluationReport<ProblemType>;
using Storage = StellarStructureStorage<ProblemType>;
[[nodiscard]] static Report Success(
const std::shared_ptr<Storage> &storage,
StellarEquilibriumEvaluationDiagnostics diagnostics
) {
if (storage == nullptr) {
throw std::invalid_argument("A stellar-equilibrium report requires owned result storage.");
}
storage->certification = StellarViewCertification::structure;
return Report{true, std::move(diagnostics), std::nullopt, storage, storage->viewGeneration};
}
[[nodiscard]] static Report Failure(
const std::shared_ptr<Storage> &storage,
StellarEquilibriumEvaluationDiagnostics diagnostics,
const StellarEquilibriumFailureReason reason,
std::string message
) {
if (storage == nullptr) {
throw std::invalid_argument("A stellar-equilibrium report requires owned result storage.");
}
storage->certification = StellarViewCertification::checkpoint;
StellarEquilibriumFailureReport failure{
.reason = reason,
.message = std::move(message),
.completedNonlinearIterations = diagnostics.acceptedNonlinearIterations,
.initialResidualNorm = diagnostics.initialResidualNorm,
.finalResidualNorm = diagnostics.finalResidualNorm
};
return Report{
false, std::move(diagnostics), std::optional<StellarEquilibriumFailureReport>{std::move(failure)},
storage, storage->viewGeneration
};
}
};
} // namespace mean_field::solver::detail