From b3c04d507a1d7bc321ba6edd9dde33f6713345c1 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Tue, 8 Sep 2026 06:36:39 -0400 Subject: [PATCH] feat(newton): first newton solver implementation --- CMakeLists.txt | 37 +- experiments/full_stellar_preconditioning.cpp | 6 +- .../material_surface_preconditioning.cpp | 8 +- experiments/preconditioning_diagnostics.cpp | 2 +- .../contexts/gravity_field_context.cpp | 121 +- libmeanfield/impl/operators/gravity_field.cpp | 15 +- .../gravity_displacement_force_kernels.cpp | 225 +- .../rotation_displacement_force_kernels.cpp | 213 +- .../operators/prepared_angular_momentum.cpp | 361 +- .../operators/prepared_barotropic_closure.cpp | 256 +- .../prepared_displacement_operator.cpp | 133 +- .../prepared_gravity_displacement_force.cpp | 145 +- .../operators/prepared_gravity_source.cpp | 178 +- .../impl/operators/prepared_hdiv_mass.cpp | 200 +- .../prepared_hydrostatic_equilibrium.cpp | 241 +- .../operators/prepared_mass_normalization.cpp | 243 +- .../operators/prepared_pressure_force.cpp | 297 +- .../prepared_rotation_displacement_force.cpp | 135 +- .../prepared_stellar_equilibrium.cpp | 278 +- .../seed/stellar_equilibrium_projection.cpp | 19 +- .../equilibrium/stellar_discretization.cppm | 196 +- libmeanfield/interface/mean_field.cppm | 3 + .../compiled_fixed_angular_momentum.cppm | 9 +- .../interface/models/specifications.cppm | 564 ++- .../interface/models/typed_stellar_model.cppm | 38 +- .../interface/normalization/operators.cppm | 105 +- .../normalization/physical_riesz.cppm | 312 +- .../interface/normalization/plan.cppm | 145 +- .../normalization/stellar_equilibrium.cppm | 309 +- .../contexts/gravity_field_context.cppm | 38 +- .../interface/operators/gravity_field.cppm | 7 + .../gravity_displacement_force_kernels.cppm | 21 + .../rotation_displacement_force_kernels.cppm | 21 + .../operators/prepared_angular_momentum.cppm | 61 +- .../prepared_barotropic_closure_operator.cppm | 29 + .../prepared_displacement_operator.cppm | 35 +- .../prepared_gravity_displacement_force.cppm | 12 +- .../operators/prepared_gravity_source.cppm | 23 +- .../operators/prepared_hdiv_mass.cppm | 24 +- ...ared_hydrostatic_equilibrium_operator.cppm | 47 +- .../prepared_mass_normalization.cppm | 42 +- .../operators/prepared_pressure_force.cppm | 29 +- .../prepared_rotation_displacement_force.cppm | 16 +- .../prepared_stellar_equilibrium.cppm | 84 +- ...prepared_variadic_stellar_equilibrium.cppm | 2119 ++++---- .../interface/operators/root_manifest.cppm | 623 +-- .../stellar_equilibrium_compiler.cppm | 1495 +++--- .../stellar_equilibrium_problem.cppm | 229 +- .../operators/stellar_equilibrium_system.cppm | 5 +- .../equilibrium_coordinates.cppm | 43 +- .../preconditioning/material_surface.cppm | 231 +- .../preconditioning/preconditioning.cppm | 1 + .../preconditioning/specification_border.cppm | 1028 ++-- .../preconditioning/stellar_equilibrium.cppm | 132 +- .../preconditioning/stellar_recipe.cppm | 83 + .../preconditioning/stellar_structure.cppm | 36 +- .../seed/stellar_equilibrium_projection.cppm | 179 +- .../interface/solver/linear_backend.cppm | 868 ++++ libmeanfield/interface/solver/newton.cppm | 579 +++ .../interface/solver/stellar_context.cppm | 1642 ++++++ .../interface/solver/stellar_equilibrium.cppm | 4 + .../solver/stellar_equilibrium_types.cppm | 60 + .../interface/solver/stellar_structure.cppm | 486 ++ sandbox.cpp | 236 + .../fixed_magnetic_specific_energy.cpp | 1600 +++--- tests/models/model_specifications.cpp | 92 +- .../models/physics_specification_frontend.cpp | 868 ++-- tests/models/typed_stellar_model.cpp | 57 +- tests/mpi/distributed_execution.cpp | 1017 ++-- tests/normalization/physical_riesz.cpp | 559 +-- tests/normalization/plan.cpp | 160 +- tests/normalization/stellar_equilibrium.cpp | 780 ++- .../contexts/gravity_field_context.cpp | 86 + .../operators/gravity_displacement_force.cpp | 100 + tests/operators/prepared_angular_momentum.cpp | 367 +- .../operators/prepared_barotropic_closure.cpp | 127 + ...ed_central_density_stellar_equilibrium.cpp | 89 +- .../prepared_displacement_operator.cpp | 51 + tests/operators/prepared_gravity_source.cpp | 51 + tests/operators/prepared_hdiv_mass.cpp | 51 + .../prepared_hydrostatic_equilibrium.cpp | 73 + .../operators/prepared_mass_normalization.cpp | 45 + tests/operators/prepared_pressure_force.cpp | 112 + .../prepared_rotation_displacement_force.cpp | 100 + .../prepared_stellar_equilibrium.cpp | 63 + tests/operators/root_manifest.cpp | 707 ++- .../stellar_equilibrium_compiler.cpp | 4397 ++++++++--------- .../operators/stellar_equilibrium_system.cpp | 289 +- .../equilibrium_coordinates.cpp | 29 +- tests/preconditioning/material_surface.cpp | 156 +- .../preconditioning/specification_border.cpp | 860 ++-- tests/preconditioning/stellar_equilibrium.cpp | 150 +- tests/preconditioning/stellar_structure.cpp | 31 +- tests/seed/stellar_equilibrium_projection.cpp | 94 +- .../stellar_equilibrium_architecture.cpp | 1049 ++++ ...llar_equilibrium_architecture_internal.cpp | 319 ++ tests/solver/stellar_equilibrium_runtime.cpp | 1455 ++++++ tests/user-api/stellar_equilibrium.cpp | 91 +- 98 files changed, 20397 insertions(+), 11040 deletions(-) create mode 100644 libmeanfield/interface/preconditioning/stellar_recipe.cppm create mode 100644 libmeanfield/interface/solver/linear_backend.cppm create mode 100644 libmeanfield/interface/solver/newton.cppm create mode 100644 libmeanfield/interface/solver/stellar_context.cppm create mode 100644 libmeanfield/interface/solver/stellar_equilibrium.cppm create mode 100644 libmeanfield/interface/solver/stellar_equilibrium_types.cppm create mode 100644 libmeanfield/interface/solver/stellar_structure.cppm create mode 100644 sandbox.cpp create mode 100644 tests/solver/stellar_equilibrium_architecture.cpp create mode 100644 tests/solver/stellar_equilibrium_architecture_internal.cpp create mode 100644 tests/solver/stellar_equilibrium_runtime.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7aeb884..6788573 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,7 +145,13 @@ target_sources(mean_field libmeanfield/interface/quadrature/policy.cppm libmeanfield/interface/quadrature/mfem.cppm libmeanfield/interface/solver/fields.cppm + libmeanfield/interface/solver/linear_backend.cppm + libmeanfield/interface/solver/newton.cppm libmeanfield/interface/solver/preconditioning_diagnostics.cppm + libmeanfield/interface/solver/stellar_equilibrium_types.cppm + libmeanfield/interface/solver/stellar_structure.cppm + libmeanfield/interface/solver/stellar_context.cppm + libmeanfield/interface/solver/stellar_equilibrium.cppm libmeanfield/interface/preconditioning/backend.cppm libmeanfield/interface/preconditioning/backend_implementations.cppm libmeanfield/interface/preconditioning/gravity_field.cppm @@ -155,6 +161,7 @@ target_sources(mean_field libmeanfield/interface/preconditioning/stellar_structure.cppm libmeanfield/interface/preconditioning/specification_border.cppm libmeanfield/interface/preconditioning/equilibrium_coordinates.cppm + libmeanfield/interface/preconditioning/stellar_recipe.cppm libmeanfield/interface/preconditioning/preconditioning.cppm libmeanfield/interface/normalization/plan.cppm libmeanfield/interface/normalization/physical_riesz.cppm @@ -346,10 +353,18 @@ add_executable(tests tests/normalization/stellar_equilibrium.cpp tests/user-api/stellar_equilibrium.cpp tests/solver/preconditioning_diagnostics.cpp + tests/solver/stellar_equilibrium_architecture.cpp + tests/solver/stellar_equilibrium_architecture_internal.cpp + tests/solver/stellar_equilibrium_runtime.cpp ) target_link_libraries(tests PRIVATE mean_field test_mod Catch2::Catch2 Boost::boost) +# A deliberately opt-in API workbench. It is compiled explicitly during API +# verification but temporary user experiments do not break the default build. +add_executable(sandbox EXCLUDE_FROM_ALL sandbox.cpp) +target_link_libraries(sandbox PRIVATE mean_field) + add_executable(mpi_tests tests/mpi/mpi_test_main.cpp tests/mpi/distributed_execution.cpp @@ -416,6 +431,26 @@ catch_discover_tests( WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" ) +add_test( + NAME mpi_single_rank_stellar_root + COMMAND + ${MPIEXEC_EXECUTABLE} + ${MPIEXEC_NUMPROC_FLAG} 1 + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + "[single-rank]" +) +set_tests_properties( + mpi_single_rank_stellar_root + PROPERTIES + LABELS "mpi;single-rank" + PROCESSORS 1 + RESOURCE_LOCK mean_field_mpi + TIMEOUT 600 + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" +) + foreach (mean_field_mpi_ranks IN ITEMS 2 4) add_test( NAME mpi_${mean_field_mpi_ranks}_ranks @@ -425,7 +460,7 @@ foreach (mean_field_mpi_ranks IN ITEMS 2 4) ${MPIEXEC_PREFLAGS} $ ${MPIEXEC_POSTFLAGS} - "[mpi]" + "[mpi]~[single-rank]" ) set_tests_properties( mpi_${mean_field_mpi_ranks}_ranks diff --git a/experiments/full_stellar_preconditioning.cpp b/experiments/full_stellar_preconditioning.cpp index e7a563a..74013a5 100644 --- a/experiments/full_stellar_preconditioning.cpp +++ b/experiments/full_stellar_preconditioning.cpp @@ -756,7 +756,7 @@ TEST_CASE( integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}), constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}}) ); - auto problem = equilibrium::discretize(model, finiteElements); + auto problem = equilibrium::discretize(model, std::move(finiteElements)); setupTimings.problemConstructionSeconds = maximumRankSeconds(constructionStart, communicator); announce(communicator, "projecting the n=3 Lane-Emden state"); @@ -919,7 +919,7 @@ TEST_CASE( integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}), constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}}) ); - auto problem = equilibrium::discretize(model, finiteElements); + auto problem = equilibrium::discretize(model, std::move(finiteElements)); setupTimings.problemConstructionSeconds = maximumRankSeconds(constructionStart, communicator); announce(communicator, "projecting the n=3 Lane-Emden state for gravity backend finalists"); @@ -1025,7 +1025,7 @@ TEST_CASE( integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}), constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}}) ); - auto problem = equilibrium::discretize(model, finiteElements); + auto problem = equilibrium::discretize(model, std::move(finiteElements)); auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = radialSampleCount})); auto dependencies = makeDependencies(); diff --git a/experiments/material_surface_preconditioning.cpp b/experiments/material_surface_preconditioning.cpp index 2fe4e41..d6f70d3 100644 --- a/experiments/material_surface_preconditioning.cpp +++ b/experiments/material_surface_preconditioning.cpp @@ -792,7 +792,7 @@ TEST_CASE( integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}), constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}}) ); - auto problem = equilibrium::discretize(model, finiteElements); + auto problem = equilibrium::discretize(model, std::move(finiteElements)); auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024})); problem.Prepare(projected.values, makeDependencies(), zeroRotation()); @@ -851,7 +851,7 @@ TEST_CASE( integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}), constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}}) ); - auto problem = equilibrium::discretize(model, finiteElements); + auto problem = equilibrium::discretize(model, std::move(finiteElements)); auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024})); problem.Prepare(projected.values, makeDependencies(), zeroRotation()); @@ -914,7 +914,7 @@ TEST_CASE( integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}), constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}}) ); - auto problem = equilibrium::discretize(model, finiteElements); + auto problem = equilibrium::discretize(model, std::move(finiteElements)); auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024})); problem.Prepare(projected.values, makeDependencies(), zeroRotation()); @@ -961,7 +961,7 @@ TEST_CASE( integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}), constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}}) ); - auto problem = equilibrium::discretize(model, finiteElements); + auto problem = equilibrium::discretize(model, std::move(finiteElements)); auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024})); problem.Prepare(projected.values, makeDependencies(), zeroRotation()); diff --git a/experiments/preconditioning_diagnostics.cpp b/experiments/preconditioning_diagnostics.cpp index 8a66edc..7e70190 100644 --- a/experiments/preconditioning_diagnostics.cpp +++ b/experiments/preconditioning_diagnostics.cpp @@ -218,7 +218,7 @@ TEST_CASE( integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}), constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}}) ); - auto problem = equilibrium::discretize(stellarModel, finiteElementModel); + auto problem = equilibrium::discretize(stellarModel, std::move(finiteElementModel)); const double problemConstructionSeconds = maximum_rank_seconds(problemConstructionStart, communicator); announce(communicator, "P0 extended baseline: projecting the Lane-Emden seed"); diff --git a/libmeanfield/impl/operators/contexts/gravity_field_context.cpp b/libmeanfield/impl/operators/contexts/gravity_field_context.cpp index 4077877..702dff4 100644 --- a/libmeanfield/impl/operators/contexts/gravity_field_context.cpp +++ b/libmeanfield/impl/operators/contexts/gravity_field_context.cpp @@ -1,5 +1,6 @@ module; #include +#include #include #include @@ -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 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 GravityFieldGeometryContext::TryPreparePrimal( + const mfem::Vector &displacement, + const DiscretizationRevision discretization_revision, + const DisplacementRevision displacement_revision + ) { + return TryPrepareImpl(displacement, discretization_revision, displacement_revision, PreparationMode::primal); + } + + GravityFieldPreparationResult 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(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 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()); diff --git a/libmeanfield/impl/operators/gravity_field.cpp b/libmeanfield/impl/operators/gravity_field.cpp index 7cf994b..8d106d4 100644 --- a/libmeanfield/impl/operators/gravity_field.cpp +++ b/libmeanfield/impl/operators/gravity_field.cpp @@ -4,6 +4,7 @@ module; #include #include #include +#include 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 + 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, diff --git a/libmeanfield/impl/operators/kernels/gravity_displacement_force_kernels.cpp b/libmeanfield/impl/operators/kernels/gravity_displacement_force_kernels.cpp index ab45396..e7f28e4 100644 --- a/libmeanfield/impl/operators/kernels/gravity_displacement_force_kernels.cpp +++ b/libmeanfield/impl/operators/kernels/gravity_displacement_force_kernels.cpp @@ -2,9 +2,12 @@ module; #include #include +#include #include +#include #include +#include 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) noexcept { + if (!rejection.has_value()) { + return 0; + } + if (rejection->reason == Reason::non_finite_arithmetic) { + return 256; + } + return static_cast(rejection->mappingStatus) + 1; + } + + [[nodiscard]] Rejection decode_rejection(const int encoded) noexcept { + if (encoded >= 256) { + return non_finite_rejection(); + } + return mapping_rejection(static_cast(encoded - 1)); + } + + [[nodiscard]] Result synchronize_rejection( + const std::optional &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 inputRejection = + inputsAreFinite ? std::optional{} : std::optional{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 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(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(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(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(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 outputRejection = vector_is_finite(actionTrue) + ? std::optional{} + : std::optional{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 diff --git a/libmeanfield/impl/operators/kernels/rotation_displacement_force_kernels.cpp b/libmeanfield/impl/operators/kernels/rotation_displacement_force_kernels.cpp index 0eac3ff..9df5e78 100644 --- a/libmeanfield/impl/operators/kernels/rotation_displacement_force_kernels.cpp +++ b/libmeanfield/impl/operators/kernels/rotation_displacement_force_kernels.cpp @@ -2,9 +2,12 @@ module; #include #include +#include #include +#include #include +#include 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) noexcept { + if (!rejection.has_value()) { + return 0; + } + if (rejection->reason == Reason::non_finite_arithmetic) { + return 256; + } + return static_cast(rejection->mappingStatus) + 1; + } + + [[nodiscard]] Rejection decode_rejection(const int encoded) noexcept { + if (encoded >= 256) { + return non_finite_rejection(); + } + return mapping_rejection(static_cast(encoded - 1)); + } + + [[nodiscard]] Result synchronize_rejection( + const std::optional &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 inputRejection = + inputsAreFinite ? std::optional{} : std::optional{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 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(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(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(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(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 outputRejection = vector_is_finite(actionTrue) + ? std::optional{} + : std::optional{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 diff --git a/libmeanfield/impl/operators/prepared_angular_momentum.cpp b/libmeanfield/impl/operators/prepared_angular_momentum.cpp index 8354d05..5eda443 100644 --- a/libmeanfield/impl/operators/prepared_angular_momentum.cpp +++ b/libmeanfield/impl/operators/prepared_angular_momentum.cpp @@ -3,9 +3,14 @@ module; #include #include #include +#include +#include +#include +#include #include #include +#include module mean_field; @@ -18,10 +23,71 @@ namespace { return DomainSchema::template attribute_belongs_to(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 synchronize_mapping_failure( + const std::optional localFailure, + const MPI_Comm communicator + ) { + int localFailures[2]{0, 0}; + if (localFailure.has_value()) { + const int encodedStatus = static_cast(*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(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::quadrature::QuadratureRole::discretization, - transformation.OrderW(), - std::array{2}, - mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array{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 + 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 PreparedAngularMomentumOperator::TryAssembleResidual() { double localMomentOfInertia = 0.0; for (const ElementPAData &data : m_elements) { for (const QuadraturePointData &point : data.quadraturePoints) { - localMomentOfInertia += point.density * point.cylindricalRadiusSquared * - point.mappingContext.quadrature.weight; + localMomentOfInertia += + point.density * point.cylindricalRadiusSquared * point.mappingContext.quadrature.weight; } } m_momentOfInertia = GlobalSum(localMomentOfInertia); + if (!std::isfinite(m_momentOfInertia)) { + return AngularMomentumPreparationRejection{ + .reason = AngularMomentumPreparationRejectionReason::non_finite_moment_of_inertia, + .momentOfInertia = m_momentOfInertia + }; + } + if (m_momentOfInertia < 0.0) { + return AngularMomentumPreparationRejection{ + .reason = AngularMomentumPreparationRejectionReason::negative_moment_of_inertia, + .momentOfInertia = m_momentOfInertia + }; + } MFEM_VERIFY( - std::isfinite(m_momentOfInertia) && m_momentOfInertia >= 0.0, - "PreparedAngularMomentumOperator assembled an invalid moment of inertia." + std::isfinite(m_constraint.targetAngularMomentum().value()), + "PreparedAngularMomentumOperator has a non-finite configured target angular momentum." ); m_currentAngularMomentum = m_angularVelocity * m_momentOfInertia; m_cachedResidual.SetSize(1); m_cachedResidual(0) = m_currentAngularMomentum - m_constraint.targetAngularMomentum().value(); + if (!std::isfinite(m_currentAngularMomentum) || !std::isfinite(m_cachedResidual(0))) { + return AngularMomentumPreparationRejection{ + .reason = AngularMomentumPreparationRejectionReason::non_finite_residual, + .momentOfInertia = m_momentOfInertia + }; + } ++m_preparationCount; + return std::nullopt; } void PreparedAngularMomentumOperator::BuildResidual(mfem::Vector &residual) const { @@ -331,9 +465,8 @@ namespace mean_field::operators { ++m_residualApplicationCount; } - double PreparedAngularMomentumOperator::EvaluateDensityMomentActionLocal( - const mfem::Vector &densityVariation - ) const { + double + PreparedAngularMomentumOperator::EvaluateDensityMomentActionLocal(const mfem::Vector &densityVariation) const { MFEM_VERIFY( densityVariation.Size() == m_fem.densityFes->GetTrueVSize(), "Angular-momentum density action has the wrong true-vector size." @@ -346,8 +479,8 @@ namespace mean_field::operators { data.densityDofTransformation->InvTransformPrimal(m_elementDensityVariation); } for (const QuadraturePointData &point : data.quadraturePoints) { - localAction += (m_elementDensityVariation * point.densityShape) * - point.cylindricalRadiusSquared * point.mappingContext.quadrature.weight; + localAction += (m_elementDensityVariation * point.densityShape) * point.cylindricalRadiusSquared * + point.mappingContext.quadrature.weight; } } return localAction; @@ -369,42 +502,33 @@ namespace mean_field::operators { if (data.displacementDofTransformation != nullptr) { data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation); } - const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId); + const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId); const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId); const mapping::ElementDisplacementData baseDisplacementData = mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement); const mapping::ElementDisplacementData directionData = mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation); const mapping::ElementCompactificationData compactificationData( - compactificationElement, - data.compactification + compactificationElement, data.compactification ); const mapping::ElementMappingData mappingData{ - .displacement = baseDisplacementData, - .compactification = compactificationData + .displacement = baseDisplacementData, .compactification = compactificationData }; mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId); for (const QuadraturePointData &point : data.quadraturePoints) { const mapping::MappingStatus status = m_domainMapper.EvaluateVolumeVariation( - mappingData, - directionData, - *transformation, - point.integrationPoint, - point.mappingContext, - workspace, - variation + mappingData, directionData, *transformation, point.integrationPoint, point.mappingContext, + workspace, variation ); MFEM_VERIFY( status == mapping::MappingStatus::valid, "Mapped angular-momentum variation is invalid. Element: " << data.elementId ); const double radiusSquaredVariation = CylindricalRadiusSquaredVariation( - point.mappingContext.mapping.physical_position, - variation.mapping.physical_position_variation + point.mappingContext.mapping.physical_position, variation.mapping.physical_position_variation ); - localAction += point.density * - (radiusSquaredVariation * point.mappingContext.quadrature.weight + - point.cylindricalRadiusSquared * variation.weight_variation); + localAction += point.density * (radiusSquaredVariation * point.mappingContext.quadrature.weight + + point.cylindricalRadiusSquared * variation.weight_variation); } } return localAction; @@ -419,7 +543,7 @@ namespace mean_field::operators { densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size(), "Angular-momentum density action has the wrong reduced size." ); - validate_finite_vector(densityVariation, "Angular-momentum density direction is non-finite."); + verify_finite_vector(densityVariation, "Angular-momentum density direction is non-finite."); m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue); action.SetSize(1); action(0) = m_angularVelocity * GlobalSum(EvaluateDensityMomentActionLocal(m_densityVariationTrue)); @@ -435,11 +559,10 @@ namespace mean_field::operators { displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(), "Angular-momentum displacement action has the wrong reduced size." ); - validate_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite."); + verify_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite."); m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue); action.SetSize(1); - action(0) = m_angularVelocity * - GlobalSum(EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue)); + action(0) = m_angularVelocity * GlobalSum(EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue)); ++m_actionStatistics.displacementApplications; } @@ -466,24 +589,22 @@ namespace mean_field::operators { displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(), "Angular-momentum complete action has incompatible reduced coordinates." ); - validate_finite_vector(densityVariation, "Angular-momentum density direction is non-finite."); - validate_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite."); + verify_finite_vector(densityVariation, "Angular-momentum density direction is non-finite."); + verify_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite."); MFEM_VERIFY(std::isfinite(angularVelocityVariation), "Angular-velocity direction is non-finite."); m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue); m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue); const double localMomentAction = EvaluateDensityMomentActionLocal(m_densityVariationTrue) + EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue); action.SetSize(1); - action(0) = m_angularVelocity * GlobalSum(localMomentAction) + - m_momentOfInertia * angularVelocityVariation; + action(0) = m_angularVelocity * GlobalSum(localMomentAction) + m_momentOfInertia * angularVelocityVariation; ++m_actionStatistics.completeApplications; } - double PreparedAngularMomentumOperator::CylindricalRadiusSquared( - const mfem::Vector &physicalPosition - ) const noexcept { - const auto &axis = m_constraint.specification().axis(); - const auto ¢er = m_constraint.specification().center(); + double + PreparedAngularMomentumOperator::CylindricalRadiusSquared(const mfem::Vector &physicalPosition) const noexcept { + const auto &axis = m_constraint.specification().axis(); + const auto ¢er = m_constraint.specification().center(); double radiusSquared = 0.0; double axialPosition = 0.0; for (int component = 0; component < 3; ++component) { @@ -491,18 +612,22 @@ namespace mean_field::operators { radiusSquared += relative * relative; axialPosition += axis[static_cast(component)] * relative; } - return std::max(0.0, radiusSquared - axialPosition * axialPosition); + const double perpendicularRadiusSquared = radiusSquared - axialPosition * axialPosition; + if (!std::isfinite(perpendicularRadiusSquared)) { + return perpendicularRadiusSquared; + } + return std::max(0.0, perpendicularRadiusSquared); } double PreparedAngularMomentumOperator::CylindricalRadiusSquaredVariation( const mfem::Vector &physicalPosition, const mfem::Vector &physicalPositionVariation ) const noexcept { - const auto &axis = m_constraint.specification().axis(); - const auto ¢er = m_constraint.specification().center(); + const auto &axis = m_constraint.specification().axis(); + const auto ¢er = m_constraint.specification().center(); double relativeDotVariation = 0.0; - double axialPosition = 0.0; - double axialVariation = 0.0; + double axialPosition = 0.0; + double axialVariation = 0.0; for (int component = 0; component < 3; ++component) { const double relative = physicalPosition(component) - center[static_cast(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) }; } diff --git a/libmeanfield/impl/operators/prepared_barotropic_closure.cpp b/libmeanfield/impl/operators/prepared_barotropic_closure.cpp index 01abd79..b524ee0 100644 --- a/libmeanfield/impl/operators/prepared_barotropic_closure.cpp +++ b/libmeanfield/impl/operators/prepared_barotropic_closure.cpp @@ -3,10 +3,15 @@ module; #include #include #include +#include #include #include +#include +#include #include +#include + 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 ¤t, + const BarotropicRejection candidate + ) { + if (!current.has_value() || rejection_priority(candidate) > rejection_priority(*current)) { + current = candidate; + } + } + + [[nodiscard]] std::optional synchronize_rejection( + const std::optional &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 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(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(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; diff --git a/libmeanfield/impl/operators/prepared_displacement_operator.cpp b/libmeanfield/impl/operators/prepared_displacement_operator.cpp index 21d17cf..3576639 100644 --- a/libmeanfield/impl/operators/prepared_displacement_operator.cpp +++ b/libmeanfield/impl/operators/prepared_displacement_operator.cpp @@ -1,6 +1,13 @@ module; +#include +#include #include +#include +#include +#include + +#include 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 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 { diff --git a/libmeanfield/impl/operators/prepared_gravity_displacement_force.cpp b/libmeanfield/impl/operators/prepared_gravity_displacement_force.cpp index ca912d2..71d186a 100644 --- a/libmeanfield/impl/operators/prepared_gravity_displacement_force.cpp +++ b/libmeanfield/impl/operators/prepared_gravity_displacement_force.cpp @@ -1,6 +1,13 @@ module; +#include +#include +#include +#include +#include + #include +#include 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(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) noexcept { + if (!rejection.has_value()) { + return 0; + } + if (rejection->reason == Reason::non_finite_arithmetic) { + return 256; + } + return static_cast(rejection->mappingStatus) + 1; + } + + [[nodiscard]] Rejection decode_rejection(const int encoded) { + if (encoded >= 256) { + return non_finite_rejection(); + } + return mapping_rejection(static_cast(encoded - 1)); + } + + [[nodiscard]] std::expected< + void, + Rejection> + synchronize_rejection( + const std::optional &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 localElementRejection = + elementResult.has_value() ? std::optional{} : std::optional{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 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 { diff --git a/libmeanfield/impl/operators/prepared_gravity_source.cpp b/libmeanfield/impl/operators/prepared_gravity_source.cpp index 37948ea..93d5c69 100644 --- a/libmeanfield/impl/operators/prepared_gravity_source.cpp +++ b/libmeanfield/impl/operators/prepared_gravity_source.cpp @@ -1,10 +1,16 @@ module; #include "profile.h" +#include #include #include +#include #include #include #include +#include +#include + +#include 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 localFailures{0, 0, localNonFiniteArithmetic ? 1 : 0}; + if (localMappingStatus != mean_field::mapping::MappingStatus::valid) { + const int encodedStatus = static_cast(localMappingStatus) + 1; + localFailures[is_candidate_mapping_failure(localMappingStatus) ? 0 : 1] = encodedStatus; + } + + std::array globalFailures{}; + if (MPI_Allreduce( + localFailures.data(), globalFailures.data(), static_cast(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(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(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(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, diff --git a/libmeanfield/impl/operators/prepared_hdiv_mass.cpp b/libmeanfield/impl/operators/prepared_hdiv_mass.cpp index 85710b8..36596d9 100644 --- a/libmeanfield/impl/operators/prepared_hdiv_mass.cpp +++ b/libmeanfield/impl/operators/prepared_hdiv_mass.cpp @@ -1,9 +1,16 @@ module; #include "profile.h" +#include #include #include +#include #include #include +#include +#include +#include + +#include 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 localFailures{0, 0, localNonFiniteArithmetic ? 1 : 0}; + if (localMappingStatus != mean_field::mapping::MappingStatus::valid) { + const int encodedStatus = static_cast(localMappingStatus) + 1; + localFailures[is_candidate_mapping_failure(localMappingStatus) ? 0 : 1] = encodedStatus; + } + + std::array globalFailures{}; + if (MPI_Allreduce( + localFailures.data(), globalFailures.data(), static_cast(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(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(right) > static_cast(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(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(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(m_fem, m_domain_mapper, m_displacement_true, false); - m_vacuum_mass_coefficient = + auto vacuumMassCoefficient = std::make_unique(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(m_fem.gravityFluxFes.get()); - m_vacuum_mass_form = std::make_unique(m_fem.gravityFluxFes.get()); + m_stellar_mass_form = std::make_unique(m_fem.gravityFluxFes.get()); + m_vacuum_mass_form = std::make_unique(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( diff --git a/libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp b/libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp index 8ba7a78..1377f29 100644 --- a/libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp +++ b/libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp @@ -4,6 +4,11 @@ module; #include #include #include +#include +#include +#include +#include +#include #include @@ -18,6 +23,82 @@ namespace { return DomainSchema::template attribute_belongs_to(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 synchronize_mapping_failure( + const std::optional localFailure, + const MPI_Comm communicator + ) { + int localFailures[2]{0, 0}; + if (localFailure.has_value()) { + const int encodedStatus = static_cast(*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(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 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(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); diff --git a/libmeanfield/impl/operators/prepared_mass_normalization.cpp b/libmeanfield/impl/operators/prepared_mass_normalization.cpp index 0ae5a4f..5a04d0e 100644 --- a/libmeanfield/impl/operators/prepared_mass_normalization.cpp +++ b/libmeanfield/impl/operators/prepared_mass_normalization.cpp @@ -2,7 +2,13 @@ module; #include #include +#include #include +#include +#include +#include + +#include 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 ¤t, + const MassRejection candidate + ) { + if (!current.has_value() || rejection_priority(candidate) > rejection_priority(*current)) { + current = candidate; + } + } + + [[nodiscard]] std::optional synchronize_rejection( + const std::optional &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 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 + 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 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(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 + 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 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 PreparedMassNormalizationOperator::AssembleResidual() { double localMass = 0.0; + std::optional 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 + PreparedMassNormalizationOperator::UpdateResidualForTargetMass() { m_cachedResidual.SetSize(1); m_cachedResidual(0) = m_currentMass - m_targetMass; + std::optional 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; } diff --git a/libmeanfield/impl/operators/prepared_pressure_force.cpp b/libmeanfield/impl/operators/prepared_pressure_force.cpp index faf5dc1..858529a 100644 --- a/libmeanfield/impl/operators/prepared_pressure_force.cpp +++ b/libmeanfield/impl/operators/prepared_pressure_force.cpp @@ -3,10 +3,14 @@ module; #include #include #include +#include #include +#include +#include #include #include +#include module mean_field; @@ -20,6 +24,99 @@ namespace { using PressureDomain = mean_field::field::FieldDomainT; + 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) noexcept { + if (!rejection.has_value()) { + return 0; + } + switch (rejection->reason) { + case Reason::equation_of_state: + return static_cast(rejection->equationOfStateCode) + 1; + case Reason::invalid_mapping: + return 128 + static_cast(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(encoded - 128)); + } + return equation_of_state_rejection(static_cast(encoded - 1)); + } + + [[nodiscard]] std::optional synchronize_rejection( + const std::optional &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 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 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(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 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 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(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 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(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); } diff --git a/libmeanfield/impl/operators/prepared_rotation_displacement_force.cpp b/libmeanfield/impl/operators/prepared_rotation_displacement_force.cpp index abfdfd7..ee9dccf 100644 --- a/libmeanfield/impl/operators/prepared_rotation_displacement_force.cpp +++ b/libmeanfield/impl/operators/prepared_rotation_displacement_force.cpp @@ -1,7 +1,14 @@ module; #include +#include +#include #include +#include +#include +#include + +#include 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(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) noexcept { + if (!rejection.has_value()) { + return 0; + } + if (rejection->reason == Reason::non_finite_arithmetic) { + return 256; + } + return static_cast(rejection->mappingStatus) + 1; + } + + [[nodiscard]] Rejection decode_rejection(const int encoded) { + if (encoded >= 256) { + return non_finite_rejection(); + } + return mapping_rejection(static_cast(encoded - 1)); + } + + [[nodiscard]] std::expected< + void, + Rejection> + synchronize_rejection( + const std::optional &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 localRejection = elementResult.has_value() + ? std::optional{} + : std::optional{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; diff --git a/libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp b/libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp index fafb13d..ef9161a 100644 --- a/libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp +++ b/libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp @@ -3,6 +3,8 @@ module; #include #include #include +#include +#include #include #include @@ -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 + 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, diff --git a/libmeanfield/impl/seed/stellar_equilibrium_projection.cpp b/libmeanfield/impl/seed/stellar_equilibrium_projection.cpp index 66aa17a..48daafd 100644 --- a/libmeanfield/impl/seed/stellar_equilibrium_projection.cpp +++ b/libmeanfield/impl/seed/stellar_equilibrium_projection.cpp @@ -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 }; } diff --git a/libmeanfield/interface/equilibrium/stellar_discretization.cppm b/libmeanfield/interface/equilibrium/stellar_discretization.cppm index 534b3a7..b5ec55a 100644 --- a/libmeanfield/interface/equilibrium/stellar_discretization.cppm +++ b/libmeanfield/interface/equilibrium/stellar_discretization.cppm @@ -6,130 +6,124 @@ module; #include #include +#include + 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 - class StellarDiscretizationFor final { + template class StellarDiscretizationFor final { public: using NormalizationPrescriptionType = std::remove_cvref_t; - explicit StellarDiscretizationFor(fem::FEM &finiteElementModel) - requires std::same_as + 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 - : 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 = delete; - - StellarDiscretizationFor( - fem::FEM &, - const mapping::DomainMapper && - ) requires std::same_as = 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(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 TakeOwnership(fem::FEM &&finiteElementModel) { + if (!finiteElementModel.okay()) { + throw std::invalid_argument("A stellar discretization requires a complete finite-element model."); + } + return std::make_unique(std::move(finiteElementModel)); + } + + std::unique_ptr m_finiteElementModel; NormalizationPrescriptionType m_normalizationPrescription; }; template - StellarDiscretizationFor(fem::FEM &, Normalization) - -> StellarDiscretizationFor>; - - template - StellarDiscretizationFor(fem::FEM &, const mapping::DomainMapper &, Normalization) - -> StellarDiscretizationFor>; + StellarDiscretizationFor( + fem::FEM &&, + Normalization + ) -> StellarDiscretizationFor>; using StellarDiscretization = StellarDiscretizationFor; @@ -143,41 +137,17 @@ export namespace mean_field::equilibrium { template [[nodiscard]] auto makeStellarDiscretization( - fem::FEM &finiteElementModel, + fem::FEM &&finiteElementModel, Normalization normalizationPrescription ) { return StellarDiscretizationFor>{ - finiteElementModel, - std::move(normalizationPrescription) + std::move(finiteElementModel), std::move(normalizationPrescription) }; } template - [[nodiscard]] auto makeStellarDiscretization( - fem::FEM &finiteElementModel, - const mapping::DomainMapper &domainMapper, - Normalization normalizationPrescription - ) { - return StellarDiscretizationFor>{ - finiteElementModel, - domainMapper, - std::move(normalizationPrescription) - }; - } - - template - StellarDiscretizationFor> - makeStellarDiscretization( + StellarDiscretizationFor> makeStellarDiscretization( fem::FEM &, - mapping::DomainMapper &&, - Normalization - ) = delete; - - template - StellarDiscretizationFor> - makeStellarDiscretization( - fem::FEM &, - const mapping::DomainMapper &&, Normalization ) = delete; } // namespace mean_field::equilibrium diff --git a/libmeanfield/interface/mean_field.cppm b/libmeanfield/interface/mean_field.cppm index c2a042d..0d7bc50 100644 --- a/libmeanfield/interface/mean_field.cppm +++ b/libmeanfield/interface/mean_field.cppm @@ -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; diff --git a/libmeanfield/interface/models/compiled_fixed_angular_momentum.cppm b/libmeanfield/interface/models/compiled_fixed_angular_momentum.cppm index 790befd..c21db33 100644 --- a/libmeanfield/interface/models/compiled_fixed_angular_momentum.cppm +++ b/libmeanfield/interface/models/compiled_fixed_angular_momentum.cppm @@ -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}; } diff --git a/libmeanfield/interface/models/specifications.cppm b/libmeanfield/interface/models/specifications.cppm index 975e612..5ca74f3 100644 --- a/libmeanfield/interface/models/specifications.cppm +++ b/libmeanfield/interface/models/specifications.cppm @@ -57,11 +57,9 @@ export namespace mean_field::models { static constexpr std::size_t size = sizeof...(Types); }; - template - using DependsOn = ModelTypeList; + template using DependsOn = ModelTypeList; - template - using Affects = ModelTypeList; + template using Affects = ModelTypeList; /* * 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 - struct GeneratedCoordinateOf final { + template 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 - struct ConstraintOf final { + template 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 - struct Derivative final { + template struct Derivative final { using EquationType = Equation; using StateType = State; }; @@ -220,11 +215,7 @@ export namespace mean_field::models { concept PhysicalScaleRepresentedQuantity = dimensions::PhysicalQuantityType && physicalScaleForQuantity != PhysicalScaleLaw::unavailable && - requires { - typename std::bool_constant< - !static_cast( - Quantity::identifier).empty()>; - }; + requires { typename std::bool_constant(Quantity::identifier).empty()>; }; namespace detail { template [[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; - template + 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() && detail::declaredCoordinateNormalizationIsAvailable(); @@ -321,8 +312,13 @@ export namespace mean_field::models { CoordinateNormalization, CoordinateNormalization>; - template + 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; - static constexpr PhysicalScaleLaw targetScale = - physicalScaleForQuantity; + using TargetQuantity = TargetQuantityT; + using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT; + using ConstraintResidualQuantity = ConstraintResidualQuantityT; + using TargetValue = dimensions::QuantityValue; + static constexpr PhysicalScaleLaw targetScale = physicalScaleForQuantity; struct Normalization final { - using TargetQuantity = TargetQuantityT; - using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT; - using ConstraintResidualQuantity = ConstraintResidualQuantityT; - using TargetValue = dimensions::QuantityValue; - static constexpr PhysicalScaleLaw targetScale = - physicalScaleForQuantity; - using Value = CoordinateNormalization< + using TargetQuantity = TargetQuantityT; + using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT; + using ConstraintResidualQuantity = ConstraintResidualQuantityT; + using TargetValue = dimensions::QuantityValue; + static constexpr PhysicalScaleLaw targetScale = physicalScaleForQuantity; + using Value = CoordinateNormalization< RieszTopology::global_scalar, physicalScaleForQuantity>; 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 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 using BoundaryCondition = ModelDefinition; - template , - typename Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization, - typename Manifest = UnavailableGeneratedManifest> - using FixedIntegralWithMultiplier = - ModelDefinition; + 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 Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization, - typename Manifest = UnavailableGeneratedManifest> - using FixedIntegralWithPhysicalCoordinate = - ModelDefinition; + 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 Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization, - typename Manifest = UnavailableGeneratedManifest> - using PhaseCondition = - ModelDefinition; + 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 struct IsModelTypeList : std::false_type {}; + template struct IsModelTypeList : std::false_type { }; - template struct IsModelTypeList> : std::true_type {}; + template struct IsModelTypeList> : std::true_type { }; - template struct IsModelDefinition : std::false_type {}; + template struct IsModelDefinition : std::false_type { }; - template + template < + typename Specification, + FixedString StableName, + SpecificationRole Role, + GeneratedStateKind StateKind, + typename DependsOn, + typename Affects, + typename Normalization, + typename Manifest> struct IsModelDefinition< ModelDefinition> - : std::bool_constant<(StableName.view().size() > 0) && - CompatibleSpecificationRoleAndGeneratedState && - IsModelTypeList::value && - IsModelTypeList::value>{}; + : std::bool_constant< + (StableName.view().size() > 0) && CompatibleSpecificationRoleAndGeneratedState && + IsModelTypeList::value && IsModelTypeList::value> { }; template ::value> - struct DefinitionDescribesCandidate : std::false_type {}; + struct DefinitionDescribesCandidate : std::false_type { }; template struct DefinitionDescribesCandidate - : std::bool_constant> {}; + : std::bool_constant> { }; template struct SpecificationDefinitionFor { static constexpr bool available = false; }; template - struct SpecificationDefinitionFor::ModelDefinition>> { + struct SpecificationDefinitionFor< + Candidate, + std::void_t::ModelDefinition>> { using Type = typename std::remove_cvref_t::ModelDefinition; static constexpr bool available = DefinitionDescribesCandidate>::value; }; @@ -570,7 +604,7 @@ export namespace mean_field::models { template requires detail::SpecificationDefinitionFor>::available struct SpecificationTraits { - using Definition = ModelDefinitionForT; + using Definition = ModelDefinitionForT; 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, Affects, 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, Affects, 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 &axis() const noexcept { + [[nodiscard]] const std::array< + double, + 3> & + axis() const noexcept { return m_axis; } - [[nodiscard]] const std::array ¢er() 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, Affects, 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 struct ModelTypeListContains> - : std::bool_constant<(std::same_as || ...)> {}; + : std::bool_constant<(std::same_as || ...)> { }; template inline constexpr bool modelTypeListContains = ModelTypeListContains::value; template struct ResidualFor final { - using SpecificationType = Specification; + using SpecificationType = Specification; static constexpr std::size_t scalarArity = 1; }; template 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 struct PhysicalCoordinateFor final { - using SpecificationType = Specification; + using SpecificationType = Specification; static constexpr std::size_t scalarArity = 1; }; template 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 [[nodiscard]] consteval bool generatedScalarDimensionsAreCoherent() { - using Contribution = SpecificationContribution; + using Contribution = SpecificationContribution; 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; + { Normalization::targetScale } -> std::convertible_to; }; 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 || !PhysicalScaleRepresentedQuantity || - !PhysicalScaleRepresentedQuantity) { + !PhysicalScaleRepresentedQuantity + ) { return false; - } else if constexpr (!requires { - typename std::integral_constant< - PhysicalScaleLaw, - static_cast( - Normalization::targetScale)>; - typename std::integral_constant< - PhysicalScaleLaw, - static_cast( - ValueNormalization::scale)>; - typename std::integral_constant< - PhysicalScaleLaw, - static_cast( - ResidualNormalization::scale)>; - typename std::bool_constant< - static_cast( - Manifest::targetUnits) == - TargetQuantity::identifier>; - typename std::bool_constant< - static_cast( - Manifest::residualUnits) == - ConstraintResidualQuantity::identifier>; - }) { + } else if constexpr ( + !requires { + typename std::integral_constant< + PhysicalScaleLaw, static_cast(Normalization::targetScale)>; + typename std::integral_constant< + PhysicalScaleLaw, static_cast(ValueNormalization::scale)>; + typename std::integral_constant< + PhysicalScaleLaw, static_cast(ResidualNormalization::scale)>; + typename std::bool_constant< + static_cast(Manifest::targetUnits) == TargetQuantity::identifier>; + typename std::bool_constant< + static_cast(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> && - Normalization::targetScale == - physicalScaleForQuantity && - std::same_as && - std::same_as< - GeneratedCoordinateQuantity, - typename Manifest::GeneratedCoordinateQuantity> && - std::same_as< - ConstraintResidualQuantity, - typename Manifest::ConstraintResidualQuantity> && - ValueNormalization::scale == - physicalScaleForQuantity && - ResidualNormalization::scale == - physicalScaleForQuantity && - static_cast(Manifest::targetUnits) == - TargetQuantity::identifier && - static_cast(Manifest::residualUnits) == - ConstraintResidualQuantity::identifier && - std::same_as< - std::remove_cvref_t().target())>, - typename Normalization::TargetValue>; + return std::same_as< + typename Normalization::TargetValue, dimensions::QuantityValue> && + Normalization::targetScale == physicalScaleForQuantity && + std::same_as && + std::same_as< + GeneratedCoordinateQuantity, typename Manifest::GeneratedCoordinateQuantity> && + std::same_as< + ConstraintResidualQuantity, typename Manifest::ConstraintResidualQuantity> && + ValueNormalization::scale == physicalScaleForQuantity && + ResidualNormalization::scale == physicalScaleForQuantity && + static_cast(Manifest::targetUnits) == TargetQuantity::identifier && + static_cast(Manifest::residualUnits) == + ConstraintResidualQuantity::identifier && + std::same_as< + std::remove_cvref_t().target())>, + typename Normalization::TargetValue>; } } } @@ -993,20 +1028,17 @@ export namespace mean_field::models { template concept CompleteGeneratedScalarDimensionsFor = ModelSpecification && - detail::generatedScalarDimensionsAreCoherent< - std::remove_cvref_t>(); + detail::generatedScalarDimensionsAreCoherent>(); template concept CompleteGeneratedNormalizationFor = - ModelSpecification && - CompleteGeneratedScalarDimensionsFor && + ModelSpecification && CompleteGeneratedScalarDimensionsFor && (SpecificationContribution>::generatedValueArity == 0 || SpecificationContribution>::Normalization::available); template concept CompleteGeneratedManifestFor = - ModelSpecification && - CompleteGeneratedScalarDimensionsFor && + ModelSpecification && CompleteGeneratedScalarDimensionsFor && (SpecificationContribution>::generatedValueArity == 0 || SpecificationContribution>::Manifest::available); @@ -1035,7 +1067,7 @@ export namespace mean_field::models { }; template 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 struct SpecificationsForRole> { - using Type = typename ConcatenateModelTypeLists< - std::conditional_t::role == Role, ModelTypeList, - ModelTypeList<>>...>::Type; + using Type = typename ConcatenateModelTypeLists::role == Role, + ModelTypeList, + 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::key < SpecificationTraits::key), - SpecificationSetStorage, - typename PrependSpecification::Type>; + using Type = std::conditional_t< + (SpecificationTraits::key < SpecificationTraits::key), + SpecificationSetStorage, + typename PrependSpecification::Type>; }; template struct CanonicalizeSpecifications; @@ -1098,21 +1132,26 @@ export namespace mean_field::models { using CanonicalSpecificationSet = typename CanonicalizeSpecifications, Specifications...>::Type; - template consteval bool specificationKeyIsUnique() { + template < + ModelSpecification Head, + ModelSpecification... Tail> + consteval bool specificationKeyIsUnique() { constexpr auto headKey = SpecificationTraits::key; - return ((headKey.role != SpecificationTraits::key.role || - headKey.stableName != SpecificationTraits::key.stableName) && - ...); + return ( + (headKey.role != SpecificationTraits::key.role || + headKey.stableName != SpecificationTraits::key.stableName) && + ... + ); } template struct SpecificationKeysAreUnique; - template <> struct SpecificationKeysAreUnique<> : std::true_type {}; + template <> struct SpecificationKeysAreUnique<> : std::true_type { }; template struct SpecificationKeysAreUnique - : std::bool_constant() && - SpecificationKeysAreUnique::value> {}; + : std::bool_constant< + specificationKeyIsUnique() && SpecificationKeysAreUnique::value> { }; template inline constexpr std::size_t specificationRoleCount = @@ -1122,7 +1161,7 @@ export namespace mean_field::models { template struct ModelTypeListScalarArity> - : std::integral_constant {}; + : std::integral_constant { }; template inline constexpr bool isOneOf = (std::same_as || ...); @@ -1136,9 +1175,10 @@ export namespace mean_field::models { template struct ArgumentsMatchCanonicalSpecifications, Arguments...> - : std::bool_constant, CanonicalSpecifications...> && ...) && - ((typeCount == 1) && ...)> {}; + : std::bool_constant< + sizeof...(CanonicalSpecifications) == sizeof...(Arguments) && + (isOneOf, CanonicalSpecifications...> && ...) && + ((typeCount == 1) && ...)> { }; } // namespace detail template @@ -1150,12 +1190,12 @@ export namespace mean_field::models { concept ValidModelSpecificationPack = (ResolvedModelSpecification> && ...) && specificationKeysAreUnique...> && - detail::specificationRoleCount...> == 1; + detail::specificationRoleCount...> == + 1; template requires(ModelSpecification> && ...) && - specificationKeysAreUnique...> + specificationKeysAreUnique...> using SpecificationSet = detail::CanonicalSpecificationSet...>; template @@ -1203,12 +1243,13 @@ export namespace mean_field::models { [[nodiscard]] consteval SpecificationDescriptor specificationDescriptor() { using Contribution = SpecificationContribution; - return {.name = SpecificationTraits::name, - .role = SpecificationTraits::role, - .key = SpecificationTraits::key, - .generatedValueArity = detail::ModelTypeListScalarArity::value, - .generatedResidualArity = - detail::ModelTypeListScalarArity::value}; + return { + .name = SpecificationTraits::name, + .role = SpecificationTraits::role, + .key = SpecificationTraits::key, + .generatedValueArity = detail::ModelTypeListScalarArity::value, + .generatedResidualArity = detail::ModelTypeListScalarArity::value + }; } namespace detail { @@ -1231,14 +1272,12 @@ export namespace mean_field::models { std::tuple &&arguments, CanonicalArgumentsTag ) - : m_specifications( - std::get(std::move(arguments))... - ) { + : m_specifications(std::get(std::move(arguments))...) { } public: - using SpecificationTypes = SpecificationSetStorage; - using OperatorSignature = SpecificationOperatorSignature; + using SpecificationTypes = SpecificationSetStorage; + using OperatorSignature = SpecificationOperatorSignature; static constexpr bool symbolicallySquare = OperatorSignature::symbolicallySquare; @@ -1249,22 +1288,27 @@ export namespace mean_field::models { symbolicallySquare && (SpecificationContribution::hasDeclarativeDefinition && ...); template - requires ArgumentsMatchCanonicalSpecifications::value && + requires ArgumentsMatchCanonicalSpecifications< + SpecificationTypes, + Arguments...>::value && std::constructible_from< std::tuple...>, Arguments...> && - (std::constructible_from && ...) + (std::constructible_from< + Specifications, + Specifications &&> && + ...) explicit SpecifiedModel(Arguments &&...arguments) : SpecifiedModel( - std::tuple...>{ - std::forward(arguments)... - }, + std::tuple...>{std::forward(arguments)...}, CanonicalArgumentsTag{} ) { } template - requires isOneOf + requires isOneOf< + Specification, + Specifications...> [[nodiscard]] const Specification &specification() const noexcept { return std::get(m_specifications); } @@ -1282,10 +1326,11 @@ export namespace mean_field::models { runtimeDescriptors = [] { std::array descriptors{}; std::size_t index = 0; - ((descriptors[index] = {.specification = specificationDescriptor(), - .canonicalIndex = index, - .hasDeclarativeDefinition = - SpecificationContribution::hasDeclarativeDefinition}, + ((descriptors[index] = + {.specification = specificationDescriptor(), + .canonicalIndex = index, + .hasDeclarativeDefinition = + SpecificationContribution::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 - using ConstraintOf = models::stellar::equation::ConstraintOf; + template using ConstraintOf = models::stellar::equation::ConstraintOf; } // namespace equation - template - using Derivative = models::stellar::Derivative; + template using Derivative = models::stellar::Derivative; - template - using Reads = models::DependsOn; + template using Reads = models::DependsOn; - template - using Changes = models::Affects; + template using Changes = models::Affects; - 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::GeneratedCoordinateQuantity; typename std::remove_cvref_t::ConstraintResidualQuantity; typename std::remove_cvref_t::TargetValue; - requires models::GeneratedNormalizationDefinition< - typename std::remove_cvref_t::Normalization>; - requires models::GeneratedManifestDefinition< - typename std::remove_cvref_t::Manifest>; + requires models::GeneratedNormalizationDefinition::Normalization>; + requires models::GeneratedManifestDefinition::Manifest>; requires std::remove_cvref_t::Normalization::available; requires std::remove_cvref_t::Manifest::available; requires std::remove_cvref_t::dimensionallyTyped; @@ -1407,31 +1446,43 @@ export namespace mean_field::integral { using FixedTotalMass = models::FixedTotalMass; using FixedAngularMomentum = models::FixedAngularMomentum; - template , - 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; - template , - 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; - template , - 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; - template , - 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; @@ -1467,10 +1518,13 @@ export namespace mean_field::integral { export namespace mean_field::constraint { using FixedCentralDensity = models::FixedCentralDensity; - template , - 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; template < diff --git a/libmeanfield/interface/models/typed_stellar_model.cppm b/libmeanfield/interface/models/typed_stellar_model.cppm index 228f81f..ecb528f 100644 --- a/libmeanfield/interface/models/typed_stellar_model.cppm +++ b/libmeanfield/interface/models/typed_stellar_model.cppm @@ -25,10 +25,9 @@ export namespace mean_field::model { using EquationOfStateType = models::SpecificationForRoleT; - 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 using SpecificationsForRole = models::SpecificationsForRoleT; @@ -48,7 +47,9 @@ export namespace mean_field::model { models::HasUniqueSpecificationForRole; template - requires std::constructible_from + requires std::constructible_from< + Storage, + Arguments...> explicit StellarModel(Arguments &&...arguments) : m_specifications(std::forward(arguments)...) { } @@ -62,8 +63,12 @@ export namespace mean_field::model { static constexpr bool containsSpecification = Storage::template containsSpecification; template - requires models::HasUniqueSpecificationForRole - [[nodiscard]] const models::SpecificationForRoleT & + requires models::HasUniqueSpecificationForRole< + Role, + SpecificationTypes> + [[nodiscard]] const models::SpecificationForRoleT< + Role, + SpecificationTypes> & specificationForRole() const noexcept { using Specification = models::SpecificationForRoleT; return specification(); @@ -74,8 +79,9 @@ export namespace mean_field::model { } template - requires models::HasUniqueSpecificationForRole + requires models::HasUniqueSpecificationForRole< + models::SpecificationRole::boundary_condition, + SpecificationTypes> [[nodiscard]] const auto &surfaceCondition() const noexcept { return specificationForRole(); } @@ -95,20 +101,20 @@ export namespace mean_field::model { -> StellarModel...>>; namespace detail { - template struct IsStellarModel : std::false_type {}; + template struct IsStellarModel : std::false_type { }; template struct IsStellarModel< StellarModel, - std::void_t::SpecificationTypes, - typename StellarModel::OperatorSignature, - decltype(StellarModel::specificationCount), - decltype(StellarModel::hasCompleteEquilibriumDeclaration)>> - : std::true_type {}; + std::void_t< + typename StellarModel::SpecificationTypes, + typename StellarModel::OperatorSignature, + decltype(StellarModel::specificationCount), + decltype(StellarModel::hasCompleteEquilibriumDeclaration)>> : std::true_type { }; template ::value> struct StellarModelRoleSelection { - using Types = models::ModelTypeList<>; + using Types = models::ModelTypeList<>; static constexpr std::size_t count = 0; }; diff --git a/libmeanfield/interface/normalization/operators.cppm b/libmeanfield/interface/normalization/operators.cppm index fe68892..9d4804f 100644 --- a/libmeanfield/interface/normalization/operators.cppm +++ b/libmeanfield/interface/normalization/operators.cppm @@ -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 - concept RuntimePreparedNormalizationOperation = - requires(const std::remove_cvref_t &problem) { - typename std::remove_cvref_t::NormalizationPrescriptionType; - typename std::remove_cvref_t::FormType; - requires RuntimePreparedNormalizationFor< - typename std::remove_cvref_t::NormalizationPrescriptionType, - typename std::remove_cvref_t::FormType>; - { - problem.GetNormalizationPrescription() - } -> std::same_as::NormalizationPrescriptionType &>; - { - prepareStellarNormalization( - problem.GetNormalizationPrescription(), - problem) - } -> std::same_as; - }; + concept RuntimePreparedNormalizationOperation = requires(const std::remove_cvref_t &problem) { + typename std::remove_cvref_t::NormalizationPrescriptionType; + typename std::remove_cvref_t::FormType; + requires RuntimePreparedNormalizationFor< + typename std::remove_cvref_t::NormalizationPrescriptionType, + typename std::remove_cvref_t::FormType>; + { + problem.GetNormalizationPrescription() + } -> std::same_as::NormalizationPrescriptionType &>; + { + prepareStellarNormalization(problem.GetNormalizationPrescription(), problem) + } -> std::same_as; + }; template requires utils::blocks::block_form_is_valid_v
@@ -183,16 +180,14 @@ export namespace mean_field::normalization { m_residualFactors(layout.residual_offsets().Last()) { } - explicit DiagonalNormalizationBuilder( - utils::blocks::form_layout && - ) = delete; + explicit DiagonalNormalizationBuilder(utils::blocks::form_layout &&) = delete; - explicit DiagonalNormalizationBuilder( - const utils::blocks::form_layout && - ) = delete; + explicit DiagonalNormalizationBuilder(const utils::blocks::form_layout &&) = delete; template - requires utils::blocks::contains_type_v + 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; 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 - requires utils::blocks::contains_type_v + 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; 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 - requires utils::blocks::contains_type_v + requires utils::blocks::contains_type_v< + Block, + typename Form::value_blocks> void SetValueGlobal(const double physicalScale) { constexpr int block = utils::blocks::type_index_v; SetConstantMetricValueBlock(physicalScale, BlockSize(m_layout->value_offsets(), block)); } template - requires utils::blocks::contains_type_v + requires utils::blocks::contains_type_v< + Block, + typename Form::residual_blocks> void SetResidualGlobal(const double physicalScale) { constexpr int block = utils::blocks::type_index_v; mfem::Vector metric(BlockSize(m_layout->residual_offsets(), block)); @@ -246,7 +241,9 @@ export namespace mean_field::normalization { } template - requires utils::blocks::contains_type_v + 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; - 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(row)] - ? pointMetric - : bulkPrimalGramDiagonal(row); + metric(row) = isPointRow[static_cast(row)] ? pointMetric : bulkPrimalGramDiagonal(row); } SetResidualBlock(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( diff --git a/libmeanfield/interface/normalization/physical_riesz.cppm b/libmeanfield/interface/normalization/physical_riesz.cppm index c8635e6..026112d 100644 --- a/libmeanfield/interface/normalization/physical_riesz.cppm +++ b/libmeanfield/interface/normalization/physical_riesz.cppm @@ -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; + PhysicalRieszDiagonal( + dimensions::LengthValue, + double = 1.0 + ) + -> PhysicalRieszDiagonal< + ReferenceGeometry, + FixedMassBranchReference>; template struct IsPhysicalRieszDiagonal : std::false_type { }; @@ -71,8 +76,7 @@ export namespace mean_field::normalization { struct IsPhysicalRieszDiagonal> : std::true_type { }; template - concept PhysicalRieszDiagonalPrescription = - IsPhysicalRieszDiagonal>::value; + concept PhysicalRieszDiagonalPrescription = IsPhysicalRieszDiagonal>::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 + template < + RieszGeometryPolicy Geometry, + ReferenceScalePolicy ScaleSource, + typename Model> requires requires(const Model &model) { - { - model.template specification() - } -> std::same_as; + { model.template specification() } -> std::same_as; { model.template specification().targetMass() } -> std::same_as; } [[nodiscard]] StellarCharacteristicScales deriveStellarCharacteristicScales( - const PhysicalRieszDiagonal &prescription, + const PhysicalRieszDiagonal< + Geometry, + ScaleSource> &prescription, const Model &model ) { return deriveStellarCharacteristicScales( - model.template specification().targetMass(), - prescription.referenceRadius(), + model.template specification().targetMass(), prescription.referenceRadius(), prescription.gravitationalConstant() ); } @@ -179,14 +176,14 @@ export namespace mean_field::normalization { * normalization plan used by the discretization. */ template 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 { \ - static constexpr bool available = true; \ - static constexpr RieszTopology value = RieszTopology::Name; \ +#define MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(Name) \ + template <> struct DeclaredRieszTopology { \ + 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 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 { \ - static constexpr bool available = true; \ - static constexpr PhysicalScaleKind value = PhysicalScaleKind::Name; \ +#define MEAN_FIELD_DECLARED_PHYSICAL_SCALE(Name) \ + template <> struct DeclaredPhysicalScale { \ + 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 - struct CompileDeclaredPhysicalRieszCoordinate { - using Method = UnsupportedPhysicalRieszCoordinate; + template 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(Declaration::scale); using Topology = DeclaredRieszTopology; - using Scale = DeclaredPhysicalScale; + using Scale = DeclaredPhysicalScale; public: - static constexpr bool registered = static_cast(Declaration::available) && - Topology::available && Scale::available; + static constexpr bool registered = + static_cast(Declaration::available) && Topology::available && Scale::available; using Method = std::conditional_t< registered, PhysicalRieszCoordinate, @@ -259,7 +255,7 @@ export namespace mean_field::normalization { template 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::Normalization::Value> { }; template 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::Normalization::Residual> { }; - template - struct GeneratedPhysicalRieszCoverage { + template struct GeneratedPhysicalRieszCoverage { static constexpr bool complete = false; }; @@ -297,16 +290,12 @@ export namespace mean_field::normalization { models::ModelTypeList, models::ModelTypeList> { static constexpr bool complete = - (DeclaredGeneratedPhysicalRieszCoordinate< - GeneratedValues, - CoordinateKind::value>::registered && ...) && - (DeclaredGeneratedPhysicalRieszCoordinate< - GeneratedResiduals, - CoordinateKind::residual>::registered && ...); + (DeclaredGeneratedPhysicalRieszCoordinate::registered && ...) && + (DeclaredGeneratedPhysicalRieszCoordinate::registered && + ...); }; - template - struct SpecificationPhysicalRieszCoverage { + template 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 - concept PhysicalRieszCoreRuntime = - requires(const std::remove_cvref_t &core) { - { - core.GetGravityContext().GetDensityMap() - } -> std::same_as; - { - core.GetGravityContext().GetGravityGradientMap() - } -> std::same_as; - { - core.GetGravityContext().GetGravityPotentialMap() - } -> std::same_as; - { - core.GetHydrostaticOperator().GetEnthalpyMap() - } -> std::same_as; - { - core.GetDomainDeformation().parameterCount() - } -> std::same_as; - }; + concept PhysicalRieszCoreRuntime = requires(const std::remove_cvref_t &core) { + { core.GetGravityContext().GetDensityMap() } -> std::same_as; + { core.GetGravityContext().GetGravityGradientMap() } -> std::same_as; + { core.GetGravityContext().GetGravityPotentialMap() } -> std::same_as; + { core.GetHydrostaticOperator().GetEnthalpyMap() } -> std::same_as; + { core.GetDomainDeformation().parameterCount() } -> std::same_as; + }; namespace detail { template - using GeneratedPhysicalRieszMethod = - typename DeclaredGeneratedPhysicalRieszCoordinate::Method; + using GeneratedPhysicalRieszMethod = typename DeclaredGeneratedPhysicalRieszCoordinate::Method; template struct GeneratedPhysicalRieszRuntimeCoordinate : std::false_type { }; @@ -389,8 +366,7 @@ export namespace mean_field::normalization { std::void_t::topology)>> : std::bool_constant< DeclaredGeneratedPhysicalRieszCoordinate::registered && - GeneratedPhysicalRieszMethod::topology == - RieszTopology::global_scalar> { }; + GeneratedPhysicalRieszMethod::topology == RieszTopology::global_scalar> { }; template struct SpecificationPhysicalRieszRuntimeCoverage : std::false_type { }; @@ -420,21 +396,18 @@ export namespace mean_field::normalization { struct SpecificationSetPhysicalRieszRuntimeCoverage : std::false_type { }; template - struct SpecificationSetPhysicalRieszRuntimeCoverage< - models::detail::SpecificationSetStorage> - : std::bool_constant< - (SpecificationPhysicalRieszRuntimeCoverage::value && ...)> { }; + struct SpecificationSetPhysicalRieszRuntimeCoverage> + : std::bool_constant<(SpecificationPhysicalRieszRuntimeCoverage::value && ...)> { }; } // namespace detail template concept CompleteGeneratedPhysicalRieszRuntimeNormalizationFor = - detail::SpecificationPhysicalRieszRuntimeCoverage< - std::remove_cvref_t>::value; + detail::SpecificationPhysicalRieszRuntimeCoverage>::value; -#define MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(BlockType, TopologyValue, ScaleValue) \ - template <> struct PhysicalRieszBlockTraits { \ - using Method = PhysicalRieszCoordinate; \ - static constexpr bool registered = true; \ +#define MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(BlockType, TopologyValue, ScaleValue) \ + template <> struct PhysicalRieszBlockTraits { \ + using Method = PhysicalRieszCoordinate; \ + 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 - [[nodiscard]] double physicalScale( - const StellarCharacteristicScales &scales - ) { + template [[nodiscard]] double physicalScale(const StellarCharacteristicScales &scales) { static_assert(PhysicalRieszBlockTraits::registered, "The block has no Physical Riesz normalization."); - using Method = typename PhysicalRieszBlockTraits::Method; + using Method = typename PhysicalRieszBlockTraits::Method; constexpr PhysicalScaleKind scale = Method::scale; if constexpr (scale == PhysicalScaleKind::dimensionless) { return 1.0; @@ -527,9 +497,7 @@ export namespace mean_field::normalization { template struct MakePhysicalRieszPlan; template - struct MakePhysicalRieszPlan< - utils::blocks::type_list, - utils::blocks::type_list> { + struct MakePhysicalRieszPlan, utils::blocks::type_list> { using Type = NormalizationPlan< CoordinateComponent< CoordinateKind::value, @@ -544,9 +512,8 @@ export namespace mean_field::normalization { template requires utils::blocks::block_form_is_valid_v - using PhysicalRieszNormalizationPlanFor = typename detail::MakePhysicalRieszPlan< - typename Form::value_blocks, - typename Form::residual_blocks>::Type; + using PhysicalRieszNormalizationPlanFor = + typename detail::MakePhysicalRieszPlan::Type; /* * Public compile-time extension point for a normalization prescription. @@ -557,11 +524,10 @@ export namespace mean_field::normalization { * actually prepare. */ template struct NormalizationCompilation { - using Plan = NormalizationPlan<>; + using Plan = NormalizationPlan<>; static constexpr bool registered = false; - template - static constexpr bool runtimeAvailableFor = false; + template 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 requires utils::blocks::block_form_is_valid_v struct RuntimePreparedNormalizationCompilation { - using Plan = RuntimePreparedNormalizationPlanFor; + using Plan = RuntimePreparedNormalizationPlanFor; static constexpr bool registered = CompleteNormalizationFor; template @@ -581,7 +547,7 @@ export namespace mean_field::normalization { template requires utils::blocks::block_form_is_valid_v struct NormalizationCompilation { - using Plan = IdentityNormalizationPlanFor; + using Plan = IdentityNormalizationPlanFor; static constexpr bool registered = CompleteNormalizationFor; template @@ -591,21 +557,18 @@ export namespace mean_field::normalization { template requires utils::blocks::block_form_is_valid_v struct NormalizationCompilation, Form> { - using Plan = PhysicalRieszNormalizationPlanFor; + using Plan = PhysicalRieszNormalizationPlanFor; static constexpr bool registered = CompleteNormalizationFor; template static constexpr bool runtimeAvailableFor = - registered && - PhysicalRieszCoreRuntime> && - detail::SpecificationSetPhysicalRieszRuntimeCoverage< - std::remove_cvref_t>::value; + registered && PhysicalRieszCoreRuntime> && + detail::SpecificationSetPhysicalRieszRuntimeCoverage>::value; }; namespace detail { - template - struct NormalizationCompilationAudit { - using Plan = NormalizationPlan<>; + template 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, - std::remove_cvref_t>::Plan, - decltype(std::bool_constant( - NormalizationCompilation< - std::remove_cvref_t, - std::remove_cvref_t>::registered)>{})>> { - using Compilation = NormalizationCompilation< - std::remove_cvref_t, - std::remove_cvref_t>; - using Plan = typename Compilation::Plan; + typename NormalizationCompilation, std::remove_cvref_t>::Plan, + decltype(std::bool_constant(NormalizationCompilation< + std::remove_cvref_t, + std::remove_cvref_t>::registered)>{})>> { + using Compilation = NormalizationCompilation, std::remove_cvref_t>; + using Plan = typename Compilation::Plan; static constexpr bool registered = - static_cast(Compilation::registered) && - CompleteNormalizationFor>; + static_cast(Compilation::registered) && CompleteNormalizationFor>; }; } // namespace detail template - using NormalizationPlanFor = typename detail::NormalizationCompilationAudit< - std::remove_cvref_t, - Form>::Plan; + using NormalizationPlanFor = + typename detail::NormalizationCompilationAudit, Form>::Plan; template concept CompilableNormalizationFor = - detail::NormalizationCompilationAudit< - std::remove_cvref_t, - std::remove_cvref_t>::registered; + detail::NormalizationCompilationAudit, std::remove_cvref_t>::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> && utils::blocks::block_form_is_valid_v> && - CompilableNormalizationFor< - std::remove_cvref_t, - std::remove_cvref_t> && + CompilableNormalizationFor, std::remove_cvref_t> && std::same_as< - NormalizationPlanFor< - std::remove_cvref_t, - std::remove_cvref_t>, - RuntimePreparedNormalizationPlanFor< - std::remove_cvref_t, - std::remove_cvref_t>>; + NormalizationPlanFor, std::remove_cvref_t>, + RuntimePreparedNormalizationPlanFor, std::remove_cvref_t>>; 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 struct StellarNormalizationRuntimeAudit< Prescription, Form, PhysicalCore, SpecificationTypes, std::void_t< - std::enable_if_t::registered>, - decltype(std::bool_constant( - NormalizationCompilation< - Prescription, - Form>::template runtimeAvailableFor< - PhysicalCore, - SpecificationTypes>)>{})>> + std::enable_if_t::registered>, + decltype(std::bool_constant< + static_cast(NormalizationCompilation:: + template runtimeAvailableFor)>{})>> : std::bool_constant< - (std::same_as || - PhysicalRieszDiagonalPrescription || + (std::same_as || PhysicalRieszDiagonalPrescription || RuntimePreparedNormalizationFor) && - static_cast(NormalizationCompilation< - Prescription, - Form>::template runtimeAvailableFor< - PhysicalCore, - SpecificationTypes>)> { }; + static_cast(NormalizationCompilation:: + template runtimeAvailableFor)> { }; } // 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, - std::remove_cvref_t, - std::remove_cvref_t, - std::remove_cvref_t>::value; + template + concept StellarNormalizationRuntimeAvailableFor = detail::StellarNormalizationRuntimeAudit< + std::remove_cvref_t, + std::remove_cvref_t, + std::remove_cvref_t, + std::remove_cvref_t>::value; } // namespace mean_field::normalization diff --git a/libmeanfield/interface/normalization/plan.cppm b/libmeanfield/interface/normalization/plan.cppm index 0b1c3ea..05b0fde 100644 --- a/libmeanfield/interface/normalization/plan.cppm +++ b/libmeanfield/interface/normalization/plan.cppm @@ -11,10 +11,7 @@ export namespace mean_field::normalization { struct NormalizationPrescriptionTag { }; template - concept NormalizationPrescription = - std::derived_from< - std::remove_cvref_t, - NormalizationPrescriptionTag>; + concept NormalizationPrescription = std::derived_from, 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 - struct RuntimePreparedCoordinate final { + template struct RuntimePreparedCoordinate final { using PrescriptionType = std::remove_cvref_t; }; template 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 struct PhysicalRieszBlockTraits { - using Method = UnsupportedPhysicalRieszCoordinate; - static constexpr bool registered = false; + using Method = UnsupportedPhysicalRieszCoordinate; + static constexpr bool registered = false; }; - template - struct CoordinateComponent final { + template 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>; + using ResidualBlocks = + std::conditional_t>; }; namespace detail { template struct IsTypeList : std::false_type { }; - template - struct IsTypeList> : std::true_type { }; + template struct IsTypeList> : std::true_type { }; template struct IsUniqueDerivedBlockList : std::false_type { }; @@ -102,8 +91,7 @@ export namespace mean_field::normalization { template <> struct IsCoordinateMethod : std::true_type { }; template - struct IsCoordinateMethod> - : std::true_type { }; + struct IsCoordinateMethod> : std::true_type { }; template struct IsCoordinateMethod> : std::true_type { }; @@ -121,10 +109,9 @@ export namespace mean_field::normalization { template struct MethodSupportsBlock, Block> : std::bool_constant< - PhysicalRieszBlockTraits::registered && - std::same_as< - typename PhysicalRieszBlockTraits::Method, - PhysicalRieszCoordinate>> { }; + PhysicalRieszBlockTraits::registered && std::same_as< + typename PhysicalRieszBlockTraits::Method, + PhysicalRieszCoordinate>> { }; template struct MethodSupportsEveryBlock : std::false_type { }; @@ -166,8 +153,7 @@ export namespace mean_field::normalization { }(); static constexpr bool hasCoherentCoordinateLists = [] { - if constexpr (!hasValidKind || !IsTypeList::value || - !IsTypeList::value) { + if constexpr (!hasValidKind || !IsTypeList::value || !IsTypeList::value) { return false; } else if constexpr (Candidate::kind == CoordinateKind::value) { return std::same_as && @@ -182,8 +168,7 @@ export namespace mean_field::normalization { static constexpr bool valid = hasValidKind && IsTypeList::value && IsCoordinateMethod::value && hasValidBlockList && - hasCoherentCoordinateLists && - MethodSupportsEveryBlock::value; + hasCoherentCoordinateLists && MethodSupportsEveryBlock::value; }; template struct Concatenate; @@ -205,23 +190,18 @@ export namespace mean_field::normalization { template struct Append; - template - struct Append, Appended> { + template struct Append, Appended> { using Type = utils::blocks::type_list; }; template using AppendT = typename Append::Type; template - using AppendUniqueT = std::conditional_t< - utils::blocks::contains_type_v, - List, - AppendT>; + using AppendUniqueT = std::conditional_t, List, AppendT>; template struct ListDifference; - template - struct ListDifference, Excluded> { + template struct ListDifference, Excluded> { using Type = utils::blocks::type_list<>; }; @@ -260,10 +240,7 @@ export namespace mean_field::normalization { }; template - using RepeatedTypesT = typename CollectRepeatedTypes< - List, - List, - utils::blocks::type_list<>>::Type; + using RepeatedTypesT = typename CollectRepeatedTypes>::Type; template struct PlanTraits { static constexpr bool valid = false; @@ -274,23 +251,20 @@ export namespace mean_field::normalization { concept NormalizationComponent = detail::ComponentTraits>::valid; template struct NormalizationPlan final { - using ComponentTypes = utils::blocks::type_list; - using ValueBlocks = detail::ConcatenateT; - using ResidualBlocks = detail::ConcatenateT; + using ComponentTypes = utils::blocks::type_list; + using ValueBlocks = detail::ConcatenateT; + using ResidualBlocks = detail::ConcatenateT; }; namespace detail { - template - struct PlanTraits> { + template struct PlanTraits> { static constexpr bool valid = (ComponentTraits::valid && ...); }; template struct MakeIdentityPlan; template - struct MakeIdentityPlan< - utils::blocks::type_list, - utils::blocks::type_list> { + struct MakeIdentityPlan, utils::blocks::type_list> { using Type = NormalizationPlan< CoordinateComponent, IdentityCoordinate>..., CoordinateComponent< @@ -299,30 +273,18 @@ export namespace mean_field::normalization { IdentityCoordinate>...>; }; - template < - NormalizationPrescription Prescription, - typename Values, - typename Residuals> + template struct MakeRuntimePreparedPlan; - template < - NormalizationPrescription Prescription, - typename... Values, - typename... Residuals> + template struct MakeRuntimePreparedPlan< Prescription, utils::blocks::type_list, utils::blocks::type_list> { using Method = RuntimePreparedCoordinate; - using Type = NormalizationPlan< - CoordinateComponent< - CoordinateKind::value, - utils::blocks::type_list, - Method>..., - CoordinateComponent< - CoordinateKind::residual, - utils::blocks::type_list, - Method>...>; + using Type = NormalizationPlan< + CoordinateComponent, Method>..., + CoordinateComponent, Method>...>; }; } // namespace detail @@ -331,46 +293,43 @@ export namespace mean_field::normalization { template requires utils::blocks::block_form_is_valid_v - using IdentityNormalizationPlanFor = typename detail::MakeIdentityPlan< - typename Form::value_blocks, - typename Form::residual_blocks>::Type; + using IdentityNormalizationPlanFor = + typename detail::MakeIdentityPlan::Type; template requires utils::blocks::block_form_is_valid_v - using RuntimePreparedNormalizationPlanFor = - typename detail::MakeRuntimePreparedPlan< - std::remove_cvref_t, - typename Form::value_blocks, - typename Form::residual_blocks>::Type; + using RuntimePreparedNormalizationPlanFor = typename detail::MakeRuntimePreparedPlan< + std::remove_cvref_t, + typename Form::value_blocks, + typename Form::residual_blocks>::Type; template requires utils::blocks::block_form_is_valid_v struct NormalizationCoverage final { - using DeclaredValueBlocks = typename Plan::ValueBlocks; + using DeclaredValueBlocks = typename Plan::ValueBlocks; using DeclaredResidualBlocks = typename Plan::ResidualBlocks; - using MissingValueBlocks = detail::ListDifferenceT; - using UnexpectedValueBlocks = detail::ListDifferenceT; - using RepeatedValueBlocks = detail::RepeatedTypesT; + using MissingValueBlocks = detail::ListDifferenceT; + using UnexpectedValueBlocks = detail::ListDifferenceT; + using RepeatedValueBlocks = detail::RepeatedTypesT; - using MissingResidualBlocks = detail::ListDifferenceT; - using UnexpectedResidualBlocks = detail::ListDifferenceT; - using RepeatedResidualBlocks = detail::RepeatedTypesT; + using MissingResidualBlocks = detail::ListDifferenceT; + using UnexpectedResidualBlocks = + detail::ListDifferenceT; + using RepeatedResidualBlocks = detail::RepeatedTypesT; - 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 - concept CompleteNormalizationFor = utils::blocks::block_form_is_valid_v && - NormalizationPlanType && + concept CompleteNormalizationFor = utils::blocks::block_form_is_valid_v && NormalizationPlanType && NormalizationCoverage>::complete; } // namespace mean_field::normalization diff --git a/libmeanfield/interface/normalization/stellar_equilibrium.cppm b/libmeanfield/interface/normalization/stellar_equilibrium.cppm index 2f0b82e..5600cbe 100644 --- a/libmeanfield/interface/normalization/stellar_equilibrium.cppm +++ b/libmeanfield/interface/normalization/stellar_equilibrium.cppm @@ -2,6 +2,7 @@ module; #include #include +#include #include #include #include @@ -61,7 +62,7 @@ namespace mean_field::normalization::detail { const field::ScalarBoundaryDofMap &surfaceMap ) { mfem::Array marker(finiteElements.mesh->bdr_attributes.Max()); - marker = 0; + marker = 0; constexpr int attribute = DomainSchema::template boundary_attribute(); 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 - using PhysicalRieszMethodFor = typename PhysicalRieszBlockTraits::Method; + template using PhysicalRieszMethodFor = typename PhysicalRieszBlockTraits::Method; - template - struct IsGlobalGeneratedValueNormalization : std::false_type { }; + template struct IsGlobalGeneratedValueNormalization : std::false_type { }; template struct IsGlobalGeneratedValueNormalization< utils::blocks::generated_value_block, std::void_t< - decltype(PhysicalRieszMethodFor< - utils::blocks::generated_value_block>::topology), - decltype(PhysicalRieszMethodFor< - utils::blocks::generated_value_block>::scale)>> + decltype(PhysicalRieszMethodFor>::topology), + decltype(PhysicalRieszMethodFor>::scale)>> : std::bool_constant< - PhysicalRieszBlockTraits< - utils::blocks::generated_value_block>::registered && - PhysicalRieszMethodFor< - utils::blocks::generated_value_block>::topology == + PhysicalRieszBlockTraits>::registered && + PhysicalRieszMethodFor>::topology == RieszTopology::global_scalar> { }; template @@ -139,32 +134,26 @@ export namespace mean_field::normalization { Generated, Specification, std::void_t> - : std::bool_constant< - std::same_as> { }; + : std::bool_constant> { }; template struct GeneratedValueBlocksBelongToSpecification< utils::blocks::type_list...>, Specification> - : std::bool_constant< - (GeneratedCoordinateBelongsToSpecification::value && ...)> { }; + : std::bool_constant<(GeneratedCoordinateBelongsToSpecification::value && ...)> { + }; - template - struct IsGlobalGeneratedResidualNormalization : std::false_type { }; + template struct IsGlobalGeneratedResidualNormalization : std::false_type { }; template struct IsGlobalGeneratedResidualNormalization< utils::blocks::generated_residual_block, std::void_t< - decltype(PhysicalRieszMethodFor< - utils::blocks::generated_residual_block>::topology), - decltype(PhysicalRieszMethodFor< - utils::blocks::generated_residual_block>::scale)>> + decltype(PhysicalRieszMethodFor>::topology), + decltype(PhysicalRieszMethodFor>::scale)>> : std::bool_constant< - PhysicalRieszBlockTraits< - utils::blocks::generated_residual_block>::registered && - PhysicalRieszMethodFor< - utils::blocks::generated_residual_block>::topology == + PhysicalRieszBlockTraits>::registered && + PhysicalRieszMethodFor>::topology == RieszTopology::global_scalar> { }; template @@ -174,14 +163,13 @@ export namespace mean_field::normalization { struct GeneratedResidualBlocksBelongToSpecification< utils::blocks::type_list...>, Specification> - : std::bool_constant< - (GeneratedCoordinateBelongsToSpecification::value && ...)> { }; + : std::bool_constant<(GeneratedCoordinateBelongsToSpecification::value && ...)> { + }; template struct PrepareGeneratedValueNormalizations { - static constexpr bool registered = false; + static constexpr bool registered = false; - template - static constexpr bool completeFor = false; + template static constexpr bool completeFor = false; template static void Apply( @@ -192,14 +180,12 @@ export namespace mean_field::normalization { } }; - template - struct PrepareGeneratedValueNormalizations> { - static constexpr bool registered = - (IsGlobalGeneratedValueNormalization::value && ...); + template struct PrepareGeneratedValueNormalizations> { + static constexpr bool registered = (IsGlobalGeneratedValueNormalization::value && ...); template - static constexpr bool completeFor = registered && - utils::blocks::block_form_is_valid_v && + static constexpr bool completeFor = + registered && utils::blocks::block_form_is_valid_v && (utils::blocks::contains_type_v && ...); template @@ -220,10 +206,9 @@ export namespace mean_field::normalization { }; template struct PrepareGeneratedResidualNormalizations { - static constexpr bool registered = false; + static constexpr bool registered = false; - template - static constexpr bool completeFor = false; + template static constexpr bool completeFor = false; template static void Apply( @@ -236,12 +221,11 @@ export namespace mean_field::normalization { template struct PrepareGeneratedResidualNormalizations> { - static constexpr bool registered = - (IsGlobalGeneratedResidualNormalization::value && ...); + static constexpr bool registered = (IsGlobalGeneratedResidualNormalization::value && ...); template - static constexpr bool completeFor = registered && - utils::blocks::block_form_is_valid_v && + static constexpr bool completeFor = + registered && utils::blocks::block_form_is_valid_v && (utils::blocks::contains_type_v && ...); template @@ -261,15 +245,13 @@ export namespace mean_field::normalization { } }; - template - struct CompileStellarSpecificationNormalization { - using ValuePreparation = PrepareGeneratedValueNormalizations; - using ResidualPreparation = PrepareGeneratedResidualNormalizations; + template struct CompileStellarSpecificationNormalization { + using ValuePreparation = PrepareGeneratedValueNormalizations; + using ResidualPreparation = PrepareGeneratedResidualNormalizations; - static constexpr bool registered = false; + static constexpr bool registered = false; - template - static constexpr bool completeFor = false; + template static constexpr bool completeFor = false; template static void Apply( @@ -277,8 +259,7 @@ export namespace mean_field::normalization { const StellarCharacteristicScales & ) { static_assert( - completeFor, - "The specification has no complete generated-coordinate normalization." + completeFor, "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::GeneratedValueBlocks, typename operators::StellarEquilibriumSpecificationCompilation< Specification>::GeneratedResidualBlocks>> { - using OperatorCompilation = - operators::StellarEquilibriumSpecificationCompilation; - using ValuePreparation = PrepareGeneratedValueNormalizations< - typename OperatorCompilation::GeneratedValueBlocks>; - using ResidualPreparation = PrepareGeneratedResidualNormalizations< - typename OperatorCompilation::GeneratedResidualBlocks>; + using OperatorCompilation = operators::StellarEquilibriumSpecificationCompilation; + using ValuePreparation = + PrepareGeneratedValueNormalizations; + using ResidualPreparation = + PrepareGeneratedResidualNormalizations; static constexpr bool registered = OperatorCompilation::complete && - models::CompleteGeneratedNormalizationFor< - Specification> && + models::CompleteGeneratedNormalizationFor && GeneratedValueBlocksBelongToSpecification< typename OperatorCompilation::GeneratedValueBlocks, Specification>::value && GeneratedResidualBlocksBelongToSpecification< typename OperatorCompilation::GeneratedResidualBlocks, Specification>::value && - ValuePreparation::registered && - ResidualPreparation::registered; + ValuePreparation::registered && ResidualPreparation::registered; template - static constexpr bool completeFor = registered && - ValuePreparation::template completeFor && + static constexpr bool completeFor = registered && ValuePreparation::template completeFor && ResidualPreparation::template completeFor; template @@ -366,9 +342,8 @@ export namespace mean_field::normalization { Model, Form, std::void_t::SpecificationTypes>> - : std::bool_constant< - PrepareSpecificationNormalizations< - typename std::remove_cvref_t::SpecificationTypes>::template completeFor> { }; + : std::bool_constant::SpecificationTypes>::template completeFor> { }; } // namespace detail template @@ -400,9 +375,7 @@ export namespace mean_field::normalization { template concept CompleteStellarNormalizationFor = - detail::StellarModelNormalizationCoverage< - std::remove_cvref_t, - std::remove_cvref_t>::value; + detail::StellarModelNormalizationCoverage, std::remove_cvref_t>::value; /* * Physical Riesz preparation is an optional capability of a physical @@ -418,8 +391,7 @@ export namespace mean_field::normalization { template concept PhysicalRieszStellarEquilibriumProblem = - equilibrium::DiscretizedStellarEquilibriumProblem> && - requires { + equilibrium::DiscretizedStellarEquilibriumProblem> && requires { typename std::remove_cvref_t::ModelType; typename std::remove_cvref_t::FormType; typename std::remove_cvref_t::PhysicalCoreType; @@ -430,8 +402,7 @@ export namespace mean_field::normalization { typename std::remove_cvref_t::NormalizationPrescriptionType, typename std::remove_cvref_t::FormType>; requires CompleteStellarNormalizationFor< - typename std::remove_cvref_t::ModelType, - typename std::remove_cvref_t::FormType>; + typename std::remove_cvref_t::ModelType, typename std::remove_cvref_t::FormType>; requires StellarNormalizationRuntimeAvailableFor< typename std::remove_cvref_t::NormalizationPrescriptionType, typename std::remove_cvref_t::FormType, @@ -450,42 +421,36 @@ export namespace mean_field::normalization { template [[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) { using ProblemType = std::remove_cvref_t; - 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 stellarMarker = utils::domain::make_attribute_marker(*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(*finiteElements.surfaceDeformationFes); @@ -524,13 +489,11 @@ export namespace mean_field::normalization { ); const mfem::Array &surfaceRows = problem.GetPressureSurfaceRows().reduced_dofs(); builder.template SetHybridResidualBlock( - physicalScale(scales), - enthalpyDiagonal, + physicalScale(scales), enthalpyDiagonal, std::span{surfaceRows.GetData(), static_cast(surfaceRows.Size())} ); detail::PrepareSpecificationNormalizations::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::NormalizationPrescriptionType, Unnormalized> && - !PhysicalRieszDiagonalPrescription< - typename std::remove_cvref_t::NormalizationPrescriptionType> && - RuntimePreparedNormalizationOperation) - [[nodiscard]] DiagonalNormalization prepareNormalization( - const Problem &problem - ) { - return prepareStellarNormalization( - problem.GetNormalizationPrescription(), - problem - ); + !PhysicalRieszDiagonalPrescription::NormalizationPrescriptionType> && + RuntimePreparedNormalizationOperation + ) + [[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) { + return prepareStellarNormalization(problem.GetNormalizationPrescription(), problem); } /* @@ -571,9 +529,7 @@ export namespace mean_field::normalization { concept NormalizableStellarEquilibriumProblem = equilibrium::DiscretizedStellarEquilibriumProblem> && requires(const std::remove_cvref_t &problem) { - { - prepareNormalization(problem) - } -> std::same_as; + { prepareNormalization(problem) } -> std::same_as; }; struct NormalizedStellarEquilibriumStatistics final { @@ -591,17 +547,14 @@ export namespace mean_field::normalization { * implied. */ template - concept ProblemBoundStellarInverseFor = - NormalizableStellarEquilibriumProblem> && - std::derived_from, mfem::Solver> && - requires(const std::remove_cvref_t &inverse) { - { - inverse.GetProblem() - } -> std::same_as &>; - { - inverse.IsCurrent() - } -> std::same_as; - }; + concept ProblemBoundStellarInverseFor = NormalizableStellarEquilibriumProblem> && + std::derived_from, mfem::Solver> && + requires(const std::remove_cvref_t &inverse) { + { + inverse.GetProblem() + } -> std::same_as &>; + { inverse.IsCurrent() } -> std::same_as; + }; template requires ProblemBoundStellarInverseFor @@ -623,11 +576,20 @@ export namespace mean_field::normalization { using ProblemType = std::remove_cvref_t; 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 - requires ProblemBoundStellarInverseFor - [[nodiscard]] NormalizedStellarPreconditioner> + requires ProblemBoundStellarInverseFor< + PhysicalInverse, + Problem> + [[nodiscard]] NormalizedStellarPreconditioner< + Problem, + std::remove_cvref_t> MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const; [[nodiscard]] bool IsPrepared() const noexcept { @@ -802,16 +821,15 @@ export namespace mean_field::normalization { requires ProblemBoundStellarInverseFor class NormalizedStellarPreconditioner final : public mfem::Solver { private: - using ProblemType = std::remove_cvref_t; - using NormalizedOperator = NormalizedStellarEquilibriumOperator; + using ProblemType = std::remove_cvref_t; + using NormalizedOperator = NormalizedStellarEquilibriumOperator; using PhysicalInverseType = std::remove_cvref_t; [[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 template - requires ProblemBoundStellarInverseFor - NormalizedStellarPreconditioner> + requires ProblemBoundStellarInverseFor< + PhysicalInverse, + Problem> + NormalizedStellarPreconditioner< + Problem, + std::remove_cvref_t> NormalizedStellarEquilibriumOperator::MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const { VerifyPrepared(); - return NormalizedStellarPreconditioner>{ - *this, - physicalInverse - }; + return NormalizedStellarPreconditioner>{*this, physicalInverse}; } template diff --git a/libmeanfield/interface/operators/contexts/gravity_field_context.cppm b/libmeanfield/interface/operators/contexts/gravity_field_context.cppm index cad9261..28f3d58 100644 --- a/libmeanfield/interface/operators/contexts/gravity_field_context.cppm +++ b/libmeanfield/interface/operators/contexts/gravity_field_context.cppm @@ -1,8 +1,10 @@ module; #include #include +#include #include #include +#include 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 + using GravityFieldPreparationResult = std::expected; + + [[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 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 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 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 TryPrepare( + const GravityFieldStateView &state, + const GravityFieldRevisions &revisions + ); + [[nodiscard]] const GravityFieldGeometryContext &GetGeometryContext() const; [[nodiscard]] const mfem::Vector &GetDensityTrue() const; [[nodiscard]] const mfem::Vector &GetGravityGradientTrue() const; diff --git a/libmeanfield/interface/operators/gravity_field.cppm b/libmeanfield/interface/operators/gravity_field.cppm index 64acb7f..faa7db9 100644 --- a/libmeanfield/interface/operators/gravity_field.cppm +++ b/libmeanfield/interface/operators/gravity_field.cppm @@ -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 diff --git a/libmeanfield/interface/operators/kernels/gravity_displacement_force_kernels.cppm b/libmeanfield/interface/operators/kernels/gravity_displacement_force_kernels.cppm index 6a27b4b..e889f25 100644 --- a/libmeanfield/interface/operators/kernels/gravity_displacement_force_kernels.cppm +++ b/libmeanfield/interface/operators/kernels/gravity_displacement_force_kernels.cppm @@ -1,5 +1,8 @@ module; +#include +#include + #include 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; + + [[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, diff --git a/libmeanfield/interface/operators/kernels/rotation_displacement_force_kernels.cppm b/libmeanfield/interface/operators/kernels/rotation_displacement_force_kernels.cppm index 91fe8d4..e9c6e67 100644 --- a/libmeanfield/interface/operators/kernels/rotation_displacement_force_kernels.cppm +++ b/libmeanfield/interface/operators/kernels/rotation_displacement_force_kernels.cppm @@ -1,5 +1,8 @@ module; +#include +#include + #include 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; + + [[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: * diff --git a/libmeanfield/interface/operators/prepared_angular_momentum.cppm b/libmeanfield/interface/operators/prepared_angular_momentum.cppm index ced565c..0e94a52 100644 --- a/libmeanfield/interface/operators/prepared_angular_momentum.cppm +++ b/libmeanfield/interface/operators/prepared_angular_momentum.cppm @@ -2,6 +2,10 @@ module; #include #include +#include +#include +#include +#include #include #include @@ -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::quiet_NaN()}; + }; + + using AngularMomentumPreparationResult = + std::expected; + + [[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 RefreshGeometry(const mfem::Vector &displacement); + [[nodiscard]] bool RefreshDensity(const mfem::Vector &density); + [[nodiscard]] std::optional TryAssembleResidual(); void VerifyPrepared() const; [[nodiscard]] double EvaluateDensityMomentActionLocal(const mfem::Vector &densityVariation) const; diff --git a/libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm b/libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm index 014c133..5f6ea95 100644 --- a/libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm +++ b/libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm @@ -1,6 +1,7 @@ module; #include +#include #include #include @@ -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; + 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, diff --git a/libmeanfield/interface/operators/prepared_displacement_operator.cppm b/libmeanfield/interface/operators/prepared_displacement_operator.cppm index 04ebe25..455ba4d 100644 --- a/libmeanfield/interface/operators/prepared_displacement_operator.cppm +++ b/libmeanfield/interface/operators/prepared_displacement_operator.cppm @@ -2,6 +2,8 @@ module; #include #include +#include +#include #include @@ -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 AssembleResidual(); void VerifyPrepared() const; const fem::FEM &m_fem; diff --git a/libmeanfield/interface/operators/prepared_gravity_displacement_force.cppm b/libmeanfield/interface/operators/prepared_gravity_displacement_force.cppm index afebff8..b264bfe 100644 --- a/libmeanfield/interface/operators/prepared_gravity_displacement_force.cppm +++ b/libmeanfield/interface/operators/prepared_gravity_displacement_force.cppm @@ -2,6 +2,7 @@ module; #include #include +#include #include #include @@ -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, diff --git a/libmeanfield/interface/operators/prepared_gravity_source.cppm b/libmeanfield/interface/operators/prepared_gravity_source.cppm index ff32c75..6e1e763 100644 --- a/libmeanfield/interface/operators/prepared_gravity_source.cppm +++ b/libmeanfield/interface/operators/prepared_gravity_source.cppm @@ -1,7 +1,9 @@ module; #include +#include #include #include +#include #include 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; + + [[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 ); diff --git a/libmeanfield/interface/operators/prepared_hdiv_mass.cppm b/libmeanfield/interface/operators/prepared_hdiv_mass.cppm index c82bfb6..3541896 100644 --- a/libmeanfield/interface/operators/prepared_hdiv_mass.cppm +++ b/libmeanfield/interface/operators/prepared_hdiv_mass.cppm @@ -1,7 +1,9 @@ module; #include +#include #include #include +#include #include 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; + + [[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 ); diff --git a/libmeanfield/interface/operators/prepared_hydrostatic_equilibrium_operator.cppm b/libmeanfield/interface/operators/prepared_hydrostatic_equilibrium_operator.cppm index 8cb41b6..9d7702e 100644 --- a/libmeanfield/interface/operators/prepared_hydrostatic_equilibrium_operator.cppm +++ b/libmeanfield/interface/operators/prepared_hydrostatic_equilibrium_operator.cppm @@ -3,7 +3,9 @@ module; #include #include #include +#include #include +#include #include #include @@ -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; + + [[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 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; diff --git a/libmeanfield/interface/operators/prepared_mass_normalization.cppm b/libmeanfield/interface/operators/prepared_mass_normalization.cppm index c18ac15..25b882c 100644 --- a/libmeanfield/interface/operators/prepared_mass_normalization.cppm +++ b/libmeanfield/interface/operators/prepared_mass_normalization.cppm @@ -2,7 +2,9 @@ module; #include #include +#include #include +#include #include 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; + 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 + RefreshGeometry(const mfem::Vector &displacement); + [[nodiscard]] std::optional RefreshDensity(const mfem::Vector &density); + [[nodiscard]] std::optional AssembleResidual(); + [[nodiscard]] std::optional UpdateResidualForTargetMass(); void VerifyPrepared() const; [[nodiscard]] double EvaluateDensityActionLocal(const mfem::Vector &densityVariation) const; diff --git a/libmeanfield/interface/operators/prepared_pressure_force.cppm b/libmeanfield/interface/operators/prepared_pressure_force.cppm index ab2c6ef..6e48e79 100644 --- a/libmeanfield/interface/operators/prepared_pressure_force.cppm +++ b/libmeanfield/interface/operators/prepared_pressure_force.cppm @@ -3,6 +3,7 @@ module; #include #include #include +#include #include #include @@ -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 PrepareGeometry(); + [[nodiscard]] std::optional PrepareMaterialState(); void FinalizeDisplacementJacobianPreparation(); - void AssembleCachedResidual(); + [[nodiscard]] std::optional AssembleCachedResidual(); void VerifyPrepared() const; @@ -301,4 +322,4 @@ export namespace mean_field::operators { const PreparedPressureForceOperator &m_preparedOperator; }; -} // namespace mean_field::operators \ No newline at end of file +} // namespace mean_field::operators diff --git a/libmeanfield/interface/operators/prepared_rotation_displacement_force.cppm b/libmeanfield/interface/operators/prepared_rotation_displacement_force.cppm index fb547e5..87cc31e 100644 --- a/libmeanfield/interface/operators/prepared_rotation_displacement_force.cppm +++ b/libmeanfield/interface/operators/prepared_rotation_displacement_force.cppm @@ -2,6 +2,7 @@ module; #include #include +#include #include #include @@ -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, diff --git a/libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm b/libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm index 7aabf56..579004a 100644 --- a/libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm +++ b/libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm @@ -3,10 +3,14 @@ module; #include #include #include +#include +#include +#include #include #include #include +#include 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::quiet_NaN()}; + eos::EvaluationErrorCode thermodynamicErrorCode{eos::EvaluationErrorCode::nonfinite_result}; + }; + + template + using StellarEquilibriumPreparationResult = std::expected; + + [[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 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 m_gravityStateOffsets; context::gravity_field::GravityFieldLinearizationContext m_gravityContext; diff --git a/libmeanfield/interface/operators/prepared_variadic_stellar_equilibrium.cppm b/libmeanfield/interface/operators/prepared_variadic_stellar_equilibrium.cppm index baa01d0..418a979 100644 --- a/libmeanfield/interface/operators/prepared_variadic_stellar_equilibrium.cppm +++ b/libmeanfield/interface/operators/prepared_variadic_stellar_equilibrium.cppm @@ -5,6 +5,7 @@ module; #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ module; #include #include +#include export module mean_field:operators.prepared_variadic_stellar_equilibrium; @@ -41,9 +43,8 @@ export namespace mean_field::stellar { inline constexpr StructuralZero zeroDerivative{}; template - concept ContributionResult = - std::same_as, ContributionAdded> || - std::same_as, StructuralZero>; + concept ContributionResult = std::same_as, ContributionAdded> || + std::same_as, StructuralZero>; } // namespace mean_field::stellar export namespace mean_field::operators { @@ -54,15 +55,13 @@ export namespace mean_field::operators { * prepared by their own specification contribution, so this backend is * selected solely by the constitutive law. */ - template - struct StellarEquilibriumCoreRuntime { + template struct StellarEquilibriumCoreRuntime { static constexpr bool registered = false; }; - template <> - struct StellarEquilibriumCoreRuntime { + template <> struct StellarEquilibriumCoreRuntime { static constexpr bool registered = true; - using CoreType = PreparedStellarEquilibriumOperator; + using CoreType = PreparedStellarEquilibriumOperator; [[nodiscard]] static std::unique_ptr Make( fem::FEM &finiteElements, @@ -73,11 +72,7 @@ export namespace mean_field::operators { deformation::PreparedDomainDeformationRuntime domainDeformation ) { return std::make_unique( - finiteElements, - domainMapper, - equationOfState, - fixedMass, - surfaceConstraint, + finiteElements, domainMapper, equationOfState, fixedMass, surfaceConstraint, std::move(domainDeformation) ); } @@ -108,57 +103,50 @@ export namespace mean_field::operators { const StellarEquilibriumDependencies &dependencies, const physics::RigidRotation &rotation ) { - { - constantCore.GetLayout() - } -> std::same_as; - { - core.Prepare(state, dependencies, rotation) - } -> std::same_as; - { - constantCore.BuildResidual(residual) - } -> std::same_as; - { - constantCore.IsPrepared() - } -> std::convertible_to; - { - constantCore.GetFixedMassReport() - } -> std::same_as; - { - constantCore.GetDependencies() - } -> std::same_as; + { constantCore.GetLayout() } -> std::same_as; + { core.Prepare(state, dependencies, rotation) } -> std::same_as; + { constantCore.BuildResidual(residual) } -> std::same_as; + { constantCore.IsPrepared() } -> std::convertible_to; + { constantCore.GetFixedMassReport() } -> std::same_as; + { constantCore.GetDependencies() } -> std::same_as; { constantCore.GetGeneratedDisplacementDependency() } -> std::same_as; + { constantCore.GetSurfaceConstraintOperator() } -> std::same_as; + }; + + template + concept FalliblePreparedStellarEquilibriumPhysicalCore = + PreparedStellarEquilibriumPhysicalCore && requires( + std::remove_cvref_t &core, + const mfem::Vector &state, + const StellarEquilibriumDependencies &dependencies, + const physics::RigidRotation &rotation + ) { { - constantCore.GetSurfaceConstraintOperator() - } -> std::same_as; + core.TryPrepare(state, dependencies, rotation) + } -> std::same_as>; }; namespace detail { - template - struct BackendSpecificationListTraits final { - static constexpr bool valid = false; + template struct BackendSpecificationListTraits final { + static constexpr bool valid = false; - template - static constexpr bool contains = false; + template static constexpr bool contains = false; }; template - struct BackendSpecificationListTraits< - models::ModelTypeList> final { + struct BackendSpecificationListTraits> final { static constexpr bool valid = (models::ModelSpecification && ...) && - utils::blocks::types_are_unique_v< - utils::blocks::type_list>; + utils::blocks::types_are_unique_v>; template - static constexpr bool contains = - (std::same_as, Specifications> || ...); + static constexpr bool contains = (std::same_as, Specifications> || ...); }; - template - struct CoreRuntimeInterfaceAudit { - using CoreType = void; + template struct CoreRuntimeInterfaceAudit { + using CoreType = void; static constexpr bool complete = false; }; @@ -169,22 +157,19 @@ export namespace mean_field::operators { typename StellarEquilibriumCoreRuntime< typename std::remove_cvref_t::EquationOfStateType>::CoreType, typename StellarEquilibriumCoreRuntime< - typename std::remove_cvref_t::EquationOfStateType>:: - CoreType::BackendSpecifications, - std::bool_constant( - StellarEquilibriumCoreRuntime< - typename std::remove_cvref_t::EquationOfStateType>::registered)>>> { + typename std::remove_cvref_t::EquationOfStateType>::CoreType::BackendSpecifications, + std::bool_constant(StellarEquilibriumCoreRuntime::EquationOfStateType>::registered)>>> { private: - using ModelType = std::remove_cvref_t; + using ModelType = std::remove_cvref_t; using EquationOfState = typename ModelType::EquationOfStateType; - using Runtime = StellarEquilibriumCoreRuntime; + using Runtime = StellarEquilibriumCoreRuntime; public: using CoreType = typename Runtime::CoreType; static constexpr bool complete = - BackendSpecificationListTraits< - typename CoreType::BackendSpecifications>::valid && + BackendSpecificationListTraits::valid && PreparedStellarEquilibriumPhysicalCore && requires( fem::FEM &finiteElements, @@ -198,17 +183,11 @@ export namespace mean_field::operators { requires Runtime::registered; { Runtime::Make( - finiteElements, - domainMapper, - equationOfState, - fixedMass, - surfaceConstraint, + finiteElements, domainMapper, equationOfState, fixedMass, surfaceConstraint, std::move(domainDeformation) ) } -> std::same_as>; - { - Runtime::SurfaceEquationCount(core) - } -> std::convertible_to; + { Runtime::SurfaceEquationCount(core) } -> std::convertible_to; }; }; } // namespace detail @@ -221,8 +200,7 @@ export namespace mean_field::operators { */ template concept StellarEquilibriumBackendSpecificationList = - detail::BackendSpecificationListTraits< - std::remove_cvref_t>::valid; + detail::BackendSpecificationListTraits>::valid; /** * Backend runtime extension point for one physical model specification. @@ -236,21 +214,17 @@ export namespace mean_field::operators { * selected only when the concrete core owner lists that exact * specification in CoreType::BackendSpecifications. */ - template - struct StellarEquilibriumRuntimeContribution { - static constexpr bool registered = false; + template struct StellarEquilibriumRuntimeContribution { + static constexpr bool registered = false; static constexpr std::size_t rotationProviders = 0; }; - template < - template typename PreparedImplementation, - std::size_t RotationProviderCount = 0> + template