feat(newton): first newton solver implementation
This commit is contained in:
@@ -145,7 +145,13 @@ target_sources(mean_field
|
|||||||
libmeanfield/interface/quadrature/policy.cppm
|
libmeanfield/interface/quadrature/policy.cppm
|
||||||
libmeanfield/interface/quadrature/mfem.cppm
|
libmeanfield/interface/quadrature/mfem.cppm
|
||||||
libmeanfield/interface/solver/fields.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/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.cppm
|
||||||
libmeanfield/interface/preconditioning/backend_implementations.cppm
|
libmeanfield/interface/preconditioning/backend_implementations.cppm
|
||||||
libmeanfield/interface/preconditioning/gravity_field.cppm
|
libmeanfield/interface/preconditioning/gravity_field.cppm
|
||||||
@@ -155,6 +161,7 @@ target_sources(mean_field
|
|||||||
libmeanfield/interface/preconditioning/stellar_structure.cppm
|
libmeanfield/interface/preconditioning/stellar_structure.cppm
|
||||||
libmeanfield/interface/preconditioning/specification_border.cppm
|
libmeanfield/interface/preconditioning/specification_border.cppm
|
||||||
libmeanfield/interface/preconditioning/equilibrium_coordinates.cppm
|
libmeanfield/interface/preconditioning/equilibrium_coordinates.cppm
|
||||||
|
libmeanfield/interface/preconditioning/stellar_recipe.cppm
|
||||||
libmeanfield/interface/preconditioning/preconditioning.cppm
|
libmeanfield/interface/preconditioning/preconditioning.cppm
|
||||||
libmeanfield/interface/normalization/plan.cppm
|
libmeanfield/interface/normalization/plan.cppm
|
||||||
libmeanfield/interface/normalization/physical_riesz.cppm
|
libmeanfield/interface/normalization/physical_riesz.cppm
|
||||||
@@ -346,10 +353,18 @@ add_executable(tests
|
|||||||
tests/normalization/stellar_equilibrium.cpp
|
tests/normalization/stellar_equilibrium.cpp
|
||||||
tests/user-api/stellar_equilibrium.cpp
|
tests/user-api/stellar_equilibrium.cpp
|
||||||
tests/solver/preconditioning_diagnostics.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)
|
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
|
add_executable(mpi_tests
|
||||||
tests/mpi/mpi_test_main.cpp
|
tests/mpi/mpi_test_main.cpp
|
||||||
tests/mpi/distributed_execution.cpp
|
tests/mpi/distributed_execution.cpp
|
||||||
@@ -416,6 +431,26 @@ catch_discover_tests(
|
|||||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_test(
|
||||||
|
NAME mpi_single_rank_stellar_root
|
||||||
|
COMMAND
|
||||||
|
${MPIEXEC_EXECUTABLE}
|
||||||
|
${MPIEXEC_NUMPROC_FLAG} 1
|
||||||
|
${MPIEXEC_PREFLAGS}
|
||||||
|
$<TARGET_FILE:mpi_tests>
|
||||||
|
${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)
|
foreach (mean_field_mpi_ranks IN ITEMS 2 4)
|
||||||
add_test(
|
add_test(
|
||||||
NAME mpi_${mean_field_mpi_ranks}_ranks
|
NAME mpi_${mean_field_mpi_ranks}_ranks
|
||||||
@@ -425,7 +460,7 @@ foreach (mean_field_mpi_ranks IN ITEMS 2 4)
|
|||||||
${MPIEXEC_PREFLAGS}
|
${MPIEXEC_PREFLAGS}
|
||||||
$<TARGET_FILE:mpi_tests>
|
$<TARGET_FILE:mpi_tests>
|
||||||
${MPIEXEC_POSTFLAGS}
|
${MPIEXEC_POSTFLAGS}
|
||||||
"[mpi]"
|
"[mpi]~[single-rank]"
|
||||||
)
|
)
|
||||||
set_tests_properties(
|
set_tests_properties(
|
||||||
mpi_${mean_field_mpi_ranks}_ranks
|
mpi_${mean_field_mpi_ranks}_ranks
|
||||||
|
|||||||
@@ -756,7 +756,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
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);
|
setupTimings.problemConstructionSeconds = maximumRankSeconds(constructionStart, communicator);
|
||||||
|
|
||||||
announce(communicator, "projecting the n=3 Lane-Emden state");
|
announce(communicator, "projecting the n=3 Lane-Emden state");
|
||||||
@@ -919,7 +919,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
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);
|
setupTimings.problemConstructionSeconds = maximumRankSeconds(constructionStart, communicator);
|
||||||
|
|
||||||
announce(communicator, "projecting the n=3 Lane-Emden state for gravity backend finalists");
|
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}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||||
);
|
);
|
||||||
auto problem = equilibrium::discretize(model, finiteElements);
|
auto problem = equilibrium::discretize(model, std::move(finiteElements));
|
||||||
auto projected =
|
auto projected =
|
||||||
seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = radialSampleCount}));
|
seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = radialSampleCount}));
|
||||||
auto dependencies = makeDependencies();
|
auto dependencies = makeDependencies();
|
||||||
|
|||||||
@@ -792,7 +792,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
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}));
|
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024}));
|
||||||
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
||||||
|
|
||||||
@@ -851,7 +851,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
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}));
|
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024}));
|
||||||
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
||||||
|
|
||||||
@@ -914,7 +914,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
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}));
|
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024}));
|
||||||
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
||||||
|
|
||||||
@@ -961,7 +961,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
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}));
|
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024}));
|
||||||
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
||||||
|
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
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);
|
const double problemConstructionSeconds = maximum_rank_seconds(problemConstructionStart, communicator);
|
||||||
announce(communicator, "P0 extended baseline: projecting the Lane-Emden seed");
|
announce(communicator, "P0 extended baseline: projecting the Lane-Emden seed");
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
module;
|
module;
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <expected>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
|
||||||
@@ -9,6 +10,30 @@ import :operators.context.gravity_field;
|
|||||||
namespace {
|
namespace {
|
||||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
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(
|
void true_to_local(
|
||||||
const mfem::ParFiniteElementSpace &finite_element_space,
|
const mfem::ParFiniteElementSpace &finite_element_space,
|
||||||
const mfem::Vector &true_vector,
|
const mfem::Vector &true_vector,
|
||||||
@@ -295,7 +320,21 @@ namespace mean_field::operators::context::gravity_field {
|
|||||||
const DiscretizationRevision discretization_revision,
|
const DiscretizationRevision discretization_revision,
|
||||||
const DisplacementRevision displacement_revision
|
const DisplacementRevision displacement_revision
|
||||||
) {
|
) {
|
||||||
return PrepareImpl(
|
auto result = TryPrepareImpl(
|
||||||
|
displacement, discretization_revision, displacement_revision, PreparationMode::linearization
|
||||||
|
);
|
||||||
|
if (!result.has_value()) {
|
||||||
|
throwGravityFieldPreparationRejection(result.error());
|
||||||
|
}
|
||||||
|
return std::move(result).value();
|
||||||
|
}
|
||||||
|
|
||||||
|
GravityFieldPreparationResult<GravityFieldGeometryPreparation> GravityFieldGeometryContext::TryPrepare(
|
||||||
|
const mfem::Vector &displacement,
|
||||||
|
const DiscretizationRevision discretization_revision,
|
||||||
|
const DisplacementRevision displacement_revision
|
||||||
|
) {
|
||||||
|
return TryPrepareImpl(
|
||||||
displacement, discretization_revision, displacement_revision, PreparationMode::linearization
|
displacement, discretization_revision, displacement_revision, PreparationMode::linearization
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -305,10 +344,23 @@ namespace mean_field::operators::context::gravity_field {
|
|||||||
const DiscretizationRevision discretization_revision,
|
const DiscretizationRevision discretization_revision,
|
||||||
const DisplacementRevision displacement_revision
|
const DisplacementRevision displacement_revision
|
||||||
) {
|
) {
|
||||||
return PrepareImpl(displacement, discretization_revision, displacement_revision, PreparationMode::primal);
|
auto result =
|
||||||
|
TryPrepareImpl(displacement, discretization_revision, displacement_revision, PreparationMode::primal);
|
||||||
|
if (!result.has_value()) {
|
||||||
|
throwGravityFieldPreparationRejection(result.error());
|
||||||
|
}
|
||||||
|
return std::move(result).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
GravityFieldGeometryPreparation GravityFieldGeometryContext::PrepareImpl(
|
GravityFieldPreparationResult<GravityFieldGeometryPreparation> GravityFieldGeometryContext::TryPreparePrimal(
|
||||||
|
const mfem::Vector &displacement,
|
||||||
|
const DiscretizationRevision discretization_revision,
|
||||||
|
const DisplacementRevision displacement_revision
|
||||||
|
) {
|
||||||
|
return TryPrepareImpl(displacement, discretization_revision, displacement_revision, PreparationMode::primal);
|
||||||
|
}
|
||||||
|
|
||||||
|
GravityFieldPreparationResult<GravityFieldGeometryPreparation> GravityFieldGeometryContext::TryPrepareImpl(
|
||||||
const mfem::Vector &displacement,
|
const mfem::Vector &displacement,
|
||||||
const DiscretizationRevision discretization_revision,
|
const DiscretizationRevision discretization_revision,
|
||||||
const DisplacementRevision displacement_revision,
|
const DisplacementRevision displacement_revision,
|
||||||
@@ -340,19 +392,24 @@ namespace mean_field::operators::context::gravity_field {
|
|||||||
return preparation;
|
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) {
|
if (requires_variation) {
|
||||||
mass_operator.Prepare(displacement);
|
return mass_operator.TryPrepare(displacement);
|
||||||
} else {
|
|
||||||
mass_operator.PreparePrimal(displacement);
|
|
||||||
}
|
}
|
||||||
|
return mass_operator.TryPreparePrimal(displacement);
|
||||||
};
|
};
|
||||||
const auto prepare_source = [&](PreparedMappedGravitySourceOperator &source_operator) {
|
const auto prepare_source = [&](PreparedMappedGravitySourceOperator &source_operator) {
|
||||||
if (requires_variation) {
|
if (requires_variation) {
|
||||||
source_operator.Prepare(displacement);
|
return source_operator.TryPrepare(displacement);
|
||||||
} else {
|
|
||||||
source_operator.PreparePrimal(displacement);
|
|
||||||
}
|
}
|
||||||
|
return source_operator.TryPreparePrimal(displacement);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (discretization_changed) {
|
if (discretization_changed) {
|
||||||
@@ -361,8 +418,14 @@ namespace mean_field::operators::context::gravity_field {
|
|||||||
auto divergence_operator = make_divergence_operator(m_fem);
|
auto divergence_operator = make_divergence_operator(m_fem);
|
||||||
auto transpose_divergence_operator = std::make_unique<mfem::TransposeOperator>(divergence_operator.get());
|
auto transpose_divergence_operator = std::make_unique<mfem::TransposeOperator>(divergence_operator.get());
|
||||||
|
|
||||||
prepare_mass(*mass_operator);
|
auto massResult = prepare_mass(*mass_operator);
|
||||||
prepare_source(*source_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_mass_operator = std::move(mass_operator);
|
||||||
m_source_operator = std::move(source_operator);
|
m_source_operator = std::move(source_operator);
|
||||||
@@ -383,8 +446,14 @@ namespace mean_field::operators::context::gravity_field {
|
|||||||
"operator."
|
"operator."
|
||||||
);
|
);
|
||||||
|
|
||||||
prepare_mass(*m_mass_operator);
|
auto massResult = prepare_mass(*m_mass_operator);
|
||||||
prepare_source(*m_source_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_mass_operator = true;
|
||||||
preparation.rebuilt_source_operator = true;
|
preparation.rebuilt_source_operator = true;
|
||||||
@@ -510,6 +579,17 @@ namespace mean_field::operators::context::gravity_field {
|
|||||||
GravityFieldPreparationReport GravityFieldLinearizationContext::Prepare(
|
GravityFieldPreparationReport GravityFieldLinearizationContext::Prepare(
|
||||||
const GravityFieldStateView &state,
|
const GravityFieldStateView &state,
|
||||||
const GravityFieldRevisions &revisions
|
const GravityFieldRevisions &revisions
|
||||||
|
) {
|
||||||
|
auto result = TryPrepare(state, revisions);
|
||||||
|
if (!result.has_value()) {
|
||||||
|
throwGravityFieldPreparationRejection(result.error());
|
||||||
|
}
|
||||||
|
return std::move(result).value();
|
||||||
|
}
|
||||||
|
|
||||||
|
GravityFieldPreparationResult<GravityFieldPreparationReport> GravityFieldLinearizationContext::TryPrepare(
|
||||||
|
const GravityFieldStateView &state,
|
||||||
|
const GravityFieldRevisions &revisions
|
||||||
) {
|
) {
|
||||||
validate_linearization_state(
|
validate_linearization_state(
|
||||||
m_density_map, m_geometry_context.GetDisplacementMap(), m_gravity_gradient_map, m_gravity_potential_map,
|
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;
|
GravityFieldPreparationReport report;
|
||||||
|
|
||||||
report.geometry =
|
// Geometry preparation is fallible and may invalidate one of its
|
||||||
m_geometry_context.Prepare(state.displacement, revisions.discretization, revisions.displacement);
|
// 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) {
|
if (density_changed) {
|
||||||
m_density_true.SetSize(m_density_map.full_size());
|
m_density_true.SetSize(m_density_map.full_size());
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ module;
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
import :operators.gravity_field;
|
import :operators.gravity_field;
|
||||||
@@ -273,6 +274,18 @@ namespace mean_field::operators {
|
|||||||
context::gravity_field::GravityFieldPreparationReport GravityFieldOperator::Prepare(
|
context::gravity_field::GravityFieldPreparationReport GravityFieldOperator::Prepare(
|
||||||
const mfem::Vector &state,
|
const mfem::Vector &state,
|
||||||
const context::gravity_field::GravityFieldRevisions &revisions
|
const context::gravity_field::GravityFieldRevisions &revisions
|
||||||
|
) {
|
||||||
|
auto result = TryPrepare(state, revisions);
|
||||||
|
if (!result.has_value()) {
|
||||||
|
context::gravity_field::throwGravityFieldPreparationRejection(result.error());
|
||||||
|
}
|
||||||
|
return std::move(result).value();
|
||||||
|
}
|
||||||
|
|
||||||
|
context::gravity_field::GravityFieldPreparationResult<context::gravity_field::GravityFieldPreparationReport>
|
||||||
|
GravityFieldOperator::TryPrepare(
|
||||||
|
const mfem::Vector &state,
|
||||||
|
const context::gravity_field::GravityFieldRevisions &revisions
|
||||||
) {
|
) {
|
||||||
using form = utils::blocks::gravity_field_form;
|
using form = utils::blocks::gravity_field_form;
|
||||||
|
|
||||||
@@ -295,7 +308,7 @@ namespace mean_field::operators {
|
|||||||
const mfem::Vector gravity_potential =
|
const mfem::Vector gravity_potential =
|
||||||
make_read_only_value_view(state, m_state_offsets, gravity_potential_block);
|
make_read_only_value_view(state, m_state_offsets, gravity_potential_block);
|
||||||
|
|
||||||
return m_linearization_context.Prepare(
|
return m_linearization_context.TryPrepare(
|
||||||
{.density = density,
|
{.density = density,
|
||||||
.displacement = displacement,
|
.displacement = displacement,
|
||||||
.gravity_gradient = gravity_gradient,
|
.gravity_gradient = gravity_gradient,
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ module;
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <expected>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
|
|
||||||
@@ -19,6 +22,70 @@ namespace {
|
|||||||
|
|
||||||
enum class GravityDisplacementForceAction { residual, density, gravityGradient, displacement, complete };
|
enum class GravityDisplacementForceAction { residual, density, gravityGradient, displacement, complete };
|
||||||
|
|
||||||
|
using Rejection = mean_field::operators::kernels::GravityDisplacementForceRejection;
|
||||||
|
using Reason = mean_field::operators::kernels::GravityDisplacementForceRejectionReason;
|
||||||
|
using Result = mean_field::operators::kernels::GravityDisplacementForceResult;
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection mapping_rejection(const mean_field::mapping::MappingStatus status) {
|
||||||
|
MFEM_VERIFY(
|
||||||
|
status != mean_field::mapping::MappingStatus::invalid_dimension,
|
||||||
|
"The gravity-displacement-force mapping reported an invariant dimension mismatch."
|
||||||
|
);
|
||||||
|
return {.reason = Reason::invalid_mapping, .mappingStatus = status};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection non_finite_rejection() noexcept {
|
||||||
|
return {.reason = Reason::non_finite_arithmetic};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool vector_is_finite(const mfem::Vector &vector) noexcept {
|
||||||
|
for (int index = 0; index < vector.Size(); ++index) {
|
||||||
|
if (!std::isfinite(vector(index))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int encode_rejection(const std::optional<Rejection> &rejection) noexcept {
|
||||||
|
if (!rejection.has_value()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (rejection->reason == Reason::non_finite_arithmetic) {
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
return static_cast<int>(rejection->mappingStatus) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection decode_rejection(const int encoded) noexcept {
|
||||||
|
if (encoded >= 256) {
|
||||||
|
return non_finite_rejection();
|
||||||
|
}
|
||||||
|
return mapping_rejection(static_cast<mean_field::mapping::MappingStatus>(encoded - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Result synchronize_rejection(
|
||||||
|
const std::optional<Rejection> &localRejection,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
const int localEncoded = encode_rejection(localRejection);
|
||||||
|
int globalEncoded = 0;
|
||||||
|
if (MPI_Allreduce(&localEncoded, &globalEncoded, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("Could not synchronize gravity-displacement-force candidate validity.");
|
||||||
|
}
|
||||||
|
if (globalEncoded != 0) {
|
||||||
|
return std::unexpected(decode_rejection(globalEncoded));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void throw_rejection(const Rejection &rejection) {
|
||||||
|
if (rejection.reason == Reason::non_finite_arithmetic) {
|
||||||
|
throw std::domain_error("The gravity-displacement force produced non-finite arithmetic.");
|
||||||
|
}
|
||||||
|
throw std::domain_error("The gravity-displacement force encountered an invalid mapped domain.");
|
||||||
|
}
|
||||||
|
|
||||||
void true_to_local(
|
void true_to_local(
|
||||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||||
const mfem::Vector &trueVector,
|
const mfem::Vector &trueVector,
|
||||||
@@ -187,11 +254,6 @@ namespace {
|
|||||||
"The gravity-displacement-force displacement dimension does not "
|
"The gravity-displacement-force displacement dimension does not "
|
||||||
"match the mesh dimension."
|
"match the mesh dimension."
|
||||||
);
|
);
|
||||||
|
|
||||||
validate_finite_vector(
|
|
||||||
displacementTrue, "The gravity-displacement-force displacement contains a "
|
|
||||||
"non-finite value."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void validate_density(
|
void validate_density(
|
||||||
@@ -200,7 +262,6 @@ namespace {
|
|||||||
const char *message
|
const char *message
|
||||||
) {
|
) {
|
||||||
MFEM_VERIFY(density.Size() == f.densityFes->GetTrueVSize(), message);
|
MFEM_VERIFY(density.Size() == f.densityFes->GetTrueVSize(), message);
|
||||||
validate_finite_vector(density, message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void validate_gravity_gradient(
|
void validate_gravity_gradient(
|
||||||
@@ -209,11 +270,9 @@ namespace {
|
|||||||
const char *message
|
const char *message
|
||||||
) {
|
) {
|
||||||
MFEM_VERIFY(gravityGradient.Size() == f.gravityFluxFes->GetTrueVSize(), 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::fem::FEM &f,
|
||||||
const mean_field::mapping::DomainMapper &domainMapper,
|
const mean_field::mapping::DomainMapper &domainMapper,
|
||||||
const GravityDisplacementForceAction requestedAction,
|
const GravityDisplacementForceAction requestedAction,
|
||||||
@@ -223,7 +282,8 @@ namespace {
|
|||||||
const mfem::Vector *gravityGradientVariationTrue,
|
const mfem::Vector *gravityGradientVariationTrue,
|
||||||
const mfem::Vector *displacementVariationTrue,
|
const mfem::Vector *displacementVariationTrue,
|
||||||
const mfem::Vector &displacementTrue,
|
const mfem::Vector &displacementTrue,
|
||||||
mfem::Vector &actionTrue
|
mfem::Vector &actionTrue,
|
||||||
|
const bool reportCandidateRejection
|
||||||
) {
|
) {
|
||||||
validate_common_inputs(f, domainMapper, displacementTrue);
|
validate_common_inputs(f, domainMapper, displacementTrue);
|
||||||
|
|
||||||
@@ -308,6 +368,34 @@ namespace {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool inputsAreFinite = vector_is_finite(displacementTrue);
|
||||||
|
if (needsBaseDensity) {
|
||||||
|
inputsAreFinite = inputsAreFinite && vector_is_finite(*baseDensityTrue);
|
||||||
|
}
|
||||||
|
if (needsDensityVariation) {
|
||||||
|
inputsAreFinite = inputsAreFinite && vector_is_finite(*densityVariationTrue);
|
||||||
|
}
|
||||||
|
if (needsBaseGravityGradient) {
|
||||||
|
inputsAreFinite = inputsAreFinite && vector_is_finite(*baseGravityGradientTrue);
|
||||||
|
}
|
||||||
|
if (needsGravityGradientVariation) {
|
||||||
|
inputsAreFinite = inputsAreFinite && vector_is_finite(*gravityGradientVariationTrue);
|
||||||
|
}
|
||||||
|
if (needsDisplacementVariation) {
|
||||||
|
inputsAreFinite = inputsAreFinite && vector_is_finite(*displacementVariationTrue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reportCandidateRejection) {
|
||||||
|
MFEM_VERIFY(inputsAreFinite, "The gravity-displacement-force action contains non-finite input data.");
|
||||||
|
} else {
|
||||||
|
const std::optional<Rejection> inputRejection =
|
||||||
|
inputsAreFinite ? std::optional<Rejection>{} : std::optional<Rejection>{non_finite_rejection()};
|
||||||
|
auto synchronized = synchronize_rejection(inputRejection, f.mesh->GetComm());
|
||||||
|
if (!synchronized.has_value()) {
|
||||||
|
return synchronized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mfem::Vector baseDensityLocal;
|
mfem::Vector baseDensityLocal;
|
||||||
mfem::Vector densityVariationLocal;
|
mfem::Vector densityVariationLocal;
|
||||||
mfem::Vector baseGravityGradientLocal;
|
mfem::Vector baseGravityGradientLocal;
|
||||||
@@ -374,6 +462,8 @@ namespace {
|
|||||||
|
|
||||||
const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering();
|
const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering();
|
||||||
|
|
||||||
|
std::optional<Rejection> candidateRejection;
|
||||||
|
|
||||||
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
||||||
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
|
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
|
||||||
|
|
||||||
@@ -516,13 +606,19 @@ namespace {
|
|||||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||||
);
|
);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (mappingStatus != mean_field::mapping::MappingStatus::valid) {
|
||||||
mappingStatus == mean_field::mapping::MappingStatus::valid,
|
if (!reportCandidateRejection) {
|
||||||
"Stateless mapping failed in the gravity-displacement-"
|
MFEM_VERIFY(
|
||||||
"force kernel. Element: "
|
false, "Stateless mapping failed in the gravity-displacement-"
|
||||||
<< elementId << ", attribute: " << transformation->Attribute
|
"force kernel. Element: "
|
||||||
<< ", quadrature point: " << quadratureIndex << ", status: " << static_cast<int>(mappingStatus)
|
<< elementId << ", attribute: " << transformation->Attribute
|
||||||
);
|
<< ", quadrature point: " << quadratureIndex
|
||||||
|
<< ", status: " << static_cast<int>(mappingStatus)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
candidateRejection = mapping_rejection(mappingStatus);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (needsDisplacementVariation) {
|
if (needsDisplacementVariation) {
|
||||||
const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation(
|
const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation(
|
||||||
@@ -530,13 +626,19 @@ namespace {
|
|||||||
workspace, mappingVariation
|
workspace, mappingVariation
|
||||||
);
|
);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (variationStatus != mean_field::mapping::MappingStatus::valid) {
|
||||||
variationStatus == mean_field::mapping::MappingStatus::valid,
|
if (!reportCandidateRejection) {
|
||||||
"Stateless mapping variation failed in the gravity-"
|
MFEM_VERIFY(
|
||||||
"displacement-force kernel. Element: "
|
false, "Stateless mapping variation failed in the gravity-"
|
||||||
<< elementId << ", attribute: " << transformation->Attribute << ", quadrature point: "
|
"displacement-force kernel. Element: "
|
||||||
<< quadratureIndex << ", status: " << static_cast<int>(variationStatus)
|
<< elementId << ", attribute: " << transformation->Attribute
|
||||||
);
|
<< ", quadrature point: " << quadratureIndex
|
||||||
|
<< ", status: " << static_cast<int>(variationStatus)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
candidateRejection = mapping_rejection(variationStatus);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
densityElement.CalcShape(integrationPoint, densityShape);
|
densityElement.CalcShape(integrationPoint, densityShape);
|
||||||
@@ -624,10 +726,16 @@ namespace {
|
|||||||
|
|
||||||
const double contribution = displacementShape(scalarDof) * forceValue(component);
|
const double contribution = displacementShape(scalarDof) * forceValue(component);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(contribution)) {
|
||||||
std::isfinite(contribution), "The gravity-displacement-force kernel "
|
if (!reportCandidateRejection) {
|
||||||
"encountered a non-finite contribution."
|
MFEM_VERIFY(
|
||||||
);
|
false, "The gravity-displacement-force kernel "
|
||||||
|
"encountered a non-finite contribution."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
candidateRejection = non_finite_rejection();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
elementAction(vectorDof) += contribution;
|
elementAction(vectorDof) += contribution;
|
||||||
}
|
}
|
||||||
@@ -641,11 +749,48 @@ namespace {
|
|||||||
localAction.AddElementVector(displacementDofs, elementAction);
|
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);
|
local_to_true(*f.displacementFes, localAction, actionTrue);
|
||||||
|
|
||||||
|
if (reportCandidateRejection) {
|
||||||
|
const std::optional<Rejection> outputRejection = vector_is_finite(actionTrue)
|
||||||
|
? std::optional<Rejection>{}
|
||||||
|
: std::optional<Rejection>{non_finite_rejection()};
|
||||||
|
auto synchronized = synchronize_rejection(outputRejection, f.mesh->GetComm());
|
||||||
|
if (!synchronized.has_value()) {
|
||||||
|
return synchronized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace mean_field::operators::kernels {
|
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(
|
void apply_gravity_displacement_force_residual(
|
||||||
const fem::FEM &f,
|
const fem::FEM &f,
|
||||||
const mapping::DomainMapper &domainMapper,
|
const mapping::DomainMapper &domainMapper,
|
||||||
@@ -654,10 +799,12 @@ namespace mean_field::operators::kernels {
|
|||||||
const mfem::Vector &displacementTrue,
|
const mfem::Vector &displacementTrue,
|
||||||
mfem::Vector &residualTrue
|
mfem::Vector &residualTrue
|
||||||
) {
|
) {
|
||||||
apply_gravity_displacement_force_action(
|
auto result = try_apply_gravity_displacement_force_residual(
|
||||||
f, domainMapper, GravityDisplacementForceAction::residual, &densityTrue, nullptr, &gravityGradientTrue,
|
f, domainMapper, densityTrue, gravityGradientTrue, displacementTrue, residualTrue
|
||||||
nullptr, nullptr, displacementTrue, residualTrue
|
|
||||||
);
|
);
|
||||||
|
if (!result.has_value()) {
|
||||||
|
throw_rejection(result.error());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void apply_gravity_displacement_force_density_action(
|
void apply_gravity_displacement_force_density_action(
|
||||||
@@ -668,9 +815,9 @@ namespace mean_field::operators::kernels {
|
|||||||
const mfem::Vector &displacementTrue,
|
const mfem::Vector &displacementTrue,
|
||||||
mfem::Vector &actionTrue
|
mfem::Vector &actionTrue
|
||||||
) {
|
) {
|
||||||
apply_gravity_displacement_force_action(
|
(void)apply_gravity_displacement_force_action(
|
||||||
f, domainMapper, GravityDisplacementForceAction::density, nullptr, &densityVariationTrue,
|
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,
|
const mfem::Vector &displacementTrue,
|
||||||
mfem::Vector &actionTrue
|
mfem::Vector &actionTrue
|
||||||
) {
|
) {
|
||||||
apply_gravity_displacement_force_action(
|
(void)apply_gravity_displacement_force_action(
|
||||||
f, domainMapper, GravityDisplacementForceAction::gravityGradient, &baseDensityTrue, nullptr, nullptr,
|
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,
|
const mfem::Vector &displacementTrue,
|
||||||
mfem::Vector &actionTrue
|
mfem::Vector &actionTrue
|
||||||
) {
|
) {
|
||||||
apply_gravity_displacement_force_action(
|
(void)apply_gravity_displacement_force_action(
|
||||||
f, domainMapper, GravityDisplacementForceAction::displacement, &baseDensityTrue, nullptr,
|
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,
|
const mfem::Vector &displacementTrue,
|
||||||
mfem::Vector &actionTrue
|
mfem::Vector &actionTrue
|
||||||
) {
|
) {
|
||||||
apply_gravity_displacement_force_action(
|
(void)apply_gravity_displacement_force_action(
|
||||||
f, domainMapper, GravityDisplacementForceAction::complete, &baseDensityTrue, &densityVariationTrue,
|
f, domainMapper, GravityDisplacementForceAction::complete, &baseDensityTrue, &densityVariationTrue,
|
||||||
&baseGravityGradientTrue, &gravityGradientVariationTrue, &displacementVariationTrue, displacementTrue,
|
&baseGravityGradientTrue, &gravityGradientVariationTrue, &displacementVariationTrue, displacementTrue,
|
||||||
actionTrue
|
actionTrue, false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} // namespace mean_field::operators::kernels
|
} // namespace mean_field::operators::kernels
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ module;
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <expected>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
|
|
||||||
@@ -19,6 +22,70 @@ namespace {
|
|||||||
|
|
||||||
enum class RotationalDisplacementForceAction { residual, density, displacement, complete };
|
enum class RotationalDisplacementForceAction { residual, density, displacement, complete };
|
||||||
|
|
||||||
|
using Rejection = mean_field::operators::kernels::RotationalDisplacementForceRejection;
|
||||||
|
using Reason = mean_field::operators::kernels::RotationalDisplacementForceRejectionReason;
|
||||||
|
using Result = mean_field::operators::kernels::RotationalDisplacementForceResult;
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection mapping_rejection(const mean_field::mapping::MappingStatus status) {
|
||||||
|
MFEM_VERIFY(
|
||||||
|
status != mean_field::mapping::MappingStatus::invalid_dimension,
|
||||||
|
"The rotational-displacement-force mapping reported an invariant dimension mismatch."
|
||||||
|
);
|
||||||
|
return {.reason = Reason::invalid_mapping, .mappingStatus = status};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection non_finite_rejection() noexcept {
|
||||||
|
return {.reason = Reason::non_finite_arithmetic};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool vector_is_finite(const mfem::Vector &vector) noexcept {
|
||||||
|
for (int index = 0; index < vector.Size(); ++index) {
|
||||||
|
if (!std::isfinite(vector(index))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int encode_rejection(const std::optional<Rejection> &rejection) noexcept {
|
||||||
|
if (!rejection.has_value()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (rejection->reason == Reason::non_finite_arithmetic) {
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
return static_cast<int>(rejection->mappingStatus) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection decode_rejection(const int encoded) noexcept {
|
||||||
|
if (encoded >= 256) {
|
||||||
|
return non_finite_rejection();
|
||||||
|
}
|
||||||
|
return mapping_rejection(static_cast<mean_field::mapping::MappingStatus>(encoded - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Result synchronize_rejection(
|
||||||
|
const std::optional<Rejection> &localRejection,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
const int localEncoded = encode_rejection(localRejection);
|
||||||
|
int globalEncoded = 0;
|
||||||
|
if (MPI_Allreduce(&localEncoded, &globalEncoded, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("Could not synchronize rotational-displacement-force candidate validity.");
|
||||||
|
}
|
||||||
|
if (globalEncoded != 0) {
|
||||||
|
return std::unexpected(decode_rejection(globalEncoded));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void throw_rejection(const Rejection &rejection) {
|
||||||
|
if (rejection.reason == Reason::non_finite_arithmetic) {
|
||||||
|
throw std::domain_error("The rotational-displacement force produced non-finite arithmetic.");
|
||||||
|
}
|
||||||
|
throw std::domain_error("The rotational-displacement force encountered an invalid mapped domain.");
|
||||||
|
}
|
||||||
|
|
||||||
void true_to_local(
|
void true_to_local(
|
||||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||||
const mfem::Vector &trueVector,
|
const mfem::Vector &trueVector,
|
||||||
@@ -186,11 +253,6 @@ namespace {
|
|||||||
"The rotational-displacement-force displacement dimension does "
|
"The rotational-displacement-force displacement dimension does "
|
||||||
"not match the mesh dimension."
|
"not match the mesh dimension."
|
||||||
);
|
);
|
||||||
|
|
||||||
validate_finite_vector(
|
|
||||||
displacementTrue, "The rotational-displacement-force displacement contains a "
|
|
||||||
"non-finite value."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void validate_density(
|
void validate_density(
|
||||||
@@ -199,10 +261,9 @@ namespace {
|
|||||||
const char *message
|
const char *message
|
||||||
) {
|
) {
|
||||||
MFEM_VERIFY(density.Size() == f.densityFes->GetTrueVSize(), 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::fem::FEM &f,
|
||||||
const mean_field::mapping::DomainMapper &domainMapper,
|
const mean_field::mapping::DomainMapper &domainMapper,
|
||||||
const mean_field::physics::RigidRotation &rotation,
|
const mean_field::physics::RigidRotation &rotation,
|
||||||
@@ -211,7 +272,8 @@ namespace {
|
|||||||
const mfem::Vector *densityVariationTrue,
|
const mfem::Vector *densityVariationTrue,
|
||||||
const mfem::Vector *displacementVariationTrue,
|
const mfem::Vector *displacementVariationTrue,
|
||||||
const mfem::Vector &displacementTrue,
|
const mfem::Vector &displacementTrue,
|
||||||
mfem::Vector &actionTrue
|
mfem::Vector &actionTrue,
|
||||||
|
const bool reportCandidateRejection
|
||||||
) {
|
) {
|
||||||
validate_common_inputs(f, domainMapper, displacementTrue);
|
validate_common_inputs(f, domainMapper, displacementTrue);
|
||||||
|
|
||||||
@@ -261,6 +323,28 @@ namespace {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool inputsAreFinite = vector_is_finite(displacementTrue);
|
||||||
|
if (needsBaseDensity) {
|
||||||
|
inputsAreFinite = inputsAreFinite && vector_is_finite(*baseDensityTrue);
|
||||||
|
}
|
||||||
|
if (needsDensityVariation) {
|
||||||
|
inputsAreFinite = inputsAreFinite && vector_is_finite(*densityVariationTrue);
|
||||||
|
}
|
||||||
|
if (needsDisplacementVariation) {
|
||||||
|
inputsAreFinite = inputsAreFinite && vector_is_finite(*displacementVariationTrue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reportCandidateRejection) {
|
||||||
|
MFEM_VERIFY(inputsAreFinite, "The rotational-displacement-force action contains non-finite input data.");
|
||||||
|
} else {
|
||||||
|
const std::optional<Rejection> inputRejection =
|
||||||
|
inputsAreFinite ? std::optional<Rejection>{} : std::optional<Rejection>{non_finite_rejection()};
|
||||||
|
auto synchronized = synchronize_rejection(inputRejection, f.mesh->GetComm());
|
||||||
|
if (!synchronized.has_value()) {
|
||||||
|
return synchronized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mfem::Vector baseDensityLocal;
|
mfem::Vector baseDensityLocal;
|
||||||
mfem::Vector densityVariationLocal;
|
mfem::Vector densityVariationLocal;
|
||||||
mfem::Vector displacementLocal;
|
mfem::Vector displacementLocal;
|
||||||
@@ -311,6 +395,8 @@ namespace {
|
|||||||
|
|
||||||
const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering();
|
const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering();
|
||||||
|
|
||||||
|
std::optional<Rejection> candidateRejection;
|
||||||
|
|
||||||
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
||||||
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
|
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
|
||||||
|
|
||||||
@@ -427,13 +513,19 @@ namespace {
|
|||||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||||
);
|
);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (mappingStatus != mean_field::mapping::MappingStatus::valid) {
|
||||||
mappingStatus == mean_field::mapping::MappingStatus::valid,
|
if (!reportCandidateRejection) {
|
||||||
"Stateless mapping failed in the rotational-"
|
MFEM_VERIFY(
|
||||||
"displacement-force kernel. Element: "
|
false, "Stateless mapping failed in the rotational-"
|
||||||
<< elementId << ", attribute: " << transformation->Attribute
|
"displacement-force kernel. Element: "
|
||||||
<< ", quadrature point: " << quadratureIndex << ", status: " << static_cast<int>(mappingStatus)
|
<< elementId << ", attribute: " << transformation->Attribute
|
||||||
);
|
<< ", quadrature point: " << quadratureIndex
|
||||||
|
<< ", status: " << static_cast<int>(mappingStatus)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
candidateRejection = mapping_rejection(mappingStatus);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (needsDisplacementVariation) {
|
if (needsDisplacementVariation) {
|
||||||
const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation(
|
const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation(
|
||||||
@@ -441,13 +533,19 @@ namespace {
|
|||||||
workspace, mappingVariation
|
workspace, mappingVariation
|
||||||
);
|
);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (variationStatus != mean_field::mapping::MappingStatus::valid) {
|
||||||
variationStatus == mean_field::mapping::MappingStatus::valid,
|
if (!reportCandidateRejection) {
|
||||||
"Stateless mapping variation failed in the "
|
MFEM_VERIFY(
|
||||||
"rotational-displacement-force kernel. Element: "
|
false, "Stateless mapping variation failed in the "
|
||||||
<< elementId << ", attribute: " << transformation->Attribute << ", quadrature point: "
|
"rotational-displacement-force kernel. Element: "
|
||||||
<< quadratureIndex << ", status: " << static_cast<int>(variationStatus)
|
<< elementId << ", attribute: " << transformation->Attribute
|
||||||
);
|
<< ", quadrature point: " << quadratureIndex
|
||||||
|
<< ", status: " << static_cast<int>(variationStatus)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
candidateRejection = mapping_rejection(variationStatus);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
densityElement.CalcShape(integrationPoint, densityShape);
|
densityElement.CalcShape(integrationPoint, densityShape);
|
||||||
@@ -512,10 +610,16 @@ namespace {
|
|||||||
|
|
||||||
const double contribution = displacementShape(scalarDof) * weightedForce(component);
|
const double contribution = displacementShape(scalarDof) * weightedForce(component);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(contribution)) {
|
||||||
std::isfinite(contribution), "The rotational-displacement-force kernel "
|
if (!reportCandidateRejection) {
|
||||||
"encountered a non-finite contribution."
|
MFEM_VERIFY(
|
||||||
);
|
false, "The rotational-displacement-force kernel "
|
||||||
|
"encountered a non-finite contribution."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
candidateRejection = non_finite_rejection();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
elementAction(vectorDof) += contribution;
|
elementAction(vectorDof) += contribution;
|
||||||
}
|
}
|
||||||
@@ -529,11 +633,48 @@ namespace {
|
|||||||
localAction.AddElementVector(displacementDofs, elementAction);
|
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);
|
local_to_true(*f.displacementFes, localAction, actionTrue);
|
||||||
|
|
||||||
|
if (reportCandidateRejection) {
|
||||||
|
const std::optional<Rejection> outputRejection = vector_is_finite(actionTrue)
|
||||||
|
? std::optional<Rejection>{}
|
||||||
|
: std::optional<Rejection>{non_finite_rejection()};
|
||||||
|
auto synchronized = synchronize_rejection(outputRejection, f.mesh->GetComm());
|
||||||
|
if (!synchronized.has_value()) {
|
||||||
|
return synchronized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace mean_field::operators::kernels {
|
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(
|
void apply_rotational_displacement_force_residual(
|
||||||
const fem::FEM &f,
|
const fem::FEM &f,
|
||||||
const mapping::DomainMapper &domainMapper,
|
const mapping::DomainMapper &domainMapper,
|
||||||
@@ -542,10 +683,12 @@ namespace mean_field::operators::kernels {
|
|||||||
const mfem::Vector &displacementTrue,
|
const mfem::Vector &displacementTrue,
|
||||||
mfem::Vector &residualTrue
|
mfem::Vector &residualTrue
|
||||||
) {
|
) {
|
||||||
apply_rotational_displacement_force_action(
|
auto result = try_apply_rotational_displacement_force_residual(
|
||||||
f, domainMapper, rotation, RotationalDisplacementForceAction::residual, &densityTrue, nullptr, nullptr,
|
f, domainMapper, rotation, densityTrue, displacementTrue, residualTrue
|
||||||
displacementTrue, residualTrue
|
|
||||||
);
|
);
|
||||||
|
if (!result.has_value()) {
|
||||||
|
throw_rejection(result.error());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void apply_rotational_displacement_force_density_action(
|
void apply_rotational_displacement_force_density_action(
|
||||||
@@ -556,9 +699,9 @@ namespace mean_field::operators::kernels {
|
|||||||
const mfem::Vector &displacementTrue,
|
const mfem::Vector &displacementTrue,
|
||||||
mfem::Vector &actionTrue
|
mfem::Vector &actionTrue
|
||||||
) {
|
) {
|
||||||
apply_rotational_displacement_force_action(
|
(void)apply_rotational_displacement_force_action(
|
||||||
f, domainMapper, rotation, RotationalDisplacementForceAction::density, nullptr, &densityVariationTrue,
|
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,
|
const mfem::Vector &displacementTrue,
|
||||||
mfem::Vector &actionTrue
|
mfem::Vector &actionTrue
|
||||||
) {
|
) {
|
||||||
apply_rotational_displacement_force_action(
|
(void)apply_rotational_displacement_force_action(
|
||||||
f, domainMapper, rotation, RotationalDisplacementForceAction::displacement, &baseDensityTrue, nullptr,
|
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,
|
const mfem::Vector &displacementTrue,
|
||||||
mfem::Vector &actionTrue
|
mfem::Vector &actionTrue
|
||||||
) {
|
) {
|
||||||
apply_rotational_displacement_force_action(
|
(void)apply_rotational_displacement_force_action(
|
||||||
f, domainMapper, rotation, RotationalDisplacementForceAction::complete, &baseDensityTrue,
|
f, domainMapper, rotation, RotationalDisplacementForceAction::complete, &baseDensityTrue,
|
||||||
&densityVariationTrue, &displacementVariationTrue, displacementTrue, actionTrue
|
&densityVariationTrue, &displacementVariationTrue, displacementTrue, actionTrue, false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} // namespace mean_field::operators::kernels
|
} // namespace mean_field::operators::kernels
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ module;
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <expected>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
|
|
||||||
@@ -18,10 +23,71 @@ namespace {
|
|||||||
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
void validate_finite_vector(const mfem::Vector &vector, const char *message) {
|
[[nodiscard]] bool is_finite_vector(const mfem::Vector &vector) {
|
||||||
for (int index = 0; index < vector.Size(); ++index) {
|
for (int index = 0; index < vector.Size(); ++index) {
|
||||||
MFEM_VERIFY(std::isfinite(vector(index)), message);
|
if (!std::isfinite(vector(index))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void verify_finite_vector(
|
||||||
|
const mfem::Vector &vector,
|
||||||
|
const char *message
|
||||||
|
) {
|
||||||
|
MFEM_VERIFY(is_finite_vector(vector), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_candidate_mapping_failure(const mean_field::mapping::MappingStatus status) {
|
||||||
|
using mean_field::mapping::MappingStatus;
|
||||||
|
return status == MappingStatus::non_finite_input || status == MappingStatus::non_finite_result ||
|
||||||
|
status == MappingStatus::non_positive_determinant;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<mean_field::mapping::MappingStatus> synchronize_mapping_failure(
|
||||||
|
const std::optional<mean_field::mapping::MappingStatus> localFailure,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
int localFailures[2]{0, 0};
|
||||||
|
if (localFailure.has_value()) {
|
||||||
|
const int encodedStatus = static_cast<int>(*localFailure) + 1;
|
||||||
|
if (is_candidate_mapping_failure(*localFailure)) {
|
||||||
|
localFailures[0] = encodedStatus;
|
||||||
|
} else {
|
||||||
|
localFailures[1] = encodedStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int globalFailures[2]{0, 0};
|
||||||
|
if (MPI_Allreduce(localFailures, globalFailures, 2, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("PreparedAngularMomentumOperator could not synchronize mapped-geometry validity.");
|
||||||
|
}
|
||||||
|
if (globalFailures[1] != 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"PreparedAngularMomentumOperator encountered a structural mapping failure with status " +
|
||||||
|
std::to_string(globalFailures[1] - 1) + "."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (globalFailures[0] == 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return static_cast<mean_field::mapping::MappingStatus>(globalFailures[0] - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool synchronize_non_finite_failure(
|
||||||
|
const bool localFailure,
|
||||||
|
const MPI_Comm communicator,
|
||||||
|
const char *operation
|
||||||
|
) {
|
||||||
|
const int localStatus = localFailure ? 1 : 0;
|
||||||
|
int globalStatus = 0;
|
||||||
|
if (MPI_Allreduce(&localStatus, &globalStatus, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
std::string("PreparedAngularMomentumOperator could not synchronize ") + operation + "."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return globalStatus != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void true_to_local(
|
void true_to_local(
|
||||||
@@ -51,11 +117,8 @@ namespace {
|
|||||||
);
|
);
|
||||||
const mean_field::quadrature::Query query =
|
const mean_field::quadrature::Query query =
|
||||||
DensityField::make_query<mean_field::field::Density::Form::Quadrupole>(
|
DensityField::make_query<mean_field::field::Density::Form::Quadrupole>(
|
||||||
mean_field::quadrature::QuadratureRole::discretization,
|
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array<int, 1>{2},
|
||||||
transformation.OrderW(),
|
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
|
||||||
std::array<int, 1>{2},
|
|
||||||
mean_field::utils::DOMAINS::STELLAR,
|
|
||||||
mean_field::quadrature::MappingKind::general
|
|
||||||
);
|
);
|
||||||
const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType());
|
const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType());
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
@@ -108,9 +171,8 @@ namespace mean_field::operators {
|
|||||||
"PreparedAngularMomentumOperator currently requires a three-dimensional mapped domain."
|
"PreparedAngularMomentumOperator currently requires a three-dimensional mapped domain."
|
||||||
);
|
);
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr &&
|
m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr && m_fem.compactificationFes != nullptr &&
|
||||||
m_fem.compactificationFes != nullptr && m_fem.compactificationCoordinate != nullptr &&
|
m_fem.compactificationCoordinate != nullptr && m_fem.quadratureFactory != nullptr,
|
||||||
m_fem.quadratureFactory != nullptr,
|
|
||||||
"PreparedAngularMomentumOperator requires density, displacement, compactification, and quadrature data."
|
"PreparedAngularMomentumOperator requires density, displacement, compactification, and quadrature data."
|
||||||
);
|
);
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
@@ -126,31 +188,34 @@ namespace mean_field::operators {
|
|||||||
const double angularVelocity,
|
const double angularVelocity,
|
||||||
const AngularMomentumDependencies &dependencies
|
const AngularMomentumDependencies &dependencies
|
||||||
) {
|
) {
|
||||||
MFEM_VERIFY(
|
auto result = TryPrepare(angularVelocity, dependencies);
|
||||||
std::isfinite(angularVelocity),
|
if (!result.has_value()) {
|
||||||
"PreparedAngularMomentumOperator requires a finite angular-velocity coordinate."
|
throwAngularMomentumPreparationRejection(result.error());
|
||||||
);
|
}
|
||||||
|
return std::move(result).value();
|
||||||
|
}
|
||||||
|
|
||||||
|
AngularMomentumPreparationResult PreparedAngularMomentumOperator::TryPrepare(
|
||||||
|
const double angularVelocity,
|
||||||
|
const AngularMomentumDependencies &dependencies
|
||||||
|
) {
|
||||||
validate_shared_gravity_revisions(m_gravityContext, dependencies);
|
validate_shared_gravity_revisions(m_gravityContext, dependencies);
|
||||||
|
|
||||||
if (m_isPrepared) {
|
if (m_isPrepared) {
|
||||||
validate_identity_transition(
|
validate_identity_transition(
|
||||||
m_preparedDependencies.discretization,
|
m_preparedDependencies.discretization, dependencies.discretization,
|
||||||
dependencies.discretization,
|
|
||||||
"A new angular-momentum discretization identity must change its revision."
|
"A new angular-momentum discretization identity must change its revision."
|
||||||
);
|
);
|
||||||
validate_identity_transition(
|
validate_identity_transition(
|
||||||
m_preparedDependencies.density,
|
m_preparedDependencies.density, dependencies.density,
|
||||||
dependencies.density,
|
|
||||||
"A new angular-momentum density identity must change its revision."
|
"A new angular-momentum density identity must change its revision."
|
||||||
);
|
);
|
||||||
validate_identity_transition(
|
validate_identity_transition(
|
||||||
m_preparedDependencies.displacement,
|
m_preparedDependencies.displacement, dependencies.displacement,
|
||||||
dependencies.displacement,
|
|
||||||
"A new angular-momentum displacement identity must change its revision."
|
"A new angular-momentum displacement identity must change its revision."
|
||||||
);
|
);
|
||||||
validate_identity_transition(
|
validate_identity_transition(
|
||||||
m_preparedDependencies.rotation,
|
m_preparedDependencies.rotation, dependencies.rotation,
|
||||||
dependencies.rotation,
|
|
||||||
"A new angular-momentum rotation identity must change its revision."
|
"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;
|
!m_isPrepared || dependencies.discretization != m_preparedDependencies.discretization;
|
||||||
const bool refreshGeometry =
|
const bool refreshGeometry =
|
||||||
rebuildStaticPlan || dependencies.displacement != m_preparedDependencies.displacement;
|
rebuildStaticPlan || dependencies.displacement != m_preparedDependencies.displacement;
|
||||||
const bool refreshDensity = rebuildStaticPlan || dependencies.density != m_preparedDependencies.density;
|
const bool refreshDensity = rebuildStaticPlan || dependencies.density != m_preparedDependencies.density;
|
||||||
const bool updateAngularVelocity =
|
const bool updateAngularVelocity = !m_isPrepared || dependencies.rotation != m_preparedDependencies.rotation ||
|
||||||
!m_isPrepared || dependencies.rotation != m_preparedDependencies.rotation ||
|
angularVelocity != m_angularVelocity;
|
||||||
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;
|
PreparedAngularMomentumReport report;
|
||||||
if (rebuildStaticPlan) {
|
if (rebuildStaticPlan) {
|
||||||
BuildStaticPlan();
|
BuildStaticPlan();
|
||||||
report.rebuiltStaticPlan = true;
|
report.rebuiltStaticPlan = true;
|
||||||
}
|
}
|
||||||
if (refreshGeometry) {
|
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;
|
report.refreshedGeometry = true;
|
||||||
}
|
}
|
||||||
if (refreshDensity) {
|
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;
|
report.refreshedDensity = true;
|
||||||
}
|
}
|
||||||
if (updateAngularVelocity) {
|
if (updateAngularVelocity) {
|
||||||
m_angularVelocity = angularVelocity;
|
m_angularVelocity = angularVelocity;
|
||||||
report.updatedAngularVelocity = true;
|
report.updatedAngularVelocity = true;
|
||||||
}
|
}
|
||||||
if (refreshGeometry || refreshDensity || updateAngularVelocity) {
|
if (refreshGeometry || refreshDensity || updateAngularVelocity) {
|
||||||
AssembleResidual();
|
auto rejection = TryAssembleResidual();
|
||||||
|
if (rejection.has_value()) {
|
||||||
|
return std::unexpected(*rejection);
|
||||||
|
}
|
||||||
report.assembledResidual = true;
|
report.assembledResidual = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_preparedDependencies = dependencies;
|
m_preparedDependencies = dependencies;
|
||||||
m_isPrepared = true;
|
m_isPrepared = true;
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,8 +300,8 @@ namespace mean_field::operators {
|
|||||||
}
|
}
|
||||||
++localStellarElementCount;
|
++localStellarElementCount;
|
||||||
m_elements.emplace_back();
|
m_elements.emplace_back();
|
||||||
ElementPAData &data = m_elements.back();
|
ElementPAData &data = m_elements.back();
|
||||||
data.elementId = elementId;
|
data.elementId = elementId;
|
||||||
data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
|
data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
|
||||||
data.displacementDofTransformation =
|
data.displacementDofTransformation =
|
||||||
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
|
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
|
||||||
@@ -218,31 +314,35 @@ namespace mean_field::operators {
|
|||||||
data.quadraturePoints.resize(integrationRule.GetNPoints());
|
data.quadraturePoints.resize(integrationRule.GetNPoints());
|
||||||
for (int quadraturePoint = 0; quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) {
|
for (int quadraturePoint = 0; quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) {
|
||||||
QuadraturePointData &point = data.quadraturePoints[quadraturePoint];
|
QuadraturePointData &point = data.quadraturePoints[quadraturePoint];
|
||||||
point.integrationPoint = integrationRule.IntPoint(quadraturePoint);
|
point.integrationPoint = integrationRule.IntPoint(quadraturePoint);
|
||||||
point.densityShape.SetSize(densityElement.GetDof());
|
point.densityShape.SetSize(densityElement.GetDof());
|
||||||
densityElement.CalcShape(point.integrationPoint, point.densityShape);
|
densityElement.CalcShape(point.integrationPoint, point.densityShape);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int globalStellarElementCount = 0;
|
int globalStellarElementCount = 0;
|
||||||
MPI_Allreduce(
|
MFEM_VERIFY(
|
||||||
&localStellarElementCount,
|
MPI_Allreduce(
|
||||||
&globalStellarElementCount,
|
&localStellarElementCount, &globalStellarElementCount, 1, MPI_INT, MPI_SUM, m_fem.mesh->GetComm()
|
||||||
1,
|
) == MPI_SUCCESS,
|
||||||
MPI_INT,
|
"PreparedAngularMomentumOperator could not count stellar elements."
|
||||||
MPI_SUM,
|
|
||||||
m_fem.mesh->GetComm()
|
|
||||||
);
|
);
|
||||||
MFEM_VERIFY(globalStellarElementCount > 0, "PreparedAngularMomentumOperator found no stellar elements.");
|
MFEM_VERIFY(globalStellarElementCount > 0, "PreparedAngularMomentumOperator found no stellar elements.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedAngularMomentumOperator::RefreshGeometry(const mfem::Vector &displacement) {
|
std::optional<mapping::MappingStatus>
|
||||||
|
PreparedAngularMomentumOperator::RefreshGeometry(const mfem::Vector &displacement) {
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
displacement.Size() == m_fem.displacementFes->GetTrueVSize(),
|
displacement.Size() == m_fem.displacementFes->GetTrueVSize(),
|
||||||
"Angular-momentum geometry has the wrong displacement size."
|
"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;
|
mfem::Vector displacementLocal;
|
||||||
true_to_local(*m_fem.displacementFes, displacement, 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());
|
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
|
||||||
|
|
||||||
for (ElementPAData &data : m_elements) {
|
for (ElementPAData &data : m_elements) {
|
||||||
@@ -254,75 +354,109 @@ namespace mean_field::operators {
|
|||||||
if (data.compactificationDofTransformation != nullptr) {
|
if (data.compactificationDofTransformation != nullptr) {
|
||||||
data.compactificationDofTransformation->InvTransformPrimal(data.compactification);
|
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 mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
|
||||||
const mapping::ElementDisplacementData displacementData =
|
const mapping::ElementDisplacementData displacementData =
|
||||||
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
|
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
|
||||||
const mapping::ElementCompactificationData compactificationData(
|
const mapping::ElementCompactificationData compactificationData(
|
||||||
compactificationElement,
|
compactificationElement, data.compactification
|
||||||
data.compactification
|
|
||||||
);
|
);
|
||||||
const mapping::ElementMappingData mappingData{
|
const mapping::ElementMappingData mappingData{
|
||||||
.displacement = displacementData,
|
.displacement = displacementData, .compactification = compactificationData
|
||||||
.compactification = compactificationData
|
|
||||||
};
|
};
|
||||||
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
|
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
|
||||||
for (QuadraturePointData &point : data.quadraturePoints) {
|
for (QuadraturePointData &point : data.quadraturePoints) {
|
||||||
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
|
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
|
||||||
mappingData,
|
mappingData, *transformation, point.integrationPoint, workspace, point.mappingContext
|
||||||
*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
|
|
||||||
);
|
);
|
||||||
|
if (status != mapping::MappingStatus::valid) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
if (point.mappingContext.mapping.compactified) {
|
||||||
|
return mapping::MappingStatus::at_compactified_infinity;
|
||||||
|
}
|
||||||
point.cylindricalRadiusSquared =
|
point.cylindricalRadiusSquared =
|
||||||
CylindricalRadiusSquared(point.mappingContext.mapping.physical_position);
|
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) {
|
bool PreparedAngularMomentumOperator::RefreshDensity(const mfem::Vector &density) {
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(density.Size() == m_fem.densityFes->GetTrueVSize(), "Angular-momentum density has the wrong size.");
|
||||||
density.Size() == m_fem.densityFes->GetTrueVSize(),
|
if (!is_finite_vector(density)) {
|
||||||
"Angular-momentum density has the wrong size."
|
return false;
|
||||||
);
|
}
|
||||||
validate_finite_vector(density, "Angular-momentum density contains a non-finite value.");
|
|
||||||
mfem::Vector densityLocal;
|
mfem::Vector densityLocal;
|
||||||
true_to_local(*m_fem.densityFes, density, densityLocal);
|
true_to_local(*m_fem.densityFes, density, densityLocal);
|
||||||
|
if (!is_finite_vector(densityLocal)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
mfem::Vector elementDensity;
|
mfem::Vector elementDensity;
|
||||||
for (ElementPAData &data : m_elements) {
|
for (ElementPAData &data : m_elements) {
|
||||||
densityLocal.GetSubVector(data.densityDofs, elementDensity);
|
densityLocal.GetSubVector(data.densityDofs, elementDensity);
|
||||||
if (data.densityDofTransformation != nullptr) {
|
if (data.densityDofTransformation != nullptr) {
|
||||||
data.densityDofTransformation->InvTransformPrimal(elementDensity);
|
data.densityDofTransformation->InvTransformPrimal(elementDensity);
|
||||||
}
|
}
|
||||||
|
if (!is_finite_vector(elementDensity)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (QuadraturePointData &point : data.quadraturePoints) {
|
for (QuadraturePointData &point : data.quadraturePoints) {
|
||||||
point.density = elementDensity * point.densityShape;
|
point.density = elementDensity * point.densityShape;
|
||||||
MFEM_VERIFY(std::isfinite(point.density), "Angular-momentum quadrature density is non-finite.");
|
if (!std::isfinite(point.density)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedAngularMomentumOperator::AssembleResidual() {
|
std::optional<AngularMomentumPreparationRejection> PreparedAngularMomentumOperator::TryAssembleResidual() {
|
||||||
double localMomentOfInertia = 0.0;
|
double localMomentOfInertia = 0.0;
|
||||||
for (const ElementPAData &data : m_elements) {
|
for (const ElementPAData &data : m_elements) {
|
||||||
for (const QuadraturePointData &point : data.quadraturePoints) {
|
for (const QuadraturePointData &point : data.quadraturePoints) {
|
||||||
localMomentOfInertia += point.density * point.cylindricalRadiusSquared *
|
localMomentOfInertia +=
|
||||||
point.mappingContext.quadrature.weight;
|
point.density * point.cylindricalRadiusSquared * point.mappingContext.quadrature.weight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_momentOfInertia = GlobalSum(localMomentOfInertia);
|
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(
|
MFEM_VERIFY(
|
||||||
std::isfinite(m_momentOfInertia) && m_momentOfInertia >= 0.0,
|
std::isfinite(m_constraint.targetAngularMomentum().value()),
|
||||||
"PreparedAngularMomentumOperator assembled an invalid moment of inertia."
|
"PreparedAngularMomentumOperator has a non-finite configured target angular momentum."
|
||||||
);
|
);
|
||||||
m_currentAngularMomentum = m_angularVelocity * m_momentOfInertia;
|
m_currentAngularMomentum = m_angularVelocity * m_momentOfInertia;
|
||||||
m_cachedResidual.SetSize(1);
|
m_cachedResidual.SetSize(1);
|
||||||
m_cachedResidual(0) = m_currentAngularMomentum - m_constraint.targetAngularMomentum().value();
|
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;
|
++m_preparationCount;
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedAngularMomentumOperator::BuildResidual(mfem::Vector &residual) const {
|
void PreparedAngularMomentumOperator::BuildResidual(mfem::Vector &residual) const {
|
||||||
@@ -331,9 +465,8 @@ namespace mean_field::operators {
|
|||||||
++m_residualApplicationCount;
|
++m_residualApplicationCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
double PreparedAngularMomentumOperator::EvaluateDensityMomentActionLocal(
|
double
|
||||||
const mfem::Vector &densityVariation
|
PreparedAngularMomentumOperator::EvaluateDensityMomentActionLocal(const mfem::Vector &densityVariation) const {
|
||||||
) const {
|
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
densityVariation.Size() == m_fem.densityFes->GetTrueVSize(),
|
densityVariation.Size() == m_fem.densityFes->GetTrueVSize(),
|
||||||
"Angular-momentum density action has the wrong true-vector size."
|
"Angular-momentum density action has the wrong true-vector size."
|
||||||
@@ -346,8 +479,8 @@ namespace mean_field::operators {
|
|||||||
data.densityDofTransformation->InvTransformPrimal(m_elementDensityVariation);
|
data.densityDofTransformation->InvTransformPrimal(m_elementDensityVariation);
|
||||||
}
|
}
|
||||||
for (const QuadraturePointData &point : data.quadraturePoints) {
|
for (const QuadraturePointData &point : data.quadraturePoints) {
|
||||||
localAction += (m_elementDensityVariation * point.densityShape) *
|
localAction += (m_elementDensityVariation * point.densityShape) * point.cylindricalRadiusSquared *
|
||||||
point.cylindricalRadiusSquared * point.mappingContext.quadrature.weight;
|
point.mappingContext.quadrature.weight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return localAction;
|
return localAction;
|
||||||
@@ -369,42 +502,33 @@ namespace mean_field::operators {
|
|||||||
if (data.displacementDofTransformation != nullptr) {
|
if (data.displacementDofTransformation != nullptr) {
|
||||||
data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation);
|
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 mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
|
||||||
const mapping::ElementDisplacementData baseDisplacementData =
|
const mapping::ElementDisplacementData baseDisplacementData =
|
||||||
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
|
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
|
||||||
const mapping::ElementDisplacementData directionData =
|
const mapping::ElementDisplacementData directionData =
|
||||||
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
|
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
|
||||||
const mapping::ElementCompactificationData compactificationData(
|
const mapping::ElementCompactificationData compactificationData(
|
||||||
compactificationElement,
|
compactificationElement, data.compactification
|
||||||
data.compactification
|
|
||||||
);
|
);
|
||||||
const mapping::ElementMappingData mappingData{
|
const mapping::ElementMappingData mappingData{
|
||||||
.displacement = baseDisplacementData,
|
.displacement = baseDisplacementData, .compactification = compactificationData
|
||||||
.compactification = compactificationData
|
|
||||||
};
|
};
|
||||||
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
|
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
|
||||||
for (const QuadraturePointData &point : data.quadraturePoints) {
|
for (const QuadraturePointData &point : data.quadraturePoints) {
|
||||||
const mapping::MappingStatus status = m_domainMapper.EvaluateVolumeVariation(
|
const mapping::MappingStatus status = m_domainMapper.EvaluateVolumeVariation(
|
||||||
mappingData,
|
mappingData, directionData, *transformation, point.integrationPoint, point.mappingContext,
|
||||||
directionData,
|
workspace, variation
|
||||||
*transformation,
|
|
||||||
point.integrationPoint,
|
|
||||||
point.mappingContext,
|
|
||||||
workspace,
|
|
||||||
variation
|
|
||||||
);
|
);
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
status == mapping::MappingStatus::valid,
|
status == mapping::MappingStatus::valid,
|
||||||
"Mapped angular-momentum variation is invalid. Element: " << data.elementId
|
"Mapped angular-momentum variation is invalid. Element: " << data.elementId
|
||||||
);
|
);
|
||||||
const double radiusSquaredVariation = CylindricalRadiusSquaredVariation(
|
const double radiusSquaredVariation = CylindricalRadiusSquaredVariation(
|
||||||
point.mappingContext.mapping.physical_position,
|
point.mappingContext.mapping.physical_position, variation.mapping.physical_position_variation
|
||||||
variation.mapping.physical_position_variation
|
|
||||||
);
|
);
|
||||||
localAction += point.density *
|
localAction += point.density * (radiusSquaredVariation * point.mappingContext.quadrature.weight +
|
||||||
(radiusSquaredVariation * point.mappingContext.quadrature.weight +
|
point.cylindricalRadiusSquared * variation.weight_variation);
|
||||||
point.cylindricalRadiusSquared * variation.weight_variation);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return localAction;
|
return localAction;
|
||||||
@@ -419,7 +543,7 @@ namespace mean_field::operators {
|
|||||||
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size(),
|
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size(),
|
||||||
"Angular-momentum density action has the wrong 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);
|
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
|
||||||
action.SetSize(1);
|
action.SetSize(1);
|
||||||
action(0) = m_angularVelocity * GlobalSum(EvaluateDensityMomentActionLocal(m_densityVariationTrue));
|
action(0) = m_angularVelocity * GlobalSum(EvaluateDensityMomentActionLocal(m_densityVariationTrue));
|
||||||
@@ -435,11 +559,10 @@ namespace mean_field::operators {
|
|||||||
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
|
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
|
||||||
"Angular-momentum displacement action has the wrong 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);
|
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
|
||||||
action.SetSize(1);
|
action.SetSize(1);
|
||||||
action(0) = m_angularVelocity *
|
action(0) = m_angularVelocity * GlobalSum(EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue));
|
||||||
GlobalSum(EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue));
|
|
||||||
++m_actionStatistics.displacementApplications;
|
++m_actionStatistics.displacementApplications;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,24 +589,22 @@ namespace mean_field::operators {
|
|||||||
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
|
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
|
||||||
"Angular-momentum complete action has incompatible reduced coordinates."
|
"Angular-momentum complete action has incompatible reduced coordinates."
|
||||||
);
|
);
|
||||||
validate_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
|
verify_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
|
||||||
validate_finite_vector(displacementVariation, "Angular-momentum displacement 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.");
|
MFEM_VERIFY(std::isfinite(angularVelocityVariation), "Angular-velocity direction is non-finite.");
|
||||||
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
|
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
|
||||||
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
|
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
|
||||||
const double localMomentAction = EvaluateDensityMomentActionLocal(m_densityVariationTrue) +
|
const double localMomentAction = EvaluateDensityMomentActionLocal(m_densityVariationTrue) +
|
||||||
EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue);
|
EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue);
|
||||||
action.SetSize(1);
|
action.SetSize(1);
|
||||||
action(0) = m_angularVelocity * GlobalSum(localMomentAction) +
|
action(0) = m_angularVelocity * GlobalSum(localMomentAction) + m_momentOfInertia * angularVelocityVariation;
|
||||||
m_momentOfInertia * angularVelocityVariation;
|
|
||||||
++m_actionStatistics.completeApplications;
|
++m_actionStatistics.completeApplications;
|
||||||
}
|
}
|
||||||
|
|
||||||
double PreparedAngularMomentumOperator::CylindricalRadiusSquared(
|
double
|
||||||
const mfem::Vector &physicalPosition
|
PreparedAngularMomentumOperator::CylindricalRadiusSquared(const mfem::Vector &physicalPosition) const noexcept {
|
||||||
) const noexcept {
|
const auto &axis = m_constraint.specification().axis();
|
||||||
const auto &axis = m_constraint.specification().axis();
|
const auto ¢er = m_constraint.specification().center();
|
||||||
const auto ¢er = m_constraint.specification().center();
|
|
||||||
double radiusSquared = 0.0;
|
double radiusSquared = 0.0;
|
||||||
double axialPosition = 0.0;
|
double axialPosition = 0.0;
|
||||||
for (int component = 0; component < 3; ++component) {
|
for (int component = 0; component < 3; ++component) {
|
||||||
@@ -491,18 +612,22 @@ namespace mean_field::operators {
|
|||||||
radiusSquared += relative * relative;
|
radiusSquared += relative * relative;
|
||||||
axialPosition += axis[static_cast<std::size_t>(component)] * relative;
|
axialPosition += axis[static_cast<std::size_t>(component)] * relative;
|
||||||
}
|
}
|
||||||
return std::max(0.0, radiusSquared - axialPosition * axialPosition);
|
const double perpendicularRadiusSquared = radiusSquared - axialPosition * axialPosition;
|
||||||
|
if (!std::isfinite(perpendicularRadiusSquared)) {
|
||||||
|
return perpendicularRadiusSquared;
|
||||||
|
}
|
||||||
|
return std::max(0.0, perpendicularRadiusSquared);
|
||||||
}
|
}
|
||||||
|
|
||||||
double PreparedAngularMomentumOperator::CylindricalRadiusSquaredVariation(
|
double PreparedAngularMomentumOperator::CylindricalRadiusSquaredVariation(
|
||||||
const mfem::Vector &physicalPosition,
|
const mfem::Vector &physicalPosition,
|
||||||
const mfem::Vector &physicalPositionVariation
|
const mfem::Vector &physicalPositionVariation
|
||||||
) const noexcept {
|
) const noexcept {
|
||||||
const auto &axis = m_constraint.specification().axis();
|
const auto &axis = m_constraint.specification().axis();
|
||||||
const auto ¢er = m_constraint.specification().center();
|
const auto ¢er = m_constraint.specification().center();
|
||||||
double relativeDotVariation = 0.0;
|
double relativeDotVariation = 0.0;
|
||||||
double axialPosition = 0.0;
|
double axialPosition = 0.0;
|
||||||
double axialVariation = 0.0;
|
double axialVariation = 0.0;
|
||||||
for (int component = 0; component < 3; ++component) {
|
for (int component = 0; component < 3; ++component) {
|
||||||
const double relative = physicalPosition(component) - center[static_cast<std::size_t>(component)];
|
const double relative = physicalPosition(component) - center[static_cast<std::size_t>(component)];
|
||||||
relativeDotVariation += relative * physicalPositionVariation(component);
|
relativeDotVariation += relative * physicalPositionVariation(component);
|
||||||
@@ -514,7 +639,9 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
double PreparedAngularMomentumOperator::GlobalSum(const double localValue) const {
|
double PreparedAngularMomentumOperator::GlobalSum(const double localValue) const {
|
||||||
double globalValue = 0.0;
|
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;
|
return globalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,15 +681,15 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
AngularMomentumConstraintReport PreparedAngularMomentumOperator::GetConstraintReport() const {
|
AngularMomentumConstraintReport PreparedAngularMomentumOperator::GetConstraintReport() const {
|
||||||
VerifyPrepared();
|
VerifyPrepared();
|
||||||
const double target = GetTargetAngularMomentum();
|
const double target = GetTargetAngularMomentum();
|
||||||
const double residual = m_currentAngularMomentum - target;
|
const double residual = m_currentAngularMomentum - target;
|
||||||
return {
|
return {
|
||||||
.targetAngularMomentum = target,
|
.targetAngularMomentum = target,
|
||||||
.achievedAngularMomentum = m_currentAngularMomentum,
|
.achievedAngularMomentum = m_currentAngularMomentum,
|
||||||
.momentOfInertia = m_momentOfInertia,
|
.momentOfInertia = m_momentOfInertia,
|
||||||
.angularVelocity = m_angularVelocity,
|
.angularVelocity = m_angularVelocity,
|
||||||
.dimensionalResidual = residual,
|
.dimensionalResidual = residual,
|
||||||
.scaledResidual = residual / std::max(std::abs(target), 1.0e-300)
|
.scaledResidual = residual / std::max(std::abs(target), 1.0e-300)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,15 @@ module;
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
|
|
||||||
import :operators.prepared_barotropic_closure;
|
import :operators.prepared_barotropic_closure;
|
||||||
@@ -148,6 +153,154 @@ namespace {
|
|||||||
|
|
||||||
return *resolution.integration_rule;
|
return *resolution.integration_rule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using BarotropicRejection = mean_field::operators::BarotropicClosurePreparationRejection;
|
||||||
|
using BarotropicRejectionReason = mean_field::operators::BarotropicClosurePreparationRejectionReason;
|
||||||
|
|
||||||
|
[[nodiscard]] bool vector_is_finite(const mfem::Vector &vector) {
|
||||||
|
for (int index = 0; index < vector.Size(); ++index) {
|
||||||
|
if (!std::isfinite(vector(index))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rejections are selected by preparation phase, then by an explicit
|
||||||
|
* detail priority. An earlier phase wins: mapping, quadrature algebra,
|
||||||
|
* then EOS evaluation. Never depend on the declaration order or the
|
||||||
|
* underlying integer representation of either public enum.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] int mapping_status_priority(const mean_field::mapping::MappingStatus status) {
|
||||||
|
using Status = mean_field::mapping::MappingStatus;
|
||||||
|
switch (status) {
|
||||||
|
case Status::non_positive_determinant:
|
||||||
|
return 7;
|
||||||
|
case Status::non_finite_result:
|
||||||
|
return 6;
|
||||||
|
case Status::non_finite_input:
|
||||||
|
return 5;
|
||||||
|
case Status::outside_reference_domain:
|
||||||
|
return 4;
|
||||||
|
case Status::at_compactified_infinity:
|
||||||
|
return 3;
|
||||||
|
case Status::invalid_reference_radius:
|
||||||
|
return 2;
|
||||||
|
case Status::valid:
|
||||||
|
throw std::logic_error("A valid mapping cannot be a barotropic candidate rejection.");
|
||||||
|
case Status::invalid_dimension:
|
||||||
|
throw std::logic_error("A mapping dimension error cannot be a barotropic candidate rejection.");
|
||||||
|
}
|
||||||
|
throw std::logic_error("Unknown mapping status in barotropic candidate rejection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::mapping::MappingStatus mapping_status_from_priority(const int priority) {
|
||||||
|
using Status = mean_field::mapping::MappingStatus;
|
||||||
|
switch (priority) {
|
||||||
|
case 7:
|
||||||
|
return Status::non_positive_determinant;
|
||||||
|
case 6:
|
||||||
|
return Status::non_finite_result;
|
||||||
|
case 5:
|
||||||
|
return Status::non_finite_input;
|
||||||
|
case 4:
|
||||||
|
return Status::outside_reference_domain;
|
||||||
|
case 3:
|
||||||
|
return Status::at_compactified_infinity;
|
||||||
|
case 2:
|
||||||
|
return Status::invalid_reference_radius;
|
||||||
|
default:
|
||||||
|
throw std::logic_error("Invalid synchronized mapping priority for barotropic preparation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int eos_error_priority(const mean_field::eos::EvaluationErrorCode code) {
|
||||||
|
using Code = mean_field::eos::EvaluationErrorCode;
|
||||||
|
switch (code) {
|
||||||
|
case Code::outside_domain:
|
||||||
|
return 3;
|
||||||
|
case Code::nonfinite_input:
|
||||||
|
return 2;
|
||||||
|
case Code::nonfinite_result:
|
||||||
|
return 1;
|
||||||
|
case Code::unsupported_relation:
|
||||||
|
case Code::unsupported_derivative:
|
||||||
|
case Code::wrong_input_count:
|
||||||
|
case Code::wrong_input_quantity:
|
||||||
|
throw std::logic_error("A structural EOS error cannot be a barotropic candidate rejection.");
|
||||||
|
}
|
||||||
|
throw std::logic_error("Unknown EOS error in barotropic candidate rejection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::eos::EvaluationErrorCode eos_error_from_priority(const int priority) {
|
||||||
|
using Code = mean_field::eos::EvaluationErrorCode;
|
||||||
|
switch (priority) {
|
||||||
|
case 3:
|
||||||
|
return Code::outside_domain;
|
||||||
|
case 2:
|
||||||
|
return Code::nonfinite_input;
|
||||||
|
case 1:
|
||||||
|
return Code::nonfinite_result;
|
||||||
|
default:
|
||||||
|
throw std::logic_error("Invalid synchronized EOS priority for barotropic preparation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int rejection_priority(const BarotropicRejection &rejection) {
|
||||||
|
switch (rejection.reason) {
|
||||||
|
case BarotropicRejectionReason::mapping_failure:
|
||||||
|
return 300 + mapping_status_priority(rejection.mappingStatus);
|
||||||
|
case BarotropicRejectionReason::invalid_quadrature_data:
|
||||||
|
return 200;
|
||||||
|
case BarotropicRejectionReason::equation_of_state:
|
||||||
|
return 100 + eos_error_priority(rejection.equationOfStateError);
|
||||||
|
}
|
||||||
|
throw std::logic_error("Unknown barotropic candidate-rejection reason.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] BarotropicRejection rejection_from_priority(const int priority) {
|
||||||
|
if (priority >= 300) {
|
||||||
|
return {
|
||||||
|
.reason = BarotropicRejectionReason::mapping_failure,
|
||||||
|
.mappingStatus = mapping_status_from_priority(priority - 300)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (priority == 200) {
|
||||||
|
return {.reason = BarotropicRejectionReason::invalid_quadrature_data};
|
||||||
|
}
|
||||||
|
if (priority >= 100) {
|
||||||
|
return {
|
||||||
|
.reason = BarotropicRejectionReason::equation_of_state,
|
||||||
|
.equationOfStateError = eos_error_from_priority(priority - 100)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw std::logic_error("Invalid synchronized barotropic candidate-rejection priority.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void retain_higher_priority_rejection(
|
||||||
|
std::optional<BarotropicRejection> ¤t,
|
||||||
|
const BarotropicRejection candidate
|
||||||
|
) {
|
||||||
|
if (!current.has_value() || rejection_priority(candidate) > rejection_priority(*current)) {
|
||||||
|
current = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<BarotropicRejection> synchronize_rejection(
|
||||||
|
const std::optional<BarotropicRejection> &local,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
const int localPriority = local.has_value() ? rejection_priority(*local) : 0;
|
||||||
|
int globalPriority = 0;
|
||||||
|
if (MPI_Allreduce(&localPriority, &globalPriority, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("PreparedBarotropicClosureOperator could not synchronize candidate validity.");
|
||||||
|
}
|
||||||
|
if (globalPriority == 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return rejection_from_priority(globalPriority);
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace mean_field::operators {
|
namespace mean_field::operators {
|
||||||
@@ -256,6 +409,29 @@ namespace mean_field::operators {
|
|||||||
PreparedBarotropicClosureReport PreparedBarotropicClosureOperator::Prepare(
|
PreparedBarotropicClosureReport PreparedBarotropicClosureOperator::Prepare(
|
||||||
const context::barotropic::BarotropicClosureStateView &state,
|
const context::barotropic::BarotropicClosureStateView &state,
|
||||||
const context::barotropic::BarotropicClosureDependencies &dependencies
|
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;
|
PreparedBarotropicClosureReport report;
|
||||||
report.contextReport = m_context.Prepare(state, dependencies);
|
report.contextReport = m_context.Prepare(state, dependencies);
|
||||||
@@ -296,6 +472,7 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
mfem::Vector densityShape;
|
mfem::Vector densityShape;
|
||||||
mfem::Vector enthalpyShape;
|
mfem::Vector enthalpyShape;
|
||||||
|
std::optional<BarotropicClosurePreparationRejection> localRejection;
|
||||||
|
|
||||||
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
|
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
|
||||||
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
|
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
|
||||||
@@ -385,11 +562,16 @@ namespace mean_field::operators {
|
|||||||
);
|
);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
mappingStatus == mapping::MappingStatus::valid,
|
mappingStatus != mapping::MappingStatus::invalid_dimension,
|
||||||
"Stateless mapping failed while preparing the barotropic closure operator. Element: "
|
"Stateless mapping reported a dimension error while preparing the barotropic closure operator."
|
||||||
<< elementId << ", attribute: " << transformation->Attribute
|
|
||||||
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
|
|
||||||
);
|
);
|
||||||
|
if (mappingStatus != mapping::MappingStatus::valid) {
|
||||||
|
retain_higher_priority_rejection(
|
||||||
|
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::mapping_failure,
|
||||||
|
.mappingStatus = mappingStatus}
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
!mappingContext.mapping.compactified,
|
!mappingContext.mapping.compactified,
|
||||||
@@ -406,6 +588,13 @@ namespace mean_field::operators {
|
|||||||
densityElement.CalcShape(integrationPoint, densityShape);
|
densityElement.CalcShape(integrationPoint, densityShape);
|
||||||
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
|
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) {
|
for (int densityDof = 0; densityDof < densityDofCount; ++densityDof) {
|
||||||
data.densityBasis(quadraturePoint, densityDof) = densityShape(densityDof);
|
data.densityBasis(quadraturePoint, densityDof) = densityShape(densityDof);
|
||||||
}
|
}
|
||||||
@@ -416,6 +605,35 @@ namespace mean_field::operators {
|
|||||||
const double density = elementBaseDensity * densityShape;
|
const double density = elementBaseDensity * densityShape;
|
||||||
const double enthalpy = elementBaseEnthalpy * enthalpyShape;
|
const double enthalpy = elementBaseEnthalpy * enthalpyShape;
|
||||||
const double quadratureWeight = mappingContext.quadrature.weight;
|
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 dimensions::SpecificEnthalpyValue specificEnthalpy{enthalpy};
|
||||||
const double eosDensity =
|
const double eosDensity =
|
||||||
eos::evaluate<eos::quantity::Density>(m_equationOfState, specificEnthalpy).value();
|
eos::evaluate<eos::quantity::Density>(m_equationOfState, specificEnthalpy).value();
|
||||||
@@ -425,18 +643,34 @@ namespace mean_field::operators {
|
|||||||
)
|
)
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(eosDensity) || !std::isfinite(enthalpyDerivative)) {
|
||||||
std::isfinite(quadratureWeight) && quadratureWeight > 0.0 && std::isfinite(eosDensity) &&
|
retain_higher_priority_rejection(
|
||||||
std::isfinite(enthalpyDerivative),
|
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::equation_of_state,
|
||||||
"PreparedBarotropicClosureOperator encountered invalid quadrature data."
|
.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.quadratureWeights(quadraturePoint) = quadratureWeight;
|
||||||
data.weightedResidual(quadraturePoint) = quadratureWeight * (density - eosDensity);
|
data.weightedResidual(quadraturePoint) = weightedResidual;
|
||||||
data.weightedEnthalpyDerivative(quadraturePoint) = quadratureWeight * enthalpyDerivative;
|
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.");
|
MFEM_VERIFY(!m_elements.empty(), "PreparedBarotropicClosureOperator found no elements in Density::Support.");
|
||||||
|
|
||||||
m_isPrepared = true;
|
m_isPrepared = true;
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <expected>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
|
|
||||||
@@ -8,6 +15,75 @@ import :operators.prepared_displacement_residual;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using Dependencies = mean_field::operators::DisplacementResidualDependencies;
|
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
|
[[nodiscard]] mean_field::operators::context::pressure_force::PressureForceDependencies
|
||||||
make_pressure_dependencies(const Dependencies &dependencies) {
|
make_pressure_dependencies(const Dependencies &dependencies) {
|
||||||
@@ -118,6 +194,21 @@ namespace mean_field::operators {
|
|||||||
const DisplacementResidualStateView &state,
|
const DisplacementResidualStateView &state,
|
||||||
const DisplacementResidualDependencies &dependencies,
|
const DisplacementResidualDependencies &dependencies,
|
||||||
const physics::RigidRotation &rotation
|
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);
|
validate_shared_gravity_revisions(m_gravityContext, dependencies);
|
||||||
|
|
||||||
@@ -161,20 +252,47 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
PreparedDisplacementResidualReport report;
|
PreparedDisplacementResidualReport report;
|
||||||
|
|
||||||
report.pressure = m_pressureOperator.Prepare(
|
auto pressureResult = m_pressureOperator.TryPrepare(
|
||||||
{.enthalpy = state.enthalpy, .displacement = displacement}, make_pressure_dependencies(dependencies)
|
{.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
|
{.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() ||
|
if (report.DidAnyChildWork() ||
|
||||||
m_cachedResidual.Size() != m_gravityContext.GetDisplacementMap().reduced_size()) {
|
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;
|
report.assembledResidual = true;
|
||||||
|
++m_residualPreparationCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
@@ -188,7 +306,7 @@ namespace mean_field::operators {
|
|||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedDisplacementResidualOperator::AssembleResidual() {
|
std::optional<DisplacementResidualPreparationRejection> PreparedDisplacementResidualOperator::AssembleResidual() {
|
||||||
mfem::Vector pressureResidual;
|
mfem::Vector pressureResidual;
|
||||||
mfem::Vector gravityResidual;
|
mfem::Vector gravityResidual;
|
||||||
mfem::Vector rotationalResidual;
|
mfem::Vector rotationalResidual;
|
||||||
@@ -211,7 +329,10 @@ namespace mean_field::operators {
|
|||||||
"different sizes."
|
"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 {
|
void PreparedDisplacementResidualOperator::BuildResidual(mfem::Vector &residual) const {
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <expected>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
|
|
||||||
@@ -9,6 +16,8 @@ import :operators.prepared_gravity_displacement_force;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
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(
|
[[nodiscard]] bool relevant_revisions_match(
|
||||||
const mean_field::operators::context::gravity_field::GravityFieldRevisions &left,
|
const mean_field::operators::context::gravity_field::GravityFieldRevisions &left,
|
||||||
@@ -22,6 +31,69 @@ namespace {
|
|||||||
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection mapping_rejection(const mean_field::mapping::MappingStatus status) {
|
||||||
|
MFEM_VERIFY(
|
||||||
|
status != mean_field::mapping::MappingStatus::invalid_dimension,
|
||||||
|
"Prepared gravity force mapping reported an invariant dimension mismatch."
|
||||||
|
);
|
||||||
|
return {.reason = Reason::invalid_mapping, .mappingStatus = status};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection non_finite_rejection() noexcept {
|
||||||
|
return {.reason = Reason::non_finite_arithmetic};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int encode_rejection(const std::optional<Rejection> &rejection) noexcept {
|
||||||
|
if (!rejection.has_value()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (rejection->reason == Reason::non_finite_arithmetic) {
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
return static_cast<int>(rejection->mappingStatus) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection decode_rejection(const int encoded) {
|
||||||
|
if (encoded >= 256) {
|
||||||
|
return non_finite_rejection();
|
||||||
|
}
|
||||||
|
return mapping_rejection(static_cast<mean_field::mapping::MappingStatus>(encoded - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<
|
||||||
|
void,
|
||||||
|
Rejection>
|
||||||
|
synchronize_rejection(
|
||||||
|
const std::optional<Rejection> &localRejection,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
const int localEncoded = encode_rejection(localRejection);
|
||||||
|
int globalEncoded = 0;
|
||||||
|
if (MPI_Allreduce(&localEncoded, &globalEncoded, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("Could not synchronize prepared gravity-force candidate validity.");
|
||||||
|
}
|
||||||
|
if (globalEncoded != 0) {
|
||||||
|
return std::unexpected(decode_rejection(globalEncoded));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool vector_is_finite(const mfem::Vector &vector) noexcept {
|
||||||
|
for (int index = 0; index < vector.Size(); ++index) {
|
||||||
|
if (!std::isfinite(vector(index))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void throw_rejection(const Rejection &rejection) {
|
||||||
|
if (rejection.reason == Reason::non_finite_arithmetic) {
|
||||||
|
throw std::domain_error("Prepared gravity force produced non-finite arithmetic.");
|
||||||
|
}
|
||||||
|
throw std::domain_error("Prepared gravity force encountered an invalid mapped domain.");
|
||||||
|
}
|
||||||
|
|
||||||
void true_to_local(
|
void true_to_local(
|
||||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||||
const mfem::Vector &trueVector,
|
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.clear();
|
||||||
m_elements.reserve(m_fem.mesh->GetNE());
|
m_elements.reserve(m_fem.mesh->GetNE());
|
||||||
|
|
||||||
@@ -199,9 +274,12 @@ namespace mean_field::operators {
|
|||||||
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
|
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
|
||||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||||
);
|
);
|
||||||
|
if (status != mapping::MappingStatus::valid) {
|
||||||
|
return std::unexpected(mapping_rejection(status));
|
||||||
|
}
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
status == mapping::MappingStatus::valid && !mappingContext.mapping.compactified,
|
!mappingContext.mapping.compactified,
|
||||||
"Prepared gravity force encountered an invalid stellar mapping."
|
"Prepared gravity force encountered compactification on a stellar element."
|
||||||
);
|
);
|
||||||
|
|
||||||
densityElement.CalcShape(integrationPoint, densityShape);
|
densityElement.CalcShape(integrationPoint, densityShape);
|
||||||
@@ -220,11 +298,38 @@ namespace mean_field::operators {
|
|||||||
data.inverseMeshJacobians(quadraturePoint, entry) = inverseMeshJacobian(row, column);
|
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() {
|
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(
|
MFEM_VERIFY(
|
||||||
m_gravityContext.IsPrepared(), "PreparedGravityDisplacementForceOperator requires the shared "
|
m_gravityContext.IsPrepared(), "PreparedGravityDisplacementForceOperator requires the shared "
|
||||||
"gravity linearization context to be prepared first."
|
"gravity linearization context to be prepared first."
|
||||||
@@ -236,19 +341,45 @@ namespace mean_field::operators {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
kernels::apply_gravity_displacement_force_residual(
|
m_isPrepared = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build the reusable element plan before assembling the residual.
|
||||||
|
* This pass stops at the first invalid mapped quadrature point, so a
|
||||||
|
* rejected line-search candidate need not traverse the full stateless
|
||||||
|
* residual kernel. Synchronize before proceeding so every rank takes
|
||||||
|
* the same branch.
|
||||||
|
*/
|
||||||
|
const auto elementResult = TryPrepareElementData();
|
||||||
|
const std::optional<Rejection> localElementRejection =
|
||||||
|
elementResult.has_value() ? std::optional<Rejection>{} : std::optional<Rejection>{elementResult.error()};
|
||||||
|
auto synchronizedElement = synchronize_rejection(localElementRejection, m_fem.mesh->GetComm());
|
||||||
|
if (!synchronizedElement.has_value()) {
|
||||||
|
return std::unexpected(synchronizedElement.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto residualResult = kernels::try_apply_gravity_displacement_force_residual(
|
||||||
m_fem, m_domainMapper, m_gravityContext.GetDensityTrue(), m_gravityContext.GetGravityGradientTrue(),
|
m_fem, m_domainMapper, m_gravityContext.GetDensityTrue(), m_gravityContext.GetGravityGradientTrue(),
|
||||||
m_gravityContext.GetGeometryContext().GetDisplacementTrue(), m_actionTrue
|
m_gravityContext.GetGeometryContext().GetDisplacementTrue(), m_actionTrue
|
||||||
);
|
);
|
||||||
|
if (!residualResult.has_value()) {
|
||||||
|
return std::unexpected(residualResult.error());
|
||||||
|
}
|
||||||
m_cachedResidual.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
|
m_cachedResidual.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
|
||||||
m_gravityContext.GetDisplacementMap().gather(m_actionTrue, m_cachedResidual);
|
m_gravityContext.GetDisplacementMap().gather(m_actionTrue, m_cachedResidual);
|
||||||
PrepareElementData();
|
std::optional<Rejection> localRejection;
|
||||||
|
if (!vector_is_finite(m_cachedResidual)) {
|
||||||
|
localRejection = non_finite_rejection();
|
||||||
|
}
|
||||||
|
auto synchronized = synchronize_rejection(localRejection, m_fem.mesh->GetComm());
|
||||||
|
if (!synchronized.has_value()) {
|
||||||
|
return std::unexpected(synchronized.error());
|
||||||
|
}
|
||||||
m_preparedRevisions = requestedRevisions;
|
m_preparedRevisions = requestedRevisions;
|
||||||
++m_residualPreparationCount;
|
++m_residualPreparationCount;
|
||||||
m_isPrepared = true;
|
m_isPrepared = true;
|
||||||
|
|
||||||
return {.preparedResidual = true};
|
return PreparedGravityDisplacementForceReport{.preparedResidual = true};
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedGravityDisplacementForceOperator::BuildResidual(mfem::Vector &residual) const {
|
void PreparedGravityDisplacementForceOperator::BuildResidual(mfem::Vector &residual) const {
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
module;
|
module;
|
||||||
#include "profile.h"
|
#include "profile.h"
|
||||||
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
#include <numbers>
|
#include <numbers>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
import :operators.prepared_gravity_source;
|
import :operators.prepared_gravity_source;
|
||||||
@@ -12,6 +18,54 @@ import :operators.prepared_gravity_source;
|
|||||||
namespace {
|
namespace {
|
||||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_candidate_mapping_failure(const mean_field::mapping::MappingStatus status) {
|
||||||
|
using mean_field::mapping::MappingStatus;
|
||||||
|
return status == MappingStatus::non_finite_input || status == MappingStatus::non_finite_result ||
|
||||||
|
status == MappingStatus::non_positive_determinant;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::operators::GravitySourcePreparationResult synchronize_preparation_failure(
|
||||||
|
const mean_field::mapping::MappingStatus localMappingStatus,
|
||||||
|
const bool localNonFiniteArithmetic,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
std::array<int, 3> localFailures{0, 0, localNonFiniteArithmetic ? 1 : 0};
|
||||||
|
if (localMappingStatus != mean_field::mapping::MappingStatus::valid) {
|
||||||
|
const int encodedStatus = static_cast<int>(localMappingStatus) + 1;
|
||||||
|
localFailures[is_candidate_mapping_failure(localMappingStatus) ? 0 : 1] = encodedStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<int, 3> globalFailures{};
|
||||||
|
if (MPI_Allreduce(
|
||||||
|
localFailures.data(), globalFailures.data(), static_cast<int>(localFailures.size()), MPI_INT, MPI_MAX,
|
||||||
|
communicator
|
||||||
|
) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("PreparedMappedGravitySourceOperator could not synchronize candidate validity.");
|
||||||
|
}
|
||||||
|
if (globalFailures[1] != 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"PreparedMappedGravitySourceOperator encountered a structural mapping failure with status " +
|
||||||
|
std::to_string(globalFailures[1] - 1) + "."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (globalFailures[0] != 0) {
|
||||||
|
return std::unexpected(
|
||||||
|
mean_field::operators::GravitySourcePreparationRejection{
|
||||||
|
.reason = mean_field::operators::GravitySourcePreparationRejectionReason::invalid_mapping,
|
||||||
|
.mappingStatus = static_cast<mean_field::mapping::MappingStatus>(globalFailures[0] - 1)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (globalFailures[2] != 0) {
|
||||||
|
return std::unexpected(
|
||||||
|
mean_field::operators::GravitySourcePreparationRejection{
|
||||||
|
.reason = mean_field::operators::GravitySourcePreparationRejectionReason::non_finite_arithmetic
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
int get_operator_height(const mean_field::fem::FEM &f) {
|
int get_operator_height(const mean_field::fem::FEM &f) {
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the "
|
f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the "
|
||||||
@@ -135,55 +189,33 @@ namespace {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (status != mean_field::mapping::MappingStatus::valid) {
|
if (status != mean_field::mapping::MappingStatus::valid) {
|
||||||
const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id);
|
m_mappingFailure = status;
|
||||||
const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id);
|
return 0.0;
|
||||||
|
|
||||||
mfem::Vector displacement_shape(displacement_element.GetDof());
|
|
||||||
mfem::Vector compactification_shape(compactification_element.GetDof());
|
|
||||||
mfem::Vector reference_position(m_domain_mapper.GetDimension());
|
|
||||||
mfem::Vector displacement_value(m_domain_mapper.GetDimension());
|
|
||||||
|
|
||||||
displacement_element.CalcShape(integration_point, displacement_shape);
|
|
||||||
compactification_element.CalcShape(integration_point, compactification_shape);
|
|
||||||
transformation.Transform(integration_point, reference_position);
|
|
||||||
m_displacement_data->GetDofMatrix().MultTranspose(displacement_shape, displacement_value);
|
|
||||||
|
|
||||||
const double compactification_coordinate = m_compactification_data->GetDofs() * compactification_shape;
|
|
||||||
|
|
||||||
MFEM_ABORT(
|
|
||||||
"Stateless domain mapping failed while preparing the "
|
|
||||||
"gravity "
|
|
||||||
"source operator."
|
|
||||||
<< "\nMapping status = " << static_cast<int>(status) << "\nElement ID = " << element_id
|
|
||||||
<< "\nElement attribute = " << transformation.Attribute
|
|
||||||
<< "\nIntegration-point index = " << integration_point.index << "\nIntegration point = <"
|
|
||||||
<< integration_point.x << ", " << integration_point.y << ", " << integration_point.z << ">"
|
|
||||||
<< "\nReference position = <" << reference_position(0) << ", " << reference_position(1) << ", "
|
|
||||||
<< reference_position(2) << ">"
|
|
||||||
<< "\nReference radius = " << reference_position.Norml2() << "\nDisplacement value = <"
|
|
||||||
<< displacement_value(0) << ", " << displacement_value(1) << ", " << displacement_value(2) << ">"
|
|
||||||
<< "\nDisplacement magnitude = " << displacement_value.Norml2()
|
|
||||||
<< "\nCompactification coordinate = " << compactification_coordinate
|
|
||||||
<< "\nDisplacement ordering = " << static_cast<int>(m_fem.displacementFes->GetOrdering())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const double mapping_determinant = mapping_context.mapping.mapping_determinant;
|
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 {
|
[[nodiscard]] const mfem::DenseMatrix &GetInverseElementJacobian() const noexcept {
|
||||||
return m_inverse_element_jacobian;
|
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:
|
private:
|
||||||
void LoadElement(const int element_id) {
|
void LoadElement(const int element_id) {
|
||||||
if (element_id == m_cached_element_id) {
|
if (element_id == m_cached_element_id) {
|
||||||
@@ -239,6 +271,8 @@ namespace {
|
|||||||
mean_field::mapping::DomainMapper::Workspace m_workspace;
|
mean_field::mapping::DomainMapper::Workspace m_workspace;
|
||||||
mfem::DenseMatrix m_inverse_element_jacobian;
|
mfem::DenseMatrix m_inverse_element_jacobian;
|
||||||
int m_cached_element_id{-1};
|
int m_cached_element_id{-1};
|
||||||
|
mean_field::mapping::MappingStatus m_mappingFailure{mean_field::mapping::MappingStatus::valid};
|
||||||
|
bool m_nonFiniteArithmetic{false};
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -306,15 +340,32 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
void PreparedMappedGravitySourceOperator::Prepare(const mfem::Vector &displacement) {
|
void PreparedMappedGravitySourceOperator::Prepare(const mfem::Vector &displacement) {
|
||||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedGravitySourceOperator::Prepare linearization", 0);
|
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) {
|
void PreparedMappedGravitySourceOperator::PreparePrimal(const mfem::Vector &displacement) {
|
||||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedGravitySourceOperator::Prepare primal", 0);
|
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 mfem::Vector &displacement,
|
||||||
const PreparationMode mode
|
const PreparationMode mode
|
||||||
) {
|
) {
|
||||||
@@ -325,11 +376,18 @@ namespace mean_field::operators {
|
|||||||
"with the wrong size."
|
"with the wrong size."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bool localNonFiniteInput = false;
|
||||||
for (int i = 0; i < displacement.Size(); ++i) {
|
for (int i = 0; i < displacement.Size(); ++i) {
|
||||||
MFEM_VERIFY(
|
localNonFiniteInput = localNonFiniteInput || !std::isfinite(displacement(i));
|
||||||
std::isfinite(displacement(i)), "PreparedMappedGravitySourceOperator received a non-finite "
|
}
|
||||||
"displacement value."
|
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;
|
m_is_prepared = false;
|
||||||
@@ -340,6 +398,7 @@ namespace mean_field::operators {
|
|||||||
m_elements.reserve(m_fem.mesh->GetNE());
|
m_elements.reserve(m_fem.mesh->GetNE());
|
||||||
|
|
||||||
FrozenMappedGravitySourceCoefficient source_coefficient(m_fem, m_domain_mapper, m_displacement_true);
|
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) {
|
for (int element_id = 0; element_id < m_fem.mesh->GetNE(); ++element_id) {
|
||||||
const int attribute = m_fem.mesh->GetAttribute(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);
|
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) {
|
if (mode == PreparationMode::linearization) {
|
||||||
const mfem::DenseMatrix &inverse_element_jacobian = source_coefficient.GetInverseElementJacobian();
|
const mfem::DenseMatrix &inverse_element_jacobian = source_coefficient.GetInverseElementJacobian();
|
||||||
for (int row = 0; row < dimension; ++row) {
|
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;
|
const double quadrature_value = integration_point.weight * transformation.Weight() * coefficient_value;
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(quadrature_value) || quadrature_value <= 0.0) {
|
||||||
std::isfinite(quadrature_value) && quadrature_value > 0.0,
|
localNonFiniteQuadrature = true;
|
||||||
"Prepared gravity source operator encountered invalid "
|
break;
|
||||||
"quadrature data on element "
|
}
|
||||||
<< element_id << ", quadrature point " << quadrature_point << "."
|
|
||||||
);
|
|
||||||
|
|
||||||
data.quadrature_data(quadrature_point) = quadrature_value;
|
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.");
|
MFEM_VERIFY(!m_elements.empty(), "PreparedMappedGravitySourceOperator found no stellar elements.");
|
||||||
@@ -444,6 +519,7 @@ namespace mean_field::operators {
|
|||||||
m_is_prepared = true;
|
m_is_prepared = true;
|
||||||
m_has_variation_data = mode == PreparationMode::linearization;
|
m_has_variation_data = mode == PreparationMode::linearization;
|
||||||
++m_preparation_count;
|
++m_preparation_count;
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
void PreparedMappedGravitySourceOperator::Mult(
|
void PreparedMappedGravitySourceOperator::Mult(
|
||||||
const mfem::Vector &density,
|
const mfem::Vector &density,
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
module;
|
module;
|
||||||
#include "profile.h"
|
#include "profile.h"
|
||||||
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
import :operators.prepared_hdiv_mass;
|
import :operators.prepared_hdiv_mass;
|
||||||
@@ -11,6 +18,83 @@ import :operators.prepared_hdiv_mass;
|
|||||||
namespace {
|
namespace {
|
||||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_candidate_mapping_failure(const mean_field::mapping::MappingStatus status) {
|
||||||
|
using mean_field::mapping::MappingStatus;
|
||||||
|
return status == MappingStatus::non_finite_input || status == MappingStatus::non_finite_result ||
|
||||||
|
status == MappingStatus::non_positive_determinant;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::operators::HDivMassPreparationResult synchronize_preparation_failure(
|
||||||
|
const mean_field::mapping::MappingStatus localMappingStatus,
|
||||||
|
const bool localNonFiniteArithmetic,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
std::array<int, 3> localFailures{0, 0, localNonFiniteArithmetic ? 1 : 0};
|
||||||
|
if (localMappingStatus != mean_field::mapping::MappingStatus::valid) {
|
||||||
|
const int encodedStatus = static_cast<int>(localMappingStatus) + 1;
|
||||||
|
localFailures[is_candidate_mapping_failure(localMappingStatus) ? 0 : 1] = encodedStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<int, 3> globalFailures{};
|
||||||
|
if (MPI_Allreduce(
|
||||||
|
localFailures.data(), globalFailures.data(), static_cast<int>(localFailures.size()), MPI_INT, MPI_MAX,
|
||||||
|
communicator
|
||||||
|
) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("PreparedMappedHDivMassOperator could not synchronize candidate validity.");
|
||||||
|
}
|
||||||
|
if (globalFailures[1] != 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"PreparedMappedHDivMassOperator encountered a structural mapping failure with status " +
|
||||||
|
std::to_string(globalFailures[1] - 1) + "."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (globalFailures[0] != 0) {
|
||||||
|
return std::unexpected(
|
||||||
|
mean_field::operators::HDivMassPreparationRejection{
|
||||||
|
.reason = mean_field::operators::HDivMassPreparationRejectionReason::invalid_mapping,
|
||||||
|
.mappingStatus = static_cast<mean_field::mapping::MappingStatus>(globalFailures[0] - 1)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (globalFailures[2] != 0) {
|
||||||
|
return std::unexpected(
|
||||||
|
mean_field::operators::HDivMassPreparationRejection{
|
||||||
|
.reason = mean_field::operators::HDivMassPreparationRejectionReason::non_finite_arithmetic
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::mapping::MappingStatus higher_priority_mapping_status(
|
||||||
|
const mean_field::mapping::MappingStatus left,
|
||||||
|
const mean_field::mapping::MappingStatus right
|
||||||
|
) noexcept {
|
||||||
|
if (left == mean_field::mapping::MappingStatus::valid) {
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
if (right == mean_field::mapping::MappingStatus::valid) {
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
const bool leftIsCandidate = is_candidate_mapping_failure(left);
|
||||||
|
const bool rightIsCandidate = is_candidate_mapping_failure(right);
|
||||||
|
if (leftIsCandidate != rightIsCandidate) {
|
||||||
|
return leftIsCandidate ? right : left;
|
||||||
|
}
|
||||||
|
return static_cast<int>(right) > static_cast<int>(left) ? right : left;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool matrix_is_finite(const mfem::DenseMatrix &matrix) noexcept {
|
||||||
|
for (int row = 0; row < matrix.Height(); ++row) {
|
||||||
|
for (int column = 0; column < matrix.Width(); ++column) {
|
||||||
|
if (!std::isfinite(matrix(row, column))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int get_operator_size(const mean_field::fem::FEM &f) {
|
int get_operator_size(const mean_field::fem::FEM &f) {
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the "
|
f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the "
|
||||||
@@ -270,27 +354,30 @@ namespace {
|
|||||||
mapping_data, transformation, integration_point, m_workspace, mapping_context
|
mapping_data, transformation, integration_point, m_workspace, mapping_context
|
||||||
);
|
);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (status != mean_field::mapping::MappingStatus::valid) {
|
||||||
status == mean_field::mapping::MappingStatus::valid,
|
m_mappingFailure = higher_priority_mapping_status(m_mappingFailure, status);
|
||||||
"Stateless domain mapping failed while preparing the H(div) "
|
mass_tensor.SetSize(m_domain_mapper.GetDimension());
|
||||||
"mass "
|
mass_tensor = 0.0;
|
||||||
"operator. Mapping status = "
|
return;
|
||||||
<< static_cast<int>(status) << ", element ID = " << element_id
|
}
|
||||||
<< ", element attribute = " << transformation.Attribute
|
|
||||||
<< ", coefficient domain = " << (m_elevates_vacuum ? "vacuum" : "stellar")
|
|
||||||
);
|
|
||||||
|
|
||||||
const mfem::DenseMatrix &mapping_jacobian = mapping_context.mapping.mapping_jacobian;
|
const mfem::DenseMatrix &mapping_jacobian = mapping_context.mapping.mapping_jacobian;
|
||||||
const double mapping_determinant = mapping_context.mapping.mapping_determinant;
|
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);
|
mfem::MultAtB(mapping_jacobian, mapping_jacobian, mass_tensor);
|
||||||
mass_tensor *= 1.0 / mapping_determinant;
|
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:
|
private:
|
||||||
@@ -348,6 +435,8 @@ namespace {
|
|||||||
mean_field::mapping::DomainMapper::Workspace m_workspace;
|
mean_field::mapping::DomainMapper::Workspace m_workspace;
|
||||||
int m_cached_element_id{-1};
|
int m_cached_element_id{-1};
|
||||||
bool m_elevates_vacuum;
|
bool m_elevates_vacuum;
|
||||||
|
mean_field::mapping::MappingStatus m_mappingFailure{mean_field::mapping::MappingStatus::valid};
|
||||||
|
bool m_nonFiniteArithmetic{false};
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -417,7 +506,7 @@ namespace mean_field::operators {
|
|||||||
validate_uniform_domain_discretization(f, m_vacuum_marker, vacuum_element_id);
|
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);
|
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::PrepareVariationData", 0);
|
||||||
|
|
||||||
m_variationElements.clear();
|
m_variationElements.clear();
|
||||||
@@ -480,28 +569,42 @@ namespace mean_field::operators {
|
|||||||
const mapping::MappingStatus status = m_domain_mapper.EvaluateVolume(
|
const mapping::MappingStatus status = m_domain_mapper.EvaluateVolume(
|
||||||
mappingData, *transformation, integrationPoint, m_variationWorkspace, mappingContext
|
mappingData, *transformation, integrationPoint, m_variationWorkspace, mappingContext
|
||||||
);
|
);
|
||||||
MFEM_VERIFY(
|
if (status != mapping::MappingStatus::valid) {
|
||||||
status == mapping::MappingStatus::valid,
|
return status;
|
||||||
"Prepared H(div) variation data encountered an invalid mapping. Element: "
|
}
|
||||||
<< elementId << ", quadrature point: " << quadraturePoint
|
|
||||||
<< ", status: " << static_cast<int>(status)
|
|
||||||
);
|
|
||||||
freeze_mapping_context(mappingContext, quadraturePoint, data.frozenMappingData);
|
freeze_mapping_context(mappingContext, quadraturePoint, data.frozenMappingData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return mapping::MappingStatus::valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) {
|
void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) {
|
||||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::Prepare linearization", 0);
|
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) {
|
void PreparedMappedHDivMassOperator::PreparePrimal(const mfem::Vector &displacement) {
|
||||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::Prepare primal", 0);
|
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 mfem::Vector &displacement,
|
||||||
const PreparationMode mode
|
const PreparationMode mode
|
||||||
) {
|
) {
|
||||||
@@ -512,12 +615,18 @@ namespace mean_field::operators {
|
|||||||
"the wrong size."
|
"the wrong size."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bool localNonFiniteInput = false;
|
||||||
for (int i = 0; i < displacement.Size(); ++i) {
|
for (int i = 0; i < displacement.Size(); ++i) {
|
||||||
MFEM_VERIFY(
|
localNonFiniteInput = localNonFiniteInput || !std::isfinite(displacement(i));
|
||||||
std::isfinite(displacement(i)), "PreparedMappedHDivMassOperator received a non-finite "
|
}
|
||||||
"displacement "
|
if (auto inputResult = synchronize_preparation_failure(
|
||||||
"value."
|
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;
|
m_is_prepared = false;
|
||||||
@@ -540,13 +649,17 @@ namespace mean_field::operators {
|
|||||||
m_stellar_mass_coefficient.reset();
|
m_stellar_mass_coefficient.reset();
|
||||||
m_vacuum_mass_coefficient.reset();
|
m_vacuum_mass_coefficient.reset();
|
||||||
|
|
||||||
m_stellar_mass_coefficient =
|
auto stellarMassCoefficient =
|
||||||
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, false);
|
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, false);
|
||||||
m_vacuum_mass_coefficient =
|
auto vacuumMassCoefficient =
|
||||||
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, true);
|
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, true);
|
||||||
|
auto *stellarMassCoefficientView = stellarMassCoefficient.get();
|
||||||
|
auto *vacuumMassCoefficientView = vacuumMassCoefficient.get();
|
||||||
|
m_stellar_mass_coefficient = std::move(stellarMassCoefficient);
|
||||||
|
m_vacuum_mass_coefficient = std::move(vacuumMassCoefficient);
|
||||||
|
|
||||||
m_stellar_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
|
m_stellar_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
|
||||||
m_vacuum_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
|
m_vacuum_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
|
||||||
m_stellar_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
m_stellar_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||||
m_vacuum_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_stellar_mass_form->Assemble();
|
||||||
m_vacuum_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) {
|
if (mode == PreparationMode::linearization) {
|
||||||
PrepareVariationData();
|
if (localMappingFailure == mapping::MappingStatus::valid && !localNonFiniteArithmetic) {
|
||||||
m_has_variation_data = true;
|
localMappingFailure = PrepareVariationData();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
m_variationElements.clear();
|
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;
|
++m_preparation_count;
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedMappedHDivMassOperator::Mult(
|
void PreparedMappedHDivMassOperator::Mult(
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ module;
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
|
||||||
@@ -18,6 +23,82 @@ namespace {
|
|||||||
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_candidate_mapping_failure(const mean_field::mapping::MappingStatus status) {
|
||||||
|
using mean_field::mapping::MappingStatus;
|
||||||
|
|
||||||
|
return status == MappingStatus::non_finite_input || status == MappingStatus::non_finite_result ||
|
||||||
|
status == MappingStatus::non_positive_determinant;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<mean_field::mapping::MappingStatus> synchronize_mapping_failure(
|
||||||
|
const std::optional<mean_field::mapping::MappingStatus> localFailure,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
int localFailures[2]{0, 0};
|
||||||
|
if (localFailure.has_value()) {
|
||||||
|
const int encodedStatus = static_cast<int>(*localFailure) + 1;
|
||||||
|
if (is_candidate_mapping_failure(*localFailure)) {
|
||||||
|
localFailures[0] = encodedStatus;
|
||||||
|
} else {
|
||||||
|
localFailures[1] = encodedStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int globalFailures[2]{0, 0};
|
||||||
|
if (MPI_Allreduce(localFailures, globalFailures, 2, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"PreparedHydrostaticEquilibriumOperator could not synchronize mapped-geometry validity."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalFailures[1] != 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"PreparedHydrostaticEquilibriumOperator encountered a structural mapping failure with status " +
|
||||||
|
std::to_string(globalFailures[1] - 1) + "."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalFailures[0] == 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<mean_field::mapping::MappingStatus>(globalFailures[0] - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool synchronize_non_finite_failure(
|
||||||
|
const bool localFailure,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
const int localStatus = localFailure ? 1 : 0;
|
||||||
|
int globalStatus = 0;
|
||||||
|
if (MPI_Allreduce(&localStatus, &globalStatus, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"PreparedHydrostaticEquilibriumOperator could not synchronize finite-arithmetic validity."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return globalStatus != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_finite(const mfem::Vector &vector) {
|
||||||
|
for (int entry = 0; entry < vector.Size(); ++entry) {
|
||||||
|
if (!std::isfinite(vector(entry))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_finite(const mfem::DenseMatrix &matrix) {
|
||||||
|
for (int row = 0; row < matrix.Height(); ++row) {
|
||||||
|
for (int column = 0; column < matrix.Width(); ++column) {
|
||||||
|
if (!std::isfinite(matrix(row, column))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void true_to_local(
|
void true_to_local(
|
||||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||||
const mfem::Vector &trueVector,
|
const mfem::Vector &trueVector,
|
||||||
@@ -291,8 +372,21 @@ namespace mean_field::operators {
|
|||||||
const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies,
|
const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies,
|
||||||
const physics::RigidRotation &rotation
|
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 =
|
const bool rotationObjectChanged =
|
||||||
!m_context.IsPrepared() || dependencies.rotation != m_context.GetDependencies().rotation;
|
!wasPrepared || !m_context.IsPrepared() || dependencies.rotation != m_context.GetDependencies().rotation;
|
||||||
|
|
||||||
PreparedHydrostaticEquilibriumReport report;
|
PreparedHydrostaticEquilibriumReport report;
|
||||||
|
|
||||||
@@ -310,25 +404,58 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
m_isPrepared = false;
|
m_isPrepared = false;
|
||||||
|
|
||||||
if (report.contextReport.preparedStaticDependencies) {
|
if (report.contextReport.preparedStaticDependencies || !wasPrepared) {
|
||||||
PrepareStaticPlan();
|
PrepareStaticPlan();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (report.contextReport.preparedGeometryState) {
|
if (report.contextReport.preparedGeometryState || !wasPrepared) {
|
||||||
PrepareGeometry();
|
const auto mappingFailure = synchronize_mapping_failure(PrepareGeometry(), m_fem.mesh->GetComm());
|
||||||
PrepareAlgebraicJacobianBlocks();
|
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;
|
report.preparedAlgebraicJacobianBlocks = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (report.contextReport.preparedRotationDependencies) {
|
if (report.contextReport.preparedRotationDependencies || !wasPrepared) {
|
||||||
PrepareRotation();
|
if (synchronize_non_finite_failure(PrepareRotation(), m_fem.mesh->GetComm())) {
|
||||||
|
return std::unexpected(
|
||||||
|
HydrostaticEquilibriumPreparationRejection{
|
||||||
|
.reason = HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (report.contextReport.preparedBaseState) {
|
if (report.contextReport.preparedBaseState || !wasPrepared) {
|
||||||
PrepareBaseState();
|
if (synchronize_non_finite_failure(PrepareBaseState(), m_fem.mesh->GetComm())) {
|
||||||
|
return std::unexpected(
|
||||||
|
HydrostaticEquilibriumPreparationRejection{
|
||||||
|
.reason = HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
FinalizeDisplacementJacobianPreparation();
|
FinalizeDisplacementJacobianPreparation();
|
||||||
AssembleCachedResidual();
|
if (synchronize_non_finite_failure(AssembleCachedResidual(), m_fem.mesh->GetComm())) {
|
||||||
++m_residualPreparationCount;
|
return std::unexpected(
|
||||||
|
HydrostaticEquilibriumPreparationRejection{
|
||||||
|
.reason = HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
report.preparedDisplacementJacobianData = true;
|
report.preparedDisplacementJacobianData = true;
|
||||||
report.preparedResidual = true;
|
report.preparedResidual = true;
|
||||||
}
|
}
|
||||||
@@ -343,6 +470,16 @@ namespace mean_field::operators {
|
|||||||
"The prepared hydrostatic residual has the wrong supported size."
|
"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;
|
m_isPrepared = true;
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
@@ -422,7 +559,7 @@ namespace mean_field::operators {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedHydrostaticEquilibriumOperator::PrepareGeometry() {
|
std::optional<mapping::MappingStatus> PreparedHydrostaticEquilibriumOperator::PrepareGeometry() {
|
||||||
mfem::Vector displacementLocal;
|
mfem::Vector displacementLocal;
|
||||||
|
|
||||||
true_to_local(*m_fem.displacementFes, m_context.GetDisplacementTrue(), displacementLocal);
|
true_to_local(*m_fem.displacementFes, m_context.GetDisplacementTrue(), displacementLocal);
|
||||||
@@ -491,39 +628,37 @@ namespace mean_field::operators {
|
|||||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||||
);
|
);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (mappingStatus != mapping::MappingStatus::valid) {
|
||||||
mappingStatus == mapping::MappingStatus::valid,
|
return mappingStatus;
|
||||||
"Stateless mapping failed while preparing "
|
}
|
||||||
"hydrostatic geometry. Element: "
|
|
||||||
<< data.elementId << ", attribute: " << transformation->Attribute
|
|
||||||
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
|
|
||||||
);
|
|
||||||
|
|
||||||
const double quadratureWeight = mappingContext.quadrature.weight;
|
const double quadratureWeight = mappingContext.quadrature.weight;
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(quadratureWeight)) {
|
||||||
std::isfinite(quadratureWeight) && quadratureWeight > 0.0,
|
return mapping::MappingStatus::non_finite_result;
|
||||||
"Prepared hydrostatic geometry encountered "
|
}
|
||||||
"an invalid quadrature weight."
|
if (quadratureWeight <= 0.0) {
|
||||||
);
|
return mapping::MappingStatus::non_positive_determinant;
|
||||||
|
}
|
||||||
|
|
||||||
data.quadratureWeights(quadraturePoint) = quadratureWeight;
|
data.quadratureWeights(quadraturePoint) = quadratureWeight;
|
||||||
|
|
||||||
for (int component = 0; component < m_fem.mesh->Dimension(); ++component) {
|
for (int component = 0; component < m_fem.mesh->Dimension(); ++component) {
|
||||||
const double position = mappingContext.mapping.physical_position(component);
|
const double position = mappingContext.mapping.physical_position(component);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(position)) {
|
||||||
std::isfinite(position), "Prepared hydrostatic geometry encountered "
|
return mapping::MappingStatus::non_finite_result;
|
||||||
"a non-finite physical position."
|
}
|
||||||
);
|
|
||||||
|
|
||||||
data.physicalPositions(quadraturePoint, component) = position;
|
data.physicalPositions(quadraturePoint, component) = position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedHydrostaticEquilibriumOperator::PrepareAlgebraicJacobianBlocks() {
|
bool PreparedHydrostaticEquilibriumOperator::PrepareAlgebraicJacobianBlocks() {
|
||||||
for (ElementPAData &data : m_elements) {
|
for (ElementPAData &data : m_elements) {
|
||||||
const int quadraturePointCount = data.quadratureWeights.Size();
|
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_VERIFY(m_rotation.has_value(), "Prepared hydrostatic rotation has no frozen state.");
|
||||||
|
|
||||||
mfem::Vector physicalPosition(m_fem.mesh->Dimension());
|
mfem::Vector physicalPosition(m_fem.mesh->Dimension());
|
||||||
@@ -598,10 +738,9 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
const double rotationPotential = m_rotation->potential(physicalPosition);
|
const double rotationPotential = m_rotation->potential(physicalPosition);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(rotationPotential)) {
|
||||||
std::isfinite(rotationPotential), "Prepared hydrostatic rotation encountered "
|
return true;
|
||||||
"a non-finite potential."
|
}
|
||||||
);
|
|
||||||
|
|
||||||
data.rotationPotential(quadraturePoint) = rotationPotential;
|
data.rotationPotential(quadraturePoint) = rotationPotential;
|
||||||
|
|
||||||
@@ -612,18 +751,19 @@ namespace mean_field::operators {
|
|||||||
const double gradientComponent =
|
const double gradientComponent =
|
||||||
m_rotation->potential_directional_derivative(physicalPosition, coordinateDirection);
|
m_rotation->potential_directional_derivative(physicalPosition, coordinateDirection);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(gradientComponent)) {
|
||||||
std::isfinite(gradientComponent), "Prepared hydrostatic rotation encountered "
|
return true;
|
||||||
"a non-finite potential gradient."
|
}
|
||||||
);
|
|
||||||
|
|
||||||
data.rotationGradient(quadraturePoint, component) = gradientComponent;
|
data.rotationGradient(quadraturePoint, component) = gradientComponent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedHydrostaticEquilibriumOperator::PrepareBaseState() {
|
bool PreparedHydrostaticEquilibriumOperator::PrepareBaseState() {
|
||||||
mfem::Vector enthalpyLocal;
|
mfem::Vector enthalpyLocal;
|
||||||
mfem::Vector gravityPotentialLocal;
|
mfem::Vector gravityPotentialLocal;
|
||||||
|
|
||||||
@@ -675,16 +815,17 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
const double weightedResidual = data.quadratureWeights(quadraturePoint) * imbalance;
|
const double weightedResidual = data.quadratureWeights(quadraturePoint) * imbalance;
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(imbalance) || !std::isfinite(weightedResidual)) {
|
||||||
std::isfinite(weightedResidual), "Prepared hydrostatic base state encountered "
|
return true;
|
||||||
"a non-finite residual value."
|
}
|
||||||
);
|
|
||||||
|
|
||||||
data.weightedResidual(quadraturePoint) = weightedResidual;
|
data.weightedResidual(quadraturePoint) = weightedResidual;
|
||||||
|
|
||||||
data.hydrostaticImbalance(quadraturePoint) = imbalance;
|
data.hydrostaticImbalance(quadraturePoint) = imbalance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedHydrostaticEquilibriumOperator::FinalizeDisplacementJacobianPreparation() {
|
void PreparedHydrostaticEquilibriumOperator::FinalizeDisplacementJacobianPreparation() {
|
||||||
@@ -703,11 +844,9 @@ namespace mean_field::operators {
|
|||||||
"has inconsistent frozen data."
|
"has inconsistent frozen data."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
++m_displacementJacobianStatistics.preparations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedHydrostaticEquilibriumOperator::AssembleCachedResidual() {
|
bool PreparedHydrostaticEquilibriumOperator::AssembleCachedResidual() {
|
||||||
mfem::Vector localResidual(m_fem.enthalpyFes->GetVSize());
|
mfem::Vector localResidual(m_fem.enthalpyFes->GetVSize());
|
||||||
|
|
||||||
localResidual = 0.0;
|
localResidual = 0.0;
|
||||||
@@ -729,6 +868,8 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
m_cachedResidual.SetSize(m_context.GetEnthalpyMap().reduced_size());
|
m_cachedResidual.SetSize(m_context.GetEnthalpyMap().reduced_size());
|
||||||
m_context.GetEnthalpyMap().gather(m_fullEnthalpyAction, m_cachedResidual);
|
m_context.GetEnthalpyMap().gather(m_fullEnthalpyAction, m_cachedResidual);
|
||||||
|
|
||||||
|
return !is_finite(m_cachedResidual);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedHydrostaticEquilibriumOperator::BuildResidual(mfem::Vector &residual) const {
|
void PreparedHydrostaticEquilibriumOperator::BuildResidual(mfem::Vector &residual) const {
|
||||||
@@ -926,9 +1067,9 @@ namespace mean_field::operators {
|
|||||||
);
|
);
|
||||||
weightedVariation.SetSize(quadraturePointCount);
|
weightedVariation.SetSize(quadraturePointCount);
|
||||||
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
|
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
|
||||||
weightedVariation(quadraturePoint) =
|
weightedVariation(quadraturePoint) = -2.0 * fractionalAngularVelocityVariation *
|
||||||
-2.0 * fractionalAngularVelocityVariation * data.quadratureWeights(quadraturePoint) *
|
data.quadratureWeights(quadraturePoint) *
|
||||||
data.rotationPotential(quadraturePoint);
|
data.rotationPotential(quadraturePoint);
|
||||||
}
|
}
|
||||||
elementAction.SetSize(data.enthalpyDofs.Size());
|
elementAction.SetSize(data.enthalpyDofs.Size());
|
||||||
data.enthalpyBasis.MultTranspose(weightedVariation, elementAction);
|
data.enthalpyBasis.MultTranspose(weightedVariation, elementAction);
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ module;
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <expected>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
|
|
||||||
@@ -116,6 +122,109 @@ namespace {
|
|||||||
) {
|
) {
|
||||||
MFEM_VERIFY(prepared.identity == requested.identity || prepared.revision != requested.revision, message);
|
MFEM_VERIFY(prepared.identity == requested.identity || prepared.revision != requested.revision, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using MassRejection = mean_field::operators::MassNormalizationPreparationRejection;
|
||||||
|
using MassRejectionReason = mean_field::operators::MassNormalizationPreparationRejectionReason;
|
||||||
|
|
||||||
|
[[nodiscard]] int mapping_status_priority(const mean_field::mapping::MappingStatus status) {
|
||||||
|
using Status = mean_field::mapping::MappingStatus;
|
||||||
|
switch (status) {
|
||||||
|
case Status::non_positive_determinant:
|
||||||
|
return 7;
|
||||||
|
case Status::non_finite_result:
|
||||||
|
return 6;
|
||||||
|
case Status::non_finite_input:
|
||||||
|
return 5;
|
||||||
|
case Status::outside_reference_domain:
|
||||||
|
return 4;
|
||||||
|
case Status::at_compactified_infinity:
|
||||||
|
return 3;
|
||||||
|
case Status::invalid_reference_radius:
|
||||||
|
return 2;
|
||||||
|
case Status::valid:
|
||||||
|
throw std::logic_error("A valid mapping cannot be a mass-normalization candidate rejection.");
|
||||||
|
case Status::invalid_dimension:
|
||||||
|
throw std::logic_error("A mapping dimension error cannot be a mass-normalization candidate rejection.");
|
||||||
|
}
|
||||||
|
throw std::logic_error("Unknown mapping status in mass-normalization candidate rejection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::mapping::MappingStatus mapping_status_from_priority(const int priority) {
|
||||||
|
using Status = mean_field::mapping::MappingStatus;
|
||||||
|
switch (priority) {
|
||||||
|
case 7:
|
||||||
|
return Status::non_positive_determinant;
|
||||||
|
case 6:
|
||||||
|
return Status::non_finite_result;
|
||||||
|
case 5:
|
||||||
|
return Status::non_finite_input;
|
||||||
|
case 4:
|
||||||
|
return Status::outside_reference_domain;
|
||||||
|
case 3:
|
||||||
|
return Status::at_compactified_infinity;
|
||||||
|
case 2:
|
||||||
|
return Status::invalid_reference_radius;
|
||||||
|
default:
|
||||||
|
throw std::logic_error("Invalid synchronized mapping priority for mass normalization.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Phase priority is explicit and independent of enum representation:
|
||||||
|
* mapping wins over interpolation, which wins over assembled-mass
|
||||||
|
* arithmetic. The mapping detail is likewise selected explicitly.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] int rejection_priority(const MassRejection &rejection) {
|
||||||
|
switch (rejection.reason) {
|
||||||
|
case MassRejectionReason::mapping_failure:
|
||||||
|
return 300 + mapping_status_priority(rejection.mappingStatus);
|
||||||
|
case MassRejectionReason::non_finite_density_interpolation:
|
||||||
|
return 200;
|
||||||
|
case MassRejectionReason::non_finite_assembled_mass:
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
throw std::logic_error("Unknown mass-normalization candidate-rejection reason.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MassRejection rejection_from_priority(const int priority) {
|
||||||
|
if (priority >= 300) {
|
||||||
|
return {
|
||||||
|
.reason = MassRejectionReason::mapping_failure,
|
||||||
|
.mappingStatus = mapping_status_from_priority(priority - 300)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (priority == 200) {
|
||||||
|
return {.reason = MassRejectionReason::non_finite_density_interpolation};
|
||||||
|
}
|
||||||
|
if (priority == 100) {
|
||||||
|
return {.reason = MassRejectionReason::non_finite_assembled_mass};
|
||||||
|
}
|
||||||
|
throw std::logic_error("Invalid synchronized mass-normalization candidate-rejection priority.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void retain_higher_priority_rejection(
|
||||||
|
std::optional<MassRejection> ¤t,
|
||||||
|
const MassRejection candidate
|
||||||
|
) {
|
||||||
|
if (!current.has_value() || rejection_priority(candidate) > rejection_priority(*current)) {
|
||||||
|
current = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<MassRejection> synchronize_rejection(
|
||||||
|
const std::optional<MassRejection> &local,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
const int localPriority = local.has_value() ? rejection_priority(*local) : 0;
|
||||||
|
int globalPriority = 0;
|
||||||
|
if (MPI_Allreduce(&localPriority, &globalPriority, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("PreparedMassNormalizationOperator could not synchronize candidate validity.");
|
||||||
|
}
|
||||||
|
if (globalPriority == 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return rejection_from_priority(globalPriority);
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace mean_field::operators {
|
namespace mean_field::operators {
|
||||||
@@ -154,6 +263,26 @@ namespace mean_field::operators {
|
|||||||
PreparedMassNormalizationReport PreparedMassNormalizationOperator::Prepare(
|
PreparedMassNormalizationReport PreparedMassNormalizationOperator::Prepare(
|
||||||
const MassNormalizationStateView &state,
|
const MassNormalizationStateView &state,
|
||||||
const MassNormalizationDependencies &dependencies
|
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(
|
MFEM_VERIFY(
|
||||||
std::isfinite(state.targetMass) && state.targetMass > 0.0,
|
std::isfinite(state.targetMass) && state.targetMass > 0.0,
|
||||||
@@ -195,6 +324,7 @@ namespace mean_field::operators {
|
|||||||
m_isPrepared = false;
|
m_isPrepared = false;
|
||||||
|
|
||||||
PreparedMassNormalizationReport report;
|
PreparedMassNormalizationReport report;
|
||||||
|
std::optional<MassNormalizationPreparationRejection> localRejection;
|
||||||
|
|
||||||
if (rebuildStaticPlan) {
|
if (rebuildStaticPlan) {
|
||||||
BuildStaticPlan();
|
BuildStaticPlan();
|
||||||
@@ -202,27 +332,37 @@ namespace mean_field::operators {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (refreshGeometry) {
|
if (refreshGeometry) {
|
||||||
RefreshGeometry(m_gravityContext.GetGeometryContext().GetDisplacementTrue());
|
localRejection = RefreshGeometry(m_gravityContext.GetGeometryContext().GetDisplacementTrue());
|
||||||
report.refreshedGeometry = true;
|
report.refreshedGeometry = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refreshDensity) {
|
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;
|
report.refreshedDensity = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto globalRejection = synchronize_rejection(localRejection, m_fem.mesh->GetComm());
|
||||||
|
globalRejection.has_value()) {
|
||||||
|
return std::unexpected(*globalRejection);
|
||||||
|
}
|
||||||
|
|
||||||
if (updateTargetMass) {
|
if (updateTargetMass) {
|
||||||
m_targetMass = state.targetMass;
|
m_targetMass = state.targetMass;
|
||||||
report.updatedTargetMass = true;
|
report.updatedTargetMass = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refreshGeometry || refreshDensity) {
|
if (refreshGeometry || refreshDensity) {
|
||||||
AssembleResidual();
|
if (auto rejection = AssembleResidual(); rejection.has_value()) {
|
||||||
|
return std::unexpected(*rejection);
|
||||||
|
}
|
||||||
report.assembledResidual = true;
|
report.assembledResidual = true;
|
||||||
} else if (updateTargetMass) {
|
} else if (updateTargetMass) {
|
||||||
m_cachedResidual.SetSize(1);
|
if (auto rejection = UpdateResidualForTargetMass(); rejection.has_value()) {
|
||||||
m_cachedResidual(0) = m_currentMass - m_targetMass;
|
return std::unexpected(*rejection);
|
||||||
++m_preparationCount;
|
}
|
||||||
report.assembledResidual = true;
|
report.assembledResidual = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +378,13 @@ namespace mean_field::operators {
|
|||||||
return Prepare({.targetMass = constraint.targetMass().value()}, dependencies);
|
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() {
|
void PreparedMassNormalizationOperator::BuildStaticPlan() {
|
||||||
m_elements.clear();
|
m_elements.clear();
|
||||||
m_elements.reserve(m_fem.mesh->GetNE());
|
m_elements.reserve(m_fem.mesh->GetNE());
|
||||||
@@ -287,14 +434,17 @@ namespace mean_field::operators {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int globalStellarElementCount = 0;
|
int globalStellarElementCount = 0;
|
||||||
MPI_Allreduce(
|
if (MPI_Allreduce(
|
||||||
&localStellarElementCount, &globalStellarElementCount, 1, MPI_INT, MPI_SUM, m_fem.mesh->GetComm()
|
&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.");
|
MFEM_VERIFY(globalStellarElementCount > 0, "PreparedMassNormalizationOperator found no stellar elements.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedMassNormalizationOperator::RefreshGeometry(const mfem::Vector &displacement) {
|
std::optional<MassNormalizationPreparationRejection>
|
||||||
|
PreparedMassNormalizationOperator::RefreshGeometry(const mfem::Vector &displacement) {
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
displacement.Size() == m_fem.displacementFes->GetTrueVSize(),
|
displacement.Size() == m_fem.displacementFes->GetTrueVSize(),
|
||||||
"PreparedMassNormalizationOperator received a displacement "
|
"PreparedMassNormalizationOperator received a displacement "
|
||||||
@@ -309,6 +459,7 @@ namespace mean_field::operators {
|
|||||||
true_to_local(*m_fem.displacementFes, displacement, displacementLocal);
|
true_to_local(*m_fem.displacementFes, displacement, displacementLocal);
|
||||||
|
|
||||||
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
|
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
|
||||||
|
std::optional<MassNormalizationPreparationRejection> rejection;
|
||||||
|
|
||||||
for (ElementPAData &data : m_elements) {
|
for (ElementPAData &data : m_elements) {
|
||||||
displacementLocal.GetSubVector(data.displacementDofs, data.baseDisplacement);
|
displacementLocal.GetSubVector(data.displacementDofs, data.baseDisplacement);
|
||||||
@@ -346,17 +497,23 @@ namespace mean_field::operators {
|
|||||||
);
|
);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
status == mapping::MappingStatus::valid, "Stateless mapping failed while preparing mass "
|
status != mapping::MappingStatus::invalid_dimension,
|
||||||
"normalization. Element: "
|
"Stateless mapping reported a dimension error while preparing mass normalization."
|
||||||
<< data.elementId
|
|
||||||
<< ", attribute: " << transformation->Attribute
|
|
||||||
<< ", status: " << static_cast<int>(status)
|
|
||||||
);
|
);
|
||||||
|
if (status != mapping::MappingStatus::valid) {
|
||||||
|
retain_higher_priority_rejection(
|
||||||
|
rejection, {.reason = MassNormalizationPreparationRejectionReason::mapping_failure,
|
||||||
|
.mappingStatus = status}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return rejection;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedMassNormalizationOperator::RefreshDensity(const mfem::Vector &density) {
|
std::optional<MassNormalizationPreparationRejection>
|
||||||
|
PreparedMassNormalizationOperator::RefreshDensity(const mfem::Vector &density) {
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
density.Size() == m_fem.densityFes->GetTrueVSize(),
|
density.Size() == m_fem.densityFes->GetTrueVSize(),
|
||||||
"PreparedMassNormalizationOperator received a density vector "
|
"PreparedMassNormalizationOperator received a density vector "
|
||||||
@@ -371,6 +528,7 @@ namespace mean_field::operators {
|
|||||||
true_to_local(*m_fem.densityFes, density, densityLocal);
|
true_to_local(*m_fem.densityFes, density, densityLocal);
|
||||||
|
|
||||||
mfem::Vector elementDensity;
|
mfem::Vector elementDensity;
|
||||||
|
std::optional<MassNormalizationPreparationRejection> rejection;
|
||||||
|
|
||||||
for (ElementPAData &data : m_elements) {
|
for (ElementPAData &data : m_elements) {
|
||||||
densityLocal.GetSubVector(data.densityDofs, elementDensity);
|
densityLocal.GetSubVector(data.densityDofs, elementDensity);
|
||||||
@@ -381,29 +539,62 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
for (QuadraturePointData &point : data.quadraturePoints) {
|
for (QuadraturePointData &point : data.quadraturePoints) {
|
||||||
point.density = elementDensity * point.densityShape;
|
point.density = elementDensity * point.densityShape;
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(point.density)) {
|
||||||
std::isfinite(point.density), "PreparedMassNormalizationOperator produced a non-finite "
|
retain_higher_priority_rejection(
|
||||||
"quadrature density."
|
rejection,
|
||||||
);
|
{.reason = MassNormalizationPreparationRejectionReason::non_finite_density_interpolation}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return rejection;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedMassNormalizationOperator::AssembleResidual() {
|
std::optional<MassNormalizationPreparationRejection> PreparedMassNormalizationOperator::AssembleResidual() {
|
||||||
double localMass = 0.0;
|
double localMass = 0.0;
|
||||||
|
std::optional<MassNormalizationPreparationRejection> localRejection;
|
||||||
|
|
||||||
for (const ElementPAData &data : m_elements) {
|
for (const ElementPAData &data : m_elements) {
|
||||||
for (const QuadraturePointData &point : data.quadraturePoints) {
|
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);
|
if (auto globalRejection = synchronize_rejection(localRejection, m_fem.mesh->GetComm());
|
||||||
MFEM_VERIFY(std::isfinite(m_currentMass), "PreparedMassNormalizationOperator assembled a non-finite mass.");
|
globalRejection.has_value()) {
|
||||||
|
return globalRejection;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentMass = GlobalSum(localMass);
|
||||||
|
if (!std::isfinite(m_currentMass)) {
|
||||||
|
return MassNormalizationPreparationRejection{
|
||||||
|
.reason = MassNormalizationPreparationRejectionReason::non_finite_assembled_mass
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return UpdateResidualForTargetMass();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<MassNormalizationPreparationRejection>
|
||||||
|
PreparedMassNormalizationOperator::UpdateResidualForTargetMass() {
|
||||||
m_cachedResidual.SetSize(1);
|
m_cachedResidual.SetSize(1);
|
||||||
m_cachedResidual(0) = m_currentMass - m_targetMass;
|
m_cachedResidual(0) = m_currentMass - m_targetMass;
|
||||||
|
std::optional<MassNormalizationPreparationRejection> localRejection;
|
||||||
|
if (!std::isfinite(m_cachedResidual(0))) {
|
||||||
|
localRejection = {.reason = MassNormalizationPreparationRejectionReason::non_finite_assembled_mass};
|
||||||
|
}
|
||||||
|
if (auto globalRejection = synchronize_rejection(localRejection, m_fem.mesh->GetComm());
|
||||||
|
globalRejection.has_value()) {
|
||||||
|
return globalRejection;
|
||||||
|
}
|
||||||
++m_preparationCount;
|
++m_preparationCount;
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedMassNormalizationOperator::BuildResidual(mfem::Vector &residual) const {
|
void PreparedMassNormalizationOperator::BuildResidual(mfem::Vector &residual) const {
|
||||||
@@ -716,7 +907,9 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
double PreparedMassNormalizationOperator::GlobalSum(const double localValue) const {
|
double PreparedMassNormalizationOperator::GlobalSum(const double localValue) const {
|
||||||
double globalValue = 0.0;
|
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;
|
return globalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ module;
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
|
|
||||||
@@ -20,6 +24,99 @@ namespace {
|
|||||||
|
|
||||||
using PressureDomain = mean_field::field::FieldDomainT<mean_field::field::Enthalpy>;
|
using PressureDomain = mean_field::field::FieldDomainT<mean_field::field::Enthalpy>;
|
||||||
|
|
||||||
|
using Rejection = mean_field::operators::PressureForcePreparationRejection;
|
||||||
|
using Reason = mean_field::operators::PressureForcePreparationRejectionReason;
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection equation_of_state_rejection(const mean_field::eos::EvaluationErrorCode code) noexcept {
|
||||||
|
return {.reason = Reason::equation_of_state, .equationOfStateCode = code};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection mapping_rejection(const mean_field::mapping::MappingStatus status) {
|
||||||
|
MFEM_VERIFY(
|
||||||
|
status != mean_field::mapping::MappingStatus::invalid_dimension,
|
||||||
|
"Prepared pressure-force mapping reported an invariant dimension mismatch."
|
||||||
|
);
|
||||||
|
return {.reason = Reason::invalid_mapping, .mappingStatus = status};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection non_finite_rejection() noexcept {
|
||||||
|
return {.reason = Reason::non_finite_arithmetic};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int encode_rejection(const std::optional<Rejection> &rejection) noexcept {
|
||||||
|
if (!rejection.has_value()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
switch (rejection->reason) {
|
||||||
|
case Reason::equation_of_state:
|
||||||
|
return static_cast<int>(rejection->equationOfStateCode) + 1;
|
||||||
|
case Reason::invalid_mapping:
|
||||||
|
return 128 + static_cast<int>(rejection->mappingStatus);
|
||||||
|
case Reason::non_finite_arithmetic:
|
||||||
|
default:
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection decode_rejection(const int encoded) {
|
||||||
|
if (encoded >= 256) {
|
||||||
|
return non_finite_rejection();
|
||||||
|
}
|
||||||
|
if (encoded >= 128) {
|
||||||
|
return mapping_rejection(static_cast<mean_field::mapping::MappingStatus>(encoded - 128));
|
||||||
|
}
|
||||||
|
return equation_of_state_rejection(static_cast<mean_field::eos::EvaluationErrorCode>(encoded - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<Rejection> synchronize_rejection(
|
||||||
|
const std::optional<Rejection> &localRejection,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
const int localEncoded = encode_rejection(localRejection);
|
||||||
|
int globalEncoded = 0;
|
||||||
|
if (MPI_Allreduce(&localEncoded, &globalEncoded, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("PreparedPressureForceOperator could not synchronize candidate validity.");
|
||||||
|
}
|
||||||
|
if (globalEncoded == 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return decode_rejection(globalEncoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool vector_is_finite(const mfem::Vector &vector) noexcept {
|
||||||
|
for (int index = 0; index < vector.Size(); ++index) {
|
||||||
|
if (!std::isfinite(vector(index))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool matrix_is_finite(const mfem::DenseMatrix &matrix) noexcept {
|
||||||
|
for (int row = 0; row < matrix.Height(); ++row) {
|
||||||
|
for (int column = 0; column < matrix.Width(); ++column) {
|
||||||
|
if (!std::isfinite(matrix(row, column))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void throw_rejection(const Rejection &rejection) {
|
||||||
|
switch (rejection.reason) {
|
||||||
|
case Reason::equation_of_state:
|
||||||
|
throw mean_field::eos::EvaluationError(
|
||||||
|
rejection.equationOfStateCode, "PreparedPressureForceOperator encountered invalid thermodynamic data."
|
||||||
|
);
|
||||||
|
case Reason::invalid_mapping:
|
||||||
|
throw std::domain_error("PreparedPressureForceOperator encountered an invalid mapped domain.");
|
||||||
|
case Reason::non_finite_arithmetic:
|
||||||
|
default:
|
||||||
|
throw std::domain_error("PreparedPressureForceOperator produced non-finite arithmetic.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void verify_required_spaces(const mean_field::fem::FEM &f) {
|
void verify_required_spaces(const mean_field::fem::FEM &f) {
|
||||||
MFEM_VERIFY(f.mesh != nullptr, "PreparedPressureForceOperator requires a mesh.");
|
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::PressureForceStateView &state,
|
||||||
const context::pressure_force::PressureForceDependencies &dependencies
|
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;
|
PreparedPressureForceReport report;
|
||||||
|
|
||||||
report.contextReport = m_context.Prepare(state, dependencies);
|
report.contextReport = m_context.Prepare(state, dependencies);
|
||||||
|
|
||||||
if (!report.contextReport.DidAnyWork() && m_isPrepared) {
|
if (!report.contextReport.DidAnyWork() && wasPrepared) {
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,20 +429,38 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
m_isPrepared = false;
|
m_isPrepared = false;
|
||||||
|
|
||||||
if (report.contextReport.preparedStaticDependencies) {
|
if (report.contextReport.preparedStaticDependencies || !wasPrepared) {
|
||||||
PrepareStaticPlan();
|
PrepareStaticPlan();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (report.contextReport.preparedGeometryState) {
|
if (report.contextReport.preparedGeometryState || !wasPrepared) {
|
||||||
PrepareGeometry();
|
const auto globalGeometryFailure = synchronize_rejection(PrepareGeometry(), m_fem.enthalpyFes->GetComm());
|
||||||
|
if (globalGeometryFailure.has_value()) {
|
||||||
|
return std::unexpected(*globalGeometryFailure);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (report.contextReport.preparedMaterialState) {
|
std::optional<PressureForcePreparationRejection> localMaterialFailure;
|
||||||
PrepareMaterialState();
|
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();
|
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;
|
++m_residualPreparationCount;
|
||||||
|
|
||||||
@@ -455,7 +585,7 @@ namespace mean_field::operators {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedPressureForceOperator::PrepareGeometry() {
|
std::optional<PressureForcePreparationRejection> PreparedPressureForceOperator::PrepareGeometry() {
|
||||||
mfem::Vector displacementLocal;
|
mfem::Vector displacementLocal;
|
||||||
|
|
||||||
true_to_local(*m_fem.displacementFes, m_baseDisplacementTrue, displacementLocal);
|
true_to_local(*m_fem.displacementFes, m_baseDisplacementTrue, displacementLocal);
|
||||||
@@ -526,21 +656,18 @@ namespace mean_field::operators {
|
|||||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||||
);
|
);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (mappingStatus != mapping::MappingStatus::valid) {
|
||||||
mappingStatus == mapping::MappingStatus::valid,
|
return mapping_rejection(mappingStatus);
|
||||||
"Stateless mapping failed while preparing "
|
}
|
||||||
"pressure-force geometry. Element: "
|
|
||||||
<< data.elementId << ", attribute: " << transformation->Attribute
|
|
||||||
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
|
|
||||||
);
|
|
||||||
|
|
||||||
const double quadratureWeight = mappingContext.quadrature.weight;
|
const double quadratureWeight = mappingContext.quadrature.weight;
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(quadratureWeight)) {
|
||||||
std::isfinite(quadratureWeight) && quadratureWeight > 0.0,
|
return mapping_rejection(mapping::MappingStatus::non_finite_result);
|
||||||
"Prepared pressure-force geometry encountered an "
|
}
|
||||||
"invalid quadrature weight."
|
if (quadratureWeight <= 0.0) {
|
||||||
);
|
return mapping_rejection(mapping::MappingStatus::non_positive_determinant);
|
||||||
|
}
|
||||||
|
|
||||||
data.quadratureWeights(quadraturePoint) = quadratureWeight;
|
data.quadratureWeights(quadraturePoint) = quadratureWeight;
|
||||||
|
|
||||||
@@ -559,11 +686,15 @@ namespace mean_field::operators {
|
|||||||
physicalTestGradient.SetSize(referenceTestGradient.Height(), mappingContext.quadrature.J_inv.Width());
|
physicalTestGradient.SetSize(referenceTestGradient.Height(), mappingContext.quadrature.J_inv.Width());
|
||||||
|
|
||||||
mfem::Mult(referenceTestGradient, mappingContext.quadrature.J_inv, physicalTestGradient);
|
mfem::Mult(referenceTestGradient, mappingContext.quadrature.J_inv, physicalTestGradient);
|
||||||
|
if (!matrix_is_finite(physicalTestGradient)) {
|
||||||
|
return non_finite_rejection();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedPressureForceOperator::PrepareMaterialState() {
|
std::optional<PressureForcePreparationRejection> PreparedPressureForceOperator::PrepareMaterialState() {
|
||||||
mfem::Vector enthalpyLocal;
|
mfem::Vector enthalpyLocal;
|
||||||
|
|
||||||
true_to_local(*m_fem.enthalpyFes, m_baseEnthalpyTrue, 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 int dimension = m_fem.mesh->Dimension();
|
||||||
|
|
||||||
const mfem::Ordering::Type displacementOrdering = m_fem.displacementFes->GetOrdering();
|
const mfem::Ordering::Type displacementOrdering = m_fem.displacementFes->GetOrdering();
|
||||||
|
std::optional<PressureForcePreparationRejection> materialFailure;
|
||||||
|
|
||||||
for (ElementPAData &data : m_elements) {
|
for (ElementPAData &data : m_elements) {
|
||||||
enthalpyLocal.GetSubVector(data.enthalpyDofs, elementEnthalpy);
|
enthalpyLocal.GetSubVector(data.enthalpyDofs, elementEnthalpy);
|
||||||
@@ -619,6 +751,19 @@ namespace mean_field::operators {
|
|||||||
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
|
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
|
||||||
const double enthalpy = quadratureEnthalpy(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 dimensions::SpecificEnthalpyValue specificEnthalpy{enthalpy};
|
||||||
const double pressure =
|
const double pressure =
|
||||||
eos::evaluate<eos::quantity::Pressure>(m_equationOfState, specificEnthalpy).value();
|
eos::evaluate<eos::quantity::Pressure>(m_equationOfState, specificEnthalpy).value();
|
||||||
@@ -631,11 +776,12 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
const double quadratureWeight = data.quadratureWeights(quadraturePoint);
|
const double quadratureWeight = data.quadratureWeights(quadraturePoint);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
if (!std::isfinite(pressure) || !std::isfinite(pressureDerivative)) {
|
||||||
std::isfinite(pressure) && std::isfinite(pressureDerivative),
|
materialFailure = equation_of_state_rejection(eos::EvaluationErrorCode::nonfinite_result);
|
||||||
"Prepared pressure-force material state encountered "
|
data.pressure(quadraturePoint) = 0.0;
|
||||||
"a non-finite EOS value."
|
data.pressureDerivative(quadraturePoint) = 0.0;
|
||||||
);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
data.pressure(quadraturePoint) = pressure;
|
data.pressure(quadraturePoint) = pressure;
|
||||||
|
|
||||||
@@ -659,19 +805,32 @@ namespace mean_field::operators {
|
|||||||
const double weightedTestGradient =
|
const double weightedTestGradient =
|
||||||
quadratureWeight * physicalTestGradient(scalarDof, component);
|
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) {
|
for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount; ++enthalpyDof) {
|
||||||
data.enthalpyJacobian(vectorDof, enthalpyDof) -=
|
const double jacobianContribution = pressureDerivative * weightedTestGradient *
|
||||||
pressureDerivative * weightedTestGradient *
|
data.enthalpyBasis(quadraturePoint, enthalpyDof);
|
||||||
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() {
|
void PreparedPressureForceOperator::FinalizeDisplacementJacobianPreparation() {
|
||||||
@@ -700,11 +859,9 @@ namespace mean_field::operators {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
++m_displacementJacobianStatistics.preparations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparedPressureForceOperator::AssembleCachedResidual() {
|
std::optional<PressureForcePreparationRejection> PreparedPressureForceOperator::AssembleCachedResidual() {
|
||||||
mfem::Vector localResidual(m_fem.displacementFes->GetVSize());
|
mfem::Vector localResidual(m_fem.displacementFes->GetVSize());
|
||||||
|
|
||||||
localResidual = 0.0;
|
localResidual = 0.0;
|
||||||
@@ -729,6 +886,10 @@ namespace mean_field::operators {
|
|||||||
* FieldDofMap::gather does not resize its destination.
|
* FieldDofMap::gather does not resize its destination.
|
||||||
*/
|
*/
|
||||||
m_displacementMap.gather(m_fullDisplacementAction, m_cachedResidual);
|
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 {
|
void PreparedPressureForceOperator::BuildResidual(mfem::Vector &residual) const {
|
||||||
@@ -816,15 +977,12 @@ namespace mean_field::operators {
|
|||||||
mfem::Vector elementDisplacementVariation;
|
mfem::Vector elementDisplacementVariation;
|
||||||
mfem::Vector elementAction;
|
mfem::Vector elementAction;
|
||||||
|
|
||||||
mfem::DenseMatrix referenceDisplacementDShape;
|
|
||||||
mfem::DenseMatrix referenceDisplacementJacobian;
|
mfem::DenseMatrix referenceDisplacementJacobian;
|
||||||
mfem::DenseMatrix inverseElementJacobianVariation;
|
mfem::DenseMatrix inverseElementJacobianVariation;
|
||||||
mfem::DenseMatrix matrixTemporary;
|
mfem::DenseMatrix matrixTemporary;
|
||||||
mfem::DenseMatrix physicalTestGradientVariation;
|
mfem::DenseMatrix physicalTestGradientVariation;
|
||||||
|
|
||||||
const int dimension = m_fem.mesh->Dimension();
|
const int dimension = m_fem.mesh->Dimension();
|
||||||
|
|
||||||
const mfem::Ordering::Type displacementOrdering = m_fem.displacementFes->GetOrdering();
|
|
||||||
|
|
||||||
for (const ElementPAData &data : m_elements) {
|
for (const ElementPAData &data : m_elements) {
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
@@ -849,14 +1007,13 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
|
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
|
||||||
|
|
||||||
const mapping::ElementDisplacementData directionData =
|
const int quadraturePointCount = data.integrationRule->GetNPoints();
|
||||||
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacementVariation);
|
|
||||||
|
|
||||||
const int quadraturePointCount = data.integrationRule->GetNPoints();
|
const int scalarDisplacementDofCount = displacementElement.GetDof();
|
||||||
|
|
||||||
const int scalarDisplacementDofCount = displacementElement.GetDof();
|
const mfem::DenseMatrix directionDofs(
|
||||||
|
elementDisplacementVariation.GetData(), scalarDisplacementDofCount, dimension
|
||||||
const mfem::DenseMatrix &directionDofs = directionData.GetDofMatrix();
|
);
|
||||||
|
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
static_cast<int>(data.baseMappingContexts.size()) == quadraturePointCount &&
|
static_cast<int>(data.baseMappingContexts.size()) == quadraturePointCount &&
|
||||||
@@ -869,17 +1026,15 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
elementAction = 0.0;
|
elementAction = 0.0;
|
||||||
|
|
||||||
referenceDisplacementDShape.SetSize(scalarDisplacementDofCount, dimension);
|
|
||||||
referenceDisplacementJacobian.SetSize(dimension, dimension);
|
referenceDisplacementJacobian.SetSize(dimension, dimension);
|
||||||
inverseElementJacobianVariation.SetSize(dimension, dimension);
|
inverseElementJacobianVariation.SetSize(dimension, dimension);
|
||||||
matrixTemporary.SetSize(dimension, dimension);
|
matrixTemporary.SetSize(dimension, dimension);
|
||||||
physicalTestGradientVariation.SetSize(scalarDisplacementDofCount, dimension);
|
physicalTestGradientVariation.SetSize(scalarDisplacementDofCount, dimension);
|
||||||
|
|
||||||
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
|
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, referenceTestGradient, referenceDisplacementJacobian);
|
||||||
mfem::MultAtB(directionDofs, referenceDisplacementDShape, referenceDisplacementJacobian);
|
|
||||||
|
|
||||||
const mfem::DenseMatrix &inverseElementJacobian =
|
const mfem::DenseMatrix &inverseElementJacobian =
|
||||||
data.baseMappingContexts[quadraturePoint].quadrature.J_inv;
|
data.baseMappingContexts[quadraturePoint].quadrature.J_inv;
|
||||||
@@ -893,39 +1048,35 @@ namespace mean_field::operators {
|
|||||||
mfem::Mult(matrixTemporary, inverseElementJacobian, inverseElementJacobianVariation);
|
mfem::Mult(matrixTemporary, inverseElementJacobian, inverseElementJacobianVariation);
|
||||||
inverseElementJacobianVariation *= -1.0;
|
inverseElementJacobianVariation *= -1.0;
|
||||||
|
|
||||||
mfem::Mult(
|
mfem::Mult(referenceTestGradient, inverseElementJacobianVariation, physicalTestGradientVariation);
|
||||||
data.referenceTestGradients[quadraturePoint], inverseElementJacobianVariation,
|
|
||||||
physicalTestGradientVariation
|
|
||||||
);
|
|
||||||
|
|
||||||
const mfem::DenseMatrix &physicalTestGradient = data.physicalTestGradients[quadraturePoint];
|
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) {
|
||||||
for (int component = 0; component < dimension; ++component) {
|
const double *variationColumn =
|
||||||
const int vectorDof = vector_dof_index(
|
physicalTestGradientVariation.GetData() + component * scalarDisplacementDofCount;
|
||||||
displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension
|
const double *physicalColumn =
|
||||||
);
|
physicalTestGradient.GetData() + component * scalarDisplacementDofCount;
|
||||||
|
double *actionColumn = elementAction.GetData() + component * scalarDisplacementDofCount;
|
||||||
|
|
||||||
const double gradientWeightVariation = data.quadratureWeights(quadraturePoint) *
|
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
|
||||||
physicalTestGradientVariation(scalarDof, component) +
|
const double gradientWeightVariation =
|
||||||
data.quadratureWeights(quadraturePoint) *
|
quadratureWeight * variationColumn[scalarDof] +
|
||||||
logarithmicJacobianVariation *
|
quadratureWeight * logarithmicJacobianVariation * physicalColumn[scalarDof];
|
||||||
physicalTestGradient(scalarDof, component);
|
const double contribution = pressure * gradientWeightVariation;
|
||||||
|
|
||||||
const double contribution = data.pressure(quadraturePoint) * gradientWeightVariation;
|
actionColumn[scalarDof] -= contribution;
|
||||||
|
|
||||||
MFEM_VERIFY(
|
|
||||||
std::isfinite(gradientWeightVariation) && std::isfinite(contribution),
|
|
||||||
"Prepared pressure-force displacement "
|
|
||||||
"Jacobian encountered a non-finite "
|
|
||||||
"contribution."
|
|
||||||
);
|
|
||||||
|
|
||||||
elementAction(vectorDof) -= contribution;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MFEM_VERIFY(
|
||||||
|
vector_is_finite(elementAction),
|
||||||
|
"Prepared pressure-force displacement Jacobian encountered a non-finite element action."
|
||||||
|
);
|
||||||
|
|
||||||
if (data.displacementDofTransformation != nullptr) {
|
if (data.displacementDofTransformation != nullptr) {
|
||||||
data.displacementDofTransformation->TransformDual(elementAction);
|
data.displacementDofTransformation->TransformDual(elementAction);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <expected>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
module mean_field;
|
module mean_field;
|
||||||
|
|
||||||
@@ -10,11 +17,76 @@ import :operators.prepared_rotational_displacement_force;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
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) {
|
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
|
||||||
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection mapping_rejection(const mean_field::mapping::MappingStatus status) {
|
||||||
|
MFEM_VERIFY(
|
||||||
|
status != mean_field::mapping::MappingStatus::invalid_dimension,
|
||||||
|
"Prepared rotational force mapping reported an invariant dimension mismatch."
|
||||||
|
);
|
||||||
|
return {.reason = Reason::invalid_mapping, .mappingStatus = status};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection non_finite_rejection() noexcept {
|
||||||
|
return {.reason = Reason::non_finite_arithmetic};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int encode_rejection(const std::optional<Rejection> &rejection) noexcept {
|
||||||
|
if (!rejection.has_value()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (rejection->reason == Reason::non_finite_arithmetic) {
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
return static_cast<int>(rejection->mappingStatus) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rejection decode_rejection(const int encoded) {
|
||||||
|
if (encoded >= 256) {
|
||||||
|
return non_finite_rejection();
|
||||||
|
}
|
||||||
|
return mapping_rejection(static_cast<mean_field::mapping::MappingStatus>(encoded - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<
|
||||||
|
void,
|
||||||
|
Rejection>
|
||||||
|
synchronize_rejection(
|
||||||
|
const std::optional<Rejection> &localRejection,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
const int localEncoded = encode_rejection(localRejection);
|
||||||
|
int globalEncoded = 0;
|
||||||
|
if (MPI_Allreduce(&localEncoded, &globalEncoded, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("Could not synchronize prepared rotational-force candidate validity.");
|
||||||
|
}
|
||||||
|
if (globalEncoded != 0) {
|
||||||
|
return std::unexpected(decode_rejection(globalEncoded));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool vector_is_finite(const mfem::Vector &vector) noexcept {
|
||||||
|
for (int index = 0; index < vector.Size(); ++index) {
|
||||||
|
if (!std::isfinite(vector(index))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void throw_rejection(const Rejection &rejection) {
|
||||||
|
if (rejection.reason == Reason::non_finite_arithmetic) {
|
||||||
|
throw std::domain_error("Prepared rotational force produced non-finite arithmetic.");
|
||||||
|
}
|
||||||
|
throw std::domain_error("Prepared rotational force encountered an invalid mapped domain.");
|
||||||
|
}
|
||||||
|
|
||||||
void true_to_local(
|
void true_to_local(
|
||||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||||
const mfem::Vector &trueVector,
|
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.");
|
MFEM_VERIFY(m_rotation.has_value(), "Prepared rotational force has no frozen rotation state.");
|
||||||
|
|
||||||
m_elements.clear();
|
m_elements.clear();
|
||||||
@@ -197,9 +272,12 @@ namespace mean_field::operators {
|
|||||||
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
|
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
|
||||||
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
mappingData, *transformation, integrationPoint, workspace, mappingContext
|
||||||
);
|
);
|
||||||
|
if (status != mapping::MappingStatus::valid) {
|
||||||
|
return std::unexpected(mapping_rejection(status));
|
||||||
|
}
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
status == mapping::MappingStatus::valid && !mappingContext.mapping.compactified,
|
!mappingContext.mapping.compactified,
|
||||||
"Prepared rotational force encountered an invalid stellar mapping."
|
"Prepared rotational force encountered compactification on a stellar element."
|
||||||
);
|
);
|
||||||
|
|
||||||
densityElement.CalcShape(integrationPoint, densityShape);
|
densityElement.CalcShape(integrationPoint, densityShape);
|
||||||
@@ -214,8 +292,21 @@ namespace mean_field::operators {
|
|||||||
mappingContext.quadrature.J_inv(row, column);
|
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(
|
PreparedRotationalDisplacementForceReport PreparedRotationalDisplacementForceOperator::Prepare(
|
||||||
@@ -223,13 +314,29 @@ namespace mean_field::operators {
|
|||||||
const context::rotational_displacement_force::RotationalDisplacementForceDependencies &dependencies,
|
const context::rotational_displacement_force::RotationalDisplacementForceDependencies &dependencies,
|
||||||
const physics::RigidRotation &rotation
|
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 =
|
const bool rotationChanged =
|
||||||
!m_context.IsPrepared() || dependencies.rotation != m_context.GetDependencies().rotation;
|
!m_context.IsPrepared() || dependencies.rotation != m_context.GetDependencies().rotation;
|
||||||
|
|
||||||
PreparedRotationalDisplacementForceReport report;
|
PreparedRotationalDisplacementForceReport report;
|
||||||
report.contextReport = m_context.Prepare(state, dependencies);
|
report.contextReport = m_context.Prepare(state, dependencies);
|
||||||
|
|
||||||
if (!report.contextReport.DidAnyWork()) {
|
if (!report.contextReport.DidAnyWork() && wasPrepared) {
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,14 +352,28 @@ namespace mean_field::operators {
|
|||||||
"rotation state."
|
"rotation state."
|
||||||
);
|
);
|
||||||
|
|
||||||
if (report.contextReport.preparedBaseState) {
|
if (report.contextReport.preparedBaseState || !wasPrepared) {
|
||||||
kernels::apply_rotational_displacement_force_residual(
|
auto residualResult = kernels::try_apply_rotational_displacement_force_residual(
|
||||||
m_fem, m_domainMapper, *m_rotation, m_context.GetBaseDensityTrue(), m_context.GetDisplacementTrue(),
|
m_fem, m_domainMapper, *m_rotation, m_context.GetBaseDensityTrue(), m_context.GetDisplacementTrue(),
|
||||||
m_actionTrue
|
m_actionTrue
|
||||||
);
|
);
|
||||||
|
if (!residualResult.has_value()) {
|
||||||
|
return std::unexpected(residualResult.error());
|
||||||
|
}
|
||||||
m_cachedResidual.SetSize(m_context.GetDisplacementMap().reduced_size());
|
m_cachedResidual.SetSize(m_context.GetDisplacementMap().reduced_size());
|
||||||
m_context.GetDisplacementMap().gather(m_actionTrue, m_cachedResidual);
|
m_context.GetDisplacementMap().gather(m_actionTrue, m_cachedResidual);
|
||||||
PrepareElementData();
|
|
||||||
|
const auto elementResult = TryPrepareElementData();
|
||||||
|
std::optional<Rejection> localRejection = elementResult.has_value()
|
||||||
|
? std::optional<Rejection>{}
|
||||||
|
: std::optional<Rejection>{elementResult.error()};
|
||||||
|
if (!vector_is_finite(m_cachedResidual)) {
|
||||||
|
localRejection = non_finite_rejection();
|
||||||
|
}
|
||||||
|
auto synchronized = synchronize_rejection(localRejection, m_fem.mesh->GetComm());
|
||||||
|
if (!synchronized.has_value()) {
|
||||||
|
return std::unexpected(synchronized.error());
|
||||||
|
}
|
||||||
|
|
||||||
++m_residualPreparationCount;
|
++m_residualPreparationCount;
|
||||||
report.preparedResidual = true;
|
report.preparedResidual = true;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ module;
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
#include <stdexcept>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
@@ -24,6 +26,133 @@ namespace {
|
|||||||
|
|
||||||
using StellarRootForm = mean_field::utils::blocks::surface_deformed_stellar_equilibrium_form;
|
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<
|
[[nodiscard]] std::array<
|
||||||
int,
|
int,
|
||||||
StellarRootForm::value_block_count>
|
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(
|
void validate_dependency_transition(
|
||||||
const mean_field::operators::StellarEquilibriumDependencyStamp &prepared,
|
const mean_field::operators::StellarEquilibriumDependencyStamp &prepared,
|
||||||
const mean_field::operators::StellarEquilibriumDependencyStamp &requested,
|
const mean_field::operators::StellarEquilibriumDependencyStamp &requested,
|
||||||
@@ -358,11 +496,12 @@ namespace mean_field::operators {
|
|||||||
constructionData.residualSizes,
|
constructionData.residualSizes,
|
||||||
StellarEquilibriumSpecificationModel{
|
StellarEquilibriumSpecificationModel{
|
||||||
equationOfState,
|
equationOfState,
|
||||||
surface::Isobaric{
|
surface::Isobaric{dimensions::PressureValue{surfaceConstraint.descriptor().targetPressure}},
|
||||||
dimensions::PressureValue{surfaceConstraint.descriptor().targetPressure}},
|
fixedMassConstraint.specification()
|
||||||
fixedMassConstraint.specification()},
|
},
|
||||||
constructionData.pressureSurfaceRows.size()
|
constructionData.pressureSurfaceRows.size()
|
||||||
),
|
),
|
||||||
|
m_communicator(f.mesh->GetComm()),
|
||||||
m_gravityStateOffsets(constructionData.gravityStateOffsets),
|
m_gravityStateOffsets(constructionData.gravityStateOffsets),
|
||||||
m_gravityContext(
|
m_gravityContext(
|
||||||
f,
|
f,
|
||||||
@@ -452,11 +591,38 @@ namespace mean_field::operators {
|
|||||||
const mfem::Vector &state,
|
const mfem::Vector &state,
|
||||||
const StellarEquilibriumDependencies &dependencies,
|
const StellarEquilibriumDependencies &dependencies,
|
||||||
const physics::RigidRotation &rotation
|
const physics::RigidRotation &rotation
|
||||||
|
) {
|
||||||
|
auto result = TryPrepare(state, dependencies, rotation);
|
||||||
|
if (!result.has_value()) {
|
||||||
|
throwStellarEquilibriumPreparationRejection(result.error());
|
||||||
|
}
|
||||||
|
return std::move(result).value();
|
||||||
|
}
|
||||||
|
|
||||||
|
StellarEquilibriumPreparationResult<PreparedStellarEquilibriumReport>
|
||||||
|
PreparedStellarEquilibriumOperator::TryPrepare(
|
||||||
|
const mfem::Vector &state,
|
||||||
|
const StellarEquilibriumDependencies &dependencies,
|
||||||
|
const physics::RigidRotation &rotation
|
||||||
) {
|
) {
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
state.Size() == Width(), "PreparedStellarEquilibriumOperator received a state with the wrong size."
|
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;
|
const bool wasPrepared = m_isPrepared;
|
||||||
if (wasPrepared) {
|
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 reducedDensity = rootState.block(utils::blocks::density_field.mass_term);
|
||||||
const auto surfaceDeformationParameters =
|
const auto surfaceDeformationParameters =
|
||||||
@@ -505,8 +671,7 @@ namespace mean_field::operators {
|
|||||||
const auto gravityGradient = rootState.block(utils::blocks::gravity_field.gradient_term);
|
const auto gravityGradient = rootState.block(utils::blocks::gravity_field.gradient_term);
|
||||||
const auto gravityPotential = rootState.block(utils::blocks::gravity_field.poisson_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 reducedEnthalpy = rootState.block(utils::blocks::enthalpy_field.specific_term);
|
||||||
const auto bernoulli =
|
const auto bernoulli = rootState.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
|
||||||
rootState.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
|
|
||||||
|
|
||||||
const bool generatedGeometryChanged =
|
const bool generatedGeometryChanged =
|
||||||
!wasPrepared || dependencies.discretization != m_preparedDependencies.discretization ||
|
!wasPrepared || dependencies.discretization != m_preparedDependencies.discretization ||
|
||||||
@@ -515,9 +680,28 @@ namespace mean_field::operators {
|
|||||||
PreparedStellarEquilibriumReport report;
|
PreparedStellarEquilibriumReport report;
|
||||||
if (generatedGeometryChanged) {
|
if (generatedGeometryChanged) {
|
||||||
m_surfaceDeformationParameters = surfaceDeformationParameters;
|
m_surfaceDeformationParameters = surfaceDeformationParameters;
|
||||||
m_generatedGeometryReport = m_domainDeformation.buildValidatedVolumeDisplacement(
|
m_domainDeformation.buildVolumeDisplacement(m_surfaceDeformationParameters, m_generatedVolumeDisplacement);
|
||||||
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_generatedDisplacementDependency.revision;
|
||||||
++m_statistics.generatedGeometryBuilds;
|
++m_statistics.generatedGeometryBuilds;
|
||||||
report.generatedVolumeDisplacement = true;
|
report.generatedVolumeDisplacement = true;
|
||||||
@@ -530,31 +714,68 @@ namespace mean_field::operators {
|
|||||||
gravityPotential
|
gravityPotential
|
||||||
);
|
);
|
||||||
|
|
||||||
report.gravity = m_gravityOperator.Prepare(
|
auto gravityResult = m_gravityOperator.TryPrepare(
|
||||||
m_gravityState, make_gravity_revisions(dependencies, m_generatedDisplacementDependency)
|
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},
|
{.density = reducedDensity, .enthalpy = reducedEnthalpy, .displacement = m_generatedVolumeDisplacement},
|
||||||
make_barotropic_closure_dependencies(dependencies, m_generatedDisplacementDependency)
|
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,
|
{.enthalpy = reducedEnthalpy,
|
||||||
.gravityPotential = gravityPotential,
|
.gravityPotential = gravityPotential,
|
||||||
.displacement = m_generatedVolumeDisplacement,
|
.displacement = m_generatedVolumeDisplacement,
|
||||||
.bernoulliConstant = bernoulli(0)},
|
.bernoulliConstant = bernoulli(0)},
|
||||||
make_hydrostatic_dependencies(dependencies, m_generatedDisplacementDependency), rotation
|
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(
|
auto massNormalizationResult = m_massNormalizationOperator.TryPrepare(
|
||||||
{.enthalpy = reducedEnthalpy},
|
|
||||||
make_displacement_dependencies(dependencies, m_generatedDisplacementDependency), rotation
|
|
||||||
);
|
|
||||||
|
|
||||||
report.massNormalization = m_massNormalizationOperator.Prepare(
|
|
||||||
m_fixedMassConstraint, make_mass_dependencies(dependencies, m_generatedDisplacementDependency)
|
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(
|
report.surfaceConstraint = m_surfaceConstraintOperator.Prepare(
|
||||||
reducedEnthalpy, !wasPrepared || dependencies.enthalpy != m_preparedDependencies.enthalpy
|
reducedEnthalpy, !wasPrepared || dependencies.enthalpy != m_preparedDependencies.enthalpy
|
||||||
@@ -636,7 +857,7 @@ namespace mean_field::operators {
|
|||||||
direction, "PreparedStellarEquilibriumOperator received a non-finite Jacobian direction."
|
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 reducedDensityDirection = rootDirection.block(utils::blocks::density_field.mass_term);
|
||||||
const auto surfaceDeformationDirection =
|
const auto surfaceDeformationDirection =
|
||||||
@@ -803,13 +1024,9 @@ namespace mean_field::operators {
|
|||||||
const mfem::Vector &densityDirection
|
const mfem::Vector &densityDirection
|
||||||
) const {
|
) const {
|
||||||
VerifyPrepared();
|
VerifyPrepared();
|
||||||
m_massNormalizationOperator.ApplyDensityJacobianAction(
|
m_massNormalizationOperator.ApplyDensityJacobianAction(densityDirection, m_densityVolumeIntegralAction);
|
||||||
densityDirection,
|
|
||||||
m_densityVolumeIntegralAction
|
|
||||||
);
|
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
m_densityVolumeIntegralAction.Size() == 1,
|
m_densityVolumeIntegralAction.Size() == 1, "The density-volume integral must produce one global scalar."
|
||||||
"The density-volume integral must produce one global scalar."
|
|
||||||
);
|
);
|
||||||
return m_densityVolumeIntegralAction(0);
|
return m_densityVolumeIntegralAction(0);
|
||||||
}
|
}
|
||||||
@@ -819,13 +1036,10 @@ namespace mean_field::operators {
|
|||||||
) const {
|
) const {
|
||||||
VerifyPrepared();
|
VerifyPrepared();
|
||||||
m_domainDeformation.applyJacobian(
|
m_domainDeformation.applyJacobian(
|
||||||
m_surfaceDeformationParameters,
|
m_surfaceDeformationParameters, surfaceShapeDirection, m_volumeDisplacementDirection
|
||||||
surfaceShapeDirection,
|
|
||||||
m_volumeDisplacementDirection
|
|
||||||
);
|
);
|
||||||
m_massNormalizationOperator.ApplyDisplacementJacobianAction(
|
m_massNormalizationOperator.ApplyDisplacementJacobianAction(
|
||||||
m_volumeDisplacementDirection,
|
m_volumeDisplacementDirection, m_densityVolumeIntegralAction
|
||||||
m_densityVolumeIntegralAction
|
|
||||||
);
|
);
|
||||||
MFEM_VERIFY(
|
MFEM_VERIFY(
|
||||||
m_densityVolumeIntegralAction.Size() == 1,
|
m_densityVolumeIntegralAction.Size() == 1,
|
||||||
|
|||||||
@@ -182,12 +182,11 @@ namespace mean_field::seed::detail {
|
|||||||
|
|
||||||
double radialMomentIntegral = 0.0;
|
double radialMomentIntegral = 0.0;
|
||||||
for (int index = 0; index + 1 < profile.radius.Size(); ++index) {
|
for (int index = 0; index + 1 < profile.radius.Size(); ++index) {
|
||||||
const double leftRadius = profile.radius(index);
|
const double leftRadius = profile.radius(index);
|
||||||
const double rightRadius = profile.radius(index + 1);
|
const double rightRadius = profile.radius(index + 1);
|
||||||
const double leftIntegrand = profile.density(index) * std::pow(leftRadius, 4);
|
const double leftIntegrand = profile.density(index) * std::pow(leftRadius, 4);
|
||||||
const double rightIntegrand = profile.density(index + 1) * std::pow(rightRadius, 4);
|
const double rightIntegrand = profile.density(index + 1) * std::pow(rightRadius, 4);
|
||||||
radialMomentIntegral +=
|
radialMomentIntegral += 0.5 * (rightRadius - leftRadius) * (leftIntegrand + rightIntegrand);
|
||||||
0.5 * (rightRadius - leftRadius) * (leftIntegrand + rightIntegrand);
|
|
||||||
}
|
}
|
||||||
const double sphericalMomentOfInertia = (8.0 * std::numbers::pi / 3.0) * radialMomentIntegral;
|
const double sphericalMomentOfInertia = (8.0 * std::numbers::pi / 3.0) * radialMomentIntegral;
|
||||||
if (!std::isfinite(sphericalMomentOfInertia) || sphericalMomentOfInertia <= 0.0) {
|
if (!std::isfinite(sphericalMomentOfInertia) || sphericalMomentOfInertia <= 0.0) {
|
||||||
@@ -208,11 +207,11 @@ namespace mean_field::seed::detail {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
.density = densityAdapter.gather(densityField),
|
.density = densityAdapter.gather(densityField),
|
||||||
.gravityGradient = gravityFluxAdapter.gather(gravitySolution.gradPhi),
|
.gravityGradient = gravityFluxAdapter.gather(gravitySolution.gradPhi),
|
||||||
.gravityPotential = gravityPotentialAdapter.gather(gravitySolution.phi),
|
.gravityPotential = gravityPotentialAdapter.gather(gravitySolution.phi),
|
||||||
.specificEnthalpy = enthalpyAdapter.gather(enthalpyField),
|
.specificEnthalpy = enthalpyAdapter.gather(enthalpyField),
|
||||||
.bernoulliConstant = -utils::G * targetMass.value() / targetRadius,
|
.bernoulliConstant = -utils::G * targetMass.value() / targetRadius,
|
||||||
.sphericalMomentOfInertia = sphericalMomentOfInertia
|
.sphericalMomentOfInertia = sphericalMomentOfInertia
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,130 +6,124 @@ module;
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
export module mean_field:equilibrium.stellar_discretization;
|
export module mean_field:equilibrium.stellar_discretization;
|
||||||
|
|
||||||
export import :fem;
|
export import :fem;
|
||||||
export import :mapping.domain_mapper;
|
export import :mapping.domain_mapper;
|
||||||
export import :normalization.physical_riesz;
|
export import :normalization.physical_riesz;
|
||||||
|
|
||||||
|
namespace mean_field::equilibrium::detail {
|
||||||
|
struct StellarEquilibriumProblemFactory;
|
||||||
|
}
|
||||||
|
|
||||||
export namespace mean_field::equilibrium {
|
export namespace mean_field::equilibrium {
|
||||||
/*
|
/*
|
||||||
* An explicit, non-owning view of the numerical discretization used by a
|
* The complete numerical discretization used by a stellar equilibrium
|
||||||
* stellar equilibrium problem. The referenced FEM and mapper must outlive
|
* problem. Moving the FEM into stable heap storage lets the problem and a
|
||||||
* every problem and structure that uses this view.
|
* completed Structure transfer unique ownership without invalidating the
|
||||||
*
|
* references retained by prepared operators. The mapper is part of that
|
||||||
* Ownership cannot move here yet because FEM currently also contains
|
* owned FEM and therefore has the same lifetime.
|
||||||
* mutable field workspaces. Separating those workspaces is a prerequisite
|
|
||||||
* for shared discretization ownership by solved Structure objects.
|
|
||||||
*/
|
*/
|
||||||
template <normalization::NormalizationPrescription Normalization>
|
template <normalization::NormalizationPrescription Normalization> class StellarDiscretizationFor final {
|
||||||
class StellarDiscretizationFor final {
|
|
||||||
public:
|
public:
|
||||||
using NormalizationPrescriptionType = std::remove_cvref_t<Normalization>;
|
using NormalizationPrescriptionType = std::remove_cvref_t<Normalization>;
|
||||||
|
|
||||||
explicit StellarDiscretizationFor(fem::FEM &finiteElementModel)
|
explicit StellarDiscretizationFor(fem::FEM &&finiteElementModel)
|
||||||
requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized>
|
requires std::same_as<
|
||||||
|
NormalizationPrescriptionType,
|
||||||
|
normalization::Unnormalized>
|
||||||
: StellarDiscretizationFor(
|
: StellarDiscretizationFor(
|
||||||
finiteElementModel,
|
std::move(finiteElementModel),
|
||||||
RequireDomainMapper(finiteElementModel),
|
|
||||||
normalization::Unnormalized{}
|
normalization::Unnormalized{}
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
StellarDiscretizationFor(
|
explicit StellarDiscretizationFor(fem::FEM &)
|
||||||
fem::FEM &finiteElementModel,
|
requires std::same_as<
|
||||||
const mapping::DomainMapper &domainMapper
|
NormalizationPrescriptionType,
|
||||||
)
|
normalization::Unnormalized>
|
||||||
requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized>
|
= delete;
|
||||||
: StellarDiscretizationFor(
|
|
||||||
finiteElementModel,
|
|
||||||
domainMapper,
|
|
||||||
normalization::Unnormalized{}
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
StellarDiscretizationFor(
|
StellarDiscretizationFor(
|
||||||
fem::FEM &,
|
fem::FEM &&finiteElementModel,
|
||||||
mapping::DomainMapper &&
|
|
||||||
) requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized> = delete;
|
|
||||||
|
|
||||||
StellarDiscretizationFor(
|
|
||||||
fem::FEM &,
|
|
||||||
const mapping::DomainMapper &&
|
|
||||||
) requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized> = delete;
|
|
||||||
|
|
||||||
StellarDiscretizationFor(
|
|
||||||
fem::FEM &finiteElementModel,
|
|
||||||
NormalizationPrescriptionType normalizationPrescription
|
NormalizationPrescriptionType normalizationPrescription
|
||||||
)
|
)
|
||||||
: StellarDiscretizationFor(
|
: m_finiteElementModel(TakeOwnership(std::move(finiteElementModel))),
|
||||||
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_normalizationPrescription(std::move(normalizationPrescription)) {
|
m_normalizationPrescription(std::move(normalizationPrescription)) {
|
||||||
if (!finiteElementModel.okay()) {
|
|
||||||
throw std::invalid_argument("A stellar discretization requires a complete finite-element model.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StellarDiscretizationFor(
|
StellarDiscretizationFor(
|
||||||
fem::FEM &,
|
fem::FEM &,
|
||||||
mapping::DomainMapper &&,
|
|
||||||
NormalizationPrescriptionType
|
NormalizationPrescriptionType
|
||||||
) = delete;
|
) = delete;
|
||||||
|
|
||||||
StellarDiscretizationFor(
|
StellarDiscretizationFor(const StellarDiscretizationFor &) = delete;
|
||||||
fem::FEM &,
|
StellarDiscretizationFor &operator=(const StellarDiscretizationFor &) = delete;
|
||||||
const mapping::DomainMapper &&,
|
StellarDiscretizationFor(StellarDiscretizationFor &&) = default;
|
||||||
NormalizationPrescriptionType
|
StellarDiscretizationFor &operator=(StellarDiscretizationFor &&) = delete;
|
||||||
) = delete;
|
|
||||||
|
|
||||||
[[nodiscard]] fem::FEM &finiteElementModel() const noexcept {
|
[[nodiscard]] const mapping::DomainMapper &domainMapper() const & {
|
||||||
return *m_finiteElementModel;
|
const auto &finiteElementModel = RequireFiniteElementModel();
|
||||||
}
|
|
||||||
|
|
||||||
[[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) {
|
|
||||||
if (finiteElementModel.domainMapperStateless == nullptr) {
|
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;
|
return *finiteElementModel.domainMapperStateless;
|
||||||
}
|
}
|
||||||
|
|
||||||
fem::FEM *m_finiteElementModel;
|
[[nodiscard]] const mapping::DomainMapper &domainMapper() const && = delete;
|
||||||
const mapping::DomainMapper *m_domainMapper;
|
|
||||||
|
[[nodiscard]] MPI_Comm communicator() const & {
|
||||||
|
const auto &finiteElementModel = RequireFiniteElementModel();
|
||||||
|
if (finiteElementModel.mesh == nullptr) {
|
||||||
|
throw std::logic_error("The stellar discretization has no parallel mesh.");
|
||||||
|
}
|
||||||
|
return finiteElementModel.mesh->GetComm();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MPI_Comm communicator() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] const NormalizationPrescriptionType &normalizationPrescription() const & noexcept {
|
||||||
|
return m_normalizationPrescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const NormalizationPrescriptionType &normalizationPrescription() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] bool isCurrent() const noexcept {
|
||||||
|
return m_finiteElementModel != nullptr && m_finiteElementModel->okay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct detail::StellarEquilibriumProblemFactory;
|
||||||
|
|
||||||
|
[[nodiscard]] fem::FEM &MutableFiniteElementModelForAssembly() & {
|
||||||
|
return const_cast<fem::FEM &>(RequireFiniteElementModel());
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const fem::FEM &RequireFiniteElementModel() const {
|
||||||
|
if (m_finiteElementModel == nullptr) {
|
||||||
|
throw std::logic_error("A moved-from stellar discretization has no finite-element model.");
|
||||||
|
}
|
||||||
|
return *m_finiteElementModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static std::unique_ptr<fem::FEM> TakeOwnership(fem::FEM &&finiteElementModel) {
|
||||||
|
if (!finiteElementModel.okay()) {
|
||||||
|
throw std::invalid_argument("A stellar discretization requires a complete finite-element model.");
|
||||||
|
}
|
||||||
|
return std::make_unique<fem::FEM>(std::move(finiteElementModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<fem::FEM> m_finiteElementModel;
|
||||||
NormalizationPrescriptionType m_normalizationPrescription;
|
NormalizationPrescriptionType m_normalizationPrescription;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <normalization::NormalizationPrescription Normalization>
|
template <normalization::NormalizationPrescription Normalization>
|
||||||
StellarDiscretizationFor(fem::FEM &, Normalization)
|
StellarDiscretizationFor(
|
||||||
-> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
|
fem::FEM &&,
|
||||||
|
Normalization
|
||||||
template <normalization::NormalizationPrescription Normalization>
|
) -> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
|
||||||
StellarDiscretizationFor(fem::FEM &, const mapping::DomainMapper &, Normalization)
|
|
||||||
-> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
|
|
||||||
|
|
||||||
using StellarDiscretization = StellarDiscretizationFor<normalization::Unnormalized>;
|
using StellarDiscretization = StellarDiscretizationFor<normalization::Unnormalized>;
|
||||||
|
|
||||||
@@ -143,41 +137,17 @@ export namespace mean_field::equilibrium {
|
|||||||
|
|
||||||
template <normalization::NormalizationPrescription Normalization>
|
template <normalization::NormalizationPrescription Normalization>
|
||||||
[[nodiscard]] auto makeStellarDiscretization(
|
[[nodiscard]] auto makeStellarDiscretization(
|
||||||
fem::FEM &finiteElementModel,
|
fem::FEM &&finiteElementModel,
|
||||||
Normalization normalizationPrescription
|
Normalization normalizationPrescription
|
||||||
) {
|
) {
|
||||||
return StellarDiscretizationFor<std::remove_cvref_t<Normalization>>{
|
return StellarDiscretizationFor<std::remove_cvref_t<Normalization>>{
|
||||||
finiteElementModel,
|
std::move(finiteElementModel), std::move(normalizationPrescription)
|
||||||
std::move(normalizationPrescription)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <normalization::NormalizationPrescription Normalization>
|
template <normalization::NormalizationPrescription Normalization>
|
||||||
[[nodiscard]] auto makeStellarDiscretization(
|
StellarDiscretizationFor<std::remove_cvref_t<Normalization>> makeStellarDiscretization(
|
||||||
fem::FEM &finiteElementModel,
|
|
||||||
const mapping::DomainMapper &domainMapper,
|
|
||||||
Normalization normalizationPrescription
|
|
||||||
) {
|
|
||||||
return StellarDiscretizationFor<std::remove_cvref_t<Normalization>>{
|
|
||||||
finiteElementModel,
|
|
||||||
domainMapper,
|
|
||||||
std::move(normalizationPrescription)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
template <normalization::NormalizationPrescription Normalization>
|
|
||||||
StellarDiscretizationFor<std::remove_cvref_t<Normalization>>
|
|
||||||
makeStellarDiscretization(
|
|
||||||
fem::FEM &,
|
fem::FEM &,
|
||||||
mapping::DomainMapper &&,
|
|
||||||
Normalization
|
|
||||||
) = delete;
|
|
||||||
|
|
||||||
template <normalization::NormalizationPrescription Normalization>
|
|
||||||
StellarDiscretizationFor<std::remove_cvref_t<Normalization>>
|
|
||||||
makeStellarDiscretization(
|
|
||||||
fem::FEM &,
|
|
||||||
const mapping::DomainMapper &&,
|
|
||||||
Normalization
|
Normalization
|
||||||
) = delete;
|
) = delete;
|
||||||
} // namespace mean_field::equilibrium
|
} // namespace mean_field::equilibrium
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ export import :integrators.viscosity;
|
|||||||
export import :quadrature.policy;
|
export import :quadrature.policy;
|
||||||
export import :quadrature.mfem;
|
export import :quadrature.mfem;
|
||||||
export import :solver.fields;
|
export import :solver.fields;
|
||||||
|
export import :solver.linear_backend;
|
||||||
export import :solver.preconditioning_diagnostics;
|
export import :solver.preconditioning_diagnostics;
|
||||||
|
export import :solver.stellar_equilibrium_types;
|
||||||
|
export import :solver.stellar_equilibrium;
|
||||||
export import :preconditioning;
|
export import :preconditioning;
|
||||||
export import :normalization;
|
export import :normalization;
|
||||||
export import :utils.blocks;
|
export import :utils.blocks;
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ export namespace mean_field::models {
|
|||||||
|
|
||||||
class CompiledFixedAngularMomentum final {
|
class CompiledFixedAngularMomentum final {
|
||||||
public:
|
public:
|
||||||
using SpecificationType = FixedAngularMomentum;
|
using SpecificationType = FixedAngularMomentum;
|
||||||
using LayoutRequest = FixedAngularMomentumLayoutRequest;
|
using LayoutRequest = FixedAngularMomentumLayoutRequest;
|
||||||
using AngularVelocityType = typename LayoutRequest::GeneratedValueType;
|
using AngularVelocityType = typename LayoutRequest::GeneratedValueType;
|
||||||
using ResidualType = typename LayoutRequest::GeneratedResidualType;
|
using ResidualType = typename LayoutRequest::GeneratedResidualType;
|
||||||
using AngularVelocityField = field::AngularVelocity;
|
using AngularVelocityField = field::AngularVelocity;
|
||||||
@@ -63,9 +63,8 @@ export namespace mean_field::models {
|
|||||||
FixedAngularMomentum m_specification;
|
FixedAngularMomentum m_specification;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] inline CompiledFixedAngularMomentum compileConstraint(
|
[[nodiscard]] inline CompiledFixedAngularMomentum
|
||||||
const FixedAngularMomentum specification
|
compileConstraint(const FixedAngularMomentum specification) noexcept {
|
||||||
) noexcept {
|
|
||||||
return CompiledFixedAngularMomentum{specification};
|
return CompiledFixedAngularMomentum{specification};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,11 +57,9 @@ export namespace mean_field::models {
|
|||||||
static constexpr std::size_t size = sizeof...(Types);
|
static constexpr std::size_t size = sizeof...(Types);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... ValueBlocks>
|
template <typename... ValueBlocks> using DependsOn = ModelTypeList<ValueBlocks...>;
|
||||||
using DependsOn = ModelTypeList<ValueBlocks...>;
|
|
||||||
|
|
||||||
template <typename... ResidualBlocks>
|
template <typename... ResidualBlocks> using Affects = ModelTypeList<ResidualBlocks...>;
|
||||||
using Affects = ModelTypeList<ResidualBlocks...>;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Physics vocabulary for declaring how a stellar specification couples to
|
* 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
|
* the physics-facing spelling for coupled global constraints: an
|
||||||
* extension names the constraint it reads, never its solver block.
|
* extension names the constraint it reads, never its solver block.
|
||||||
*/
|
*/
|
||||||
template <typename Specification>
|
template <typename Specification> struct GeneratedCoordinateOf final {
|
||||||
struct GeneratedCoordinateOf final {
|
|
||||||
using SpecificationType = Specification;
|
using SpecificationType = Specification;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,8 +105,7 @@ export namespace mean_field::models {
|
|||||||
struct OwnConstraint final { };
|
struct OwnConstraint final { };
|
||||||
|
|
||||||
/* The scalar constraint equation owned by another specification. */
|
/* The scalar constraint equation owned by another specification. */
|
||||||
template <typename Specification>
|
template <typename Specification> struct ConstraintOf final {
|
||||||
struct ConstraintOf final {
|
|
||||||
using SpecificationType = Specification;
|
using SpecificationType = Specification;
|
||||||
};
|
};
|
||||||
} // namespace equation
|
} // namespace equation
|
||||||
@@ -119,8 +115,7 @@ export namespace mean_field::models {
|
|||||||
* Runtime providers consume this vocabulary without learning backend
|
* Runtime providers consume this vocabulary without learning backend
|
||||||
* row and column block types.
|
* row and column block types.
|
||||||
*/
|
*/
|
||||||
template <typename Equation, typename State>
|
template <typename Equation, typename State> struct Derivative final {
|
||||||
struct Derivative final {
|
|
||||||
using EquationType = Equation;
|
using EquationType = Equation;
|
||||||
using StateType = State;
|
using StateType = State;
|
||||||
};
|
};
|
||||||
@@ -220,11 +215,7 @@ export namespace mean_field::models {
|
|||||||
concept PhysicalScaleRepresentedQuantity =
|
concept PhysicalScaleRepresentedQuantity =
|
||||||
dimensions::PhysicalQuantityType<Quantity> &&
|
dimensions::PhysicalQuantityType<Quantity> &&
|
||||||
physicalScaleForQuantity<Quantity> != PhysicalScaleLaw::unavailable &&
|
physicalScaleForQuantity<Quantity> != PhysicalScaleLaw::unavailable &&
|
||||||
requires {
|
requires { typename std::bool_constant<!static_cast<std::string_view>(Quantity::identifier).empty()>; };
|
||||||
typename std::bool_constant<
|
|
||||||
!static_cast<std::string_view>(
|
|
||||||
Quantity::identifier).empty()>;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename Candidate> [[nodiscard]] consteval bool declaredCoordinateNormalizationIsAvailable() {
|
template <typename Candidate> [[nodiscard]] consteval bool declaredCoordinateNormalizationIsAvailable() {
|
||||||
@@ -270,8 +261,7 @@ export namespace mean_field::models {
|
|||||||
case SpecificationRole::boundary_condition:
|
case SpecificationRole::boundary_condition:
|
||||||
return kind == GeneratedStateKind::none;
|
return kind == GeneratedStateKind::none;
|
||||||
case SpecificationRole::invariant:
|
case SpecificationRole::invariant:
|
||||||
return kind == GeneratedStateKind::multiplier ||
|
return kind == GeneratedStateKind::multiplier || kind == GeneratedStateKind::physical_coordinate;
|
||||||
kind == GeneratedStateKind::physical_coordinate;
|
|
||||||
case SpecificationRole::phase_condition:
|
case SpecificationRole::phase_condition:
|
||||||
case SpecificationRole::gauge_choice:
|
case SpecificationRole::gauge_choice:
|
||||||
return kind == GeneratedStateKind::solver_border;
|
return kind == GeneratedStateKind::solver_border;
|
||||||
@@ -304,11 +294,12 @@ export namespace mean_field::models {
|
|||||||
using UnavailableCoordinateNormalization =
|
using UnavailableCoordinateNormalization =
|
||||||
CoordinateNormalization<RieszTopology::unavailable, PhysicalScaleLaw::unavailable>;
|
CoordinateNormalization<RieszTopology::unavailable, PhysicalScaleLaw::unavailable>;
|
||||||
|
|
||||||
template <typename ValueNormalization = UnavailableCoordinateNormalization,
|
template <
|
||||||
typename ResidualNormalization = UnavailableCoordinateNormalization>
|
typename ValueNormalization = UnavailableCoordinateNormalization,
|
||||||
|
typename ResidualNormalization = UnavailableCoordinateNormalization>
|
||||||
struct GeneratedNormalization final {
|
struct GeneratedNormalization final {
|
||||||
using Value = ValueNormalization;
|
using Value = ValueNormalization;
|
||||||
using Residual = ResidualNormalization;
|
using Residual = ResidualNormalization;
|
||||||
|
|
||||||
static constexpr bool available = detail::declaredCoordinateNormalizationIsAvailable<Value>() &&
|
static constexpr bool available = detail::declaredCoordinateNormalizationIsAvailable<Value>() &&
|
||||||
detail::declaredCoordinateNormalizationIsAvailable<Residual>();
|
detail::declaredCoordinateNormalizationIsAvailable<Residual>();
|
||||||
@@ -321,8 +312,13 @@ export namespace mean_field::models {
|
|||||||
CoordinateNormalization<RieszTopology::global_scalar, ValueScale>,
|
CoordinateNormalization<RieszTopology::global_scalar, ValueScale>,
|
||||||
CoordinateNormalization<RieszTopology::global_scalar, ResidualScale>>;
|
CoordinateNormalization<RieszTopology::global_scalar, ResidualScale>>;
|
||||||
|
|
||||||
template <FixedString ValueStableId = "", FixedString ValueSymbol = "", FixedString ResidualStableId = "",
|
template <
|
||||||
FixedString ResidualSymbol = "", FixedString TargetUnits = "", FixedString ResidualUnits = "">
|
FixedString ValueStableId = "",
|
||||||
|
FixedString ValueSymbol = "",
|
||||||
|
FixedString ResidualStableId = "",
|
||||||
|
FixedString ResidualSymbol = "",
|
||||||
|
FixedString TargetUnits = "",
|
||||||
|
FixedString ResidualUnits = "">
|
||||||
struct GeneratedManifest final {
|
struct GeneratedManifest final {
|
||||||
private:
|
private:
|
||||||
inline static constexpr auto valueStableIdStorage = ValueStableId;
|
inline static constexpr auto valueStableIdStorage = ValueStableId;
|
||||||
@@ -366,21 +362,19 @@ export namespace mean_field::models {
|
|||||||
FixedString ResidualStableId,
|
FixedString ResidualStableId,
|
||||||
FixedString ResidualSymbol>
|
FixedString ResidualSymbol>
|
||||||
struct DimensionalScalarConstraint final {
|
struct DimensionalScalarConstraint final {
|
||||||
using TargetQuantity = TargetQuantityT;
|
using TargetQuantity = TargetQuantityT;
|
||||||
using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT;
|
using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT;
|
||||||
using ConstraintResidualQuantity = ConstraintResidualQuantityT;
|
using ConstraintResidualQuantity = ConstraintResidualQuantityT;
|
||||||
using TargetValue = dimensions::QuantityValue<TargetQuantity>;
|
using TargetValue = dimensions::QuantityValue<TargetQuantity>;
|
||||||
static constexpr PhysicalScaleLaw targetScale =
|
static constexpr PhysicalScaleLaw targetScale = physicalScaleForQuantity<TargetQuantity>;
|
||||||
physicalScaleForQuantity<TargetQuantity>;
|
|
||||||
|
|
||||||
struct Normalization final {
|
struct Normalization final {
|
||||||
using TargetQuantity = TargetQuantityT;
|
using TargetQuantity = TargetQuantityT;
|
||||||
using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT;
|
using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT;
|
||||||
using ConstraintResidualQuantity = ConstraintResidualQuantityT;
|
using ConstraintResidualQuantity = ConstraintResidualQuantityT;
|
||||||
using TargetValue = dimensions::QuantityValue<TargetQuantity>;
|
using TargetValue = dimensions::QuantityValue<TargetQuantity>;
|
||||||
static constexpr PhysicalScaleLaw targetScale =
|
static constexpr PhysicalScaleLaw targetScale = physicalScaleForQuantity<TargetQuantity>;
|
||||||
physicalScaleForQuantity<TargetQuantity>;
|
using Value = CoordinateNormalization<
|
||||||
using Value = CoordinateNormalization<
|
|
||||||
RieszTopology::global_scalar,
|
RieszTopology::global_scalar,
|
||||||
physicalScaleForQuantity<GeneratedCoordinateQuantity>>;
|
physicalScaleForQuantity<GeneratedCoordinateQuantity>>;
|
||||||
using Residual = CoordinateNormalization<
|
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 residualSymbol = residualSymbolStorage.view();
|
||||||
static constexpr std::string_view targetUnits = TargetQuantity::identifier;
|
static constexpr std::string_view targetUnits = TargetQuantity::identifier;
|
||||||
static constexpr std::string_view residualUnits = ConstraintResidualQuantity::identifier;
|
static constexpr std::string_view residualUnits = ConstraintResidualQuantity::identifier;
|
||||||
static constexpr bool available =
|
static constexpr bool available = !valueStableId.empty() && !valueSymbol.empty() &&
|
||||||
!valueStableId.empty() && !valueSymbol.empty() &&
|
!residualStableId.empty() && !residualSymbol.empty() &&
|
||||||
!residualStableId.empty() && !residualSymbol.empty() &&
|
!targetUnits.empty() && !residualUnits.empty();
|
||||||
!targetUnits.empty() && !residualUnits.empty();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr bool dimensionallyTyped = true;
|
static constexpr bool dimensionallyTyped = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Specification, FixedString StableName, SpecificationRole Role,
|
template <
|
||||||
GeneratedStateKind StateKind = GeneratedStateKind::none, typename DependsOnBlocks = ModelTypeList<>,
|
typename Specification,
|
||||||
typename AffectedResidualBlocks = ModelTypeList<>,
|
FixedString StableName,
|
||||||
typename NormalizationDefinition = UnavailableGeneratedNormalization,
|
SpecificationRole Role,
|
||||||
typename ManifestDefinition = UnavailableGeneratedManifest>
|
GeneratedStateKind StateKind = GeneratedStateKind::none,
|
||||||
|
typename DependsOnBlocks = ModelTypeList<>,
|
||||||
|
typename AffectedResidualBlocks = ModelTypeList<>,
|
||||||
|
typename NormalizationDefinition = UnavailableGeneratedNormalization,
|
||||||
|
typename ManifestDefinition = UnavailableGeneratedManifest>
|
||||||
struct ModelDefinition final {
|
struct ModelDefinition final {
|
||||||
using SpecificationType = Specification;
|
using SpecificationType = Specification;
|
||||||
using DependsOn = DependsOnBlocks;
|
using DependsOn = DependsOnBlocks;
|
||||||
@@ -447,26 +444,56 @@ export namespace mean_field::models {
|
|||||||
template <typename Specification, FixedString Name>
|
template <typename Specification, FixedString Name>
|
||||||
using BoundaryCondition = ModelDefinition<Specification, Name, SpecificationRole::boundary_condition>;
|
using BoundaryCondition = ModelDefinition<Specification, Name, SpecificationRole::boundary_condition>;
|
||||||
|
|
||||||
template <typename Specification, FixedString Name, typename DependsOn = ModelTypeList<>,
|
template <
|
||||||
typename Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization,
|
typename Specification,
|
||||||
typename Manifest = UnavailableGeneratedManifest>
|
FixedString Name,
|
||||||
using FixedIntegralWithMultiplier =
|
typename DependsOn = ModelTypeList<>,
|
||||||
ModelDefinition<Specification, Name, SpecificationRole::invariant, GeneratedStateKind::multiplier, DependsOn,
|
typename Affects = ModelTypeList<>,
|
||||||
Affects, Normalization, Manifest>;
|
typename Normalization = UnavailableGeneratedNormalization,
|
||||||
|
typename Manifest = UnavailableGeneratedManifest>
|
||||||
|
using FixedIntegralWithMultiplier = ModelDefinition<
|
||||||
|
Specification,
|
||||||
|
Name,
|
||||||
|
SpecificationRole::invariant,
|
||||||
|
GeneratedStateKind::multiplier,
|
||||||
|
DependsOn,
|
||||||
|
Affects,
|
||||||
|
Normalization,
|
||||||
|
Manifest>;
|
||||||
|
|
||||||
template <typename Specification, FixedString Name, typename DependsOn = ModelTypeList<>,
|
template <
|
||||||
typename Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization,
|
typename Specification,
|
||||||
typename Manifest = UnavailableGeneratedManifest>
|
FixedString Name,
|
||||||
using FixedIntegralWithPhysicalCoordinate =
|
typename DependsOn = ModelTypeList<>,
|
||||||
ModelDefinition<Specification, Name, SpecificationRole::invariant, GeneratedStateKind::physical_coordinate,
|
typename Affects = ModelTypeList<>,
|
||||||
DependsOn, Affects, Normalization, Manifest>;
|
typename Normalization = UnavailableGeneratedNormalization,
|
||||||
|
typename Manifest = UnavailableGeneratedManifest>
|
||||||
|
using FixedIntegralWithPhysicalCoordinate = ModelDefinition<
|
||||||
|
Specification,
|
||||||
|
Name,
|
||||||
|
SpecificationRole::invariant,
|
||||||
|
GeneratedStateKind::physical_coordinate,
|
||||||
|
DependsOn,
|
||||||
|
Affects,
|
||||||
|
Normalization,
|
||||||
|
Manifest>;
|
||||||
|
|
||||||
template <typename Specification, FixedString Name, typename DependsOn = ModelTypeList<>,
|
template <
|
||||||
typename Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization,
|
typename Specification,
|
||||||
typename Manifest = UnavailableGeneratedManifest>
|
FixedString Name,
|
||||||
using PhaseCondition =
|
typename DependsOn = ModelTypeList<>,
|
||||||
ModelDefinition<Specification, Name, SpecificationRole::phase_condition, GeneratedStateKind::solver_border,
|
typename Affects = ModelTypeList<>,
|
||||||
DependsOn, Affects, Normalization, Manifest>;
|
typename Normalization = UnavailableGeneratedNormalization,
|
||||||
|
typename Manifest = UnavailableGeneratedManifest>
|
||||||
|
using PhaseCondition = ModelDefinition<
|
||||||
|
Specification,
|
||||||
|
Name,
|
||||||
|
SpecificationRole::phase_condition,
|
||||||
|
GeneratedStateKind::solver_border,
|
||||||
|
DependsOn,
|
||||||
|
Affects,
|
||||||
|
Normalization,
|
||||||
|
Manifest>;
|
||||||
|
|
||||||
struct SpecificationKey final {
|
struct SpecificationKey final {
|
||||||
SpecificationRole role;
|
SpecificationRole role;
|
||||||
@@ -510,35 +537,42 @@ export namespace mean_field::models {
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename Candidate> struct IsModelTypeList : std::false_type {};
|
template <typename Candidate> struct IsModelTypeList : std::false_type { };
|
||||||
|
|
||||||
template <typename... Types> struct IsModelTypeList<ModelTypeList<Types...>> : std::true_type {};
|
template <typename... Types> struct IsModelTypeList<ModelTypeList<Types...>> : std::true_type { };
|
||||||
|
|
||||||
template <typename Candidate> struct IsModelDefinition : std::false_type {};
|
template <typename Candidate> struct IsModelDefinition : std::false_type { };
|
||||||
|
|
||||||
template <typename Specification, FixedString StableName, SpecificationRole Role, GeneratedStateKind StateKind,
|
template <
|
||||||
typename DependsOn, typename Affects, typename Normalization, typename Manifest>
|
typename Specification,
|
||||||
|
FixedString StableName,
|
||||||
|
SpecificationRole Role,
|
||||||
|
GeneratedStateKind StateKind,
|
||||||
|
typename DependsOn,
|
||||||
|
typename Affects,
|
||||||
|
typename Normalization,
|
||||||
|
typename Manifest>
|
||||||
struct IsModelDefinition<
|
struct IsModelDefinition<
|
||||||
ModelDefinition<Specification, StableName, Role, StateKind, DependsOn, Affects, Normalization, Manifest>>
|
ModelDefinition<Specification, StableName, Role, StateKind, DependsOn, Affects, Normalization, Manifest>>
|
||||||
: std::bool_constant<(StableName.view().size() > 0) &&
|
: std::bool_constant<
|
||||||
CompatibleSpecificationRoleAndGeneratedState<Role, StateKind> &&
|
(StableName.view().size() > 0) && CompatibleSpecificationRoleAndGeneratedState<Role, StateKind> &&
|
||||||
IsModelTypeList<DependsOn>::value &&
|
IsModelTypeList<DependsOn>::value && IsModelTypeList<Affects>::value> { };
|
||||||
IsModelTypeList<Affects>::value>{};
|
|
||||||
|
|
||||||
template <typename Definition, typename Candidate, bool = IsModelDefinition<Definition>::value>
|
template <typename Definition, typename Candidate, bool = IsModelDefinition<Definition>::value>
|
||||||
struct DefinitionDescribesCandidate : std::false_type {};
|
struct DefinitionDescribesCandidate : std::false_type { };
|
||||||
|
|
||||||
template <typename Definition, typename Candidate>
|
template <typename Definition, typename Candidate>
|
||||||
struct DefinitionDescribesCandidate<Definition, Candidate, true>
|
struct DefinitionDescribesCandidate<Definition, Candidate, true>
|
||||||
: std::bool_constant<std::same_as<typename Definition::SpecificationType, Candidate>> {};
|
: std::bool_constant<std::same_as<typename Definition::SpecificationType, Candidate>> { };
|
||||||
|
|
||||||
template <typename Candidate, typename = void> struct SpecificationDefinitionFor {
|
template <typename Candidate, typename = void> struct SpecificationDefinitionFor {
|
||||||
static constexpr bool available = false;
|
static constexpr bool available = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
struct SpecificationDefinitionFor<Candidate,
|
struct SpecificationDefinitionFor<
|
||||||
std::void_t<typename std::remove_cvref_t<Candidate>::ModelDefinition>> {
|
Candidate,
|
||||||
|
std::void_t<typename std::remove_cvref_t<Candidate>::ModelDefinition>> {
|
||||||
using Type = typename std::remove_cvref_t<Candidate>::ModelDefinition;
|
using Type = typename std::remove_cvref_t<Candidate>::ModelDefinition;
|
||||||
static constexpr bool available = DefinitionDescribesCandidate<Type, std::remove_cvref_t<Candidate>>::value;
|
static constexpr bool available = DefinitionDescribesCandidate<Type, std::remove_cvref_t<Candidate>>::value;
|
||||||
};
|
};
|
||||||
@@ -570,7 +604,7 @@ export namespace mean_field::models {
|
|||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
requires detail::SpecificationDefinitionFor<std::remove_cvref_t<Candidate>>::available
|
requires detail::SpecificationDefinitionFor<std::remove_cvref_t<Candidate>>::available
|
||||||
struct SpecificationTraits<Candidate> {
|
struct SpecificationTraits<Candidate> {
|
||||||
using Definition = ModelDefinitionForT<Candidate>;
|
using Definition = ModelDefinitionForT<Candidate>;
|
||||||
|
|
||||||
static constexpr std::string_view name = Definition::name;
|
static constexpr std::string_view name = Definition::name;
|
||||||
static constexpr SpecificationRole role = Definition::role;
|
static constexpr SpecificationRole role = Definition::role;
|
||||||
@@ -604,7 +638,8 @@ export namespace mean_field::models {
|
|||||||
"R_M">;
|
"R_M">;
|
||||||
using TargetValue = typename ScalarDescription::TargetValue;
|
using TargetValue = typename ScalarDescription::TargetValue;
|
||||||
using ModelDefinition = FixedIntegralWithMultiplier<
|
using ModelDefinition = FixedIntegralWithMultiplier<
|
||||||
FixedTotalMass, "FixedTotalMass",
|
FixedTotalMass,
|
||||||
|
"FixedTotalMass",
|
||||||
DependsOn<stellar::state::Density, stellar::state::SurfaceShape>,
|
DependsOn<stellar::state::Density, stellar::state::SurfaceShape>,
|
||||||
Affects<stellar::equation::HydrostaticBalance>,
|
Affects<stellar::equation::HydrostaticBalance>,
|
||||||
typename ScalarDescription::Normalization,
|
typename ScalarDescription::Normalization,
|
||||||
@@ -615,9 +650,13 @@ export namespace mean_field::models {
|
|||||||
|
|
||||||
explicit FixedTotalMass(const TargetValue targetMass) : m_targetMass(targetMass) {
|
explicit FixedTotalMass(const TargetValue targetMass) : m_targetMass(targetMass) {
|
||||||
if (!std::isfinite(targetMass.value()) || targetMass.value() <= 0.0) {
|
if (!std::isfinite(targetMass.value()) || targetMass.value() <= 0.0) {
|
||||||
throw std::invalid_argument(std::format("The fixed total mass must be finite and positive. "
|
throw std::invalid_argument(
|
||||||
"Instead M = {} was provided.",
|
std::format(
|
||||||
targetMass.value()));
|
"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">;
|
"R_J">;
|
||||||
using TargetValue = typename ScalarDescription::TargetValue;
|
using TargetValue = typename ScalarDescription::TargetValue;
|
||||||
using ModelDefinition = FixedIntegralWithPhysicalCoordinate<
|
using ModelDefinition = FixedIntegralWithPhysicalCoordinate<
|
||||||
FixedAngularMomentum, "FixedAngularMomentum",
|
FixedAngularMomentum,
|
||||||
DependsOn<
|
"FixedAngularMomentum",
|
||||||
stellar::state::Density,
|
DependsOn<stellar::state::Density, stellar::state::SurfaceShape, stellar::state::OwnGeneratedCoordinate>,
|
||||||
stellar::state::SurfaceShape,
|
|
||||||
stellar::state::OwnGeneratedCoordinate>,
|
|
||||||
Affects<stellar::equation::SurfaceShapeBalance, stellar::equation::HydrostaticBalance>,
|
Affects<stellar::equation::SurfaceShapeBalance, stellar::equation::HydrostaticBalance>,
|
||||||
typename ScalarDescription::Normalization,
|
typename ScalarDescription::Normalization,
|
||||||
typename ScalarDescription::Manifest>;
|
typename ScalarDescription::Manifest>;
|
||||||
|
|
||||||
explicit FixedAngularMomentum(const Parameters parameters)
|
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) {
|
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 "
|
throw std::invalid_argument(
|
||||||
"nonnegative. Instead J = {} was "
|
std::format(
|
||||||
"provided.",
|
"The fixed total angular momentum must be finite and "
|
||||||
m_targetAngularMomentum.value()));
|
"nonnegative. Instead J = {} was "
|
||||||
|
"provided.",
|
||||||
|
m_targetAngularMomentum.value()
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double axisNormSquared = 0.0;
|
double axisNormSquared = 0.0;
|
||||||
for (std::size_t component = 0; component < m_axis.size(); ++component) {
|
for (std::size_t component = 0; component < m_axis.size(); ++component) {
|
||||||
if (!std::isfinite(m_axis[component]) || !std::isfinite(m_center[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 "
|
throw std::invalid_argument(
|
||||||
"only finite values.");
|
"A fixed-angular-momentum rotation axis and center must contain "
|
||||||
|
"only finite values."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
axisNormSquared += m_axis[component] * m_axis[component];
|
axisNormSquared += m_axis[component] * m_axis[component];
|
||||||
}
|
}
|
||||||
@@ -698,11 +743,17 @@ export namespace mean_field::models {
|
|||||||
return m_targetAngularMomentum;
|
return m_targetAngularMomentum;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] const std::array<double, 3> &axis() const noexcept {
|
[[nodiscard]] const std::array<
|
||||||
|
double,
|
||||||
|
3> &
|
||||||
|
axis() const noexcept {
|
||||||
return m_axis;
|
return m_axis;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] const std::array<double, 3> ¢er() const noexcept {
|
[[nodiscard]] const std::array<
|
||||||
|
double,
|
||||||
|
3> &
|
||||||
|
center() const noexcept {
|
||||||
return m_center;
|
return m_center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,7 +779,8 @@ export namespace mean_field::models {
|
|||||||
"R_rho_c">;
|
"R_rho_c">;
|
||||||
using TargetValue = typename ScalarDescription::TargetValue;
|
using TargetValue = typename ScalarDescription::TargetValue;
|
||||||
using ModelDefinition = PhaseCondition<
|
using ModelDefinition = PhaseCondition<
|
||||||
FixedCentralDensity, "FixedCentralDensity",
|
FixedCentralDensity,
|
||||||
|
"FixedCentralDensity",
|
||||||
DependsOn<stellar::state::SpecificEnthalpy>,
|
DependsOn<stellar::state::SpecificEnthalpy>,
|
||||||
Affects<stellar::equation::HydrostaticBalance>,
|
Affects<stellar::equation::HydrostaticBalance>,
|
||||||
typename ScalarDescription::Normalization,
|
typename ScalarDescription::Normalization,
|
||||||
@@ -739,9 +791,13 @@ export namespace mean_field::models {
|
|||||||
|
|
||||||
explicit FixedCentralDensity(const TargetValue targetDensity) : m_targetDensity(targetDensity) {
|
explicit FixedCentralDensity(const TargetValue targetDensity) : m_targetDensity(targetDensity) {
|
||||||
if (!std::isfinite(targetDensity.value()) || targetDensity.value() <= 0.0) {
|
if (!std::isfinite(targetDensity.value()) || targetDensity.value() <= 0.0) {
|
||||||
throw std::invalid_argument(std::format("The fixed central density must be finite and positive. "
|
throw std::invalid_argument(
|
||||||
"Instead rho_c = {} was provided.",
|
std::format(
|
||||||
targetDensity.value()));
|
"The fixed central density must be finite and positive. "
|
||||||
|
"Instead rho_c = {} was provided.",
|
||||||
|
targetDensity.value()
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,19 +817,19 @@ export namespace mean_field::models {
|
|||||||
|
|
||||||
template <typename Query, typename... Types>
|
template <typename Query, typename... Types>
|
||||||
struct ModelTypeListContains<Query, ModelTypeList<Types...>>
|
struct ModelTypeListContains<Query, ModelTypeList<Types...>>
|
||||||
: std::bool_constant<(std::same_as<Query, Types> || ...)> {};
|
: std::bool_constant<(std::same_as<Query, Types> || ...)> { };
|
||||||
|
|
||||||
template <typename Query, typename List>
|
template <typename Query, typename List>
|
||||||
inline constexpr bool modelTypeListContains = ModelTypeListContains<Query, List>::value;
|
inline constexpr bool modelTypeListContains = ModelTypeListContains<Query, List>::value;
|
||||||
|
|
||||||
template <typename Specification> struct ResidualFor final {
|
template <typename Specification> struct ResidualFor final {
|
||||||
using SpecificationType = Specification;
|
using SpecificationType = Specification;
|
||||||
|
|
||||||
static constexpr std::size_t scalarArity = 1;
|
static constexpr std::size_t scalarArity = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Specification> struct MultiplierFor final {
|
template <typename Specification> struct MultiplierFor final {
|
||||||
using SpecificationType = Specification;
|
using SpecificationType = Specification;
|
||||||
|
|
||||||
static constexpr std::size_t scalarArity = 1;
|
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
|
// A generated state variable that participates directly in the physical
|
||||||
// equations, rather than serving only as a Lagrange multiplier or border.
|
// equations, rather than serving only as a Lagrange multiplier or border.
|
||||||
template <typename Specification> struct PhysicalCoordinateFor final {
|
template <typename Specification> struct PhysicalCoordinateFor final {
|
||||||
using SpecificationType = Specification;
|
using SpecificationType = Specification;
|
||||||
|
|
||||||
static constexpr std::size_t scalarArity = 1;
|
static constexpr std::size_t scalarArity = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Specification> struct BorderFor final {
|
template <typename Specification> struct BorderFor final {
|
||||||
using SpecificationType = Specification;
|
using SpecificationType = Specification;
|
||||||
|
|
||||||
static constexpr std::size_t scalarArity = 1;
|
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 generatedValueArity = Definition::generatedValueArity;
|
||||||
static constexpr std::size_t generatedResidualArity = Definition::generatedResidualArity;
|
static constexpr std::size_t generatedResidualArity = Definition::generatedResidualArity;
|
||||||
static constexpr bool isDefined = Definition::structurallyAvailable;
|
static constexpr bool isDefined = Definition::structurallyAvailable;
|
||||||
static constexpr bool hasDeclarativeDefinition = Definition::structurallyAvailable;
|
static constexpr bool hasDeclarativeDefinition = Definition::structurallyAvailable;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <ModelSpecification Specification>
|
template <ModelSpecification Specification>
|
||||||
[[nodiscard]] consteval bool generatedScalarDimensionsAreCoherent() {
|
[[nodiscard]] consteval bool generatedScalarDimensionsAreCoherent() {
|
||||||
using Contribution = SpecificationContribution<Specification>;
|
using Contribution = SpecificationContribution<Specification>;
|
||||||
using Normalization = typename Contribution::Normalization;
|
using Normalization = typename Contribution::Normalization;
|
||||||
using Manifest = typename Contribution::Manifest;
|
using Manifest = typename Contribution::Manifest;
|
||||||
|
|
||||||
if constexpr (Contribution::generatedValueArity == 0) {
|
if constexpr (Contribution::generatedValueArity == 0) {
|
||||||
return true;
|
return true;
|
||||||
@@ -899,9 +955,7 @@ export namespace mean_field::models {
|
|||||||
typename Normalization::GeneratedCoordinateQuantity;
|
typename Normalization::GeneratedCoordinateQuantity;
|
||||||
typename Normalization::ConstraintResidualQuantity;
|
typename Normalization::ConstraintResidualQuantity;
|
||||||
typename Normalization::TargetValue;
|
typename Normalization::TargetValue;
|
||||||
{
|
{ Normalization::targetScale } -> std::convertible_to<PhysicalScaleLaw>;
|
||||||
Normalization::targetScale
|
|
||||||
} -> std::convertible_to<PhysicalScaleLaw>;
|
|
||||||
};
|
};
|
||||||
constexpr bool manifestIsTyped = requires {
|
constexpr bool manifestIsTyped = requires {
|
||||||
typename Manifest::TargetQuantity;
|
typename Manifest::TargetQuantity;
|
||||||
@@ -918,72 +972,53 @@ export namespace mean_field::models {
|
|||||||
} else if constexpr (!normalizationIsTyped || !manifestIsTyped) {
|
} else if constexpr (!normalizationIsTyped || !manifestIsTyped) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
using TargetQuantity = typename Normalization::TargetQuantity;
|
using TargetQuantity = typename Normalization::TargetQuantity;
|
||||||
using GeneratedCoordinateQuantity =
|
using GeneratedCoordinateQuantity = typename Normalization::GeneratedCoordinateQuantity;
|
||||||
typename Normalization::GeneratedCoordinateQuantity;
|
using ConstraintResidualQuantity = typename Normalization::ConstraintResidualQuantity;
|
||||||
using ConstraintResidualQuantity =
|
using ValueNormalization = typename Normalization::Value;
|
||||||
typename Normalization::ConstraintResidualQuantity;
|
using ResidualNormalization = typename Normalization::Residual;
|
||||||
using ValueNormalization = typename Normalization::Value;
|
|
||||||
using ResidualNormalization = typename Normalization::Residual;
|
|
||||||
|
|
||||||
if constexpr (
|
if constexpr (
|
||||||
!PhysicalScaleRepresentedQuantity<TargetQuantity> ||
|
!PhysicalScaleRepresentedQuantity<TargetQuantity> ||
|
||||||
!PhysicalScaleRepresentedQuantity<GeneratedCoordinateQuantity> ||
|
!PhysicalScaleRepresentedQuantity<GeneratedCoordinateQuantity> ||
|
||||||
!PhysicalScaleRepresentedQuantity<ConstraintResidualQuantity>) {
|
!PhysicalScaleRepresentedQuantity<ConstraintResidualQuantity>
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
} else if constexpr (!requires {
|
} else if constexpr (
|
||||||
typename std::integral_constant<
|
!requires {
|
||||||
PhysicalScaleLaw,
|
typename std::integral_constant<
|
||||||
static_cast<PhysicalScaleLaw>(
|
PhysicalScaleLaw, static_cast<PhysicalScaleLaw>(Normalization::targetScale)>;
|
||||||
Normalization::targetScale)>;
|
typename std::integral_constant<
|
||||||
typename std::integral_constant<
|
PhysicalScaleLaw, static_cast<PhysicalScaleLaw>(ValueNormalization::scale)>;
|
||||||
PhysicalScaleLaw,
|
typename std::integral_constant<
|
||||||
static_cast<PhysicalScaleLaw>(
|
PhysicalScaleLaw, static_cast<PhysicalScaleLaw>(ResidualNormalization::scale)>;
|
||||||
ValueNormalization::scale)>;
|
typename std::bool_constant<
|
||||||
typename std::integral_constant<
|
static_cast<std::string_view>(Manifest::targetUnits) == TargetQuantity::identifier>;
|
||||||
PhysicalScaleLaw,
|
typename std::bool_constant<
|
||||||
static_cast<PhysicalScaleLaw>(
|
static_cast<std::string_view>(Manifest::residualUnits) ==
|
||||||
ResidualNormalization::scale)>;
|
ConstraintResidualQuantity::identifier>;
|
||||||
typename std::bool_constant<
|
}
|
||||||
static_cast<std::string_view>(
|
) {
|
||||||
Manifest::targetUnits) ==
|
|
||||||
TargetQuantity::identifier>;
|
|
||||||
typename std::bool_constant<
|
|
||||||
static_cast<std::string_view>(
|
|
||||||
Manifest::residualUnits) ==
|
|
||||||
ConstraintResidualQuantity::identifier>;
|
|
||||||
}) {
|
|
||||||
return false;
|
return false;
|
||||||
} else if constexpr (!requires(const Specification &specification) {
|
} else if constexpr (!requires(const Specification &specification) { specification.target(); }) {
|
||||||
specification.target();
|
|
||||||
}) {
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return
|
return std::same_as<
|
||||||
std::same_as<
|
typename Normalization::TargetValue, dimensions::QuantityValue<TargetQuantity>> &&
|
||||||
typename Normalization::TargetValue,
|
Normalization::targetScale == physicalScaleForQuantity<TargetQuantity> &&
|
||||||
dimensions::QuantityValue<TargetQuantity>> &&
|
std::same_as<TargetQuantity, typename Manifest::TargetQuantity> &&
|
||||||
Normalization::targetScale ==
|
std::same_as<
|
||||||
physicalScaleForQuantity<TargetQuantity> &&
|
GeneratedCoordinateQuantity, typename Manifest::GeneratedCoordinateQuantity> &&
|
||||||
std::same_as<TargetQuantity, typename Manifest::TargetQuantity> &&
|
std::same_as<
|
||||||
std::same_as<
|
ConstraintResidualQuantity, typename Manifest::ConstraintResidualQuantity> &&
|
||||||
GeneratedCoordinateQuantity,
|
ValueNormalization::scale == physicalScaleForQuantity<GeneratedCoordinateQuantity> &&
|
||||||
typename Manifest::GeneratedCoordinateQuantity> &&
|
ResidualNormalization::scale == physicalScaleForQuantity<ConstraintResidualQuantity> &&
|
||||||
std::same_as<
|
static_cast<std::string_view>(Manifest::targetUnits) == TargetQuantity::identifier &&
|
||||||
ConstraintResidualQuantity,
|
static_cast<std::string_view>(Manifest::residualUnits) ==
|
||||||
typename Manifest::ConstraintResidualQuantity> &&
|
ConstraintResidualQuantity::identifier &&
|
||||||
ValueNormalization::scale ==
|
std::same_as<
|
||||||
physicalScaleForQuantity<GeneratedCoordinateQuantity> &&
|
std::remove_cvref_t<decltype(std::declval<const Specification &>().target())>,
|
||||||
ResidualNormalization::scale ==
|
typename Normalization::TargetValue>;
|
||||||
physicalScaleForQuantity<ConstraintResidualQuantity> &&
|
|
||||||
static_cast<std::string_view>(Manifest::targetUnits) ==
|
|
||||||
TargetQuantity::identifier &&
|
|
||||||
static_cast<std::string_view>(Manifest::residualUnits) ==
|
|
||||||
ConstraintResidualQuantity::identifier &&
|
|
||||||
std::same_as<
|
|
||||||
std::remove_cvref_t<decltype(
|
|
||||||
std::declval<const Specification &>().target())>,
|
|
||||||
typename Normalization::TargetValue>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -993,20 +1028,17 @@ export namespace mean_field::models {
|
|||||||
template <typename Specification>
|
template <typename Specification>
|
||||||
concept CompleteGeneratedScalarDimensionsFor =
|
concept CompleteGeneratedScalarDimensionsFor =
|
||||||
ModelSpecification<Specification> &&
|
ModelSpecification<Specification> &&
|
||||||
detail::generatedScalarDimensionsAreCoherent<
|
detail::generatedScalarDimensionsAreCoherent<std::remove_cvref_t<Specification>>();
|
||||||
std::remove_cvref_t<Specification>>();
|
|
||||||
|
|
||||||
template <typename Specification>
|
template <typename Specification>
|
||||||
concept CompleteGeneratedNormalizationFor =
|
concept CompleteGeneratedNormalizationFor =
|
||||||
ModelSpecification<Specification> &&
|
ModelSpecification<Specification> && CompleteGeneratedScalarDimensionsFor<Specification> &&
|
||||||
CompleteGeneratedScalarDimensionsFor<Specification> &&
|
|
||||||
(SpecificationContribution<std::remove_cvref_t<Specification>>::generatedValueArity == 0 ||
|
(SpecificationContribution<std::remove_cvref_t<Specification>>::generatedValueArity == 0 ||
|
||||||
SpecificationContribution<std::remove_cvref_t<Specification>>::Normalization::available);
|
SpecificationContribution<std::remove_cvref_t<Specification>>::Normalization::available);
|
||||||
|
|
||||||
template <typename Specification>
|
template <typename Specification>
|
||||||
concept CompleteGeneratedManifestFor =
|
concept CompleteGeneratedManifestFor =
|
||||||
ModelSpecification<Specification> &&
|
ModelSpecification<Specification> && CompleteGeneratedScalarDimensionsFor<Specification> &&
|
||||||
CompleteGeneratedScalarDimensionsFor<Specification> &&
|
|
||||||
(SpecificationContribution<std::remove_cvref_t<Specification>>::generatedValueArity == 0 ||
|
(SpecificationContribution<std::remove_cvref_t<Specification>>::generatedValueArity == 0 ||
|
||||||
SpecificationContribution<std::remove_cvref_t<Specification>>::Manifest::available);
|
SpecificationContribution<std::remove_cvref_t<Specification>>::Manifest::available);
|
||||||
|
|
||||||
@@ -1035,7 +1067,7 @@ export namespace mean_field::models {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <SpecificationRole Role, typename SpecificationSet> struct SpecificationsForRole {
|
template <SpecificationRole Role, typename SpecificationSet> struct SpecificationsForRole {
|
||||||
using Type = ModelTypeList<>;
|
using Type = ModelTypeList<>;
|
||||||
|
|
||||||
static constexpr bool available = false;
|
static constexpr bool available = false;
|
||||||
static constexpr std::size_t count = 0;
|
static constexpr std::size_t count = 0;
|
||||||
@@ -1043,9 +1075,10 @@ export namespace mean_field::models {
|
|||||||
|
|
||||||
template <SpecificationRole Role, ModelSpecification... Specifications>
|
template <SpecificationRole Role, ModelSpecification... Specifications>
|
||||||
struct SpecificationsForRole<Role, SpecificationSetStorage<Specifications...>> {
|
struct SpecificationsForRole<Role, SpecificationSetStorage<Specifications...>> {
|
||||||
using Type = typename ConcatenateModelTypeLists<
|
using Type = typename ConcatenateModelTypeLists<std::conditional_t<
|
||||||
std::conditional_t<SpecificationTraits<Specifications>::role == Role, ModelTypeList<Specifications>,
|
SpecificationTraits<Specifications>::role == Role,
|
||||||
ModelTypeList<>>...>::Type;
|
ModelTypeList<Specifications>,
|
||||||
|
ModelTypeList<>>...>::Type;
|
||||||
|
|
||||||
static constexpr bool available = true;
|
static constexpr bool available = true;
|
||||||
static constexpr std::size_t count = Type::size;
|
static constexpr std::size_t count = Type::size;
|
||||||
@@ -1077,9 +1110,10 @@ export namespace mean_field::models {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using Type = std::conditional_t<(SpecificationTraits<Specification>::key < SpecificationTraits<Head>::key),
|
using Type = std::conditional_t<
|
||||||
SpecificationSetStorage<Specification, Head, Tail...>,
|
(SpecificationTraits<Specification>::key < SpecificationTraits<Head>::key),
|
||||||
typename PrependSpecification<Head, InsertedTail>::Type>;
|
SpecificationSetStorage<Specification, Head, Tail...>,
|
||||||
|
typename PrependSpecification<Head, InsertedTail>::Type>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Set, ModelSpecification... Specifications> struct CanonicalizeSpecifications;
|
template <typename Set, ModelSpecification... Specifications> struct CanonicalizeSpecifications;
|
||||||
@@ -1098,21 +1132,26 @@ export namespace mean_field::models {
|
|||||||
using CanonicalSpecificationSet =
|
using CanonicalSpecificationSet =
|
||||||
typename CanonicalizeSpecifications<SpecificationSetStorage<>, Specifications...>::Type;
|
typename CanonicalizeSpecifications<SpecificationSetStorage<>, Specifications...>::Type;
|
||||||
|
|
||||||
template <ModelSpecification Head, ModelSpecification... Tail> consteval bool specificationKeyIsUnique() {
|
template <
|
||||||
|
ModelSpecification Head,
|
||||||
|
ModelSpecification... Tail>
|
||||||
|
consteval bool specificationKeyIsUnique() {
|
||||||
constexpr auto headKey = SpecificationTraits<Head>::key;
|
constexpr auto headKey = SpecificationTraits<Head>::key;
|
||||||
return ((headKey.role != SpecificationTraits<Tail>::key.role ||
|
return (
|
||||||
headKey.stableName != SpecificationTraits<Tail>::key.stableName) &&
|
(headKey.role != SpecificationTraits<Tail>::key.role ||
|
||||||
...);
|
headKey.stableName != SpecificationTraits<Tail>::key.stableName) &&
|
||||||
|
...
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <ModelSpecification... Specifications> struct SpecificationKeysAreUnique;
|
template <ModelSpecification... Specifications> struct SpecificationKeysAreUnique;
|
||||||
|
|
||||||
template <> struct SpecificationKeysAreUnique<> : std::true_type {};
|
template <> struct SpecificationKeysAreUnique<> : std::true_type { };
|
||||||
|
|
||||||
template <ModelSpecification Head, ModelSpecification... Tail>
|
template <ModelSpecification Head, ModelSpecification... Tail>
|
||||||
struct SpecificationKeysAreUnique<Head, Tail...>
|
struct SpecificationKeysAreUnique<Head, Tail...>
|
||||||
: std::bool_constant<specificationKeyIsUnique<Head, Tail...>() &&
|
: std::bool_constant<
|
||||||
SpecificationKeysAreUnique<Tail...>::value> {};
|
specificationKeyIsUnique<Head, Tail...>() && SpecificationKeysAreUnique<Tail...>::value> { };
|
||||||
|
|
||||||
template <SpecificationRole Role, ModelSpecification... Specifications>
|
template <SpecificationRole Role, ModelSpecification... Specifications>
|
||||||
inline constexpr std::size_t specificationRoleCount =
|
inline constexpr std::size_t specificationRoleCount =
|
||||||
@@ -1122,7 +1161,7 @@ export namespace mean_field::models {
|
|||||||
|
|
||||||
template <typename... Types>
|
template <typename... Types>
|
||||||
struct ModelTypeListScalarArity<ModelTypeList<Types...>>
|
struct ModelTypeListScalarArity<ModelTypeList<Types...>>
|
||||||
: std::integral_constant<std::size_t, (std::size_t{0} + ... + Types::scalarArity)> {};
|
: std::integral_constant<std::size_t, (std::size_t{0} + ... + Types::scalarArity)> { };
|
||||||
|
|
||||||
template <typename Query, typename... Types>
|
template <typename Query, typename... Types>
|
||||||
inline constexpr bool isOneOf = (std::same_as<Query, Types> || ...);
|
inline constexpr bool isOneOf = (std::same_as<Query, Types> || ...);
|
||||||
@@ -1136,9 +1175,10 @@ export namespace mean_field::models {
|
|||||||
|
|
||||||
template <ModelSpecification... CanonicalSpecifications, typename... Arguments>
|
template <ModelSpecification... CanonicalSpecifications, typename... Arguments>
|
||||||
struct ArgumentsMatchCanonicalSpecifications<SpecificationSetStorage<CanonicalSpecifications...>, Arguments...>
|
struct ArgumentsMatchCanonicalSpecifications<SpecificationSetStorage<CanonicalSpecifications...>, Arguments...>
|
||||||
: std::bool_constant<sizeof...(CanonicalSpecifications) == sizeof...(Arguments) &&
|
: std::bool_constant<
|
||||||
(isOneOf<std::remove_cvref_t<Arguments>, CanonicalSpecifications...> && ...) &&
|
sizeof...(CanonicalSpecifications) == sizeof...(Arguments) &&
|
||||||
((typeCount<CanonicalSpecifications, Arguments...> == 1) && ...)> {};
|
(isOneOf<std::remove_cvref_t<Arguments>, CanonicalSpecifications...> && ...) &&
|
||||||
|
((typeCount<CanonicalSpecifications, Arguments...> == 1) && ...)> { };
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <typename... Specifications>
|
template <typename... Specifications>
|
||||||
@@ -1150,12 +1190,12 @@ export namespace mean_field::models {
|
|||||||
concept ValidModelSpecificationPack =
|
concept ValidModelSpecificationPack =
|
||||||
(ResolvedModelSpecification<std::remove_cvref_t<Specifications>> && ...) &&
|
(ResolvedModelSpecification<std::remove_cvref_t<Specifications>> && ...) &&
|
||||||
specificationKeysAreUnique<std::remove_cvref_t<Specifications>...> &&
|
specificationKeysAreUnique<std::remove_cvref_t<Specifications>...> &&
|
||||||
detail::specificationRoleCount<SpecificationRole::constitutive_law,
|
detail::specificationRoleCount<SpecificationRole::constitutive_law, std::remove_cvref_t<Specifications>...> ==
|
||||||
std::remove_cvref_t<Specifications>...> == 1;
|
1;
|
||||||
|
|
||||||
template <typename... Specifications>
|
template <typename... Specifications>
|
||||||
requires(ModelSpecification<std::remove_cvref_t<Specifications>> && ...) &&
|
requires(ModelSpecification<std::remove_cvref_t<Specifications>> && ...) &&
|
||||||
specificationKeysAreUnique<std::remove_cvref_t<Specifications>...>
|
specificationKeysAreUnique<std::remove_cvref_t<Specifications>...>
|
||||||
using SpecificationSet = detail::CanonicalSpecificationSet<std::remove_cvref_t<Specifications>...>;
|
using SpecificationSet = detail::CanonicalSpecificationSet<std::remove_cvref_t<Specifications>...>;
|
||||||
|
|
||||||
template <SpecificationRole Role, typename SpecificationSet>
|
template <SpecificationRole Role, typename SpecificationSet>
|
||||||
@@ -1203,12 +1243,13 @@ export namespace mean_field::models {
|
|||||||
[[nodiscard]] consteval SpecificationDescriptor specificationDescriptor() {
|
[[nodiscard]] consteval SpecificationDescriptor specificationDescriptor() {
|
||||||
using Contribution = SpecificationContribution<Specification>;
|
using Contribution = SpecificationContribution<Specification>;
|
||||||
|
|
||||||
return {.name = SpecificationTraits<Specification>::name,
|
return {
|
||||||
.role = SpecificationTraits<Specification>::role,
|
.name = SpecificationTraits<Specification>::name,
|
||||||
.key = SpecificationTraits<Specification>::key,
|
.role = SpecificationTraits<Specification>::role,
|
||||||
.generatedValueArity = detail::ModelTypeListScalarArity<typename Contribution::GeneratedValues>::value,
|
.key = SpecificationTraits<Specification>::key,
|
||||||
.generatedResidualArity =
|
.generatedValueArity = detail::ModelTypeListScalarArity<typename Contribution::GeneratedValues>::value,
|
||||||
detail::ModelTypeListScalarArity<typename Contribution::GeneratedResiduals>::value};
|
.generatedResidualArity = detail::ModelTypeListScalarArity<typename Contribution::GeneratedResiduals>::value
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
@@ -1231,14 +1272,12 @@ export namespace mean_field::models {
|
|||||||
std::tuple<ArgumentTypes...> &&arguments,
|
std::tuple<ArgumentTypes...> &&arguments,
|
||||||
CanonicalArgumentsTag
|
CanonicalArgumentsTag
|
||||||
)
|
)
|
||||||
: m_specifications(
|
: m_specifications(std::get<Specifications>(std::move(arguments))...) {
|
||||||
std::get<Specifications>(std::move(arguments))...
|
|
||||||
) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using SpecificationTypes = SpecificationSetStorage<Specifications...>;
|
using SpecificationTypes = SpecificationSetStorage<Specifications...>;
|
||||||
using OperatorSignature = SpecificationOperatorSignature<SpecificationTypes>;
|
using OperatorSignature = SpecificationOperatorSignature<SpecificationTypes>;
|
||||||
|
|
||||||
static constexpr bool symbolicallySquare = OperatorSignature::symbolicallySquare;
|
static constexpr bool symbolicallySquare = OperatorSignature::symbolicallySquare;
|
||||||
|
|
||||||
@@ -1249,22 +1288,27 @@ export namespace mean_field::models {
|
|||||||
symbolicallySquare && (SpecificationContribution<Specifications>::hasDeclarativeDefinition && ...);
|
symbolicallySquare && (SpecificationContribution<Specifications>::hasDeclarativeDefinition && ...);
|
||||||
|
|
||||||
template <typename... Arguments>
|
template <typename... Arguments>
|
||||||
requires ArgumentsMatchCanonicalSpecifications<SpecificationTypes, Arguments...>::value &&
|
requires ArgumentsMatchCanonicalSpecifications<
|
||||||
|
SpecificationTypes,
|
||||||
|
Arguments...>::value &&
|
||||||
std::constructible_from<
|
std::constructible_from<
|
||||||
std::tuple<std::remove_cvref_t<Arguments>...>,
|
std::tuple<std::remove_cvref_t<Arguments>...>,
|
||||||
Arguments...> &&
|
Arguments...> &&
|
||||||
(std::constructible_from<Specifications, Specifications &&> && ...)
|
(std::constructible_from<
|
||||||
|
Specifications,
|
||||||
|
Specifications &&> &&
|
||||||
|
...)
|
||||||
explicit SpecifiedModel(Arguments &&...arguments)
|
explicit SpecifiedModel(Arguments &&...arguments)
|
||||||
: SpecifiedModel(
|
: SpecifiedModel(
|
||||||
std::tuple<std::remove_cvref_t<Arguments>...>{
|
std::tuple<std::remove_cvref_t<Arguments>...>{std::forward<Arguments>(arguments)...},
|
||||||
std::forward<Arguments>(arguments)...
|
|
||||||
},
|
|
||||||
CanonicalArgumentsTag{}
|
CanonicalArgumentsTag{}
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <ModelSpecification Specification>
|
template <ModelSpecification Specification>
|
||||||
requires isOneOf<Specification, Specifications...>
|
requires isOneOf<
|
||||||
|
Specification,
|
||||||
|
Specifications...>
|
||||||
[[nodiscard]] const Specification &specification() const noexcept {
|
[[nodiscard]] const Specification &specification() const noexcept {
|
||||||
return std::get<Specification>(m_specifications);
|
return std::get<Specification>(m_specifications);
|
||||||
}
|
}
|
||||||
@@ -1282,10 +1326,11 @@ export namespace mean_field::models {
|
|||||||
runtimeDescriptors = [] {
|
runtimeDescriptors = [] {
|
||||||
std::array<RuntimeSpecificationDescriptor, sizeof...(Specifications)> descriptors{};
|
std::array<RuntimeSpecificationDescriptor, sizeof...(Specifications)> descriptors{};
|
||||||
std::size_t index = 0;
|
std::size_t index = 0;
|
||||||
((descriptors[index] = {.specification = specificationDescriptor<Specifications>(),
|
((descriptors[index] =
|
||||||
.canonicalIndex = index,
|
{.specification = specificationDescriptor<Specifications>(),
|
||||||
.hasDeclarativeDefinition =
|
.canonicalIndex = index,
|
||||||
SpecificationContribution<Specifications>::hasDeclarativeDefinition},
|
.hasDeclarativeDefinition =
|
||||||
|
SpecificationContribution<Specifications>::hasDeclarativeDefinition},
|
||||||
++index),
|
++index),
|
||||||
...);
|
...);
|
||||||
return descriptors;
|
return descriptors;
|
||||||
@@ -1346,27 +1391,23 @@ export namespace mean_field::stellar {
|
|||||||
} // namespace state
|
} // namespace state
|
||||||
|
|
||||||
namespace equation {
|
namespace equation {
|
||||||
using GravityGradientDefinition = models::stellar::equation::GravityGradientDefinition;
|
using GravityGradientDefinition = models::stellar::equation::GravityGradientDefinition;
|
||||||
using PoissonEquation = models::stellar::equation::PoissonEquation;
|
using PoissonEquation = models::stellar::equation::PoissonEquation;
|
||||||
using DensityClosure = models::stellar::equation::DensityClosure;
|
using DensityClosure = models::stellar::equation::DensityClosure;
|
||||||
using SurfaceShapeBalance = models::stellar::equation::SurfaceShapeBalance;
|
using SurfaceShapeBalance = models::stellar::equation::SurfaceShapeBalance;
|
||||||
using HydrostaticBalance = models::stellar::equation::HydrostaticBalance;
|
using HydrostaticBalance = models::stellar::equation::HydrostaticBalance;
|
||||||
using OwnConstraint = models::stellar::equation::OwnConstraint;
|
using OwnConstraint = models::stellar::equation::OwnConstraint;
|
||||||
|
|
||||||
template <typename Specification>
|
template <typename Specification> using ConstraintOf = models::stellar::equation::ConstraintOf<Specification>;
|
||||||
using ConstraintOf = models::stellar::equation::ConstraintOf<Specification>;
|
|
||||||
} // namespace equation
|
} // namespace equation
|
||||||
|
|
||||||
template <typename Equation, typename State>
|
template <typename Equation, typename State> using Derivative = models::stellar::Derivative<Equation, State>;
|
||||||
using Derivative = models::stellar::Derivative<Equation, State>;
|
|
||||||
|
|
||||||
template <typename... Quantities>
|
template <typename... Quantities> using Reads = models::DependsOn<Quantities...>;
|
||||||
using Reads = models::DependsOn<Quantities...>;
|
|
||||||
|
|
||||||
template <typename... Equations>
|
template <typename... Equations> using Changes = models::Affects<Equations...>;
|
||||||
using Changes = models::Affects<Equations...>;
|
|
||||||
|
|
||||||
using PhysicalScale = models::PhysicalScaleLaw;
|
using PhysicalScale = models::PhysicalScaleLaw;
|
||||||
|
|
||||||
template <
|
template <
|
||||||
models::PhysicalScaleRepresentedQuantity TargetQuantity,
|
models::PhysicalScaleRepresentedQuantity TargetQuantity,
|
||||||
@@ -1393,10 +1434,8 @@ export namespace mean_field::stellar {
|
|||||||
typename std::remove_cvref_t<Candidate>::GeneratedCoordinateQuantity;
|
typename std::remove_cvref_t<Candidate>::GeneratedCoordinateQuantity;
|
||||||
typename std::remove_cvref_t<Candidate>::ConstraintResidualQuantity;
|
typename std::remove_cvref_t<Candidate>::ConstraintResidualQuantity;
|
||||||
typename std::remove_cvref_t<Candidate>::TargetValue;
|
typename std::remove_cvref_t<Candidate>::TargetValue;
|
||||||
requires models::GeneratedNormalizationDefinition<
|
requires models::GeneratedNormalizationDefinition<typename std::remove_cvref_t<Candidate>::Normalization>;
|
||||||
typename std::remove_cvref_t<Candidate>::Normalization>;
|
requires models::GeneratedManifestDefinition<typename std::remove_cvref_t<Candidate>::Manifest>;
|
||||||
requires models::GeneratedManifestDefinition<
|
|
||||||
typename std::remove_cvref_t<Candidate>::Manifest>;
|
|
||||||
requires std::remove_cvref_t<Candidate>::Normalization::available;
|
requires std::remove_cvref_t<Candidate>::Normalization::available;
|
||||||
requires std::remove_cvref_t<Candidate>::Manifest::available;
|
requires std::remove_cvref_t<Candidate>::Manifest::available;
|
||||||
requires std::remove_cvref_t<Candidate>::dimensionallyTyped;
|
requires std::remove_cvref_t<Candidate>::dimensionallyTyped;
|
||||||
@@ -1407,31 +1446,43 @@ export namespace mean_field::integral {
|
|||||||
using FixedTotalMass = models::FixedTotalMass;
|
using FixedTotalMass = models::FixedTotalMass;
|
||||||
using FixedAngularMomentum = models::FixedAngularMomentum;
|
using FixedAngularMomentum = models::FixedAngularMomentum;
|
||||||
|
|
||||||
template <typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>,
|
template <
|
||||||
typename Affects = models::ModelTypeList<>,
|
typename Specification,
|
||||||
typename Normalization = models::UnavailableGeneratedNormalization,
|
models::FixedString Name,
|
||||||
typename Manifest = models::UnavailableGeneratedManifest>
|
typename DependsOn = models::ModelTypeList<>,
|
||||||
|
typename Affects = models::ModelTypeList<>,
|
||||||
|
typename Normalization = models::UnavailableGeneratedNormalization,
|
||||||
|
typename Manifest = models::UnavailableGeneratedManifest>
|
||||||
using FixedIntegralWithMultiplier =
|
using FixedIntegralWithMultiplier =
|
||||||
models::FixedIntegralWithMultiplier<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
|
models::FixedIntegralWithMultiplier<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
|
||||||
|
|
||||||
template <typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>,
|
template <
|
||||||
typename Affects = models::ModelTypeList<>,
|
typename Specification,
|
||||||
typename Normalization = models::UnavailableGeneratedNormalization,
|
models::FixedString Name,
|
||||||
typename Manifest = models::UnavailableGeneratedManifest>
|
typename DependsOn = models::ModelTypeList<>,
|
||||||
|
typename Affects = models::ModelTypeList<>,
|
||||||
|
typename Normalization = models::UnavailableGeneratedNormalization,
|
||||||
|
typename Manifest = models::UnavailableGeneratedManifest>
|
||||||
using FixedWithMultiplier =
|
using FixedWithMultiplier =
|
||||||
FixedIntegralWithMultiplier<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
|
FixedIntegralWithMultiplier<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
|
||||||
|
|
||||||
template <typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>,
|
template <
|
||||||
typename Affects = models::ModelTypeList<>,
|
typename Specification,
|
||||||
typename Normalization = models::UnavailableGeneratedNormalization,
|
models::FixedString Name,
|
||||||
typename Manifest = models::UnavailableGeneratedManifest>
|
typename DependsOn = models::ModelTypeList<>,
|
||||||
|
typename Affects = models::ModelTypeList<>,
|
||||||
|
typename Normalization = models::UnavailableGeneratedNormalization,
|
||||||
|
typename Manifest = models::UnavailableGeneratedManifest>
|
||||||
using FixedIntegralWithPhysicalCoordinate =
|
using FixedIntegralWithPhysicalCoordinate =
|
||||||
models::FixedIntegralWithPhysicalCoordinate<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
|
models::FixedIntegralWithPhysicalCoordinate<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
|
||||||
|
|
||||||
template <typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>,
|
template <
|
||||||
typename Affects = models::ModelTypeList<>,
|
typename Specification,
|
||||||
typename Normalization = models::UnavailableGeneratedNormalization,
|
models::FixedString Name,
|
||||||
typename Manifest = models::UnavailableGeneratedManifest>
|
typename DependsOn = models::ModelTypeList<>,
|
||||||
|
typename Affects = models::ModelTypeList<>,
|
||||||
|
typename Normalization = models::UnavailableGeneratedNormalization,
|
||||||
|
typename Manifest = models::UnavailableGeneratedManifest>
|
||||||
using FixedWithPhysicalCoordinate =
|
using FixedWithPhysicalCoordinate =
|
||||||
FixedIntegralWithPhysicalCoordinate<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
|
FixedIntegralWithPhysicalCoordinate<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
|
||||||
|
|
||||||
@@ -1467,10 +1518,13 @@ export namespace mean_field::integral {
|
|||||||
export namespace mean_field::constraint {
|
export namespace mean_field::constraint {
|
||||||
using FixedCentralDensity = models::FixedCentralDensity;
|
using FixedCentralDensity = models::FixedCentralDensity;
|
||||||
|
|
||||||
template <typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>,
|
template <
|
||||||
typename Affects = models::ModelTypeList<>,
|
typename Specification,
|
||||||
typename Normalization = models::UnavailableGeneratedNormalization,
|
models::FixedString Name,
|
||||||
typename Manifest = models::UnavailableGeneratedManifest>
|
typename DependsOn = models::ModelTypeList<>,
|
||||||
|
typename Affects = models::ModelTypeList<>,
|
||||||
|
typename Normalization = models::UnavailableGeneratedNormalization,
|
||||||
|
typename Manifest = models::UnavailableGeneratedManifest>
|
||||||
using PhaseCondition = models::PhaseCondition<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
|
using PhaseCondition = models::PhaseCondition<Specification, Name, DependsOn, Affects, Normalization, Manifest>;
|
||||||
|
|
||||||
template <
|
template <
|
||||||
|
|||||||
@@ -25,10 +25,9 @@ export namespace mean_field::model {
|
|||||||
using EquationOfStateType =
|
using EquationOfStateType =
|
||||||
models::SpecificationForRoleT<models::SpecificationRole::constitutive_law, SpecificationTypes>;
|
models::SpecificationForRoleT<models::SpecificationRole::constitutive_law, SpecificationTypes>;
|
||||||
|
|
||||||
static constexpr std::size_t specificationCount = sizeof...(CanonicalSpecifications);
|
static constexpr std::size_t specificationCount = sizeof...(CanonicalSpecifications);
|
||||||
static constexpr bool symbolicallySquare = Storage::symbolicallySquare;
|
static constexpr bool symbolicallySquare = Storage::symbolicallySquare;
|
||||||
static constexpr bool hasCompleteEquilibriumDeclaration =
|
static constexpr bool hasCompleteEquilibriumDeclaration = Storage::hasCompleteEquilibriumDeclaration;
|
||||||
Storage::hasCompleteEquilibriumDeclaration;
|
|
||||||
|
|
||||||
template <models::SpecificationRole Role>
|
template <models::SpecificationRole Role>
|
||||||
using SpecificationsForRole = models::SpecificationsForRoleT<Role, SpecificationTypes>;
|
using SpecificationsForRole = models::SpecificationsForRoleT<Role, SpecificationTypes>;
|
||||||
@@ -48,7 +47,9 @@ export namespace mean_field::model {
|
|||||||
models::HasUniqueSpecificationForRole<Role, SpecificationTypes>;
|
models::HasUniqueSpecificationForRole<Role, SpecificationTypes>;
|
||||||
|
|
||||||
template <typename... Arguments>
|
template <typename... Arguments>
|
||||||
requires std::constructible_from<Storage, Arguments...>
|
requires std::constructible_from<
|
||||||
|
Storage,
|
||||||
|
Arguments...>
|
||||||
explicit StellarModel(Arguments &&...arguments) : m_specifications(std::forward<Arguments>(arguments)...) {
|
explicit StellarModel(Arguments &&...arguments) : m_specifications(std::forward<Arguments>(arguments)...) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,8 +63,12 @@ export namespace mean_field::model {
|
|||||||
static constexpr bool containsSpecification = Storage::template containsSpecification<Specification>;
|
static constexpr bool containsSpecification = Storage::template containsSpecification<Specification>;
|
||||||
|
|
||||||
template <models::SpecificationRole Role>
|
template <models::SpecificationRole Role>
|
||||||
requires models::HasUniqueSpecificationForRole<Role, SpecificationTypes>
|
requires models::HasUniqueSpecificationForRole<
|
||||||
[[nodiscard]] const models::SpecificationForRoleT<Role, SpecificationTypes> &
|
Role,
|
||||||
|
SpecificationTypes>
|
||||||
|
[[nodiscard]] const models::SpecificationForRoleT<
|
||||||
|
Role,
|
||||||
|
SpecificationTypes> &
|
||||||
specificationForRole() const noexcept {
|
specificationForRole() const noexcept {
|
||||||
using Specification = models::SpecificationForRoleT<Role, SpecificationTypes>;
|
using Specification = models::SpecificationForRoleT<Role, SpecificationTypes>;
|
||||||
return specification<Specification>();
|
return specification<Specification>();
|
||||||
@@ -74,8 +79,9 @@ export namespace mean_field::model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename = void>
|
template <typename = void>
|
||||||
requires models::HasUniqueSpecificationForRole<models::SpecificationRole::boundary_condition,
|
requires models::HasUniqueSpecificationForRole<
|
||||||
SpecificationTypes>
|
models::SpecificationRole::boundary_condition,
|
||||||
|
SpecificationTypes>
|
||||||
[[nodiscard]] const auto &surfaceCondition() const noexcept {
|
[[nodiscard]] const auto &surfaceCondition() const noexcept {
|
||||||
return specificationForRole<models::SpecificationRole::boundary_condition>();
|
return specificationForRole<models::SpecificationRole::boundary_condition>();
|
||||||
}
|
}
|
||||||
@@ -95,20 +101,20 @@ export namespace mean_field::model {
|
|||||||
-> StellarModel<models::SpecificationSet<std::remove_cvref_t<Specifications>...>>;
|
-> StellarModel<models::SpecificationSet<std::remove_cvref_t<Specifications>...>>;
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename Candidate, typename = void> struct IsStellarModel : std::false_type {};
|
template <typename Candidate, typename = void> struct IsStellarModel : std::false_type { };
|
||||||
|
|
||||||
template <typename SpecificationSet>
|
template <typename SpecificationSet>
|
||||||
struct IsStellarModel<
|
struct IsStellarModel<
|
||||||
StellarModel<SpecificationSet>,
|
StellarModel<SpecificationSet>,
|
||||||
std::void_t<typename StellarModel<SpecificationSet>::SpecificationTypes,
|
std::void_t<
|
||||||
typename StellarModel<SpecificationSet>::OperatorSignature,
|
typename StellarModel<SpecificationSet>::SpecificationTypes,
|
||||||
decltype(StellarModel<SpecificationSet>::specificationCount),
|
typename StellarModel<SpecificationSet>::OperatorSignature,
|
||||||
decltype(StellarModel<SpecificationSet>::hasCompleteEquilibriumDeclaration)>>
|
decltype(StellarModel<SpecificationSet>::specificationCount),
|
||||||
: std::true_type {};
|
decltype(StellarModel<SpecificationSet>::hasCompleteEquilibriumDeclaration)>> : std::true_type { };
|
||||||
|
|
||||||
template <models::SpecificationRole Role, typename Candidate, bool = IsStellarModel<Candidate>::value>
|
template <models::SpecificationRole Role, typename Candidate, bool = IsStellarModel<Candidate>::value>
|
||||||
struct StellarModelRoleSelection {
|
struct StellarModelRoleSelection {
|
||||||
using Types = models::ModelTypeList<>;
|
using Types = models::ModelTypeList<>;
|
||||||
|
|
||||||
static constexpr std::size_t count = 0;
|
static constexpr std::size_t count = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export namespace mean_field::normalization {
|
|||||||
}
|
}
|
||||||
mfem::Vector state(stateSize);
|
mfem::Vector state(stateSize);
|
||||||
mfem::Vector residual(residualSize);
|
mfem::Vector residual(residualSize);
|
||||||
state = 1.0;
|
state = 1.0;
|
||||||
residual = 1.0;
|
residual = 1.0;
|
||||||
return {std::move(state), std::move(residual)};
|
return {std::move(state), std::move(residual)};
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ export namespace mean_field::normalization {
|
|||||||
}
|
}
|
||||||
for (int index = 0; index < input.Size(); ++index) {
|
for (int index = 0; index < input.Size(); ++index) {
|
||||||
const double value = input(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
|
* the policy and is found by ADL, so adding a normalization family does
|
||||||
* not edit a library registry or switch. */
|
* not edit a library registry or switch. */
|
||||||
template <typename Problem>
|
template <typename Problem>
|
||||||
concept RuntimePreparedNormalizationOperation =
|
concept RuntimePreparedNormalizationOperation = requires(const std::remove_cvref_t<Problem> &problem) {
|
||||||
requires(const std::remove_cvref_t<Problem> &problem) {
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType;
|
||||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType;
|
typename std::remove_cvref_t<Problem>::FormType;
|
||||||
typename std::remove_cvref_t<Problem>::FormType;
|
requires RuntimePreparedNormalizationFor<
|
||||||
requires RuntimePreparedNormalizationFor<
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
typename std::remove_cvref_t<Problem>::FormType>;
|
||||||
typename std::remove_cvref_t<Problem>::FormType>;
|
{
|
||||||
{
|
problem.GetNormalizationPrescription()
|
||||||
problem.GetNormalizationPrescription()
|
} -> std::same_as<const typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType &>;
|
||||||
} -> std::same_as<const typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType &>;
|
{
|
||||||
{
|
prepareStellarNormalization(problem.GetNormalizationPrescription(), problem)
|
||||||
prepareStellarNormalization(
|
} -> std::same_as<DiagonalNormalization>;
|
||||||
problem.GetNormalizationPrescription(),
|
};
|
||||||
problem)
|
|
||||||
} -> std::same_as<DiagonalNormalization>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
requires utils::blocks::block_form_is_valid_v<Form>
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
@@ -183,16 +180,14 @@ export namespace mean_field::normalization {
|
|||||||
m_residualFactors(layout.residual_offsets().Last()) {
|
m_residualFactors(layout.residual_offsets().Last()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit DiagonalNormalizationBuilder(
|
explicit DiagonalNormalizationBuilder(utils::blocks::form_layout<Form> &&) = delete;
|
||||||
utils::blocks::form_layout<Form> &&
|
|
||||||
) = delete;
|
|
||||||
|
|
||||||
explicit DiagonalNormalizationBuilder(
|
explicit DiagonalNormalizationBuilder(const utils::blocks::form_layout<Form> &&) = delete;
|
||||||
const utils::blocks::form_layout<Form> &&
|
|
||||||
) = delete;
|
|
||||||
|
|
||||||
template <typename Block>
|
template <typename Block>
|
||||||
requires utils::blocks::contains_type_v<Block, typename Form::value_blocks>
|
requires utils::blocks::contains_type_v<
|
||||||
|
Block,
|
||||||
|
typename Form::value_blocks>
|
||||||
void SetValueBlock(
|
void SetValueBlock(
|
||||||
const double physicalScale,
|
const double physicalScale,
|
||||||
const mfem::Vector &primalGramDiagonal
|
const mfem::Vector &primalGramDiagonal
|
||||||
@@ -200,18 +195,17 @@ export namespace mean_field::normalization {
|
|||||||
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
|
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
|
||||||
RequireUnassigned(m_valueAssigned[block], "value");
|
RequireUnassigned(m_valueAssigned[block], "value");
|
||||||
AssignBlock(
|
AssignBlock(
|
||||||
m_stateFactors,
|
m_stateFactors, m_layout->value_offsets()[block],
|
||||||
m_layout->value_offsets()[block],
|
m_layout->value_offsets()[block + 1] - m_layout->value_offsets()[block], physicalScale,
|
||||||
m_layout->value_offsets()[block + 1] - m_layout->value_offsets()[block],
|
primalGramDiagonal, false
|
||||||
physicalScale,
|
|
||||||
primalGramDiagonal,
|
|
||||||
false
|
|
||||||
);
|
);
|
||||||
m_valueAssigned[block] = true;
|
m_valueAssigned[block] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Block>
|
template <typename Block>
|
||||||
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
|
requires utils::blocks::contains_type_v<
|
||||||
|
Block,
|
||||||
|
typename Form::residual_blocks>
|
||||||
void SetResidualBlock(
|
void SetResidualBlock(
|
||||||
const double physicalScale,
|
const double physicalScale,
|
||||||
const mfem::Vector &primalGramDiagonal
|
const mfem::Vector &primalGramDiagonal
|
||||||
@@ -219,25 +213,26 @@ export namespace mean_field::normalization {
|
|||||||
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
||||||
RequireUnassigned(m_residualAssigned[block], "residual");
|
RequireUnassigned(m_residualAssigned[block], "residual");
|
||||||
AssignBlock(
|
AssignBlock(
|
||||||
m_residualFactors,
|
m_residualFactors, m_layout->residual_offsets()[block],
|
||||||
m_layout->residual_offsets()[block],
|
m_layout->residual_offsets()[block + 1] - m_layout->residual_offsets()[block], physicalScale,
|
||||||
m_layout->residual_offsets()[block + 1] - m_layout->residual_offsets()[block],
|
primalGramDiagonal, true
|
||||||
physicalScale,
|
|
||||||
primalGramDiagonal,
|
|
||||||
true
|
|
||||||
);
|
);
|
||||||
m_residualAssigned[block] = true;
|
m_residualAssigned[block] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Block>
|
template <typename Block>
|
||||||
requires utils::blocks::contains_type_v<Block, typename Form::value_blocks>
|
requires utils::blocks::contains_type_v<
|
||||||
|
Block,
|
||||||
|
typename Form::value_blocks>
|
||||||
void SetValueGlobal(const double physicalScale) {
|
void SetValueGlobal(const double physicalScale) {
|
||||||
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
|
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
|
||||||
SetConstantMetricValueBlock<Block>(physicalScale, BlockSize(m_layout->value_offsets(), block));
|
SetConstantMetricValueBlock<Block>(physicalScale, BlockSize(m_layout->value_offsets(), block));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Block>
|
template <typename Block>
|
||||||
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
|
requires utils::blocks::contains_type_v<
|
||||||
|
Block,
|
||||||
|
typename Form::residual_blocks>
|
||||||
void SetResidualGlobal(const double physicalScale) {
|
void SetResidualGlobal(const double physicalScale) {
|
||||||
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
||||||
mfem::Vector metric(BlockSize(m_layout->residual_offsets(), block));
|
mfem::Vector metric(BlockSize(m_layout->residual_offsets(), block));
|
||||||
@@ -246,7 +241,9 @@ export namespace mean_field::normalization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Block>
|
template <typename Block>
|
||||||
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
|
requires utils::blocks::contains_type_v<
|
||||||
|
Block,
|
||||||
|
typename Form::residual_blocks>
|
||||||
void SetHybridResidualBlock(
|
void SetHybridResidualBlock(
|
||||||
const double physicalScale,
|
const double physicalScale,
|
||||||
const mfem::Vector &bulkPrimalGramDiagonal,
|
const mfem::Vector &bulkPrimalGramDiagonal,
|
||||||
@@ -254,7 +251,7 @@ export namespace mean_field::normalization {
|
|||||||
const double pointMetric = 1.0
|
const double pointMetric = 1.0
|
||||||
) {
|
) {
|
||||||
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
||||||
const int size = BlockSize(m_layout->residual_offsets(), block);
|
const int size = BlockSize(m_layout->residual_offsets(), block);
|
||||||
if (bulkPrimalGramDiagonal.Size() != size) {
|
if (bulkPrimalGramDiagonal.Size() != size) {
|
||||||
throw std::invalid_argument("The hybrid residual Gram diagonal has the wrong 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);
|
mfem::Vector metric(size);
|
||||||
for (int row = 0; row < size; ++row) {
|
for (int row = 0; row < size; ++row) {
|
||||||
metric(row) = isPointRow[static_cast<std::size_t>(row)]
|
metric(row) = isPointRow[static_cast<std::size_t>(row)] ? pointMetric : bulkPrimalGramDiagonal(row);
|
||||||
? pointMetric
|
|
||||||
: bulkPrimalGramDiagonal(row);
|
|
||||||
}
|
}
|
||||||
SetResidualBlock<Block>(physicalScale, metric);
|
SetResidualBlock<Block>(physicalScale, metric);
|
||||||
}
|
}
|
||||||
@@ -345,9 +340,7 @@ export namespace mean_field::normalization {
|
|||||||
const double metric = primalGramDiagonal(index);
|
const double metric = primalGramDiagonal(index);
|
||||||
ValidateMetric(metric);
|
ValidateMetric(metric);
|
||||||
const double rieszFactor = std::sqrt(metric);
|
const double rieszFactor = std::sqrt(metric);
|
||||||
const double factor = dual
|
const double factor = dual ? 1.0 / (physicalScale * rieszFactor) : rieszFactor / physicalScale;
|
||||||
? 1.0 / (physicalScale * rieszFactor)
|
|
||||||
: rieszFactor / physicalScale;
|
|
||||||
if (!std::isfinite(factor) || factor <= 0.0) {
|
if (!std::isfinite(factor) || factor <= 0.0) {
|
||||||
throw std::overflow_error("A normalization factor is not finite and positive.");
|
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 mfem::Operator &physicalJacobian,
|
||||||
const DiagonalNormalization &normalization
|
const DiagonalNormalization &normalization
|
||||||
)
|
)
|
||||||
: mfem::Operator(normalization.ResidualSize(), normalization.StateSize()),
|
: mfem::Operator(
|
||||||
|
normalization.ResidualSize(),
|
||||||
|
normalization.StateSize()
|
||||||
|
),
|
||||||
m_physicalJacobian(&physicalJacobian),
|
m_physicalJacobian(&physicalJacobian),
|
||||||
m_normalization(&normalization),
|
m_normalization(&normalization),
|
||||||
m_physicalDirection(normalization.StateSize()),
|
m_physicalDirection(normalization.StateSize()),
|
||||||
@@ -421,7 +417,10 @@ export namespace mean_field::normalization {
|
|||||||
const mfem::Operator &physicalInverse,
|
const mfem::Operator &physicalInverse,
|
||||||
const DiagonalNormalization &normalization
|
const DiagonalNormalization &normalization
|
||||||
)
|
)
|
||||||
: mfem::Operator(normalization.StateSize(), normalization.ResidualSize()),
|
: mfem::Operator(
|
||||||
|
normalization.StateSize(),
|
||||||
|
normalization.ResidualSize()
|
||||||
|
),
|
||||||
m_physicalInverse(&physicalInverse),
|
m_physicalInverse(&physicalInverse),
|
||||||
m_normalization(&normalization),
|
m_normalization(&normalization),
|
||||||
m_physicalResidual(normalization.ResidualSize()),
|
m_physicalResidual(normalization.ResidualSize()),
|
||||||
@@ -548,7 +547,7 @@ export namespace mean_field::normalization {
|
|||||||
const mfem::Operator &,
|
const mfem::Operator &,
|
||||||
const mfem::Operator &,
|
const mfem::Operator &,
|
||||||
const DiagonalNormalization &&
|
const DiagonalNormalization &&
|
||||||
) = delete;
|
) = delete;
|
||||||
|
|
||||||
ScaledPreconditioner(const ScaledPreconditioner &) = delete;
|
ScaledPreconditioner(const ScaledPreconditioner &) = delete;
|
||||||
ScaledPreconditioner &operator=(const ScaledPreconditioner &) = delete;
|
ScaledPreconditioner &operator=(const ScaledPreconditioner &) = delete;
|
||||||
@@ -557,9 +556,7 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
void SetOperator(const mfem::Operator &normalizedJacobian) override {
|
void SetOperator(const mfem::Operator &normalizedJacobian) override {
|
||||||
if (normalizedJacobian.Width() != Width() || normalizedJacobian.Height() != Height()) {
|
if (normalizedJacobian.Width() != Width() || normalizedJacobian.Height() != Height()) {
|
||||||
throw std::invalid_argument(
|
throw std::invalid_argument("The scaled preconditioner received an incompatible normalized Jacobian.");
|
||||||
"The scaled preconditioner received an incompatible normalized Jacobian."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (&normalizedJacobian != m_expectedNormalizedJacobian) {
|
if (&normalizedJacobian != m_expectedNormalizedJacobian) {
|
||||||
throw std::invalid_argument(
|
throw std::invalid_argument(
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
template <
|
template <
|
||||||
RieszGeometryPolicy GeometryPolicy = ReferenceGeometry,
|
RieszGeometryPolicy GeometryPolicy = ReferenceGeometry,
|
||||||
ReferenceScalePolicy ScalePolicy = FixedMassBranchReference>
|
ReferenceScalePolicy ScalePolicy = FixedMassBranchReference>
|
||||||
class PhysicalRieszDiagonal final : public NormalizationPrescriptionTag {
|
class PhysicalRieszDiagonal final : public NormalizationPrescriptionTag {
|
||||||
public:
|
public:
|
||||||
using Geometry = GeometryPolicy;
|
using Geometry = GeometryPolicy;
|
||||||
using ScaleSource = ScalePolicy;
|
using ScaleSource = ScalePolicy;
|
||||||
|
|
||||||
explicit PhysicalRieszDiagonal(
|
explicit PhysicalRieszDiagonal(
|
||||||
@@ -62,8 +62,13 @@ export namespace mean_field::normalization {
|
|||||||
double m_gravitationalConstant;
|
double m_gravitationalConstant;
|
||||||
};
|
};
|
||||||
|
|
||||||
PhysicalRieszDiagonal(dimensions::LengthValue, double = 1.0)
|
PhysicalRieszDiagonal(
|
||||||
-> PhysicalRieszDiagonal<ReferenceGeometry, FixedMassBranchReference>;
|
dimensions::LengthValue,
|
||||||
|
double = 1.0
|
||||||
|
)
|
||||||
|
-> PhysicalRieszDiagonal<
|
||||||
|
ReferenceGeometry,
|
||||||
|
FixedMassBranchReference>;
|
||||||
|
|
||||||
template <typename Candidate> struct IsPhysicalRieszDiagonal : std::false_type { };
|
template <typename Candidate> struct IsPhysicalRieszDiagonal : std::false_type { };
|
||||||
|
|
||||||
@@ -71,8 +76,7 @@ export namespace mean_field::normalization {
|
|||||||
struct IsPhysicalRieszDiagonal<PhysicalRieszDiagonal<Geometry, ScaleSource>> : std::true_type { };
|
struct IsPhysicalRieszDiagonal<PhysicalRieszDiagonal<Geometry, ScaleSource>> : std::true_type { };
|
||||||
|
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept PhysicalRieszDiagonalPrescription =
|
concept PhysicalRieszDiagonalPrescription = IsPhysicalRieszDiagonal<std::remove_cvref_t<Candidate>>::value;
|
||||||
IsPhysicalRieszDiagonal<std::remove_cvref_t<Candidate>>::value;
|
|
||||||
|
|
||||||
struct StellarCharacteristicScales final {
|
struct StellarCharacteristicScales final {
|
||||||
dimensions::MassValue mass;
|
dimensions::MassValue mass;
|
||||||
@@ -93,7 +97,7 @@ export namespace mean_field::normalization {
|
|||||||
const dimensions::LengthValue radius,
|
const dimensions::LengthValue radius,
|
||||||
const double gravitationalConstant = 1.0
|
const double gravitationalConstant = 1.0
|
||||||
) {
|
) {
|
||||||
const double massValue = mass.value();
|
const double massValue = mass.value();
|
||||||
const double radiusValue = radius.value();
|
const double radiusValue = radius.value();
|
||||||
if (!std::isfinite(massValue) || massValue <= 0.0) {
|
if (!std::isfinite(massValue) || massValue <= 0.0) {
|
||||||
throw std::invalid_argument("Characteristic stellar scales require a finite, positive mass.");
|
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 radiusSquared = radiusValue * radiusValue;
|
||||||
const double radiusCubed = radiusSquared * radiusValue;
|
const double radiusCubed = radiusSquared * radiusValue;
|
||||||
const double density = massValue / radiusCubed;
|
const double density = massValue / radiusCubed;
|
||||||
const double acceleration = gravitationalConstant * massValue / radiusSquared;
|
const double acceleration = gravitationalConstant * massValue / radiusSquared;
|
||||||
const double inverseTimeSquared = gravitationalConstant * massValue / radiusCubed;
|
const double inverseTimeSquared = gravitationalConstant * massValue / radiusCubed;
|
||||||
const double specificEnergy = gravitationalConstant * massValue / radiusValue;
|
const double specificEnergy = gravitationalConstant * massValue / radiusValue;
|
||||||
const double pressure = gravitationalConstant * massValue * massValue /
|
const double pressure = gravitationalConstant * massValue * massValue / (radiusSquared * radiusSquared);
|
||||||
(radiusSquared * radiusSquared);
|
|
||||||
const double angularVelocity = std::sqrt(inverseTimeSquared);
|
const double angularVelocity = std::sqrt(inverseTimeSquared);
|
||||||
const double angularMomentum = massValue * std::sqrt(gravitationalConstant * massValue * radiusValue);
|
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[] = {
|
const double derived[] = {density, acceleration, inverseTimeSquared, specificEnergy,
|
||||||
density,
|
pressure, angularVelocity, angularMomentum, force};
|
||||||
acceleration,
|
|
||||||
inverseTimeSquared,
|
|
||||||
specificEnergy,
|
|
||||||
pressure,
|
|
||||||
angularVelocity,
|
|
||||||
angularMomentum,
|
|
||||||
force
|
|
||||||
};
|
|
||||||
for (const double value : derived) {
|
for (const double value : derived) {
|
||||||
if (!std::isfinite(value) || value <= 0.0) {
|
if (!std::isfinite(value) || value <= 0.0) {
|
||||||
throw std::overflow_error("A derived characteristic stellar scale is not finite and positive.");
|
throw std::overflow_error("A derived characteristic stellar scale is not finite and positive.");
|
||||||
@@ -136,36 +131,38 @@ export namespace mean_field::normalization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
.mass = mass,
|
.mass = mass,
|
||||||
.radius = radius,
|
.radius = radius,
|
||||||
.gravitationalConstant = gravitationalConstant,
|
.gravitationalConstant = gravitationalConstant,
|
||||||
.density = density,
|
.density = density,
|
||||||
.acceleration = acceleration,
|
.acceleration = acceleration,
|
||||||
.inverseTimeSquared = inverseTimeSquared,
|
.inverseTimeSquared = inverseTimeSquared,
|
||||||
.specificEnergy = specificEnergy,
|
.specificEnergy = specificEnergy,
|
||||||
.pressure = pressure,
|
.pressure = pressure,
|
||||||
.angularVelocity = angularVelocity,
|
.angularVelocity = angularVelocity,
|
||||||
.angularMomentum = angularMomentum,
|
.angularMomentum = angularMomentum,
|
||||||
.force = force
|
.force = force
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource, typename Model>
|
template <
|
||||||
|
RieszGeometryPolicy Geometry,
|
||||||
|
ReferenceScalePolicy ScaleSource,
|
||||||
|
typename Model>
|
||||||
requires requires(const Model &model) {
|
requires requires(const Model &model) {
|
||||||
{
|
{ model.template specification<models::FixedTotalMass>() } -> std::same_as<const models::FixedTotalMass &>;
|
||||||
model.template specification<models::FixedTotalMass>()
|
|
||||||
} -> std::same_as<const models::FixedTotalMass &>;
|
|
||||||
{
|
{
|
||||||
model.template specification<models::FixedTotalMass>().targetMass()
|
model.template specification<models::FixedTotalMass>().targetMass()
|
||||||
} -> std::same_as<dimensions::MassValue>;
|
} -> std::same_as<dimensions::MassValue>;
|
||||||
}
|
}
|
||||||
[[nodiscard]] StellarCharacteristicScales deriveStellarCharacteristicScales(
|
[[nodiscard]] StellarCharacteristicScales deriveStellarCharacteristicScales(
|
||||||
const PhysicalRieszDiagonal<Geometry, ScaleSource> &prescription,
|
const PhysicalRieszDiagonal<
|
||||||
|
Geometry,
|
||||||
|
ScaleSource> &prescription,
|
||||||
const Model &model
|
const Model &model
|
||||||
) {
|
) {
|
||||||
return deriveStellarCharacteristicScales(
|
return deriveStellarCharacteristicScales(
|
||||||
model.template specification<models::FixedTotalMass>().targetMass(),
|
model.template specification<models::FixedTotalMass>().targetMass(), prescription.referenceRadius(),
|
||||||
prescription.referenceRadius(),
|
|
||||||
prescription.gravitationalConstant()
|
prescription.gravitationalConstant()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -179,14 +176,14 @@ export namespace mean_field::normalization {
|
|||||||
* normalization plan used by the discretization.
|
* normalization plan used by the discretization.
|
||||||
*/
|
*/
|
||||||
template <models::RieszTopology Topology> struct DeclaredRieszTopology {
|
template <models::RieszTopology Topology> struct DeclaredRieszTopology {
|
||||||
static constexpr bool available = false;
|
static constexpr bool available = false;
|
||||||
static constexpr RieszTopology value = RieszTopology::identity;
|
static constexpr RieszTopology value = RieszTopology::identity;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(Name) \
|
#define MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(Name) \
|
||||||
template <> struct DeclaredRieszTopology<models::RieszTopology::Name> { \
|
template <> struct DeclaredRieszTopology<models::RieszTopology::Name> { \
|
||||||
static constexpr bool available = true; \
|
static constexpr bool available = true; \
|
||||||
static constexpr RieszTopology value = RieszTopology::Name; \
|
static constexpr RieszTopology value = RieszTopology::Name; \
|
||||||
}
|
}
|
||||||
|
|
||||||
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(identity);
|
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(identity);
|
||||||
@@ -199,14 +196,14 @@ export namespace mean_field::normalization {
|
|||||||
#undef MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY
|
#undef MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY
|
||||||
|
|
||||||
template <models::PhysicalScaleLaw Scale> struct DeclaredPhysicalScale {
|
template <models::PhysicalScaleLaw Scale> struct DeclaredPhysicalScale {
|
||||||
static constexpr bool available = false;
|
static constexpr bool available = false;
|
||||||
static constexpr PhysicalScaleKind value = PhysicalScaleKind::dimensionless;
|
static constexpr PhysicalScaleKind value = PhysicalScaleKind::dimensionless;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MEAN_FIELD_DECLARED_PHYSICAL_SCALE(Name) \
|
#define MEAN_FIELD_DECLARED_PHYSICAL_SCALE(Name) \
|
||||||
template <> struct DeclaredPhysicalScale<models::PhysicalScaleLaw::Name> { \
|
template <> struct DeclaredPhysicalScale<models::PhysicalScaleLaw::Name> { \
|
||||||
static constexpr bool available = true; \
|
static constexpr bool available = true; \
|
||||||
static constexpr PhysicalScaleKind value = PhysicalScaleKind::Name; \
|
static constexpr PhysicalScaleKind value = PhysicalScaleKind::Name; \
|
||||||
}
|
}
|
||||||
|
|
||||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(dimensionless);
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(dimensionless);
|
||||||
@@ -223,9 +220,8 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
#undef MEAN_FIELD_DECLARED_PHYSICAL_SCALE
|
#undef MEAN_FIELD_DECLARED_PHYSICAL_SCALE
|
||||||
|
|
||||||
template <typename Declaration, typename = void>
|
template <typename Declaration, typename = void> struct CompileDeclaredPhysicalRieszCoordinate {
|
||||||
struct CompileDeclaredPhysicalRieszCoordinate {
|
using Method = UnsupportedPhysicalRieszCoordinate;
|
||||||
using Method = UnsupportedPhysicalRieszCoordinate;
|
|
||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -246,11 +242,11 @@ export namespace mean_field::normalization {
|
|||||||
static constexpr models::PhysicalScaleLaw declaredScale =
|
static constexpr models::PhysicalScaleLaw declaredScale =
|
||||||
static_cast<models::PhysicalScaleLaw>(Declaration::scale);
|
static_cast<models::PhysicalScaleLaw>(Declaration::scale);
|
||||||
using Topology = DeclaredRieszTopology<declaredTopology>;
|
using Topology = DeclaredRieszTopology<declaredTopology>;
|
||||||
using Scale = DeclaredPhysicalScale<declaredScale>;
|
using Scale = DeclaredPhysicalScale<declaredScale>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr bool registered = static_cast<bool>(Declaration::available) &&
|
static constexpr bool registered =
|
||||||
Topology::available && Scale::available;
|
static_cast<bool>(Declaration::available) && Topology::available && Scale::available;
|
||||||
using Method = std::conditional_t<
|
using Method = std::conditional_t<
|
||||||
registered,
|
registered,
|
||||||
PhysicalRieszCoordinate<Topology::value, Scale::value>,
|
PhysicalRieszCoordinate<Topology::value, Scale::value>,
|
||||||
@@ -259,7 +255,7 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
template <typename Generated, CoordinateKind Kind, typename = void>
|
template <typename Generated, CoordinateKind Kind, typename = void>
|
||||||
struct DeclaredGeneratedPhysicalRieszCoordinate {
|
struct DeclaredGeneratedPhysicalRieszCoordinate {
|
||||||
using Method = UnsupportedPhysicalRieszCoordinate;
|
using Method = UnsupportedPhysicalRieszCoordinate;
|
||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -271,9 +267,8 @@ export namespace mean_field::normalization {
|
|||||||
typename Generated::SpecificationType,
|
typename Generated::SpecificationType,
|
||||||
typename models::SpecificationContribution<
|
typename models::SpecificationContribution<
|
||||||
typename Generated::SpecificationType>::Normalization::Value>>
|
typename Generated::SpecificationType>::Normalization::Value>>
|
||||||
: CompileDeclaredPhysicalRieszCoordinate<
|
: CompileDeclaredPhysicalRieszCoordinate<typename models::SpecificationContribution<
|
||||||
typename models::SpecificationContribution<
|
typename Generated::SpecificationType>::Normalization::Value> { };
|
||||||
typename Generated::SpecificationType>::Normalization::Value> { };
|
|
||||||
|
|
||||||
template <typename Generated>
|
template <typename Generated>
|
||||||
struct DeclaredGeneratedPhysicalRieszCoordinate<
|
struct DeclaredGeneratedPhysicalRieszCoordinate<
|
||||||
@@ -283,12 +278,10 @@ export namespace mean_field::normalization {
|
|||||||
typename Generated::SpecificationType,
|
typename Generated::SpecificationType,
|
||||||
typename models::SpecificationContribution<
|
typename models::SpecificationContribution<
|
||||||
typename Generated::SpecificationType>::Normalization::Residual>>
|
typename Generated::SpecificationType>::Normalization::Residual>>
|
||||||
: CompileDeclaredPhysicalRieszCoordinate<
|
: CompileDeclaredPhysicalRieszCoordinate<typename models::SpecificationContribution<
|
||||||
typename models::SpecificationContribution<
|
typename Generated::SpecificationType>::Normalization::Residual> { };
|
||||||
typename Generated::SpecificationType>::Normalization::Residual> { };
|
|
||||||
|
|
||||||
template <typename GeneratedValues, typename GeneratedResiduals>
|
template <typename GeneratedValues, typename GeneratedResiduals> struct GeneratedPhysicalRieszCoverage {
|
||||||
struct GeneratedPhysicalRieszCoverage {
|
|
||||||
static constexpr bool complete = false;
|
static constexpr bool complete = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -297,16 +290,12 @@ export namespace mean_field::normalization {
|
|||||||
models::ModelTypeList<GeneratedValues...>,
|
models::ModelTypeList<GeneratedValues...>,
|
||||||
models::ModelTypeList<GeneratedResiduals...>> {
|
models::ModelTypeList<GeneratedResiduals...>> {
|
||||||
static constexpr bool complete =
|
static constexpr bool complete =
|
||||||
(DeclaredGeneratedPhysicalRieszCoordinate<
|
(DeclaredGeneratedPhysicalRieszCoordinate<GeneratedValues, CoordinateKind::value>::registered && ...) &&
|
||||||
GeneratedValues,
|
(DeclaredGeneratedPhysicalRieszCoordinate<GeneratedResiduals, CoordinateKind::residual>::registered &&
|
||||||
CoordinateKind::value>::registered && ...) &&
|
...);
|
||||||
(DeclaredGeneratedPhysicalRieszCoordinate<
|
|
||||||
GeneratedResiduals,
|
|
||||||
CoordinateKind::residual>::registered && ...);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Specification, typename = void>
|
template <typename Specification, typename = void> struct SpecificationPhysicalRieszCoverage {
|
||||||
struct SpecificationPhysicalRieszCoverage {
|
|
||||||
static constexpr bool complete = false;
|
static constexpr bool complete = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -355,29 +344,17 @@ export namespace mean_field::normalization {
|
|||||||
* adapter can consult the same authority without importing one another.
|
* adapter can consult the same authority without importing one another.
|
||||||
*/
|
*/
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept PhysicalRieszCoreRuntime =
|
concept PhysicalRieszCoreRuntime = requires(const std::remove_cvref_t<Candidate> &core) {
|
||||||
requires(const std::remove_cvref_t<Candidate> &core) {
|
{ core.GetGravityContext().GetDensityMap() } -> std::same_as<const field::FieldDofMap &>;
|
||||||
{
|
{ core.GetGravityContext().GetGravityGradientMap() } -> std::same_as<const field::FieldDofMap &>;
|
||||||
core.GetGravityContext().GetDensityMap()
|
{ core.GetGravityContext().GetGravityPotentialMap() } -> std::same_as<const field::FieldDofMap &>;
|
||||||
} -> std::same_as<const field::FieldDofMap &>;
|
{ core.GetHydrostaticOperator().GetEnthalpyMap() } -> std::same_as<const field::FieldDofMap &>;
|
||||||
{
|
{ core.GetDomainDeformation().parameterCount() } -> std::same_as<int>;
|
||||||
core.GetGravityContext().GetGravityGradientMap()
|
};
|
||||||
} -> std::same_as<const field::FieldDofMap &>;
|
|
||||||
{
|
|
||||||
core.GetGravityContext().GetGravityPotentialMap()
|
|
||||||
} -> std::same_as<const field::FieldDofMap &>;
|
|
||||||
{
|
|
||||||
core.GetHydrostaticOperator().GetEnthalpyMap()
|
|
||||||
} -> std::same_as<const field::FieldDofMap &>;
|
|
||||||
{
|
|
||||||
core.GetDomainDeformation().parameterCount()
|
|
||||||
} -> std::same_as<int>;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename Generated, CoordinateKind Kind>
|
template <typename Generated, CoordinateKind Kind>
|
||||||
using GeneratedPhysicalRieszMethod =
|
using GeneratedPhysicalRieszMethod = typename DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::Method;
|
||||||
typename DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::Method;
|
|
||||||
|
|
||||||
template <typename Generated, CoordinateKind Kind, typename = void>
|
template <typename Generated, CoordinateKind Kind, typename = void>
|
||||||
struct GeneratedPhysicalRieszRuntimeCoordinate : std::false_type { };
|
struct GeneratedPhysicalRieszRuntimeCoordinate : std::false_type { };
|
||||||
@@ -389,8 +366,7 @@ export namespace mean_field::normalization {
|
|||||||
std::void_t<decltype(GeneratedPhysicalRieszMethod<Generated, Kind>::topology)>>
|
std::void_t<decltype(GeneratedPhysicalRieszMethod<Generated, Kind>::topology)>>
|
||||||
: std::bool_constant<
|
: std::bool_constant<
|
||||||
DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::registered &&
|
DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::registered &&
|
||||||
GeneratedPhysicalRieszMethod<Generated, Kind>::topology ==
|
GeneratedPhysicalRieszMethod<Generated, Kind>::topology == RieszTopology::global_scalar> { };
|
||||||
RieszTopology::global_scalar> { };
|
|
||||||
|
|
||||||
template <typename Specification, typename = void>
|
template <typename Specification, typename = void>
|
||||||
struct SpecificationPhysicalRieszRuntimeCoverage : std::false_type { };
|
struct SpecificationPhysicalRieszRuntimeCoverage : std::false_type { };
|
||||||
@@ -420,21 +396,18 @@ export namespace mean_field::normalization {
|
|||||||
struct SpecificationSetPhysicalRieszRuntimeCoverage : std::false_type { };
|
struct SpecificationSetPhysicalRieszRuntimeCoverage : std::false_type { };
|
||||||
|
|
||||||
template <models::ModelSpecification... Specifications>
|
template <models::ModelSpecification... Specifications>
|
||||||
struct SpecificationSetPhysicalRieszRuntimeCoverage<
|
struct SpecificationSetPhysicalRieszRuntimeCoverage<models::detail::SpecificationSetStorage<Specifications...>>
|
||||||
models::detail::SpecificationSetStorage<Specifications...>>
|
: std::bool_constant<(SpecificationPhysicalRieszRuntimeCoverage<Specifications>::value && ...)> { };
|
||||||
: std::bool_constant<
|
|
||||||
(SpecificationPhysicalRieszRuntimeCoverage<Specifications>::value && ...)> { };
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <typename Specification>
|
template <typename Specification>
|
||||||
concept CompleteGeneratedPhysicalRieszRuntimeNormalizationFor =
|
concept CompleteGeneratedPhysicalRieszRuntimeNormalizationFor =
|
||||||
detail::SpecificationPhysicalRieszRuntimeCoverage<
|
detail::SpecificationPhysicalRieszRuntimeCoverage<std::remove_cvref_t<Specification>>::value;
|
||||||
std::remove_cvref_t<Specification>>::value;
|
|
||||||
|
|
||||||
#define MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(BlockType, TopologyValue, ScaleValue) \
|
#define MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(BlockType, TopologyValue, ScaleValue) \
|
||||||
template <> struct PhysicalRieszBlockTraits<BlockType> { \
|
template <> struct PhysicalRieszBlockTraits<BlockType> { \
|
||||||
using Method = PhysicalRieszCoordinate<RieszTopology::TopologyValue, PhysicalScaleKind::ScaleValue>; \
|
using Method = PhysicalRieszCoordinate<RieszTopology::TopologyValue, PhysicalScaleKind::ScaleValue>; \
|
||||||
static constexpr bool registered = true; \
|
static constexpr bool registered = true; \
|
||||||
}
|
}
|
||||||
|
|
||||||
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||||
@@ -490,12 +463,9 @@ export namespace mean_field::normalization {
|
|||||||
);
|
);
|
||||||
#undef MEAN_FIELD_PHYSICAL_RIESZ_TRAIT
|
#undef MEAN_FIELD_PHYSICAL_RIESZ_TRAIT
|
||||||
|
|
||||||
template <typename Block>
|
template <typename Block> [[nodiscard]] double physicalScale(const StellarCharacteristicScales &scales) {
|
||||||
[[nodiscard]] double physicalScale(
|
|
||||||
const StellarCharacteristicScales &scales
|
|
||||||
) {
|
|
||||||
static_assert(PhysicalRieszBlockTraits<Block>::registered, "The block has no Physical Riesz normalization.");
|
static_assert(PhysicalRieszBlockTraits<Block>::registered, "The block has no Physical Riesz normalization.");
|
||||||
using Method = typename PhysicalRieszBlockTraits<Block>::Method;
|
using Method = typename PhysicalRieszBlockTraits<Block>::Method;
|
||||||
constexpr PhysicalScaleKind scale = Method::scale;
|
constexpr PhysicalScaleKind scale = Method::scale;
|
||||||
if constexpr (scale == PhysicalScaleKind::dimensionless) {
|
if constexpr (scale == PhysicalScaleKind::dimensionless) {
|
||||||
return 1.0;
|
return 1.0;
|
||||||
@@ -527,9 +497,7 @@ export namespace mean_field::normalization {
|
|||||||
template <typename Values, typename Residuals> struct MakePhysicalRieszPlan;
|
template <typename Values, typename Residuals> struct MakePhysicalRieszPlan;
|
||||||
|
|
||||||
template <typename... Values, typename... Residuals>
|
template <typename... Values, typename... Residuals>
|
||||||
struct MakePhysicalRieszPlan<
|
struct MakePhysicalRieszPlan<utils::blocks::type_list<Values...>, utils::blocks::type_list<Residuals...>> {
|
||||||
utils::blocks::type_list<Values...>,
|
|
||||||
utils::blocks::type_list<Residuals...>> {
|
|
||||||
using Type = NormalizationPlan<
|
using Type = NormalizationPlan<
|
||||||
CoordinateComponent<
|
CoordinateComponent<
|
||||||
CoordinateKind::value,
|
CoordinateKind::value,
|
||||||
@@ -544,9 +512,8 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
requires utils::blocks::block_form_is_valid_v<Form>
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
using PhysicalRieszNormalizationPlanFor = typename detail::MakePhysicalRieszPlan<
|
using PhysicalRieszNormalizationPlanFor =
|
||||||
typename Form::value_blocks,
|
typename detail::MakePhysicalRieszPlan<typename Form::value_blocks, typename Form::residual_blocks>::Type;
|
||||||
typename Form::residual_blocks>::Type;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Public compile-time extension point for a normalization prescription.
|
* Public compile-time extension point for a normalization prescription.
|
||||||
@@ -557,11 +524,10 @@ export namespace mean_field::normalization {
|
|||||||
* actually prepare.
|
* actually prepare.
|
||||||
*/
|
*/
|
||||||
template <typename Prescription, typename Form> struct NormalizationCompilation {
|
template <typename Prescription, typename Form> struct NormalizationCompilation {
|
||||||
using Plan = NormalizationPlan<>;
|
using Plan = NormalizationPlan<>;
|
||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
|
|
||||||
template <typename PhysicalCore, typename SpecificationTypes>
|
template <typename PhysicalCore, typename SpecificationTypes> static constexpr bool runtimeAvailableFor = false;
|
||||||
static constexpr bool runtimeAvailableFor = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Astronomy/numerics-facing package for a policy which prepares one
|
/* Astronomy/numerics-facing package for a policy which prepares one
|
||||||
@@ -571,7 +537,7 @@ export namespace mean_field::normalization {
|
|||||||
template <NormalizationPrescription Prescription, typename Form>
|
template <NormalizationPrescription Prescription, typename Form>
|
||||||
requires utils::blocks::block_form_is_valid_v<Form>
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
struct RuntimePreparedNormalizationCompilation {
|
struct RuntimePreparedNormalizationCompilation {
|
||||||
using Plan = RuntimePreparedNormalizationPlanFor<Prescription, Form>;
|
using Plan = RuntimePreparedNormalizationPlanFor<Prescription, Form>;
|
||||||
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
||||||
|
|
||||||
template <typename PhysicalCore, typename SpecificationTypes>
|
template <typename PhysicalCore, typename SpecificationTypes>
|
||||||
@@ -581,7 +547,7 @@ export namespace mean_field::normalization {
|
|||||||
template <typename Form>
|
template <typename Form>
|
||||||
requires utils::blocks::block_form_is_valid_v<Form>
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
struct NormalizationCompilation<Unnormalized, Form> {
|
struct NormalizationCompilation<Unnormalized, Form> {
|
||||||
using Plan = IdentityNormalizationPlanFor<Form>;
|
using Plan = IdentityNormalizationPlanFor<Form>;
|
||||||
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
||||||
|
|
||||||
template <typename PhysicalCore, typename SpecificationTypes>
|
template <typename PhysicalCore, typename SpecificationTypes>
|
||||||
@@ -591,21 +557,18 @@ export namespace mean_field::normalization {
|
|||||||
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource, typename Form>
|
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource, typename Form>
|
||||||
requires utils::blocks::block_form_is_valid_v<Form>
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
struct NormalizationCompilation<PhysicalRieszDiagonal<Geometry, ScaleSource>, Form> {
|
struct NormalizationCompilation<PhysicalRieszDiagonal<Geometry, ScaleSource>, Form> {
|
||||||
using Plan = PhysicalRieszNormalizationPlanFor<Form>;
|
using Plan = PhysicalRieszNormalizationPlanFor<Form>;
|
||||||
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
||||||
|
|
||||||
template <typename PhysicalCore, typename SpecificationTypes>
|
template <typename PhysicalCore, typename SpecificationTypes>
|
||||||
static constexpr bool runtimeAvailableFor =
|
static constexpr bool runtimeAvailableFor =
|
||||||
registered &&
|
registered && PhysicalRieszCoreRuntime<std::remove_cvref_t<PhysicalCore>> &&
|
||||||
PhysicalRieszCoreRuntime<std::remove_cvref_t<PhysicalCore>> &&
|
detail::SpecificationSetPhysicalRieszRuntimeCoverage<std::remove_cvref_t<SpecificationTypes>>::value;
|
||||||
detail::SpecificationSetPhysicalRieszRuntimeCoverage<
|
|
||||||
std::remove_cvref_t<SpecificationTypes>>::value;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename Prescription, typename Form, typename = void>
|
template <typename Prescription, typename Form, typename = void> struct NormalizationCompilationAudit {
|
||||||
struct NormalizationCompilationAudit {
|
using Plan = NormalizationPlan<>;
|
||||||
using Plan = NormalizationPlan<>;
|
|
||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -616,34 +579,25 @@ export namespace mean_field::normalization {
|
|||||||
Prescription,
|
Prescription,
|
||||||
Form,
|
Form,
|
||||||
std::void_t<
|
std::void_t<
|
||||||
typename NormalizationCompilation<
|
typename NormalizationCompilation<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>>::Plan,
|
||||||
std::remove_cvref_t<Prescription>,
|
decltype(std::bool_constant<static_cast<bool>(NormalizationCompilation<
|
||||||
std::remove_cvref_t<Form>>::Plan,
|
std::remove_cvref_t<Prescription>,
|
||||||
decltype(std::bool_constant<static_cast<bool>(
|
std::remove_cvref_t<Form>>::registered)>{})>> {
|
||||||
NormalizationCompilation<
|
using Compilation = NormalizationCompilation<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>>;
|
||||||
std::remove_cvref_t<Prescription>,
|
using Plan = typename Compilation::Plan;
|
||||||
std::remove_cvref_t<Form>>::registered)>{})>> {
|
|
||||||
using Compilation = NormalizationCompilation<
|
|
||||||
std::remove_cvref_t<Prescription>,
|
|
||||||
std::remove_cvref_t<Form>>;
|
|
||||||
using Plan = typename Compilation::Plan;
|
|
||||||
|
|
||||||
static constexpr bool registered =
|
static constexpr bool registered =
|
||||||
static_cast<bool>(Compilation::registered) &&
|
static_cast<bool>(Compilation::registered) && CompleteNormalizationFor<Plan, std::remove_cvref_t<Form>>;
|
||||||
CompleteNormalizationFor<Plan, std::remove_cvref_t<Form>>;
|
|
||||||
};
|
};
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <NormalizationPrescription Prescription, typename Form>
|
template <NormalizationPrescription Prescription, typename Form>
|
||||||
using NormalizationPlanFor = typename detail::NormalizationCompilationAudit<
|
using NormalizationPlanFor =
|
||||||
std::remove_cvref_t<Prescription>,
|
typename detail::NormalizationCompilationAudit<std::remove_cvref_t<Prescription>, Form>::Plan;
|
||||||
Form>::Plan;
|
|
||||||
|
|
||||||
template <typename Prescription, typename Form>
|
template <typename Prescription, typename Form>
|
||||||
concept CompilableNormalizationFor =
|
concept CompilableNormalizationFor =
|
||||||
detail::NormalizationCompilationAudit<
|
detail::NormalizationCompilationAudit<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>>::registered;
|
||||||
std::remove_cvref_t<Prescription>,
|
|
||||||
std::remove_cvref_t<Form>>::registered;
|
|
||||||
|
|
||||||
/* The public runtime-preparation adapter is intentionally narrower than
|
/* The public runtime-preparation adapter is intentionally narrower than
|
||||||
* an arbitrary complete plan: every coordinate must name the exact policy
|
* an arbitrary complete plan: every coordinate must name the exact policy
|
||||||
@@ -654,16 +608,10 @@ export namespace mean_field::normalization {
|
|||||||
concept RuntimePreparedNormalizationFor =
|
concept RuntimePreparedNormalizationFor =
|
||||||
NormalizationPrescription<std::remove_cvref_t<Prescription>> &&
|
NormalizationPrescription<std::remove_cvref_t<Prescription>> &&
|
||||||
utils::blocks::block_form_is_valid_v<std::remove_cvref_t<Form>> &&
|
utils::blocks::block_form_is_valid_v<std::remove_cvref_t<Form>> &&
|
||||||
CompilableNormalizationFor<
|
CompilableNormalizationFor<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>> &&
|
||||||
std::remove_cvref_t<Prescription>,
|
|
||||||
std::remove_cvref_t<Form>> &&
|
|
||||||
std::same_as<
|
std::same_as<
|
||||||
NormalizationPlanFor<
|
NormalizationPlanFor<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>>,
|
||||||
std::remove_cvref_t<Prescription>,
|
RuntimePreparedNormalizationPlanFor<std::remove_cvref_t<Prescription>, std::remove_cvref_t<Form>>>;
|
||||||
std::remove_cvref_t<Form>>,
|
|
||||||
RuntimePreparedNormalizationPlanFor<
|
|
||||||
std::remove_cvref_t<Prescription>,
|
|
||||||
std::remove_cvref_t<Form>>>;
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <
|
template <
|
||||||
@@ -674,35 +622,22 @@ export namespace mean_field::normalization {
|
|||||||
typename = void>
|
typename = void>
|
||||||
struct StellarNormalizationRuntimeAudit : std::false_type { };
|
struct StellarNormalizationRuntimeAudit : std::false_type { };
|
||||||
|
|
||||||
template <
|
template <typename Prescription, typename Form, typename PhysicalCore, typename SpecificationTypes>
|
||||||
typename Prescription,
|
|
||||||
typename Form,
|
|
||||||
typename PhysicalCore,
|
|
||||||
typename SpecificationTypes>
|
|
||||||
struct StellarNormalizationRuntimeAudit<
|
struct StellarNormalizationRuntimeAudit<
|
||||||
Prescription,
|
Prescription,
|
||||||
Form,
|
Form,
|
||||||
PhysicalCore,
|
PhysicalCore,
|
||||||
SpecificationTypes,
|
SpecificationTypes,
|
||||||
std::void_t<
|
std::void_t<
|
||||||
std::enable_if_t<NormalizationCompilationAudit<
|
std::enable_if_t<NormalizationCompilationAudit<Prescription, Form>::registered>,
|
||||||
Prescription,
|
decltype(std::bool_constant<
|
||||||
Form>::registered>,
|
static_cast<bool>(NormalizationCompilation<Prescription, Form>::
|
||||||
decltype(std::bool_constant<static_cast<bool>(
|
template runtimeAvailableFor<PhysicalCore, SpecificationTypes>)>{})>>
|
||||||
NormalizationCompilation<
|
|
||||||
Prescription,
|
|
||||||
Form>::template runtimeAvailableFor<
|
|
||||||
PhysicalCore,
|
|
||||||
SpecificationTypes>)>{})>>
|
|
||||||
: std::bool_constant<
|
: std::bool_constant<
|
||||||
(std::same_as<Prescription, Unnormalized> ||
|
(std::same_as<Prescription, Unnormalized> || PhysicalRieszDiagonalPrescription<Prescription> ||
|
||||||
PhysicalRieszDiagonalPrescription<Prescription> ||
|
|
||||||
RuntimePreparedNormalizationFor<Prescription, Form>) &&
|
RuntimePreparedNormalizationFor<Prescription, Form>) &&
|
||||||
static_cast<bool>(NormalizationCompilation<
|
static_cast<bool>(NormalizationCompilation<Prescription, Form>::
|
||||||
Prescription,
|
template runtimeAvailableFor<PhysicalCore, SpecificationTypes>)> { };
|
||||||
Form>::template runtimeAvailableFor<
|
|
||||||
PhysicalCore,
|
|
||||||
SpecificationTypes>)> { };
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -714,15 +649,10 @@ export namespace mean_field::normalization {
|
|||||||
* assembly and global-scalar runtime preparation for every generated
|
* assembly and global-scalar runtime preparation for every generated
|
||||||
* coordinate in the specification pack.
|
* coordinate in the specification pack.
|
||||||
*/
|
*/
|
||||||
template <
|
template <typename Prescription, typename Form, typename PhysicalCore, typename SpecificationTypes>
|
||||||
typename Prescription,
|
concept StellarNormalizationRuntimeAvailableFor = detail::StellarNormalizationRuntimeAudit<
|
||||||
typename Form,
|
std::remove_cvref_t<Prescription>,
|
||||||
typename PhysicalCore,
|
std::remove_cvref_t<Form>,
|
||||||
typename SpecificationTypes>
|
std::remove_cvref_t<PhysicalCore>,
|
||||||
concept StellarNormalizationRuntimeAvailableFor =
|
std::remove_cvref_t<SpecificationTypes>>::value;
|
||||||
detail::StellarNormalizationRuntimeAudit<
|
|
||||||
std::remove_cvref_t<Prescription>,
|
|
||||||
std::remove_cvref_t<Form>,
|
|
||||||
std::remove_cvref_t<PhysicalCore>,
|
|
||||||
std::remove_cvref_t<SpecificationTypes>>::value;
|
|
||||||
} // namespace mean_field::normalization
|
} // namespace mean_field::normalization
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ export namespace mean_field::normalization {
|
|||||||
struct NormalizationPrescriptionTag { };
|
struct NormalizationPrescriptionTag { };
|
||||||
|
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept NormalizationPrescription =
|
concept NormalizationPrescription = std::derived_from<std::remove_cvref_t<Candidate>, NormalizationPrescriptionTag>;
|
||||||
std::derived_from<
|
|
||||||
std::remove_cvref_t<Candidate>,
|
|
||||||
NormalizationPrescriptionTag>;
|
|
||||||
|
|
||||||
enum class CoordinateKind { value, residual };
|
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
|
* numerical value of that factor. The owner type prevents one policy from
|
||||||
* silently presenting another policy's runtime map as its own plan.
|
* silently presenting another policy's runtime map as its own plan.
|
||||||
*/
|
*/
|
||||||
template <NormalizationPrescription Prescription>
|
template <NormalizationPrescription Prescription> struct RuntimePreparedCoordinate final {
|
||||||
struct RuntimePreparedCoordinate final {
|
|
||||||
using PrescriptionType = std::remove_cvref_t<Prescription>;
|
using PrescriptionType = std::remove_cvref_t<Prescription>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <RieszTopology Topology, PhysicalScaleKind Scale> struct PhysicalRieszCoordinate final {
|
template <RieszTopology Topology, PhysicalScaleKind Scale> struct PhysicalRieszCoordinate final {
|
||||||
static constexpr RieszTopology topology = Topology;
|
static constexpr RieszTopology topology = Topology;
|
||||||
static constexpr PhysicalScaleKind scale = Scale;
|
static constexpr PhysicalScaleKind scale = Scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UnsupportedPhysicalRieszCoordinate final { };
|
struct UnsupportedPhysicalRieszCoordinate final { };
|
||||||
|
|
||||||
template <typename Block> struct PhysicalRieszBlockTraits {
|
template <typename Block> struct PhysicalRieszBlockTraits {
|
||||||
using Method = UnsupportedPhysicalRieszCoordinate;
|
using Method = UnsupportedPhysicalRieszCoordinate;
|
||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <CoordinateKind Kind, typename BlockList, typename MethodType>
|
template <CoordinateKind Kind, typename BlockList, typename MethodType> struct CoordinateComponent final {
|
||||||
struct CoordinateComponent final {
|
|
||||||
using Blocks = BlockList;
|
using Blocks = BlockList;
|
||||||
using Method = MethodType;
|
using Method = MethodType;
|
||||||
static constexpr CoordinateKind kind = Kind;
|
static constexpr CoordinateKind kind = Kind;
|
||||||
|
|
||||||
using ValueBlocks = std::conditional_t<
|
using ValueBlocks = std::conditional_t<Kind == CoordinateKind::value, BlockList, utils::blocks::type_list<>>;
|
||||||
Kind == CoordinateKind::value,
|
using ResidualBlocks =
|
||||||
BlockList,
|
std::conditional_t<Kind == CoordinateKind::residual, BlockList, utils::blocks::type_list<>>;
|
||||||
utils::blocks::type_list<>>;
|
|
||||||
using ResidualBlocks = std::conditional_t<
|
|
||||||
Kind == CoordinateKind::residual,
|
|
||||||
BlockList,
|
|
||||||
utils::blocks::type_list<>>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename Candidate> struct IsTypeList : std::false_type { };
|
template <typename Candidate> struct IsTypeList : std::false_type { };
|
||||||
|
|
||||||
template <typename... Types>
|
template <typename... Types> struct IsTypeList<utils::blocks::type_list<Types...>> : std::true_type { };
|
||||||
struct IsTypeList<utils::blocks::type_list<Types...>> : std::true_type { };
|
|
||||||
|
|
||||||
template <typename List, typename Base> struct IsUniqueDerivedBlockList : std::false_type { };
|
template <typename List, typename Base> struct IsUniqueDerivedBlockList : std::false_type { };
|
||||||
|
|
||||||
@@ -102,8 +91,7 @@ export namespace mean_field::normalization {
|
|||||||
template <> struct IsCoordinateMethod<IdentityCoordinate> : std::true_type { };
|
template <> struct IsCoordinateMethod<IdentityCoordinate> : std::true_type { };
|
||||||
|
|
||||||
template <NormalizationPrescription Prescription>
|
template <NormalizationPrescription Prescription>
|
||||||
struct IsCoordinateMethod<RuntimePreparedCoordinate<Prescription>>
|
struct IsCoordinateMethod<RuntimePreparedCoordinate<Prescription>> : std::true_type { };
|
||||||
: std::true_type { };
|
|
||||||
|
|
||||||
template <RieszTopology Topology, PhysicalScaleKind Scale>
|
template <RieszTopology Topology, PhysicalScaleKind Scale>
|
||||||
struct IsCoordinateMethod<PhysicalRieszCoordinate<Topology, Scale>> : std::true_type { };
|
struct IsCoordinateMethod<PhysicalRieszCoordinate<Topology, Scale>> : std::true_type { };
|
||||||
@@ -121,10 +109,9 @@ export namespace mean_field::normalization {
|
|||||||
template <RieszTopology Topology, PhysicalScaleKind Scale, typename Block>
|
template <RieszTopology Topology, PhysicalScaleKind Scale, typename Block>
|
||||||
struct MethodSupportsBlock<PhysicalRieszCoordinate<Topology, Scale>, Block>
|
struct MethodSupportsBlock<PhysicalRieszCoordinate<Topology, Scale>, Block>
|
||||||
: std::bool_constant<
|
: std::bool_constant<
|
||||||
PhysicalRieszBlockTraits<Block>::registered &&
|
PhysicalRieszBlockTraits<Block>::registered && std::same_as<
|
||||||
std::same_as<
|
typename PhysicalRieszBlockTraits<Block>::Method,
|
||||||
typename PhysicalRieszBlockTraits<Block>::Method,
|
PhysicalRieszCoordinate<Topology, Scale>>> { };
|
||||||
PhysicalRieszCoordinate<Topology, Scale>>> { };
|
|
||||||
|
|
||||||
template <typename Method, typename List> struct MethodSupportsEveryBlock : std::false_type { };
|
template <typename Method, typename List> struct MethodSupportsEveryBlock : std::false_type { };
|
||||||
|
|
||||||
@@ -166,8 +153,7 @@ export namespace mean_field::normalization {
|
|||||||
}();
|
}();
|
||||||
|
|
||||||
static constexpr bool hasCoherentCoordinateLists = [] {
|
static constexpr bool hasCoherentCoordinateLists = [] {
|
||||||
if constexpr (!hasValidKind || !IsTypeList<ValueBlocks>::value ||
|
if constexpr (!hasValidKind || !IsTypeList<ValueBlocks>::value || !IsTypeList<ResidualBlocks>::value) {
|
||||||
!IsTypeList<ResidualBlocks>::value) {
|
|
||||||
return false;
|
return false;
|
||||||
} else if constexpr (Candidate::kind == CoordinateKind::value) {
|
} else if constexpr (Candidate::kind == CoordinateKind::value) {
|
||||||
return std::same_as<ValueBlocks, Blocks> &&
|
return std::same_as<ValueBlocks, Blocks> &&
|
||||||
@@ -182,8 +168,7 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
static constexpr bool valid = hasValidKind && IsTypeList<Blocks>::value &&
|
static constexpr bool valid = hasValidKind && IsTypeList<Blocks>::value &&
|
||||||
IsCoordinateMethod<Method>::value && hasValidBlockList &&
|
IsCoordinateMethod<Method>::value && hasValidBlockList &&
|
||||||
hasCoherentCoordinateLists &&
|
hasCoherentCoordinateLists && MethodSupportsEveryBlock<Method, Blocks>::value;
|
||||||
MethodSupportsEveryBlock<Method, Blocks>::value;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Lists> struct Concatenate;
|
template <typename... Lists> struct Concatenate;
|
||||||
@@ -205,23 +190,18 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
template <typename List, typename Type> struct Append;
|
template <typename List, typename Type> struct Append;
|
||||||
|
|
||||||
template <typename... Types, typename Appended>
|
template <typename... Types, typename Appended> struct Append<utils::blocks::type_list<Types...>, Appended> {
|
||||||
struct Append<utils::blocks::type_list<Types...>, Appended> {
|
|
||||||
using Type = utils::blocks::type_list<Types..., Appended>;
|
using Type = utils::blocks::type_list<Types..., Appended>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename List, typename Type> using AppendT = typename Append<List, Type>::Type;
|
template <typename List, typename Type> using AppendT = typename Append<List, Type>::Type;
|
||||||
|
|
||||||
template <typename List, typename Type>
|
template <typename List, typename Type>
|
||||||
using AppendUniqueT = std::conditional_t<
|
using AppendUniqueT = std::conditional_t<utils::blocks::contains_type_v<Type, List>, List, AppendT<List, Type>>;
|
||||||
utils::blocks::contains_type_v<Type, List>,
|
|
||||||
List,
|
|
||||||
AppendT<List, Type>>;
|
|
||||||
|
|
||||||
template <typename Source, typename Excluded> struct ListDifference;
|
template <typename Source, typename Excluded> struct ListDifference;
|
||||||
|
|
||||||
template <typename Excluded>
|
template <typename Excluded> struct ListDifference<utils::blocks::type_list<>, Excluded> {
|
||||||
struct ListDifference<utils::blocks::type_list<>, Excluded> {
|
|
||||||
using Type = utils::blocks::type_list<>;
|
using Type = utils::blocks::type_list<>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -260,10 +240,7 @@ export namespace mean_field::normalization {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename List>
|
template <typename List>
|
||||||
using RepeatedTypesT = typename CollectRepeatedTypes<
|
using RepeatedTypesT = typename CollectRepeatedTypes<List, List, utils::blocks::type_list<>>::Type;
|
||||||
List,
|
|
||||||
List,
|
|
||||||
utils::blocks::type_list<>>::Type;
|
|
||||||
|
|
||||||
template <typename Candidate, typename = void> struct PlanTraits {
|
template <typename Candidate, typename = void> struct PlanTraits {
|
||||||
static constexpr bool valid = false;
|
static constexpr bool valid = false;
|
||||||
@@ -274,23 +251,20 @@ export namespace mean_field::normalization {
|
|||||||
concept NormalizationComponent = detail::ComponentTraits<std::remove_cvref_t<Candidate>>::valid;
|
concept NormalizationComponent = detail::ComponentTraits<std::remove_cvref_t<Candidate>>::valid;
|
||||||
|
|
||||||
template <typename... Components> struct NormalizationPlan final {
|
template <typename... Components> struct NormalizationPlan final {
|
||||||
using ComponentTypes = utils::blocks::type_list<Components...>;
|
using ComponentTypes = utils::blocks::type_list<Components...>;
|
||||||
using ValueBlocks = detail::ConcatenateT<typename Components::ValueBlocks...>;
|
using ValueBlocks = detail::ConcatenateT<typename Components::ValueBlocks...>;
|
||||||
using ResidualBlocks = detail::ConcatenateT<typename Components::ResidualBlocks...>;
|
using ResidualBlocks = detail::ConcatenateT<typename Components::ResidualBlocks...>;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename... Components>
|
template <typename... Components> struct PlanTraits<NormalizationPlan<Components...>> {
|
||||||
struct PlanTraits<NormalizationPlan<Components...>> {
|
|
||||||
static constexpr bool valid = (ComponentTraits<Components>::valid && ...);
|
static constexpr bool valid = (ComponentTraits<Components>::valid && ...);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Values, typename Residuals> struct MakeIdentityPlan;
|
template <typename Values, typename Residuals> struct MakeIdentityPlan;
|
||||||
|
|
||||||
template <typename... Values, typename... Residuals>
|
template <typename... Values, typename... Residuals>
|
||||||
struct MakeIdentityPlan<
|
struct MakeIdentityPlan<utils::blocks::type_list<Values...>, utils::blocks::type_list<Residuals...>> {
|
||||||
utils::blocks::type_list<Values...>,
|
|
||||||
utils::blocks::type_list<Residuals...>> {
|
|
||||||
using Type = NormalizationPlan<
|
using Type = NormalizationPlan<
|
||||||
CoordinateComponent<CoordinateKind::value, utils::blocks::type_list<Values>, IdentityCoordinate>...,
|
CoordinateComponent<CoordinateKind::value, utils::blocks::type_list<Values>, IdentityCoordinate>...,
|
||||||
CoordinateComponent<
|
CoordinateComponent<
|
||||||
@@ -299,30 +273,18 @@ export namespace mean_field::normalization {
|
|||||||
IdentityCoordinate>...>;
|
IdentityCoordinate>...>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <
|
template <NormalizationPrescription Prescription, typename Values, typename Residuals>
|
||||||
NormalizationPrescription Prescription,
|
|
||||||
typename Values,
|
|
||||||
typename Residuals>
|
|
||||||
struct MakeRuntimePreparedPlan;
|
struct MakeRuntimePreparedPlan;
|
||||||
|
|
||||||
template <
|
template <NormalizationPrescription Prescription, typename... Values, typename... Residuals>
|
||||||
NormalizationPrescription Prescription,
|
|
||||||
typename... Values,
|
|
||||||
typename... Residuals>
|
|
||||||
struct MakeRuntimePreparedPlan<
|
struct MakeRuntimePreparedPlan<
|
||||||
Prescription,
|
Prescription,
|
||||||
utils::blocks::type_list<Values...>,
|
utils::blocks::type_list<Values...>,
|
||||||
utils::blocks::type_list<Residuals...>> {
|
utils::blocks::type_list<Residuals...>> {
|
||||||
using Method = RuntimePreparedCoordinate<Prescription>;
|
using Method = RuntimePreparedCoordinate<Prescription>;
|
||||||
using Type = NormalizationPlan<
|
using Type = NormalizationPlan<
|
||||||
CoordinateComponent<
|
CoordinateComponent<CoordinateKind::value, utils::blocks::type_list<Values>, Method>...,
|
||||||
CoordinateKind::value,
|
CoordinateComponent<CoordinateKind::residual, utils::blocks::type_list<Residuals>, Method>...>;
|
||||||
utils::blocks::type_list<Values>,
|
|
||||||
Method>...,
|
|
||||||
CoordinateComponent<
|
|
||||||
CoordinateKind::residual,
|
|
||||||
utils::blocks::type_list<Residuals>,
|
|
||||||
Method>...>;
|
|
||||||
};
|
};
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
@@ -331,46 +293,43 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
requires utils::blocks::block_form_is_valid_v<Form>
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
using IdentityNormalizationPlanFor = typename detail::MakeIdentityPlan<
|
using IdentityNormalizationPlanFor =
|
||||||
typename Form::value_blocks,
|
typename detail::MakeIdentityPlan<typename Form::value_blocks, typename Form::residual_blocks>::Type;
|
||||||
typename Form::residual_blocks>::Type;
|
|
||||||
|
|
||||||
template <NormalizationPrescription Prescription, typename Form>
|
template <NormalizationPrescription Prescription, typename Form>
|
||||||
requires utils::blocks::block_form_is_valid_v<Form>
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
using RuntimePreparedNormalizationPlanFor =
|
using RuntimePreparedNormalizationPlanFor = typename detail::MakeRuntimePreparedPlan<
|
||||||
typename detail::MakeRuntimePreparedPlan<
|
std::remove_cvref_t<Prescription>,
|
||||||
std::remove_cvref_t<Prescription>,
|
typename Form::value_blocks,
|
||||||
typename Form::value_blocks,
|
typename Form::residual_blocks>::Type;
|
||||||
typename Form::residual_blocks>::Type;
|
|
||||||
|
|
||||||
template <typename Form, typename Plan>
|
template <typename Form, typename Plan>
|
||||||
requires utils::blocks::block_form_is_valid_v<Form>
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
struct NormalizationCoverage final {
|
struct NormalizationCoverage final {
|
||||||
using DeclaredValueBlocks = typename Plan::ValueBlocks;
|
using DeclaredValueBlocks = typename Plan::ValueBlocks;
|
||||||
using DeclaredResidualBlocks = typename Plan::ResidualBlocks;
|
using DeclaredResidualBlocks = typename Plan::ResidualBlocks;
|
||||||
|
|
||||||
using MissingValueBlocks = detail::ListDifferenceT<typename Form::value_blocks, DeclaredValueBlocks>;
|
using MissingValueBlocks = detail::ListDifferenceT<typename Form::value_blocks, DeclaredValueBlocks>;
|
||||||
using UnexpectedValueBlocks = detail::ListDifferenceT<DeclaredValueBlocks, typename Form::value_blocks>;
|
using UnexpectedValueBlocks = detail::ListDifferenceT<DeclaredValueBlocks, typename Form::value_blocks>;
|
||||||
using RepeatedValueBlocks = detail::RepeatedTypesT<DeclaredValueBlocks>;
|
using RepeatedValueBlocks = detail::RepeatedTypesT<DeclaredValueBlocks>;
|
||||||
|
|
||||||
using MissingResidualBlocks = detail::ListDifferenceT<typename Form::residual_blocks, DeclaredResidualBlocks>;
|
using MissingResidualBlocks = detail::ListDifferenceT<typename Form::residual_blocks, DeclaredResidualBlocks>;
|
||||||
using UnexpectedResidualBlocks = detail::ListDifferenceT<DeclaredResidualBlocks, typename Form::residual_blocks>;
|
using UnexpectedResidualBlocks =
|
||||||
using RepeatedResidualBlocks = detail::RepeatedTypesT<DeclaredResidualBlocks>;
|
detail::ListDifferenceT<DeclaredResidualBlocks, typename Form::residual_blocks>;
|
||||||
|
using RepeatedResidualBlocks = detail::RepeatedTypesT<DeclaredResidualBlocks>;
|
||||||
|
|
||||||
static constexpr bool hasEveryValueBlock = MissingValueBlocks::size == 0;
|
static constexpr bool hasEveryValueBlock = MissingValueBlocks::size == 0;
|
||||||
static constexpr bool hasOnlyValueBlocks = UnexpectedValueBlocks::size == 0;
|
static constexpr bool hasOnlyValueBlocks = UnexpectedValueBlocks::size == 0;
|
||||||
static constexpr bool hasUniqueValueOwners = RepeatedValueBlocks::size == 0;
|
static constexpr bool hasUniqueValueOwners = RepeatedValueBlocks::size == 0;
|
||||||
static constexpr bool hasEveryResidualBlock = MissingResidualBlocks::size == 0;
|
static constexpr bool hasEveryResidualBlock = MissingResidualBlocks::size == 0;
|
||||||
static constexpr bool hasOnlyResidualBlocks = UnexpectedResidualBlocks::size == 0;
|
static constexpr bool hasOnlyResidualBlocks = UnexpectedResidualBlocks::size == 0;
|
||||||
static constexpr bool hasUniqueResidualOwners = RepeatedResidualBlocks::size == 0;
|
static constexpr bool hasUniqueResidualOwners = RepeatedResidualBlocks::size == 0;
|
||||||
|
|
||||||
static constexpr bool complete = hasEveryValueBlock && hasOnlyValueBlocks && hasUniqueValueOwners &&
|
static constexpr bool complete = hasEveryValueBlock && hasOnlyValueBlocks && hasUniqueValueOwners &&
|
||||||
hasEveryResidualBlock && hasOnlyResidualBlocks &&
|
hasEveryResidualBlock && hasOnlyResidualBlocks && hasUniqueResidualOwners;
|
||||||
hasUniqueResidualOwners;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Plan, typename Form>
|
template <typename Plan, typename Form>
|
||||||
concept CompleteNormalizationFor = utils::blocks::block_form_is_valid_v<Form> &&
|
concept CompleteNormalizationFor = utils::blocks::block_form_is_valid_v<Form> && NormalizationPlanType<Plan> &&
|
||||||
NormalizationPlanType<Plan> &&
|
|
||||||
NormalizationCoverage<Form, std::remove_cvref_t<Plan>>::complete;
|
NormalizationCoverage<Form, std::remove_cvref_t<Plan>>::complete;
|
||||||
} // namespace mean_field::normalization
|
} // namespace mean_field::normalization
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ module;
|
|||||||
|
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@@ -61,7 +62,7 @@ namespace mean_field::normalization::detail {
|
|||||||
const field::ScalarBoundaryDofMap &surfaceMap
|
const field::ScalarBoundaryDofMap &surfaceMap
|
||||||
) {
|
) {
|
||||||
mfem::Array<int> marker(finiteElements.mesh->bdr_attributes.Max());
|
mfem::Array<int> marker(finiteElements.mesh->bdr_attributes.Max());
|
||||||
marker = 0;
|
marker = 0;
|
||||||
constexpr int attribute = DomainSchema::template boundary_attribute<utils::domain::StellarSurface>();
|
constexpr int attribute = DomainSchema::template boundary_attribute<utils::domain::StellarSurface>();
|
||||||
if (attribute <= 0 || attribute > marker.Size()) {
|
if (attribute <= 0 || attribute > marker.Size()) {
|
||||||
throw std::invalid_argument("The reference mesh does not contain the stellar-surface boundary.");
|
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.
|
* this layer never names a concrete integral or phase constraint.
|
||||||
*/
|
*/
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename Block>
|
template <typename Block> using PhysicalRieszMethodFor = typename PhysicalRieszBlockTraits<Block>::Method;
|
||||||
using PhysicalRieszMethodFor = typename PhysicalRieszBlockTraits<Block>::Method;
|
|
||||||
|
|
||||||
template <typename Block, typename = void>
|
template <typename Block, typename = void> struct IsGlobalGeneratedValueNormalization : std::false_type { };
|
||||||
struct IsGlobalGeneratedValueNormalization : std::false_type { };
|
|
||||||
|
|
||||||
template <typename Generated>
|
template <typename Generated>
|
||||||
struct IsGlobalGeneratedValueNormalization<
|
struct IsGlobalGeneratedValueNormalization<
|
||||||
utils::blocks::generated_value_block<Generated>,
|
utils::blocks::generated_value_block<Generated>,
|
||||||
std::void_t<
|
std::void_t<
|
||||||
decltype(PhysicalRieszMethodFor<
|
decltype(PhysicalRieszMethodFor<utils::blocks::generated_value_block<Generated>>::topology),
|
||||||
utils::blocks::generated_value_block<Generated>>::topology),
|
decltype(PhysicalRieszMethodFor<utils::blocks::generated_value_block<Generated>>::scale)>>
|
||||||
decltype(PhysicalRieszMethodFor<
|
|
||||||
utils::blocks::generated_value_block<Generated>>::scale)>>
|
|
||||||
: std::bool_constant<
|
: std::bool_constant<
|
||||||
PhysicalRieszBlockTraits<
|
PhysicalRieszBlockTraits<utils::blocks::generated_value_block<Generated>>::registered &&
|
||||||
utils::blocks::generated_value_block<Generated>>::registered &&
|
PhysicalRieszMethodFor<utils::blocks::generated_value_block<Generated>>::topology ==
|
||||||
PhysicalRieszMethodFor<
|
|
||||||
utils::blocks::generated_value_block<Generated>>::topology ==
|
|
||||||
RieszTopology::global_scalar> { };
|
RieszTopology::global_scalar> { };
|
||||||
|
|
||||||
template <typename Blocks, typename Specification>
|
template <typename Blocks, typename Specification>
|
||||||
@@ -139,32 +134,26 @@ export namespace mean_field::normalization {
|
|||||||
Generated,
|
Generated,
|
||||||
Specification,
|
Specification,
|
||||||
std::void_t<typename Generated::SpecificationType>>
|
std::void_t<typename Generated::SpecificationType>>
|
||||||
: std::bool_constant<
|
: std::bool_constant<std::same_as<typename Generated::SpecificationType, Specification>> { };
|
||||||
std::same_as<typename Generated::SpecificationType, Specification>> { };
|
|
||||||
|
|
||||||
template <typename Specification, typename... Generated>
|
template <typename Specification, typename... Generated>
|
||||||
struct GeneratedValueBlocksBelongToSpecification<
|
struct GeneratedValueBlocksBelongToSpecification<
|
||||||
utils::blocks::type_list<utils::blocks::generated_value_block<Generated>...>,
|
utils::blocks::type_list<utils::blocks::generated_value_block<Generated>...>,
|
||||||
Specification>
|
Specification>
|
||||||
: std::bool_constant<
|
: std::bool_constant<(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> {
|
||||||
(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> { };
|
};
|
||||||
|
|
||||||
template <typename Block, typename = void>
|
template <typename Block, typename = void> struct IsGlobalGeneratedResidualNormalization : std::false_type { };
|
||||||
struct IsGlobalGeneratedResidualNormalization : std::false_type { };
|
|
||||||
|
|
||||||
template <typename Generated>
|
template <typename Generated>
|
||||||
struct IsGlobalGeneratedResidualNormalization<
|
struct IsGlobalGeneratedResidualNormalization<
|
||||||
utils::blocks::generated_residual_block<Generated>,
|
utils::blocks::generated_residual_block<Generated>,
|
||||||
std::void_t<
|
std::void_t<
|
||||||
decltype(PhysicalRieszMethodFor<
|
decltype(PhysicalRieszMethodFor<utils::blocks::generated_residual_block<Generated>>::topology),
|
||||||
utils::blocks::generated_residual_block<Generated>>::topology),
|
decltype(PhysicalRieszMethodFor<utils::blocks::generated_residual_block<Generated>>::scale)>>
|
||||||
decltype(PhysicalRieszMethodFor<
|
|
||||||
utils::blocks::generated_residual_block<Generated>>::scale)>>
|
|
||||||
: std::bool_constant<
|
: std::bool_constant<
|
||||||
PhysicalRieszBlockTraits<
|
PhysicalRieszBlockTraits<utils::blocks::generated_residual_block<Generated>>::registered &&
|
||||||
utils::blocks::generated_residual_block<Generated>>::registered &&
|
PhysicalRieszMethodFor<utils::blocks::generated_residual_block<Generated>>::topology ==
|
||||||
PhysicalRieszMethodFor<
|
|
||||||
utils::blocks::generated_residual_block<Generated>>::topology ==
|
|
||||||
RieszTopology::global_scalar> { };
|
RieszTopology::global_scalar> { };
|
||||||
|
|
||||||
template <typename Blocks, typename Specification>
|
template <typename Blocks, typename Specification>
|
||||||
@@ -174,14 +163,13 @@ export namespace mean_field::normalization {
|
|||||||
struct GeneratedResidualBlocksBelongToSpecification<
|
struct GeneratedResidualBlocksBelongToSpecification<
|
||||||
utils::blocks::type_list<utils::blocks::generated_residual_block<Generated>...>,
|
utils::blocks::type_list<utils::blocks::generated_residual_block<Generated>...>,
|
||||||
Specification>
|
Specification>
|
||||||
: std::bool_constant<
|
: std::bool_constant<(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> {
|
||||||
(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> { };
|
};
|
||||||
|
|
||||||
template <typename Blocks> struct PrepareGeneratedValueNormalizations {
|
template <typename Blocks> struct PrepareGeneratedValueNormalizations {
|
||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form> static constexpr bool completeFor = false;
|
||||||
static constexpr bool completeFor = false;
|
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
static void Apply(
|
static void Apply(
|
||||||
@@ -192,14 +180,12 @@ export namespace mean_field::normalization {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Blocks>
|
template <typename... Blocks> struct PrepareGeneratedValueNormalizations<utils::blocks::type_list<Blocks...>> {
|
||||||
struct PrepareGeneratedValueNormalizations<utils::blocks::type_list<Blocks...>> {
|
static constexpr bool registered = (IsGlobalGeneratedValueNormalization<Blocks>::value && ...);
|
||||||
static constexpr bool registered =
|
|
||||||
(IsGlobalGeneratedValueNormalization<Blocks>::value && ...);
|
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
static constexpr bool completeFor = registered &&
|
static constexpr bool completeFor =
|
||||||
utils::blocks::block_form_is_valid_v<Form> &&
|
registered && utils::blocks::block_form_is_valid_v<Form> &&
|
||||||
(utils::blocks::contains_type_v<Blocks, typename Form::value_blocks> && ...);
|
(utils::blocks::contains_type_v<Blocks, typename Form::value_blocks> && ...);
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
@@ -220,10 +206,9 @@ export namespace mean_field::normalization {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename Blocks> struct PrepareGeneratedResidualNormalizations {
|
template <typename Blocks> struct PrepareGeneratedResidualNormalizations {
|
||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form> static constexpr bool completeFor = false;
|
||||||
static constexpr bool completeFor = false;
|
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
static void Apply(
|
static void Apply(
|
||||||
@@ -236,12 +221,11 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
template <typename... Blocks>
|
template <typename... Blocks>
|
||||||
struct PrepareGeneratedResidualNormalizations<utils::blocks::type_list<Blocks...>> {
|
struct PrepareGeneratedResidualNormalizations<utils::blocks::type_list<Blocks...>> {
|
||||||
static constexpr bool registered =
|
static constexpr bool registered = (IsGlobalGeneratedResidualNormalization<Blocks>::value && ...);
|
||||||
(IsGlobalGeneratedResidualNormalization<Blocks>::value && ...);
|
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
static constexpr bool completeFor = registered &&
|
static constexpr bool completeFor =
|
||||||
utils::blocks::block_form_is_valid_v<Form> &&
|
registered && utils::blocks::block_form_is_valid_v<Form> &&
|
||||||
(utils::blocks::contains_type_v<Blocks, typename Form::residual_blocks> && ...);
|
(utils::blocks::contains_type_v<Blocks, typename Form::residual_blocks> && ...);
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
@@ -261,15 +245,13 @@ export namespace mean_field::normalization {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Specification, typename = void>
|
template <typename Specification, typename = void> struct CompileStellarSpecificationNormalization {
|
||||||
struct CompileStellarSpecificationNormalization {
|
using ValuePreparation = PrepareGeneratedValueNormalizations<void>;
|
||||||
using ValuePreparation = PrepareGeneratedValueNormalizations<void>;
|
using ResidualPreparation = PrepareGeneratedResidualNormalizations<void>;
|
||||||
using ResidualPreparation = PrepareGeneratedResidualNormalizations<void>;
|
|
||||||
|
|
||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form> static constexpr bool completeFor = false;
|
||||||
static constexpr bool completeFor = false;
|
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
static void Apply(
|
static void Apply(
|
||||||
@@ -277,8 +259,7 @@ export namespace mean_field::normalization {
|
|||||||
const StellarCharacteristicScales &
|
const StellarCharacteristicScales &
|
||||||
) {
|
) {
|
||||||
static_assert(
|
static_assert(
|
||||||
completeFor<Form>,
|
completeFor<Form>, "The specification has no complete generated-coordinate normalization."
|
||||||
"The specification has no complete generated-coordinate normalization."
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -287,32 +268,27 @@ export namespace mean_field::normalization {
|
|||||||
struct CompileStellarSpecificationNormalization<
|
struct CompileStellarSpecificationNormalization<
|
||||||
Specification,
|
Specification,
|
||||||
std::void_t<
|
std::void_t<
|
||||||
typename operators::StellarEquilibriumSpecificationCompilation<
|
typename operators::StellarEquilibriumSpecificationCompilation<Specification>::GeneratedValueBlocks,
|
||||||
Specification>::GeneratedValueBlocks,
|
|
||||||
typename operators::StellarEquilibriumSpecificationCompilation<
|
typename operators::StellarEquilibriumSpecificationCompilation<
|
||||||
Specification>::GeneratedResidualBlocks>> {
|
Specification>::GeneratedResidualBlocks>> {
|
||||||
using OperatorCompilation =
|
using OperatorCompilation = operators::StellarEquilibriumSpecificationCompilation<Specification>;
|
||||||
operators::StellarEquilibriumSpecificationCompilation<Specification>;
|
using ValuePreparation =
|
||||||
using ValuePreparation = PrepareGeneratedValueNormalizations<
|
PrepareGeneratedValueNormalizations<typename OperatorCompilation::GeneratedValueBlocks>;
|
||||||
typename OperatorCompilation::GeneratedValueBlocks>;
|
using ResidualPreparation =
|
||||||
using ResidualPreparation = PrepareGeneratedResidualNormalizations<
|
PrepareGeneratedResidualNormalizations<typename OperatorCompilation::GeneratedResidualBlocks>;
|
||||||
typename OperatorCompilation::GeneratedResidualBlocks>;
|
|
||||||
|
|
||||||
static constexpr bool registered = OperatorCompilation::complete &&
|
static constexpr bool registered = OperatorCompilation::complete &&
|
||||||
models::CompleteGeneratedNormalizationFor<
|
models::CompleteGeneratedNormalizationFor<Specification> &&
|
||||||
Specification> &&
|
|
||||||
GeneratedValueBlocksBelongToSpecification<
|
GeneratedValueBlocksBelongToSpecification<
|
||||||
typename OperatorCompilation::GeneratedValueBlocks,
|
typename OperatorCompilation::GeneratedValueBlocks,
|
||||||
Specification>::value &&
|
Specification>::value &&
|
||||||
GeneratedResidualBlocksBelongToSpecification<
|
GeneratedResidualBlocksBelongToSpecification<
|
||||||
typename OperatorCompilation::GeneratedResidualBlocks,
|
typename OperatorCompilation::GeneratedResidualBlocks,
|
||||||
Specification>::value &&
|
Specification>::value &&
|
||||||
ValuePreparation::registered &&
|
ValuePreparation::registered && ResidualPreparation::registered;
|
||||||
ResidualPreparation::registered;
|
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
static constexpr bool completeFor = registered &&
|
static constexpr bool completeFor = registered && ValuePreparation::template completeFor<Form> &&
|
||||||
ValuePreparation::template completeFor<Form> &&
|
|
||||||
ResidualPreparation::template completeFor<Form>;
|
ResidualPreparation::template completeFor<Form>;
|
||||||
|
|
||||||
template <typename Form>
|
template <typename Form>
|
||||||
@@ -366,9 +342,8 @@ export namespace mean_field::normalization {
|
|||||||
Model,
|
Model,
|
||||||
Form,
|
Form,
|
||||||
std::void_t<typename std::remove_cvref_t<Model>::SpecificationTypes>>
|
std::void_t<typename std::remove_cvref_t<Model>::SpecificationTypes>>
|
||||||
: std::bool_constant<
|
: std::bool_constant<PrepareSpecificationNormalizations<
|
||||||
PrepareSpecificationNormalizations<
|
typename std::remove_cvref_t<Model>::SpecificationTypes>::template completeFor<Form>> { };
|
||||||
typename std::remove_cvref_t<Model>::SpecificationTypes>::template completeFor<Form>> { };
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <typename Specification>
|
template <typename Specification>
|
||||||
@@ -400,9 +375,7 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
template <typename Model, typename Form>
|
template <typename Model, typename Form>
|
||||||
concept CompleteStellarNormalizationFor =
|
concept CompleteStellarNormalizationFor =
|
||||||
detail::StellarModelNormalizationCoverage<
|
detail::StellarModelNormalizationCoverage<std::remove_cvref_t<Model>, std::remove_cvref_t<Form>>::value;
|
||||||
std::remove_cvref_t<Model>,
|
|
||||||
std::remove_cvref_t<Form>>::value;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Physical Riesz preparation is an optional capability of a physical
|
* Physical Riesz preparation is an optional capability of a physical
|
||||||
@@ -418,8 +391,7 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
template <typename Problem>
|
template <typename Problem>
|
||||||
concept PhysicalRieszStellarEquilibriumProblem =
|
concept PhysicalRieszStellarEquilibriumProblem =
|
||||||
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> && requires {
|
||||||
requires {
|
|
||||||
typename std::remove_cvref_t<Problem>::ModelType;
|
typename std::remove_cvref_t<Problem>::ModelType;
|
||||||
typename std::remove_cvref_t<Problem>::FormType;
|
typename std::remove_cvref_t<Problem>::FormType;
|
||||||
typename std::remove_cvref_t<Problem>::PhysicalCoreType;
|
typename std::remove_cvref_t<Problem>::PhysicalCoreType;
|
||||||
@@ -430,8 +402,7 @@ export namespace mean_field::normalization {
|
|||||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||||
typename std::remove_cvref_t<Problem>::FormType>;
|
typename std::remove_cvref_t<Problem>::FormType>;
|
||||||
requires CompleteStellarNormalizationFor<
|
requires CompleteStellarNormalizationFor<
|
||||||
typename std::remove_cvref_t<Problem>::ModelType,
|
typename std::remove_cvref_t<Problem>::ModelType, typename std::remove_cvref_t<Problem>::FormType>;
|
||||||
typename std::remove_cvref_t<Problem>::FormType>;
|
|
||||||
requires StellarNormalizationRuntimeAvailableFor<
|
requires StellarNormalizationRuntimeAvailableFor<
|
||||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||||
typename std::remove_cvref_t<Problem>::FormType,
|
typename std::remove_cvref_t<Problem>::FormType,
|
||||||
@@ -450,42 +421,36 @@ export namespace mean_field::normalization {
|
|||||||
template <PhysicalRieszStellarEquilibriumProblem Problem>
|
template <PhysicalRieszStellarEquilibriumProblem Problem>
|
||||||
[[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) {
|
[[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) {
|
||||||
using ProblemType = std::remove_cvref_t<Problem>;
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
using Form = typename ProblemType::FormType;
|
using Form = typename ProblemType::FormType;
|
||||||
|
|
||||||
const fem::FEM &finiteElements = problem.GetDiscretization().finiteElementModel();
|
const fem::FEM &finiteElements =
|
||||||
|
equilibrium::detail::StellarEquilibriumProblemFactory::FiniteElementModel(problem);
|
||||||
if (!finiteElements.okay()) {
|
if (!finiteElements.okay()) {
|
||||||
throw std::invalid_argument("Physical Riesz preparation requires a current finite-element model.");
|
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 &gravityContext = physical.GetGravityContext();
|
||||||
const auto &enthalpyMap = physical.GetHydrostaticOperator().GetEnthalpyMap();
|
const auto &enthalpyMap = physical.GetHydrostaticOperator().GetEnthalpyMap();
|
||||||
const auto scales = deriveStellarCharacteristicScales(
|
const auto scales =
|
||||||
problem.GetNormalizationPrescription(),
|
deriveStellarCharacteristicScales(problem.GetNormalizationPrescription(), problem.GetStellarModel());
|
||||||
problem.GetStellarModel()
|
|
||||||
);
|
|
||||||
|
|
||||||
mfem::Array<int> stellarMarker =
|
mfem::Array<int> stellarMarker =
|
||||||
utils::domain::make_attribute_marker<utils::domain::Stellar, detail::DomainSchema>(*finiteElements.mesh);
|
utils::domain::make_attribute_marker<utils::domain::Stellar, detail::DomainSchema>(*finiteElements.mesh);
|
||||||
const mfem::Vector densityDiagonal = detail::GatherDiagonal(
|
const mfem::Vector densityDiagonal = detail::GatherDiagonal(
|
||||||
detail::AssembleScalarMassDiagonal(*finiteElements.densityFes, &stellarMarker),
|
detail::AssembleScalarMassDiagonal(*finiteElements.densityFes, &stellarMarker),
|
||||||
gravityContext.GetDensityMap(),
|
gravityContext.GetDensityMap(), "density"
|
||||||
"density"
|
|
||||||
);
|
);
|
||||||
const mfem::Vector enthalpyDiagonal = detail::GatherDiagonal(
|
const mfem::Vector enthalpyDiagonal = detail::GatherDiagonal(
|
||||||
detail::AssembleScalarMassDiagonal(*finiteElements.enthalpyFes, &stellarMarker),
|
detail::AssembleScalarMassDiagonal(*finiteElements.enthalpyFes, &stellarMarker), enthalpyMap, "enthalpy"
|
||||||
enthalpyMap,
|
|
||||||
"enthalpy"
|
|
||||||
);
|
);
|
||||||
const mfem::Vector gravityGradientDiagonal = detail::GatherDiagonal(
|
const mfem::Vector gravityGradientDiagonal = detail::GatherDiagonal(
|
||||||
detail::AssembleHDivMassDiagonal(*finiteElements.gravityFluxFes),
|
detail::AssembleHDivMassDiagonal(*finiteElements.gravityFluxFes), gravityContext.GetGravityGradientMap(),
|
||||||
gravityContext.GetGravityGradientMap(),
|
|
||||||
"gravity-gradient"
|
"gravity-gradient"
|
||||||
);
|
);
|
||||||
const mfem::Vector gravityPotentialDiagonal = detail::GatherDiagonal(
|
const mfem::Vector gravityPotentialDiagonal = detail::GatherDiagonal(
|
||||||
detail::AssembleScalarMassDiagonal(*finiteElements.gravityPotentialFes),
|
detail::AssembleScalarMassDiagonal(*finiteElements.gravityPotentialFes),
|
||||||
gravityContext.GetGravityPotentialMap(),
|
gravityContext.GetGravityPotentialMap(), "gravity-potential"
|
||||||
"gravity-potential"
|
|
||||||
);
|
);
|
||||||
const field::ScalarBoundaryDofMap surfaceMap =
|
const field::ScalarBoundaryDofMap surfaceMap =
|
||||||
field::make_stellar_surface_scalar_dof_map<detail::DomainSchema>(*finiteElements.surfaceDeformationFes);
|
field::make_stellar_surface_scalar_dof_map<detail::DomainSchema>(*finiteElements.surfaceDeformationFes);
|
||||||
@@ -524,13 +489,11 @@ export namespace mean_field::normalization {
|
|||||||
);
|
);
|
||||||
const mfem::Array<int> &surfaceRows = problem.GetPressureSurfaceRows().reduced_dofs();
|
const mfem::Array<int> &surfaceRows = problem.GetPressureSurfaceRows().reduced_dofs();
|
||||||
builder.template SetHybridResidualBlock<utils::blocks::enthalpy::specific::residual>(
|
builder.template SetHybridResidualBlock<utils::blocks::enthalpy::specific::residual>(
|
||||||
physicalScale<utils::blocks::enthalpy::specific::residual>(scales),
|
physicalScale<utils::blocks::enthalpy::specific::residual>(scales), enthalpyDiagonal,
|
||||||
enthalpyDiagonal,
|
|
||||||
std::span<const int>{surfaceRows.GetData(), static_cast<std::size_t>(surfaceRows.Size())}
|
std::span<const int>{surfaceRows.GetData(), static_cast<std::size_t>(surfaceRows.Size())}
|
||||||
);
|
);
|
||||||
detail::PrepareSpecificationNormalizations<typename ProblemType::ModelType::SpecificationTypes>::Apply(
|
detail::PrepareSpecificationNormalizations<typename ProblemType::ModelType::SpecificationTypes>::Apply(
|
||||||
builder,
|
builder, scales
|
||||||
scales
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return std::move(builder).Build();
|
return std::move(builder).Build();
|
||||||
@@ -548,16 +511,11 @@ export namespace mean_field::normalization {
|
|||||||
!std::same_as<
|
!std::same_as<
|
||||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||||
Unnormalized> &&
|
Unnormalized> &&
|
||||||
!PhysicalRieszDiagonalPrescription<
|
!PhysicalRieszDiagonalPrescription<typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType> &&
|
||||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType> &&
|
RuntimePreparedNormalizationOperation<Problem>
|
||||||
RuntimePreparedNormalizationOperation<Problem>)
|
)
|
||||||
[[nodiscard]] DiagonalNormalization prepareNormalization(
|
[[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) {
|
||||||
const Problem &problem
|
return prepareStellarNormalization(problem.GetNormalizationPrescription(), problem);
|
||||||
) {
|
|
||||||
return prepareStellarNormalization(
|
|
||||||
problem.GetNormalizationPrescription(),
|
|
||||||
problem
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -571,9 +529,7 @@ export namespace mean_field::normalization {
|
|||||||
concept NormalizableStellarEquilibriumProblem =
|
concept NormalizableStellarEquilibriumProblem =
|
||||||
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||||
requires(const std::remove_cvref_t<Problem> &problem) {
|
requires(const std::remove_cvref_t<Problem> &problem) {
|
||||||
{
|
{ prepareNormalization(problem) } -> std::same_as<DiagonalNormalization>;
|
||||||
prepareNormalization(problem)
|
|
||||||
} -> std::same_as<DiagonalNormalization>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NormalizedStellarEquilibriumStatistics final {
|
struct NormalizedStellarEquilibriumStatistics final {
|
||||||
@@ -591,17 +547,14 @@ export namespace mean_field::normalization {
|
|||||||
* implied.
|
* implied.
|
||||||
*/
|
*/
|
||||||
template <typename Candidate, typename Problem>
|
template <typename Candidate, typename Problem>
|
||||||
concept ProblemBoundStellarInverseFor =
|
concept ProblemBoundStellarInverseFor = NormalizableStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||||
NormalizableStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
std::derived_from<std::remove_cvref_t<Candidate>, mfem::Solver> &&
|
||||||
std::derived_from<std::remove_cvref_t<Candidate>, mfem::Solver> &&
|
requires(const std::remove_cvref_t<Candidate> &inverse) {
|
||||||
requires(const std::remove_cvref_t<Candidate> &inverse) {
|
{
|
||||||
{
|
inverse.GetProblem()
|
||||||
inverse.GetProblem()
|
} -> std::same_as<const std::remove_cvref_t<Problem> &>;
|
||||||
} -> std::same_as<const std::remove_cvref_t<Problem> &>;
|
{ inverse.IsCurrent() } -> std::same_as<bool>;
|
||||||
{
|
};
|
||||||
inverse.IsCurrent()
|
|
||||||
} -> std::same_as<bool>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <NormalizableStellarEquilibriumProblem Problem, typename PhysicalInverse>
|
template <NormalizableStellarEquilibriumProblem Problem, typename PhysicalInverse>
|
||||||
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
||||||
@@ -623,11 +576,20 @@ export namespace mean_field::normalization {
|
|||||||
using ProblemType = std::remove_cvref_t<Problem>;
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
using Report = typename ProblemType::Report;
|
||||||
|
using PreparationResult = typename ProblemType::PreparationResult;
|
||||||
|
|
||||||
explicit NormalizedStellarEquilibriumOperator(ProblemType &problem)
|
explicit NormalizedStellarEquilibriumOperator(ProblemType &problem)
|
||||||
: mfem::Operator(problem.EquationSize(), problem.StateSize()),
|
: mfem::Operator(
|
||||||
|
problem.EquationSize(),
|
||||||
|
problem.StateSize()
|
||||||
|
),
|
||||||
m_problem(&problem),
|
m_problem(&problem),
|
||||||
m_normalization(prepareNormalization(problem)),
|
m_normalization(prepareNormalization(problem)),
|
||||||
m_scaledJacobian(problem.GetLinearizationOperator(), m_normalization),
|
m_scaledJacobian(
|
||||||
|
problem.GetLinearizationOperator(),
|
||||||
|
m_normalization
|
||||||
|
),
|
||||||
m_physicalState(problem.StateSize()),
|
m_physicalState(problem.StateSize()),
|
||||||
m_physicalResidual(problem.EquationSize()),
|
m_physicalResidual(problem.EquationSize()),
|
||||||
m_normalizedResidual(problem.EquationSize()) {
|
m_normalizedResidual(problem.EquationSize()) {
|
||||||
@@ -646,7 +608,9 @@ export namespace mean_field::normalization {
|
|||||||
const mfem::Vector &normalizedState,
|
const mfem::Vector &normalizedState,
|
||||||
const operators::StellarEquilibriumDependencies &dependencies,
|
const operators::StellarEquilibriumDependencies &dependencies,
|
||||||
const physics::RigidRotation &rotation
|
const physics::RigidRotation &rotation
|
||||||
) requires(ProblemType::generatedRotationProviderCount == 0) {
|
)
|
||||||
|
requires(ProblemType::generatedRotationProviderCount == 0)
|
||||||
|
{
|
||||||
if (normalizedState.Size() != Width()) {
|
if (normalizedState.Size() != Width()) {
|
||||||
throw std::invalid_argument("The normalized stellar state has the wrong size.");
|
throw std::invalid_argument("The normalized stellar state has the wrong size.");
|
||||||
}
|
}
|
||||||
@@ -662,10 +626,37 @@ export namespace mean_field::normalization {
|
|||||||
return report;
|
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(
|
[[nodiscard]] auto Prepare(
|
||||||
const mfem::Vector &normalizedState,
|
const mfem::Vector &normalizedState,
|
||||||
const operators::StellarEquilibriumDependencies &dependencies
|
const operators::StellarEquilibriumDependencies &dependencies
|
||||||
) requires(ProblemType::generatedRotationProviderCount == 1) {
|
)
|
||||||
|
requires(ProblemType::generatedRotationProviderCount == 1)
|
||||||
|
{
|
||||||
if (normalizedState.Size() != Width()) {
|
if (normalizedState.Size() != Width()) {
|
||||||
throw std::invalid_argument("The normalized stellar state has the wrong size.");
|
throw std::invalid_argument("The normalized stellar state has the wrong size.");
|
||||||
}
|
}
|
||||||
@@ -681,6 +672,30 @@ export namespace mean_field::normalization {
|
|||||||
return report;
|
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 {
|
void BuildResidual(mfem::Vector &normalizedResidual) const {
|
||||||
VerifyPrepared();
|
VerifyPrepared();
|
||||||
normalizedResidual = m_normalizedResidual;
|
normalizedResidual = m_normalizedResidual;
|
||||||
@@ -701,8 +716,8 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
void RefreshNormalization() {
|
void RefreshNormalization() {
|
||||||
DiagonalNormalization refreshed = prepareNormalization(*m_problem);
|
DiagonalNormalization refreshed = prepareNormalization(*m_problem);
|
||||||
m_normalization = std::move(refreshed);
|
m_normalization = std::move(refreshed);
|
||||||
m_isPrepared = false;
|
m_isPrepared = false;
|
||||||
++m_statistics.normalizationPreparations;
|
++m_statistics.normalizationPreparations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,8 +750,12 @@ export namespace mean_field::normalization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename PhysicalInverse>
|
template <typename PhysicalInverse>
|
||||||
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
requires ProblemBoundStellarInverseFor<
|
||||||
[[nodiscard]] NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>
|
PhysicalInverse,
|
||||||
|
Problem>
|
||||||
|
[[nodiscard]] NormalizedStellarPreconditioner<
|
||||||
|
Problem,
|
||||||
|
std::remove_cvref_t<PhysicalInverse>>
|
||||||
MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const;
|
MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const;
|
||||||
|
|
||||||
[[nodiscard]] bool IsPrepared() const noexcept {
|
[[nodiscard]] bool IsPrepared() const noexcept {
|
||||||
@@ -802,16 +821,15 @@ export namespace mean_field::normalization {
|
|||||||
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
||||||
class NormalizedStellarPreconditioner final : public mfem::Solver {
|
class NormalizedStellarPreconditioner final : public mfem::Solver {
|
||||||
private:
|
private:
|
||||||
using ProblemType = std::remove_cvref_t<Problem>;
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
using NormalizedOperator = NormalizedStellarEquilibriumOperator<ProblemType>;
|
using NormalizedOperator = NormalizedStellarEquilibriumOperator<ProblemType>;
|
||||||
using PhysicalInverseType = std::remove_cvref_t<PhysicalInverse>;
|
using PhysicalInverseType = std::remove_cvref_t<PhysicalInverse>;
|
||||||
|
|
||||||
[[nodiscard]] static PhysicalInverseType &RequireAssociatedPhysicalInverse(
|
[[nodiscard]] static PhysicalInverseType &RequireAssociatedPhysicalInverse(
|
||||||
const NormalizedOperator &normalizedOperator,
|
const NormalizedOperator &normalizedOperator,
|
||||||
PhysicalInverseType &physicalInverse
|
PhysicalInverseType &physicalInverse
|
||||||
) {
|
) {
|
||||||
if (std::addressof(physicalInverse.GetProblem()) !=
|
if (std::addressof(physicalInverse.GetProblem()) != std::addressof(normalizedOperator.GetProblem())) {
|
||||||
std::addressof(normalizedOperator.GetProblem())) {
|
|
||||||
throw std::invalid_argument(
|
throw std::invalid_argument(
|
||||||
"A normalized stellar preconditioner and its physical inverse must belong to the same problem."
|
"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_normalizedOperator(&normalizedOperator),
|
||||||
m_physicalInverse(&physicalInverse),
|
m_physicalInverse(&physicalInverse),
|
||||||
m_scaled(
|
m_scaled(
|
||||||
RequireAssociatedPhysicalInverse(normalizedOperator, physicalInverse),
|
RequireAssociatedPhysicalInverse(
|
||||||
|
normalizedOperator,
|
||||||
|
physicalInverse
|
||||||
|
),
|
||||||
normalizedOperator.GetPhysicalJacobian(),
|
normalizedOperator.GetPhysicalJacobian(),
|
||||||
normalizedOperator,
|
normalizedOperator,
|
||||||
normalizedOperator.GetNormalization()
|
normalizedOperator.GetNormalization()
|
||||||
@@ -863,8 +884,7 @@ export namespace mean_field::normalization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool IsCurrent() const {
|
[[nodiscard]] bool IsCurrent() const {
|
||||||
return m_normalizedOperator->IsPrepared() &&
|
return m_normalizedOperator->IsPrepared() && m_physicalInverse->IsCurrent();
|
||||||
m_physicalInverse->IsCurrent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] PhysicalInverseType &GetPhysicalInverse() noexcept {
|
[[nodiscard]] PhysicalInverseType &GetPhysicalInverse() noexcept {
|
||||||
@@ -904,14 +924,15 @@ export namespace mean_field::normalization {
|
|||||||
|
|
||||||
template <NormalizableStellarEquilibriumProblem Problem>
|
template <NormalizableStellarEquilibriumProblem Problem>
|
||||||
template <typename PhysicalInverse>
|
template <typename PhysicalInverse>
|
||||||
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
requires ProblemBoundStellarInverseFor<
|
||||||
NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>
|
PhysicalInverse,
|
||||||
|
Problem>
|
||||||
|
NormalizedStellarPreconditioner<
|
||||||
|
Problem,
|
||||||
|
std::remove_cvref_t<PhysicalInverse>>
|
||||||
NormalizedStellarEquilibriumOperator<Problem>::MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const {
|
NormalizedStellarEquilibriumOperator<Problem>::MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const {
|
||||||
VerifyPrepared();
|
VerifyPrepared();
|
||||||
return NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>{
|
return NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>{*this, physicalInverse};
|
||||||
*this,
|
|
||||||
physicalInverse
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <NormalizableStellarEquilibriumProblem Problem>
|
template <NormalizableStellarEquilibriumProblem Problem>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
module;
|
module;
|
||||||
#include <compare>
|
#include <compare>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
export module mean_field:operators.context.gravity_field;
|
export module mean_field:operators.context.gravity_field;
|
||||||
export import :fem;
|
export import :fem;
|
||||||
@@ -58,6 +60,23 @@ export namespace mean_field::operators::context::gravity_field {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class GravityFieldPreparationRejectionReason : std::uint8_t { invalid_mapping, non_finite_arithmetic };
|
||||||
|
|
||||||
|
struct GravityFieldPreparationRejection final {
|
||||||
|
GravityFieldPreparationRejectionReason reason{GravityFieldPreparationRejectionReason::invalid_mapping};
|
||||||
|
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Report>
|
||||||
|
using GravityFieldPreparationResult = std::expected<Report, GravityFieldPreparationRejection>;
|
||||||
|
|
||||||
|
[[noreturn]] inline void throwGravityFieldPreparationRejection(const GravityFieldPreparationRejection &rejection) {
|
||||||
|
if (rejection.reason == GravityFieldPreparationRejectionReason::non_finite_arithmetic) {
|
||||||
|
throw std::domain_error("Prepared gravity-field data contained non-finite arithmetic.");
|
||||||
|
}
|
||||||
|
throw std::domain_error("Prepared gravity-field data could not map the candidate geometry.");
|
||||||
|
}
|
||||||
|
|
||||||
class GravityFieldGeometryContext {
|
class GravityFieldGeometryContext {
|
||||||
public:
|
public:
|
||||||
GravityFieldGeometryContext(
|
GravityFieldGeometryContext(
|
||||||
@@ -76,12 +95,24 @@ export namespace mean_field::operators::context::gravity_field {
|
|||||||
DisplacementRevision displacement_revision
|
DisplacementRevision displacement_revision
|
||||||
);
|
);
|
||||||
|
|
||||||
|
[[nodiscard]] GravityFieldPreparationResult<GravityFieldGeometryPreparation> TryPrepare(
|
||||||
|
const mfem::Vector &displacement,
|
||||||
|
DiscretizationRevision discretization_revision,
|
||||||
|
DisplacementRevision displacement_revision
|
||||||
|
);
|
||||||
|
|
||||||
GravityFieldGeometryPreparation PreparePrimal(
|
GravityFieldGeometryPreparation PreparePrimal(
|
||||||
const mfem::Vector &displacement,
|
const mfem::Vector &displacement,
|
||||||
DiscretizationRevision discretization_revision,
|
DiscretizationRevision discretization_revision,
|
||||||
DisplacementRevision displacement_revision
|
DisplacementRevision displacement_revision
|
||||||
);
|
);
|
||||||
|
|
||||||
|
[[nodiscard]] GravityFieldPreparationResult<GravityFieldGeometryPreparation> TryPreparePrimal(
|
||||||
|
const mfem::Vector &displacement,
|
||||||
|
DiscretizationRevision discretization_revision,
|
||||||
|
DisplacementRevision displacement_revision
|
||||||
|
);
|
||||||
|
|
||||||
[[nodiscard]] const PreparedMappedHDivMassOperator &GetMassOperator() const;
|
[[nodiscard]] const PreparedMappedHDivMassOperator &GetMassOperator() const;
|
||||||
[[nodiscard]] const PreparedMappedGravitySourceOperator &GetSourceOperator() const;
|
[[nodiscard]] const PreparedMappedGravitySourceOperator &GetSourceOperator() const;
|
||||||
[[nodiscard]] const mfem::Operator &GetDivergenceOperator() const;
|
[[nodiscard]] const mfem::Operator &GetDivergenceOperator() const;
|
||||||
@@ -95,7 +126,7 @@ export namespace mean_field::operators::context::gravity_field {
|
|||||||
private:
|
private:
|
||||||
enum class PreparationMode : std::uint8_t { primal, linearization };
|
enum class PreparationMode : std::uint8_t { primal, linearization };
|
||||||
|
|
||||||
GravityFieldGeometryPreparation PrepareImpl(
|
[[nodiscard]] GravityFieldPreparationResult<GravityFieldGeometryPreparation> TryPrepareImpl(
|
||||||
const mfem::Vector &displacement,
|
const mfem::Vector &displacement,
|
||||||
DiscretizationRevision discretization_revision,
|
DiscretizationRevision discretization_revision,
|
||||||
DisplacementRevision displacement_revision,
|
DisplacementRevision displacement_revision,
|
||||||
@@ -147,6 +178,11 @@ export namespace mean_field::operators::context::gravity_field {
|
|||||||
const GravityFieldRevisions &revisions
|
const GravityFieldRevisions &revisions
|
||||||
);
|
);
|
||||||
|
|
||||||
|
[[nodiscard]] GravityFieldPreparationResult<GravityFieldPreparationReport> TryPrepare(
|
||||||
|
const GravityFieldStateView &state,
|
||||||
|
const GravityFieldRevisions &revisions
|
||||||
|
);
|
||||||
|
|
||||||
[[nodiscard]] const GravityFieldGeometryContext &GetGeometryContext() const;
|
[[nodiscard]] const GravityFieldGeometryContext &GetGeometryContext() const;
|
||||||
[[nodiscard]] const mfem::Vector &GetDensityTrue() const;
|
[[nodiscard]] const mfem::Vector &GetDensityTrue() const;
|
||||||
[[nodiscard]] const mfem::Vector &GetGravityGradientTrue() const;
|
[[nodiscard]] const mfem::Vector &GetGravityGradientTrue() const;
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ export namespace mean_field::operators {
|
|||||||
const context::gravity_field::GravityFieldRevisions &revisions
|
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(
|
void Mult(
|
||||||
const mfem::Vector &state,
|
const mfem::Vector &state,
|
||||||
mfem::Vector &residual
|
mfem::Vector &residual
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
|
||||||
export module mean_field:operators.kernels.gravity_displacement_force;
|
export module mean_field:operators.kernels.gravity_displacement_force;
|
||||||
@@ -8,6 +11,24 @@ export import :fem;
|
|||||||
export import :mapping.domain_mapper;
|
export import :mapping.domain_mapper;
|
||||||
|
|
||||||
export namespace mean_field::operators::kernels {
|
export namespace mean_field::operators::kernels {
|
||||||
|
enum class GravityDisplacementForceRejectionReason : std::uint8_t { invalid_mapping, non_finite_arithmetic };
|
||||||
|
|
||||||
|
struct GravityDisplacementForceRejection final {
|
||||||
|
GravityDisplacementForceRejectionReason reason{GravityDisplacementForceRejectionReason::invalid_mapping};
|
||||||
|
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
|
||||||
|
};
|
||||||
|
|
||||||
|
using GravityDisplacementForceResult = std::expected<void, GravityDisplacementForceRejection>;
|
||||||
|
|
||||||
|
[[nodiscard]] GravityDisplacementForceResult try_apply_gravity_displacement_force_residual(
|
||||||
|
const fem::FEM &f,
|
||||||
|
const mapping::DomainMapper &domainMapper,
|
||||||
|
const mfem::Vector &densityTrue,
|
||||||
|
const mfem::Vector &gravityGradientTrue,
|
||||||
|
const mfem::Vector &displacementTrue,
|
||||||
|
mfem::Vector &residualTrue
|
||||||
|
);
|
||||||
|
|
||||||
void apply_gravity_displacement_force_residual(
|
void apply_gravity_displacement_force_residual(
|
||||||
const fem::FEM &f,
|
const fem::FEM &f,
|
||||||
const mapping::DomainMapper &domainMapper,
|
const mapping::DomainMapper &domainMapper,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
|
||||||
export module mean_field:operators.kernels.rotational_displacement_force;
|
export module mean_field:operators.kernels.rotational_displacement_force;
|
||||||
@@ -9,6 +12,24 @@ export import :mapping.domain_mapper;
|
|||||||
export import :physics.rigid_rotation;
|
export import :physics.rigid_rotation;
|
||||||
|
|
||||||
export namespace mean_field::operators::kernels {
|
export namespace mean_field::operators::kernels {
|
||||||
|
enum class RotationalDisplacementForceRejectionReason : std::uint8_t { invalid_mapping, non_finite_arithmetic };
|
||||||
|
|
||||||
|
struct RotationalDisplacementForceRejection final {
|
||||||
|
RotationalDisplacementForceRejectionReason reason{RotationalDisplacementForceRejectionReason::invalid_mapping};
|
||||||
|
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
|
||||||
|
};
|
||||||
|
|
||||||
|
using RotationalDisplacementForceResult = std::expected<void, RotationalDisplacementForceRejection>;
|
||||||
|
|
||||||
|
[[nodiscard]] RotationalDisplacementForceResult try_apply_rotational_displacement_force_residual(
|
||||||
|
const fem::FEM &f,
|
||||||
|
const mapping::DomainMapper &domainMapper,
|
||||||
|
const physics::RigidRotation &rotation,
|
||||||
|
const mfem::Vector &densityTrue,
|
||||||
|
const mfem::Vector &displacementTrue,
|
||||||
|
mfem::Vector &residualTrue
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Rotational contribution to the displacement row:
|
* Rotational contribution to the displacement row:
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ module;
|
|||||||
|
|
||||||
#include <compare>
|
#include <compare>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
@@ -45,6 +49,52 @@ export namespace mean_field::operators {
|
|||||||
constexpr auto operator<=>(const PreparedAngularMomentumReport &) const = default;
|
constexpr auto operator<=>(const PreparedAngularMomentumReport &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class AngularMomentumPreparationRejectionReason : std::uint8_t {
|
||||||
|
inverted_geometry,
|
||||||
|
non_finite_geometry,
|
||||||
|
non_finite_angular_velocity,
|
||||||
|
non_finite_density,
|
||||||
|
negative_moment_of_inertia,
|
||||||
|
non_finite_moment_of_inertia,
|
||||||
|
non_finite_residual
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A trial state can fail to define the angular-momentum invariant without
|
||||||
|
* violating the operator's structural contract. Keep that distinction in
|
||||||
|
* a fixed-size value so a line search can reject the candidate without
|
||||||
|
* constructing or transporting an exception.
|
||||||
|
*/
|
||||||
|
struct AngularMomentumPreparationRejection final {
|
||||||
|
AngularMomentumPreparationRejectionReason reason{AngularMomentumPreparationRejectionReason::inverted_geometry};
|
||||||
|
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
|
||||||
|
double momentOfInertia{std::numeric_limits<double>::quiet_NaN()};
|
||||||
|
};
|
||||||
|
|
||||||
|
using AngularMomentumPreparationResult =
|
||||||
|
std::expected<PreparedAngularMomentumReport, AngularMomentumPreparationRejection>;
|
||||||
|
|
||||||
|
[[noreturn]] inline void
|
||||||
|
throwAngularMomentumPreparationRejection(const AngularMomentumPreparationRejection &rejection) {
|
||||||
|
switch (rejection.reason) {
|
||||||
|
case AngularMomentumPreparationRejectionReason::inverted_geometry:
|
||||||
|
throw std::domain_error("The angular-momentum trial inverts mapped geometry.");
|
||||||
|
case AngularMomentumPreparationRejectionReason::non_finite_geometry:
|
||||||
|
throw std::domain_error("The angular-momentum trial produced non-finite mapped geometry.");
|
||||||
|
case AngularMomentumPreparationRejectionReason::non_finite_angular_velocity:
|
||||||
|
throw std::domain_error("The angular-momentum trial has a non-finite angular velocity.");
|
||||||
|
case AngularMomentumPreparationRejectionReason::non_finite_density:
|
||||||
|
throw std::domain_error("The angular-momentum trial produced a non-finite interpolated density.");
|
||||||
|
case AngularMomentumPreparationRejectionReason::negative_moment_of_inertia:
|
||||||
|
throw std::domain_error("The angular-momentum trial produced a negative moment of inertia.");
|
||||||
|
case AngularMomentumPreparationRejectionReason::non_finite_moment_of_inertia:
|
||||||
|
throw std::domain_error("The angular-momentum trial produced a non-finite moment of inertia.");
|
||||||
|
case AngularMomentumPreparationRejectionReason::non_finite_residual:
|
||||||
|
throw std::domain_error("The angular-momentum trial produced a non-finite residual.");
|
||||||
|
}
|
||||||
|
throw std::logic_error("An unknown angular-momentum trial rejection was reported.");
|
||||||
|
}
|
||||||
|
|
||||||
struct AngularMomentumConstraintReport final {
|
struct AngularMomentumConstraintReport final {
|
||||||
double targetAngularMomentum;
|
double targetAngularMomentum;
|
||||||
double achievedAngularMomentum;
|
double achievedAngularMomentum;
|
||||||
@@ -97,6 +147,11 @@ export namespace mean_field::operators {
|
|||||||
const AngularMomentumDependencies &dependencies
|
const AngularMomentumDependencies &dependencies
|
||||||
);
|
);
|
||||||
|
|
||||||
|
[[nodiscard]] AngularMomentumPreparationResult TryPrepare(
|
||||||
|
double angularVelocity,
|
||||||
|
const AngularMomentumDependencies &dependencies
|
||||||
|
);
|
||||||
|
|
||||||
void BuildResidual(mfem::Vector &residual) const;
|
void BuildResidual(mfem::Vector &residual) const;
|
||||||
|
|
||||||
void ApplyDensityJacobianAction(
|
void ApplyDensityJacobianAction(
|
||||||
@@ -156,9 +211,9 @@ export namespace mean_field::operators {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void BuildStaticPlan();
|
void BuildStaticPlan();
|
||||||
void RefreshGeometry(const mfem::Vector &displacement);
|
[[nodiscard]] std::optional<mapping::MappingStatus> RefreshGeometry(const mfem::Vector &displacement);
|
||||||
void RefreshDensity(const mfem::Vector &density);
|
[[nodiscard]] bool RefreshDensity(const mfem::Vector &density);
|
||||||
void AssembleResidual();
|
[[nodiscard]] std::optional<AngularMomentumPreparationRejection> TryAssembleResidual();
|
||||||
void VerifyPrepared() const;
|
void VerifyPrepared() const;
|
||||||
|
|
||||||
[[nodiscard]] double EvaluateDensityMomentActionLocal(const mfem::Vector &densityVariation) const;
|
[[nodiscard]] double EvaluateDensityMomentActionLocal(const mfem::Vector &densityVariation) const;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -22,6 +23,29 @@ export namespace mean_field::operators {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class BarotropicClosurePreparationRejectionReason : std::uint8_t {
|
||||||
|
mapping_failure,
|
||||||
|
invalid_quadrature_data,
|
||||||
|
equation_of_state
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A rejected candidate is part of the nonlinear-solver control flow, not
|
||||||
|
* an exceptional API failure. Keep the payload fixed-size so it can be
|
||||||
|
* selected deterministically across ranks without allocating. Only the
|
||||||
|
* detail associated with `reason` is meaningful.
|
||||||
|
*/
|
||||||
|
struct BarotropicClosurePreparationRejection final {
|
||||||
|
BarotropicClosurePreparationRejectionReason reason{
|
||||||
|
BarotropicClosurePreparationRejectionReason::mapping_failure
|
||||||
|
};
|
||||||
|
mapping::MappingStatus mappingStatus{mapping::MappingStatus::non_finite_result};
|
||||||
|
eos::EvaluationErrorCode equationOfStateError{eos::EvaluationErrorCode::nonfinite_result};
|
||||||
|
};
|
||||||
|
|
||||||
|
using BarotropicClosurePreparationResult =
|
||||||
|
std::expected<PreparedBarotropicClosureReport, BarotropicClosurePreparationRejection>;
|
||||||
|
|
||||||
class PreparedBarotropicClosureOperator final : public mfem::Operator {
|
class PreparedBarotropicClosureOperator final : public mfem::Operator {
|
||||||
public:
|
public:
|
||||||
PreparedBarotropicClosureOperator(
|
PreparedBarotropicClosureOperator(
|
||||||
@@ -40,6 +64,11 @@ export namespace mean_field::operators {
|
|||||||
const context::barotropic::BarotropicClosureDependencies &dependencies
|
const context::barotropic::BarotropicClosureDependencies &dependencies
|
||||||
);
|
);
|
||||||
|
|
||||||
|
[[nodiscard]] BarotropicClosurePreparationResult TryPrepare(
|
||||||
|
const context::barotropic::BarotropicClosureStateView &state,
|
||||||
|
const context::barotropic::BarotropicClosureDependencies &dependencies
|
||||||
|
);
|
||||||
|
|
||||||
void Mult(
|
void Mult(
|
||||||
const mfem::Vector &densityVariation,
|
const mfem::Vector &densityVariation,
|
||||||
const mfem::Vector &enthalpyVariation,
|
const mfem::Vector &enthalpyVariation,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ module;
|
|||||||
|
|
||||||
#include <compare>
|
#include <compare>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
|
||||||
@@ -18,6 +20,28 @@ export import :physics.rigid_rotation;
|
|||||||
export import :utils.blocks;
|
export import :utils.blocks;
|
||||||
|
|
||||||
export namespace mean_field::operators {
|
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 {
|
struct DisplacementResidualDependencyStamp final {
|
||||||
std::uint64_t identity{0};
|
std::uint64_t identity{0};
|
||||||
std::uint64_t revision{0};
|
std::uint64_t revision{0};
|
||||||
@@ -104,6 +128,15 @@ export namespace mean_field::operators {
|
|||||||
const physics::RigidRotation &rotation
|
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 BuildResidual(mfem::Vector &residual) const;
|
||||||
|
|
||||||
void ApplyDensityJacobianAction(
|
void ApplyDensityJacobianAction(
|
||||||
@@ -154,7 +187,7 @@ export namespace mean_field::operators {
|
|||||||
GetGravityContext() const noexcept;
|
GetGravityContext() const noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void AssembleResidual();
|
[[nodiscard]] std::optional<DisplacementResidualPreparationRejection> AssembleResidual();
|
||||||
void VerifyPrepared() const;
|
void VerifyPrepared() const;
|
||||||
|
|
||||||
const fem::FEM &m_fem;
|
const fem::FEM &m_fem;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ module;
|
|||||||
|
|
||||||
#include <compare>
|
#include <compare>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
@@ -11,6 +12,7 @@ export module mean_field:operators.prepared_gravity_displacement_force;
|
|||||||
export import :fem;
|
export import :fem;
|
||||||
export import :mapping.domain_mapper;
|
export import :mapping.domain_mapper;
|
||||||
export import :operators.context.gravity_field;
|
export import :operators.context.gravity_field;
|
||||||
|
export import :operators.kernels.gravity_displacement_force;
|
||||||
export import :utils.blocks;
|
export import :utils.blocks;
|
||||||
|
|
||||||
export namespace mean_field::operators {
|
export namespace mean_field::operators {
|
||||||
@@ -57,6 +59,11 @@ export namespace mean_field::operators {
|
|||||||
*/
|
*/
|
||||||
PreparedGravityDisplacementForceReport Prepare();
|
PreparedGravityDisplacementForceReport Prepare();
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<
|
||||||
|
PreparedGravityDisplacementForceReport,
|
||||||
|
kernels::GravityDisplacementForceRejection>
|
||||||
|
TryPrepare();
|
||||||
|
|
||||||
void BuildResidual(mfem::Vector &residual) const;
|
void BuildResidual(mfem::Vector &residual) const;
|
||||||
|
|
||||||
void ApplyDensityJacobianAction(
|
void ApplyDensityJacobianAction(
|
||||||
@@ -106,7 +113,10 @@ export namespace mean_field::operators {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void VerifyPrepared() const;
|
void VerifyPrepared() const;
|
||||||
void PrepareElementData();
|
[[nodiscard]] std::expected<
|
||||||
|
void,
|
||||||
|
kernels::GravityDisplacementForceRejection>
|
||||||
|
TryPrepareElementData();
|
||||||
void ApplyPreparedCompleteJacobianActionTrue(
|
void ApplyPreparedCompleteJacobianActionTrue(
|
||||||
const mfem::Vector &densityVariationTrue,
|
const mfem::Vector &densityVariationTrue,
|
||||||
const mfem::Vector &displacementVariationTrue,
|
const mfem::Vector &displacementVariationTrue,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
module;
|
module;
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
export module mean_field:operators.prepared_gravity_source;
|
export module mean_field:operators.prepared_gravity_source;
|
||||||
@@ -10,6 +12,23 @@ export import :field.mfem;
|
|||||||
export import :mapping.domain_mapper;
|
export import :mapping.domain_mapper;
|
||||||
|
|
||||||
export namespace mean_field::operators {
|
export namespace mean_field::operators {
|
||||||
|
enum class GravitySourcePreparationRejectionReason : std::uint8_t { invalid_mapping, non_finite_arithmetic };
|
||||||
|
|
||||||
|
struct GravitySourcePreparationRejection final {
|
||||||
|
GravitySourcePreparationRejectionReason reason{GravitySourcePreparationRejectionReason::invalid_mapping};
|
||||||
|
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
|
||||||
|
};
|
||||||
|
|
||||||
|
using GravitySourcePreparationResult = std::expected<void, GravitySourcePreparationRejection>;
|
||||||
|
|
||||||
|
[[noreturn]] inline void
|
||||||
|
throwGravitySourcePreparationRejection(const GravitySourcePreparationRejection &rejection) {
|
||||||
|
if (rejection.reason == GravitySourcePreparationRejectionReason::non_finite_arithmetic) {
|
||||||
|
throw std::domain_error("Prepared gravity-source data contained non-finite arithmetic.");
|
||||||
|
}
|
||||||
|
throw std::domain_error("Prepared gravity-source data could not map the candidate geometry.");
|
||||||
|
}
|
||||||
|
|
||||||
class PreparedMappedGravitySourceOperator final : public mfem::Operator {
|
class PreparedMappedGravitySourceOperator final : public mfem::Operator {
|
||||||
public:
|
public:
|
||||||
PreparedMappedGravitySourceOperator(
|
PreparedMappedGravitySourceOperator(
|
||||||
@@ -19,6 +38,8 @@ export namespace mean_field::operators {
|
|||||||
|
|
||||||
void Prepare(const mfem::Vector &displacement);
|
void Prepare(const mfem::Vector &displacement);
|
||||||
void PreparePrimal(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(
|
void Mult(
|
||||||
const mfem::Vector &density,
|
const mfem::Vector &density,
|
||||||
mfem::Vector &action
|
mfem::Vector &action
|
||||||
@@ -68,7 +89,7 @@ export namespace mean_field::operators {
|
|||||||
mfem::Vector quadrature_data;
|
mfem::Vector quadrature_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
void PrepareImpl(
|
[[nodiscard]] GravitySourcePreparationResult TryPrepareImpl(
|
||||||
const mfem::Vector &displacement,
|
const mfem::Vector &displacement,
|
||||||
PreparationMode mode
|
PreparationMode mode
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
module;
|
module;
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
export module mean_field:operators.prepared_hdiv_mass;
|
export module mean_field:operators.prepared_hdiv_mass;
|
||||||
@@ -10,6 +12,22 @@ export import :field.mfem;
|
|||||||
export import :mapping.domain_mapper;
|
export import :mapping.domain_mapper;
|
||||||
|
|
||||||
export namespace mean_field::operators {
|
export namespace mean_field::operators {
|
||||||
|
enum class HDivMassPreparationRejectionReason : std::uint8_t { invalid_mapping, non_finite_arithmetic };
|
||||||
|
|
||||||
|
struct HDivMassPreparationRejection final {
|
||||||
|
HDivMassPreparationRejectionReason reason{HDivMassPreparationRejectionReason::invalid_mapping};
|
||||||
|
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
|
||||||
|
};
|
||||||
|
|
||||||
|
using HDivMassPreparationResult = std::expected<void, HDivMassPreparationRejection>;
|
||||||
|
|
||||||
|
[[noreturn]] inline void throwHDivMassPreparationRejection(const HDivMassPreparationRejection &rejection) {
|
||||||
|
if (rejection.reason == HDivMassPreparationRejectionReason::non_finite_arithmetic) {
|
||||||
|
throw std::domain_error("Prepared H(div) mass data contained non-finite arithmetic.");
|
||||||
|
}
|
||||||
|
throw std::domain_error("Prepared H(div) mass data could not map the candidate geometry.");
|
||||||
|
}
|
||||||
|
|
||||||
class PreparedMappedHDivMassOperator final : public mfem::Operator {
|
class PreparedMappedHDivMassOperator final : public mfem::Operator {
|
||||||
public:
|
public:
|
||||||
PreparedMappedHDivMassOperator(
|
PreparedMappedHDivMassOperator(
|
||||||
@@ -19,6 +37,8 @@ export namespace mean_field::operators {
|
|||||||
|
|
||||||
void Prepare(const mfem::Vector &displacement);
|
void Prepare(const mfem::Vector &displacement);
|
||||||
void PreparePrimal(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(
|
void Mult(
|
||||||
const mfem::Vector &gravity_gradient,
|
const mfem::Vector &gravity_gradient,
|
||||||
mfem::Vector &action
|
mfem::Vector &action
|
||||||
@@ -54,8 +74,8 @@ export namespace mean_field::operators {
|
|||||||
mfem::DenseMatrix frozenMappingData;
|
mfem::DenseMatrix frozenMappingData;
|
||||||
};
|
};
|
||||||
|
|
||||||
void PrepareVariationData();
|
[[nodiscard]] mapping::MappingStatus PrepareVariationData();
|
||||||
void PrepareImpl(
|
[[nodiscard]] HDivMassPreparationResult TryPrepareImpl(
|
||||||
const mfem::Vector &displacement,
|
const mfem::Vector &displacement,
|
||||||
PreparationMode mode
|
PreparationMode mode
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ module;
|
|||||||
#include <compare>
|
#include <compare>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
@@ -30,6 +32,35 @@ export namespace mean_field::operators {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class HydrostaticEquilibriumPreparationRejectionReason : std::uint8_t {
|
||||||
|
inverted_geometry,
|
||||||
|
non_finite_geometry,
|
||||||
|
non_finite_residual
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HydrostaticEquilibriumPreparationRejection final {
|
||||||
|
HydrostaticEquilibriumPreparationRejectionReason reason{
|
||||||
|
HydrostaticEquilibriumPreparationRejectionReason::inverted_geometry
|
||||||
|
};
|
||||||
|
mapping::MappingStatus mappingStatus{mapping::MappingStatus::valid};
|
||||||
|
};
|
||||||
|
|
||||||
|
using HydrostaticEquilibriumPreparationResult =
|
||||||
|
std::expected<PreparedHydrostaticEquilibriumReport, HydrostaticEquilibriumPreparationRejection>;
|
||||||
|
|
||||||
|
[[noreturn]] inline void
|
||||||
|
throwHydrostaticEquilibriumPreparationRejection(const HydrostaticEquilibriumPreparationRejection &rejection) {
|
||||||
|
switch (rejection.reason) {
|
||||||
|
case HydrostaticEquilibriumPreparationRejectionReason::non_finite_geometry:
|
||||||
|
throw std::domain_error("Prepared hydrostatic equilibrium encountered non-finite mapped geometry.");
|
||||||
|
case HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual:
|
||||||
|
throw std::domain_error("Prepared hydrostatic equilibrium produced a non-finite residual.");
|
||||||
|
case HydrostaticEquilibriumPreparationRejectionReason::inverted_geometry:
|
||||||
|
default:
|
||||||
|
throw std::domain_error("Prepared hydrostatic equilibrium encountered inverted mapped geometry.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct PreparedHydrostaticAlgebraicJacobianStatistics {
|
struct PreparedHydrostaticAlgebraicJacobianStatistics {
|
||||||
std::uint64_t preparations{0};
|
std::uint64_t preparations{0};
|
||||||
std::uint64_t enthalpyApplications{0};
|
std::uint64_t enthalpyApplications{0};
|
||||||
@@ -102,6 +133,12 @@ export namespace mean_field::operators {
|
|||||||
const physics::RigidRotation &rotation
|
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 BuildResidual(mfem::Vector &residual) const;
|
||||||
|
|
||||||
void ApplyEnthalpyJacobianAction(
|
void ApplyEnthalpyJacobianAction(
|
||||||
@@ -221,12 +258,12 @@ export namespace mean_field::operators {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void PrepareStaticPlan();
|
void PrepareStaticPlan();
|
||||||
void PrepareGeometry();
|
[[nodiscard]] std::optional<mapping::MappingStatus> PrepareGeometry();
|
||||||
void PrepareAlgebraicJacobianBlocks();
|
[[nodiscard]] bool PrepareAlgebraicJacobianBlocks();
|
||||||
void PrepareRotation();
|
[[nodiscard]] bool PrepareRotation();
|
||||||
void PrepareBaseState();
|
[[nodiscard]] bool PrepareBaseState();
|
||||||
void FinalizeDisplacementJacobianPreparation();
|
void FinalizeDisplacementJacobianPreparation();
|
||||||
void AssembleCachedResidual();
|
[[nodiscard]] bool AssembleCachedResidual();
|
||||||
void VerifyPrepared() const;
|
void VerifyPrepared() const;
|
||||||
|
|
||||||
const fem::FEM &m_fem;
|
const fem::FEM &m_fem;
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ module;
|
|||||||
|
|
||||||
#include <compare>
|
#include <compare>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
export module mean_field:operators.prepared_mass_normalization;
|
export module mean_field:operators.prepared_mass_normalization;
|
||||||
@@ -59,6 +61,28 @@ export namespace mean_field::operators {
|
|||||||
constexpr auto operator<=>(const PreparedMassNormalizationReport &) const = default;
|
constexpr auto operator<=>(const PreparedMassNormalizationReport &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class MassNormalizationPreparationRejectionReason : std::uint8_t {
|
||||||
|
mapping_failure,
|
||||||
|
non_finite_density_interpolation,
|
||||||
|
non_finite_assembled_mass
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Candidate rejection is deliberately represented without text or owned
|
||||||
|
* storage. That makes the result cheap to propagate through a line search
|
||||||
|
* and gives the MPI implementation a deterministic, allocation-free value
|
||||||
|
* to select on every rank.
|
||||||
|
*/
|
||||||
|
struct MassNormalizationPreparationRejection final {
|
||||||
|
MassNormalizationPreparationRejectionReason reason{
|
||||||
|
MassNormalizationPreparationRejectionReason::mapping_failure
|
||||||
|
};
|
||||||
|
mapping::MappingStatus mappingStatus{mapping::MappingStatus::non_finite_result};
|
||||||
|
};
|
||||||
|
|
||||||
|
using MassNormalizationPreparationResult =
|
||||||
|
std::expected<PreparedMassNormalizationReport, MassNormalizationPreparationRejection>;
|
||||||
|
|
||||||
struct PreparedMassNormalizationActionStatistics final {
|
struct PreparedMassNormalizationActionStatistics final {
|
||||||
std::uint64_t densityApplications{0};
|
std::uint64_t densityApplications{0};
|
||||||
std::uint64_t displacementApplications{0};
|
std::uint64_t displacementApplications{0};
|
||||||
@@ -109,6 +133,16 @@ export namespace mean_field::operators {
|
|||||||
const MassNormalizationDependencies &dependencies
|
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 BuildResidual(mfem::Vector &residual) const;
|
||||||
|
|
||||||
void ApplyDensityJacobianAction(
|
void ApplyDensityJacobianAction(
|
||||||
@@ -179,9 +213,11 @@ export namespace mean_field::operators {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void BuildStaticPlan();
|
void BuildStaticPlan();
|
||||||
void RefreshGeometry(const mfem::Vector &displacement);
|
[[nodiscard]] std::optional<MassNormalizationPreparationRejection>
|
||||||
void RefreshDensity(const mfem::Vector &density);
|
RefreshGeometry(const mfem::Vector &displacement);
|
||||||
void AssembleResidual();
|
[[nodiscard]] std::optional<MassNormalizationPreparationRejection> RefreshDensity(const mfem::Vector &density);
|
||||||
|
[[nodiscard]] std::optional<MassNormalizationPreparationRejection> AssembleResidual();
|
||||||
|
[[nodiscard]] std::optional<MassNormalizationPreparationRejection> UpdateResidualForTargetMass();
|
||||||
void VerifyPrepared() const;
|
void VerifyPrepared() const;
|
||||||
|
|
||||||
[[nodiscard]] double EvaluateDensityActionLocal(const mfem::Vector &densityVariation) const;
|
[[nodiscard]] double EvaluateDensityActionLocal(const mfem::Vector &densityVariation) const;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ module;
|
|||||||
#include <compare>
|
#include <compare>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -18,6 +19,18 @@ export import :operators.context.pressure_force;
|
|||||||
export import :utils.blocks;
|
export import :utils.blocks;
|
||||||
|
|
||||||
export namespace mean_field::operators {
|
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 {
|
struct PreparedPressureForceReport final {
|
||||||
context::pressure_force::PressureForcePreparationReport contextReport;
|
context::pressure_force::PressureForcePreparationReport contextReport;
|
||||||
|
|
||||||
@@ -92,6 +105,14 @@ export namespace mean_field::operators {
|
|||||||
const context::pressure_force::PressureForceDependencies &dependencies
|
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 BuildResidual(mfem::Vector &residual) const;
|
||||||
|
|
||||||
void ApplyEnthalpyJacobianAction(
|
void ApplyEnthalpyJacobianAction(
|
||||||
@@ -206,12 +227,12 @@ export namespace mean_field::operators {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void PrepareStaticPlan();
|
void PrepareStaticPlan();
|
||||||
void PrepareGeometry();
|
[[nodiscard]] std::optional<PressureForcePreparationRejection> PrepareGeometry();
|
||||||
void PrepareMaterialState();
|
[[nodiscard]] std::optional<PressureForcePreparationRejection> PrepareMaterialState();
|
||||||
|
|
||||||
void FinalizeDisplacementJacobianPreparation();
|
void FinalizeDisplacementJacobianPreparation();
|
||||||
|
|
||||||
void AssembleCachedResidual();
|
[[nodiscard]] std::optional<PressureForcePreparationRejection> AssembleCachedResidual();
|
||||||
|
|
||||||
void VerifyPrepared() const;
|
void VerifyPrepared() const;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ module;
|
|||||||
|
|
||||||
#include <compare>
|
#include <compare>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ export module mean_field:operators.prepared_rotational_displacement_force;
|
|||||||
export import :fem;
|
export import :fem;
|
||||||
export import :mapping.domain_mapper;
|
export import :mapping.domain_mapper;
|
||||||
export import :operators.context.rotational_displacement_force;
|
export import :operators.context.rotational_displacement_force;
|
||||||
|
export import :operators.kernels.rotational_displacement_force;
|
||||||
export import :physics.rigid_rotation;
|
export import :physics.rigid_rotation;
|
||||||
export import :utils.blocks;
|
export import :utils.blocks;
|
||||||
|
|
||||||
@@ -61,6 +63,15 @@ export namespace mean_field::operators {
|
|||||||
const physics::RigidRotation &rotation
|
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 BuildResidual(mfem::Vector &residual) const;
|
||||||
|
|
||||||
void ApplyDensityJacobianAction(
|
void ApplyDensityJacobianAction(
|
||||||
@@ -104,7 +115,10 @@ export namespace mean_field::operators {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void VerifyPrepared() const;
|
void VerifyPrepared() const;
|
||||||
void PrepareElementData();
|
[[nodiscard]] std::expected<
|
||||||
|
void,
|
||||||
|
kernels::RotationalDisplacementForceRejection>
|
||||||
|
TryPrepareElementData();
|
||||||
void ApplyPreparedCompleteJacobianActionTrue(
|
void ApplyPreparedCompleteJacobianActionTrue(
|
||||||
const mfem::Vector &densityVariationTrue,
|
const mfem::Vector &densityVariationTrue,
|
||||||
const mfem::Vector &displacementVariationTrue,
|
const mfem::Vector &displacementVariationTrue,
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ module;
|
|||||||
#include <compare>
|
#include <compare>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
export module mean_field:operators.prepared_stellar_equilibrium;
|
export module mean_field:operators.prepared_stellar_equilibrium;
|
||||||
|
|
||||||
@@ -72,6 +76,70 @@ export namespace mean_field::operators {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class StellarEquilibriumPreparationRejectionReason : std::uint8_t {
|
||||||
|
inverted_geometry,
|
||||||
|
non_finite_geometry,
|
||||||
|
thermodynamic_domain,
|
||||||
|
non_finite_thermodynamics,
|
||||||
|
inadmissible_physics,
|
||||||
|
non_finite_physics
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class StellarEquilibriumPreparationStage : std::uint8_t {
|
||||||
|
unspecified,
|
||||||
|
generated_geometry,
|
||||||
|
gravity,
|
||||||
|
barotropic_closure,
|
||||||
|
hydrostatic_equilibrium,
|
||||||
|
displacement_residual,
|
||||||
|
pressure_force,
|
||||||
|
gravity_displacement_force,
|
||||||
|
rotational_displacement_force,
|
||||||
|
displacement_composition,
|
||||||
|
mass_normalization,
|
||||||
|
model_specification
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A candidate state that cannot define a physical mapped domain is an
|
||||||
|
* expected line-search outcome, not an exceptional program failure. Keep
|
||||||
|
* this payload fixed-size so every trial can report it without allocating.
|
||||||
|
*/
|
||||||
|
struct StellarEquilibriumPreparationRejection final {
|
||||||
|
StellarEquilibriumPreparationRejectionReason reason{
|
||||||
|
StellarEquilibriumPreparationRejectionReason::inverted_geometry
|
||||||
|
};
|
||||||
|
StellarEquilibriumPreparationStage stage{StellarEquilibriumPreparationStage::unspecified};
|
||||||
|
double minimumJacobianDeterminant{std::numeric_limits<double>::quiet_NaN()};
|
||||||
|
eos::EvaluationErrorCode thermodynamicErrorCode{eos::EvaluationErrorCode::nonfinite_result};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Report>
|
||||||
|
using StellarEquilibriumPreparationResult = std::expected<Report, StellarEquilibriumPreparationRejection>;
|
||||||
|
|
||||||
|
[[noreturn]] inline void
|
||||||
|
throwStellarEquilibriumPreparationRejection(const StellarEquilibriumPreparationRejection &rejection) {
|
||||||
|
switch (rejection.reason) {
|
||||||
|
case StellarEquilibriumPreparationRejectionReason::non_finite_geometry:
|
||||||
|
throw std::domain_error("The prepared domain deformation has non-finite mapped geometry.");
|
||||||
|
case StellarEquilibriumPreparationRejectionReason::thermodynamic_domain:
|
||||||
|
throw eos::EvaluationError(
|
||||||
|
rejection.thermodynamicErrorCode, "The stellar state lies outside the equation-of-state domain."
|
||||||
|
);
|
||||||
|
case StellarEquilibriumPreparationRejectionReason::non_finite_thermodynamics:
|
||||||
|
throw eos::EvaluationError(
|
||||||
|
rejection.thermodynamicErrorCode, "The stellar state produced non-finite thermodynamic data."
|
||||||
|
);
|
||||||
|
case StellarEquilibriumPreparationRejectionReason::inadmissible_physics:
|
||||||
|
throw std::domain_error("The stellar state is physically inadmissible.");
|
||||||
|
case StellarEquilibriumPreparationRejectionReason::non_finite_physics:
|
||||||
|
throw std::domain_error("The stellar state produced non-finite physical data.");
|
||||||
|
case StellarEquilibriumPreparationRejectionReason::inverted_geometry:
|
||||||
|
throw std::domain_error("The prepared domain deformation inverts at least one volume element.");
|
||||||
|
}
|
||||||
|
throw std::logic_error("Unknown stellar-equilibrium candidate-rejection reason.");
|
||||||
|
}
|
||||||
|
|
||||||
struct PreparedStellarEquilibriumStatistics final {
|
struct PreparedStellarEquilibriumStatistics final {
|
||||||
std::uint64_t residualAssemblies{0};
|
std::uint64_t residualAssemblies{0};
|
||||||
std::uint64_t residualApplications{0};
|
std::uint64_t residualApplications{0};
|
||||||
@@ -159,6 +227,12 @@ export namespace mean_field::operators {
|
|||||||
const physics::RigidRotation &rotation
|
const physics::RigidRotation &rotation
|
||||||
);
|
);
|
||||||
|
|
||||||
|
[[nodiscard]] StellarEquilibriumPreparationResult<PreparedStellarEquilibriumReport> TryPrepare(
|
||||||
|
const mfem::Vector &state,
|
||||||
|
const StellarEquilibriumDependencies &dependencies,
|
||||||
|
const physics::RigidRotation &rotation
|
||||||
|
);
|
||||||
|
|
||||||
void BuildResidual(mfem::Vector &residual) const;
|
void BuildResidual(mfem::Vector &residual) const;
|
||||||
|
|
||||||
void Mult(
|
void Mult(
|
||||||
@@ -188,12 +262,9 @@ export namespace mean_field::operators {
|
|||||||
[[nodiscard]] const PreparedHydrostaticEquilibriumOperator &GetHydrostaticOperator() const noexcept;
|
[[nodiscard]] const PreparedHydrostaticEquilibriumOperator &GetHydrostaticOperator() const noexcept;
|
||||||
[[nodiscard]] const PreparedDisplacementResidualOperator &GetDisplacementOperator() const noexcept;
|
[[nodiscard]] const PreparedDisplacementResidualOperator &GetDisplacementOperator() const noexcept;
|
||||||
[[nodiscard]] const PreparedMassNormalizationOperator &GetMassNormalizationOperator() const noexcept;
|
[[nodiscard]] const PreparedMassNormalizationOperator &GetMassNormalizationOperator() const noexcept;
|
||||||
[[nodiscard]] double ApplyDensityVolumeIntegralDensityAction(
|
[[nodiscard]] double ApplyDensityVolumeIntegralDensityAction(const mfem::Vector &densityDirection) const;
|
||||||
const mfem::Vector &densityDirection
|
[[nodiscard]] double
|
||||||
) const;
|
ApplyDensityVolumeIntegralSurfaceShapeAction(const mfem::Vector &surfaceShapeDirection) const;
|
||||||
[[nodiscard]] double ApplyDensityVolumeIntegralSurfaceShapeAction(
|
|
||||||
const mfem::Vector &surfaceShapeDirection
|
|
||||||
) const;
|
|
||||||
[[nodiscard]] const PreparedPressureSurfaceConstraint &GetSurfaceConstraintOperator() const noexcept;
|
[[nodiscard]] const PreparedPressureSurfaceConstraint &GetSurfaceConstraintOperator() const noexcept;
|
||||||
[[nodiscard]] const deformation::PreparedDomainDeformationRuntime &GetDomainDeformation() const noexcept;
|
[[nodiscard]] const deformation::PreparedDomainDeformationRuntime &GetDomainDeformation() const noexcept;
|
||||||
[[nodiscard]] const mfem::Vector &GetSurfaceDeformationParameters() const;
|
[[nodiscard]] const mfem::Vector &GetSurfaceDeformationParameters() const;
|
||||||
@@ -222,6 +293,7 @@ export namespace mean_field::operators {
|
|||||||
void VerifyPrepared() const;
|
void VerifyPrepared() const;
|
||||||
|
|
||||||
StellarEquilibriumRootManifest m_rootManifest;
|
StellarEquilibriumRootManifest m_rootManifest;
|
||||||
|
MPI_Comm m_communicator{MPI_COMM_NULL};
|
||||||
mfem::Array<int> m_gravityStateOffsets;
|
mfem::Array<int> m_gravityStateOffsets;
|
||||||
|
|
||||||
context::gravity_field::GravityFieldLinearizationContext m_gravityContext;
|
context::gravity_field::GravityFieldLinearizationContext m_gravityContext;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ module;
|
|||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -28,12 +29,11 @@ export namespace mean_field::equilibrium {
|
|||||||
static constexpr bool complete = false;
|
static constexpr bool complete = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <model::StellarModelType Model>
|
template <model::StellarModelType Model> struct StellarSurfaceCompilationAudit<Model, true> {
|
||||||
struct StellarSurfaceCompilationAudit<Model, true> {
|
|
||||||
private:
|
private:
|
||||||
using ModelType = std::remove_cvref_t<Model>;
|
using ModelType = std::remove_cvref_t<Model>;
|
||||||
using EquationOfState = typename ModelType::EquationOfStateType;
|
using EquationOfState = typename ModelType::EquationOfStateType;
|
||||||
using Form = operators::CompiledStellarEquilibriumForm<ModelType>;
|
using Form = operators::CompiledStellarEquilibriumForm<ModelType>;
|
||||||
using AvailableEquations = material::StellarEquilibriumThermodynamicEquations;
|
using AvailableEquations = material::StellarEquilibriumThermodynamicEquations;
|
||||||
|
|
||||||
static constexpr bool thermodynamicsCompilable =
|
static constexpr bool thermodynamicsCompilable =
|
||||||
@@ -46,14 +46,12 @@ export namespace mean_field::equilibrium {
|
|||||||
} else {
|
} else {
|
||||||
using ThermodynamicEquations =
|
using ThermodynamicEquations =
|
||||||
material::CompiledThermodynamicEquationsT<EquationOfState, Form, AvailableEquations>;
|
material::CompiledThermodynamicEquationsT<EquationOfState, Form, AvailableEquations>;
|
||||||
using Formulation = typename ThermodynamicEquations::PressureSurfaceFormulation;
|
using Formulation = typename ThermodynamicEquations::PressureSurfaceFormulation;
|
||||||
using CompiledSurface =
|
using CompiledSurface = surface::CompiledPressureSurfaceConstraintT<Formulation, EquationOfState>;
|
||||||
surface::CompiledPressureSurfaceConstraintT<Formulation, EquationOfState>;
|
|
||||||
return requires(const ModelType &model) {
|
return requires(const ModelType &model) {
|
||||||
{
|
{
|
||||||
surface::compilePressureSurfaceConstraint<Formulation>(
|
surface::compilePressureSurfaceConstraint<Formulation>(
|
||||||
model.surfaceCondition(),
|
model.surfaceCondition(), model.equationOfState()
|
||||||
model.equationOfState()
|
|
||||||
)
|
)
|
||||||
} -> std::same_as<CompiledSurface>;
|
} -> std::same_as<CompiledSurface>;
|
||||||
};
|
};
|
||||||
@@ -77,8 +75,7 @@ export namespace mean_field::equilibrium {
|
|||||||
requires operators::StellarEquilibriumSystemCompilable<std::remove_cvref_t<Candidate>>;
|
requires operators::StellarEquilibriumSystemCompilable<std::remove_cvref_t<Candidate>>;
|
||||||
requires hasStellarEquilibriumSurfaceCompilation<std::remove_cvref_t<Candidate>>;
|
requires hasStellarEquilibriumSurfaceCompilation<std::remove_cvref_t<Candidate>>;
|
||||||
requires operators::CompilableRootManifestFor<
|
requires operators::CompilableRootManifestFor<
|
||||||
std::remove_cvref_t<Candidate>,
|
std::remove_cvref_t<Candidate>, operators::CompiledStellarEquilibriumForm<std::remove_cvref_t<Candidate>>>;
|
||||||
operators::CompiledStellarEquilibriumForm<std::remove_cvref_t<Candidate>>>;
|
|
||||||
requires operators::hasStellarEquilibriumCoreRuntime<std::remove_cvref_t<Candidate>>;
|
requires operators::hasStellarEquilibriumCoreRuntime<std::remove_cvref_t<Candidate>>;
|
||||||
requires operators::hasCompleteStellarEquilibriumRuntime<std::remove_cvref_t<Candidate>>;
|
requires operators::hasCompleteStellarEquilibriumRuntime<std::remove_cvref_t<Candidate>>;
|
||||||
requires operators::stellarEquilibriumRotationProviderCount<std::remove_cvref_t<Candidate>> <= 1;
|
requires operators::stellarEquilibriumRotationProviderCount<std::remove_cvref_t<Candidate>> <= 1;
|
||||||
@@ -105,19 +102,16 @@ export namespace mean_field::equilibrium {
|
|||||||
operators::StellarEquilibriumPhysicalCoreType<std::remove_cvref_t<Model>>,
|
operators::StellarEquilibriumPhysicalCoreType<std::remove_cvref_t<Model>>,
|
||||||
typename std::remove_cvref_t<Model>::SpecificationTypes>> { };
|
typename std::remove_cvref_t<Model>::SpecificationTypes>> { };
|
||||||
|
|
||||||
struct StellarEquilibriumProblemFactory;
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <
|
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization = StellarDiscretization>
|
||||||
StellarEquilibriumModel Model,
|
|
||||||
StellarDiscretizationType Discretization = StellarDiscretization>
|
|
||||||
requires detail::StellarEquilibriumModelDiscretizationStructureAudit<
|
requires detail::StellarEquilibriumModelDiscretizationStructureAudit<
|
||||||
std::remove_cvref_t<Model>,
|
std::remove_cvref_t<Model>,
|
||||||
std::remove_cvref_t<Discretization>>::value
|
std::remove_cvref_t<Discretization>>::value
|
||||||
class StellarEquilibriumProblem final {
|
class StellarEquilibriumProblem final {
|
||||||
public:
|
public:
|
||||||
using ModelType = std::remove_cvref_t<Model>;
|
using ModelType = std::remove_cvref_t<Model>;
|
||||||
using DiscretizationType = std::remove_cvref_t<Discretization>;
|
using DiscretizationType = std::remove_cvref_t<Discretization>;
|
||||||
using NormalizationPrescriptionType = typename DiscretizationType::NormalizationPrescriptionType;
|
using NormalizationPrescriptionType = typename DiscretizationType::NormalizationPrescriptionType;
|
||||||
|
|
||||||
static constexpr bool hasFixedCentralDensity =
|
static constexpr bool hasFixedCentralDensity =
|
||||||
@@ -128,13 +122,15 @@ export namespace mean_field::equilibrium {
|
|||||||
operators::stellarEquilibriumRotationProviderCount<ModelType>;
|
operators::stellarEquilibriumRotationProviderCount<ModelType>;
|
||||||
static constexpr bool symbolicallySquare = ModelType::symbolicallySquare;
|
static constexpr bool symbolicallySquare = ModelType::symbolicallySquare;
|
||||||
|
|
||||||
using PreparedOperatorType = operators::PreparedVariadicStellarEquilibriumOperator<ModelType>;
|
using PreparedOperatorType = operators::PreparedVariadicStellarEquilibriumOperator<ModelType>;
|
||||||
using PhysicalCoreType = typename PreparedOperatorType::PhysicalCoreType;
|
using Report = typename PreparedOperatorType::Report;
|
||||||
using FormType = operators::CompiledStellarEquilibriumForm<ModelType>;
|
using PreparationResult = typename PreparedOperatorType::PreparationResult;
|
||||||
using JacobianFormType = operators::CompiledStellarEquilibriumJacobianForm<ModelType>;
|
using PhysicalCoreType = typename PreparedOperatorType::PhysicalCoreType;
|
||||||
using ManifestType = operators::EquilibriumSystemManifest<ModelType, FormType, JacobianFormType>;
|
using FormType = operators::CompiledStellarEquilibriumForm<ModelType>;
|
||||||
using EquationOfStateType = model::EquationOfStateType<ModelType>;
|
using JacobianFormType = operators::CompiledStellarEquilibriumJacobianForm<ModelType>;
|
||||||
using SurfaceConditionType = model::SurfaceConditionType<ModelType>;
|
using ManifestType = operators::EquilibriumSystemManifest<ModelType, FormType, JacobianFormType>;
|
||||||
|
using EquationOfStateType = model::EquationOfStateType<ModelType>;
|
||||||
|
using SurfaceConditionType = model::SurfaceConditionType<ModelType>;
|
||||||
using AvailableThermodynamicEquations = material::StellarEquilibriumThermodynamicEquations;
|
using AvailableThermodynamicEquations = material::StellarEquilibriumThermodynamicEquations;
|
||||||
using ThermodynamicEquationsType =
|
using ThermodynamicEquationsType =
|
||||||
material::CompiledThermodynamicEquationsT<EquationOfStateType, FormType, AvailableThermodynamicEquations>;
|
material::CompiledThermodynamicEquationsT<EquationOfStateType, FormType, AvailableThermodynamicEquations>;
|
||||||
@@ -147,17 +143,18 @@ export namespace mean_field::equilibrium {
|
|||||||
|
|
||||||
StellarEquilibriumProblem(
|
StellarEquilibriumProblem(
|
||||||
ModelType stellarModel,
|
ModelType stellarModel,
|
||||||
DiscretizationType discretization
|
DiscretizationType discretization,
|
||||||
|
fem::FEM &finiteElementModel
|
||||||
)
|
)
|
||||||
: m_stellarModel(std::make_shared<ModelType>(std::move(stellarModel))),
|
: m_stellarModel(std::make_shared<ModelType>(std::move(stellarModel))),
|
||||||
m_discretization(std::move(discretization)),
|
m_discretization(std::move(discretization)),
|
||||||
m_compiledSurfaceConstraint(CompileSurfaceConstraint(*m_stellarModel)),
|
m_compiledSurfaceConstraint(CompileSurfaceConstraint(*m_stellarModel)),
|
||||||
m_preparedOperator(
|
m_preparedOperator(
|
||||||
m_discretization.finiteElementModel(),
|
finiteElementModel,
|
||||||
m_discretization.domainMapper(),
|
m_discretization.domainMapper(),
|
||||||
m_stellarModel,
|
m_stellarModel,
|
||||||
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
|
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
|
||||||
CompileDefaultDomainDeformation(m_discretization.finiteElementModel())
|
CompileDefaultDomainDeformation(finiteElementModel)
|
||||||
) {
|
) {
|
||||||
VerifyProblem();
|
VerifyProblem();
|
||||||
}
|
}
|
||||||
@@ -176,6 +173,12 @@ export namespace mean_field::equilibrium {
|
|||||||
return m_discretization;
|
return m_discretization;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MPI_Comm GetCommunicator() const & {
|
||||||
|
return m_discretization.communicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MPI_Comm GetCommunicator() const && = delete;
|
||||||
|
|
||||||
[[nodiscard]] const NormalizationPrescriptionType &GetNormalizationPrescription() const noexcept {
|
[[nodiscard]] const NormalizationPrescriptionType &GetNormalizationPrescription() const noexcept {
|
||||||
return m_discretization.normalizationPrescription();
|
return m_discretization.normalizationPrescription();
|
||||||
}
|
}
|
||||||
@@ -236,21 +239,54 @@ export namespace mean_field::equilibrium {
|
|||||||
const mfem::Vector &state,
|
const mfem::Vector &state,
|
||||||
const operators::StellarEquilibriumDependencies &dependencies,
|
const operators::StellarEquilibriumDependencies &dependencies,
|
||||||
const physics::RigidRotation &rotation
|
const physics::RigidRotation &rotation
|
||||||
) requires(generatedRotationProviderCount == 0) {
|
)
|
||||||
|
requires(generatedRotationProviderCount == 0)
|
||||||
|
{
|
||||||
auto report = m_preparedOperator.Prepare(state, dependencies, rotation);
|
auto report = m_preparedOperator.Prepare(state, dependencies, rotation);
|
||||||
++m_preparationGeneration;
|
++m_preparationGeneration;
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] PreparationResult TryPrepare(
|
||||||
|
const mfem::Vector &state,
|
||||||
|
const operators::StellarEquilibriumDependencies &dependencies,
|
||||||
|
const physics::RigidRotation &rotation
|
||||||
|
)
|
||||||
|
requires(generatedRotationProviderCount == 0)
|
||||||
|
{
|
||||||
|
auto result = m_preparedOperator.TryPrepare(state, dependencies, rotation);
|
||||||
|
if (!result.has_value()) {
|
||||||
|
return std::unexpected(result.error());
|
||||||
|
}
|
||||||
|
++m_preparationGeneration;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto Prepare(
|
[[nodiscard]] auto Prepare(
|
||||||
const mfem::Vector &state,
|
const mfem::Vector &state,
|
||||||
const operators::StellarEquilibriumDependencies &dependencies
|
const operators::StellarEquilibriumDependencies &dependencies
|
||||||
) requires(generatedRotationProviderCount == 1) {
|
)
|
||||||
|
requires(generatedRotationProviderCount == 1)
|
||||||
|
{
|
||||||
auto report = m_preparedOperator.Prepare(state, dependencies);
|
auto report = m_preparedOperator.Prepare(state, dependencies);
|
||||||
++m_preparationGeneration;
|
++m_preparationGeneration;
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] PreparationResult TryPrepare(
|
||||||
|
const mfem::Vector &state,
|
||||||
|
const operators::StellarEquilibriumDependencies &dependencies
|
||||||
|
)
|
||||||
|
requires(generatedRotationProviderCount == 1)
|
||||||
|
{
|
||||||
|
auto result = m_preparedOperator.TryPrepare(state, dependencies);
|
||||||
|
if (!result.has_value()) {
|
||||||
|
return std::unexpected(result.error());
|
||||||
|
}
|
||||||
|
++m_preparationGeneration;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void BuildResidual(mfem::Vector &residual) const {
|
void BuildResidual(mfem::Vector &residual) const {
|
||||||
m_preparedOperator.BuildResidual(residual);
|
m_preparedOperator.BuildResidual(residual);
|
||||||
}
|
}
|
||||||
@@ -266,8 +302,7 @@ export namespace mean_field::equilibrium {
|
|||||||
[[nodiscard]] static CompiledSurfaceConstraintType CompileSurfaceConstraint(const ModelType &stellarModel) {
|
[[nodiscard]] static CompiledSurfaceConstraintType CompileSurfaceConstraint(const ModelType &stellarModel) {
|
||||||
return surface::compilePressureSurfaceConstraint<
|
return surface::compilePressureSurfaceConstraint<
|
||||||
typename ThermodynamicEquationsType::PressureSurfaceFormulation>(
|
typename ThermodynamicEquationsType::PressureSurfaceFormulation>(
|
||||||
stellarModel.surfaceCondition(),
|
stellarModel.surfaceCondition(), stellarModel.equationOfState()
|
||||||
stellarModel.equationOfState()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,28 +353,25 @@ export namespace mean_field::equilibrium {
|
|||||||
template <
|
template <
|
||||||
typename Model,
|
typename Model,
|
||||||
typename Discretization,
|
typename Discretization,
|
||||||
bool StructurallyCompatible =
|
bool StructurallyCompatible = StellarEquilibriumModelDiscretizationStructureAudit<
|
||||||
StellarEquilibriumModelDiscretizationStructureAudit<
|
std::remove_cvref_t<Model>,
|
||||||
std::remove_cvref_t<Model>,
|
std::remove_cvref_t<Discretization>>::value>
|
||||||
std::remove_cvref_t<Discretization>>::value>
|
|
||||||
struct StellarEquilibriumModelDiscretizationOperationAudit : std::false_type { };
|
struct StellarEquilibriumModelDiscretizationOperationAudit : std::false_type { };
|
||||||
|
|
||||||
template <typename Model, typename Discretization>
|
template <typename Model, typename Discretization>
|
||||||
struct StellarEquilibriumModelDiscretizationOperationAudit<
|
struct StellarEquilibriumModelDiscretizationOperationAudit<Model, Discretization, true> {
|
||||||
Model,
|
|
||||||
Discretization,
|
|
||||||
true> {
|
|
||||||
private:
|
private:
|
||||||
using ModelType = std::remove_cvref_t<Model>;
|
using ModelType = std::remove_cvref_t<Model>;
|
||||||
using DiscretizationType = std::remove_cvref_t<Discretization>;
|
using DiscretizationType = std::remove_cvref_t<Discretization>;
|
||||||
using Problem = StellarEquilibriumProblem<ModelType, DiscretizationType>;
|
using Problem = StellarEquilibriumProblem<ModelType, DiscretizationType>;
|
||||||
using Prescription = typename DiscretizationType::NormalizationPrescriptionType;
|
using Prescription = typename DiscretizationType::NormalizationPrescriptionType;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr bool value = [] {
|
static constexpr bool value = [] {
|
||||||
if constexpr (
|
if constexpr (
|
||||||
std::same_as<Prescription, normalization::Unnormalized> ||
|
std::same_as<Prescription, normalization::Unnormalized> ||
|
||||||
normalization::PhysicalRieszDiagonalPrescription<Prescription>) {
|
normalization::PhysicalRieszDiagonalPrescription<Prescription>
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return normalization::RuntimePreparedNormalizationOperation<Problem>;
|
return normalization::RuntimePreparedNormalizationOperation<Problem>;
|
||||||
@@ -362,46 +394,99 @@ export namespace mean_field::equilibrium {
|
|||||||
std::remove_cvref_t<Model>,
|
std::remove_cvref_t<Model>,
|
||||||
std::remove_cvref_t<Discretization>>::value;
|
std::remove_cvref_t<Discretization>>::value;
|
||||||
|
|
||||||
namespace detail {
|
} // namespace mean_field::equilibrium
|
||||||
/* The structurally formed problem type is needed to probe the ADL
|
|
||||||
* operation without a recursive concept. Its constructor remains
|
|
||||||
* private, and this factory is the single construction authority after
|
|
||||||
* the complete public compatibility contract has succeeded. */
|
|
||||||
struct StellarEquilibriumProblemFactory final {
|
|
||||||
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization>
|
|
||||||
requires StellarEquilibriumModelDiscretizationCompatible<Model, Discretization>
|
|
||||||
[[nodiscard]] static auto Create(
|
|
||||||
Model &&stellarModel,
|
|
||||||
Discretization discretization
|
|
||||||
) {
|
|
||||||
using ModelType = std::remove_cvref_t<Model>;
|
|
||||||
using DiscretizationType = std::remove_cvref_t<Discretization>;
|
|
||||||
return StellarEquilibriumProblem<ModelType, DiscretizationType>{
|
|
||||||
std::forward<Model>(stellarModel),
|
|
||||||
std::move(discretization)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization>
|
namespace mean_field::equilibrium::detail {
|
||||||
requires StellarEquilibriumModelDiscretizationCompatible<Model, Discretization>
|
/* The structurally formed problem type is needed to probe the ADL
|
||||||
|
* operation without a recursive concept. Its constructor remains
|
||||||
|
* private, and this factory is the single construction authority after
|
||||||
|
* the complete public compatibility contract has succeeded. */
|
||||||
|
struct StellarEquilibriumProblemFactory final {
|
||||||
|
template <
|
||||||
|
StellarEquilibriumModel Model,
|
||||||
|
StellarDiscretizationType Discretization>
|
||||||
|
requires StellarEquilibriumModelDiscretizationCompatible<
|
||||||
|
Model,
|
||||||
|
Discretization>
|
||||||
|
[[nodiscard]] static auto Create(
|
||||||
|
Model &&stellarModel,
|
||||||
|
Discretization discretization
|
||||||
|
) {
|
||||||
|
using ModelType = std::remove_cvref_t<Model>;
|
||||||
|
using DiscretizationType = std::remove_cvref_t<Discretization>;
|
||||||
|
fem::FEM &finiteElementModel = discretization.MutableFiniteElementModelForAssembly();
|
||||||
|
return StellarEquilibriumProblem<ModelType, DiscretizationType>{
|
||||||
|
std::forward<Model>(stellarModel), std::move(discretization), finiteElementModel
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
StellarEquilibriumModel Model,
|
||||||
|
StellarDiscretizationType Discretization>
|
||||||
|
requires StellarEquilibriumModelDiscretizationCompatible<
|
||||||
|
Model,
|
||||||
|
Discretization>
|
||||||
|
[[nodiscard]] static auto CreateOwned(
|
||||||
|
Model &&stellarModel,
|
||||||
|
Discretization discretization
|
||||||
|
) {
|
||||||
|
using ModelType = std::remove_cvref_t<Model>;
|
||||||
|
using DiscretizationType = std::remove_cvref_t<Discretization>;
|
||||||
|
using ProblemType = StellarEquilibriumProblem<ModelType, DiscretizationType>;
|
||||||
|
fem::FEM &finiteElementModel = discretization.MutableFiniteElementModelForAssembly();
|
||||||
|
return std::unique_ptr<ProblemType>{
|
||||||
|
new ProblemType{std::forward<Model>(stellarModel), std::move(discretization), finiteElementModel}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <DiscretizedStellarEquilibriumProblem Problem>
|
||||||
|
[[nodiscard]] static fem::FEM &MutableFiniteElementModelForProjection(Problem &problem) {
|
||||||
|
return problem.m_discretization.MutableFiniteElementModelForAssembly();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <DiscretizedStellarEquilibriumProblem Problem>
|
||||||
|
[[nodiscard]] static const fem::FEM &FiniteElementModel(const Problem &problem) {
|
||||||
|
return problem.m_discretization.RequireFiniteElementModel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace mean_field::equilibrium::detail
|
||||||
|
|
||||||
|
export namespace mean_field::equilibrium {
|
||||||
|
template <
|
||||||
|
StellarEquilibriumModel Model,
|
||||||
|
StellarDiscretizationType Discretization>
|
||||||
|
requires StellarEquilibriumModelDiscretizationCompatible<
|
||||||
|
Model,
|
||||||
|
Discretization>
|
||||||
[[nodiscard]] auto discretize(
|
[[nodiscard]] auto discretize(
|
||||||
Model &&stellarModel,
|
Model &&stellarModel,
|
||||||
Discretization discretization
|
Discretization discretization
|
||||||
) {
|
) {
|
||||||
return detail::StellarEquilibriumProblemFactory::Create(
|
return detail::StellarEquilibriumProblemFactory::Create(
|
||||||
std::forward<Model>(stellarModel),
|
std::forward<Model>(stellarModel), std::move(discretization)
|
||||||
std::move(discretization)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <StellarEquilibriumModel Model>
|
template <StellarEquilibriumModel Model>
|
||||||
requires StellarEquilibriumModelDiscretizationCompatible<Model, StellarDiscretization>
|
requires StellarEquilibriumModelDiscretizationCompatible<
|
||||||
|
Model,
|
||||||
|
StellarDiscretization>
|
||||||
[[nodiscard]] auto discretize(
|
[[nodiscard]] auto discretize(
|
||||||
Model &&stellarModel,
|
Model &&stellarModel,
|
||||||
fem::FEM &finiteElementModel
|
fem::FEM &&finiteElementModel
|
||||||
) {
|
) {
|
||||||
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel});
|
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{std::move(finiteElementModel)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <StellarEquilibriumModel Model>
|
||||||
|
requires StellarEquilibriumModelDiscretizationCompatible<
|
||||||
|
Model,
|
||||||
|
StellarDiscretization>
|
||||||
|
StellarEquilibriumProblem<
|
||||||
|
std::remove_cvref_t<Model>,
|
||||||
|
StellarDiscretization>
|
||||||
|
discretize(
|
||||||
|
Model &&,
|
||||||
|
fem::FEM &
|
||||||
|
) = delete;
|
||||||
} // namespace mean_field::equilibrium
|
} // namespace mean_field::equilibrium
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ export namespace mean_field::equilibrium {
|
|||||||
|
|
||||||
template <StellarEquilibriumModel Model>
|
template <StellarEquilibriumModel Model>
|
||||||
[[nodiscard]] auto makeStellarEquilibriumSystem(
|
[[nodiscard]] auto makeStellarEquilibriumSystem(
|
||||||
fem::FEM &finiteElementModel,
|
fem::FEM &&finiteElementModel,
|
||||||
const mapping::DomainMapper &domainMapper,
|
|
||||||
Model &&stellarModel
|
Model &&stellarModel
|
||||||
) {
|
) {
|
||||||
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel, domainMapper});
|
return discretize(std::forward<Model>(stellarModel), std::move(finiteElementModel));
|
||||||
}
|
}
|
||||||
} // namespace mean_field::equilibrium
|
} // namespace mean_field::equilibrium
|
||||||
|
|||||||
@@ -325,8 +325,14 @@ export namespace mean_field::preconditioning {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedStellarPreconditioner(ProblemType &&, BlockType) = delete;
|
PreparedStellarPreconditioner(
|
||||||
PreparedStellarPreconditioner(const ProblemType &&, BlockType) = delete;
|
ProblemType &&,
|
||||||
|
BlockType
|
||||||
|
) = delete;
|
||||||
|
PreparedStellarPreconditioner(
|
||||||
|
const ProblemType &&,
|
||||||
|
BlockType
|
||||||
|
) = delete;
|
||||||
|
|
||||||
PreparedStellarPreconditioner(const PreparedStellarPreconditioner &) = delete;
|
PreparedStellarPreconditioner(const PreparedStellarPreconditioner &) = delete;
|
||||||
PreparedStellarPreconditioner &operator=(const PreparedStellarPreconditioner &) = delete;
|
PreparedStellarPreconditioner &operator=(const PreparedStellarPreconditioner &) = delete;
|
||||||
@@ -399,9 +405,11 @@ export namespace mean_field::preconditioning {
|
|||||||
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
|
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
|
||||||
SpecificationBorderBlockType Block>
|
SpecificationBorderBlockType Block>
|
||||||
requires EquilibriumCoordinateComponentFor<
|
requires EquilibriumCoordinateComponentFor<
|
||||||
Block,
|
Block,
|
||||||
typename std::remove_cvref_t<Problem>::FormType> &&
|
typename std::remove_cvref_t<Problem>::FormType> &&
|
||||||
SpecificationBorderPreparableFor<Problem, Block>
|
SpecificationBorderPreparableFor<
|
||||||
|
Problem,
|
||||||
|
Block>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const Problem &problem,
|
const Problem &problem,
|
||||||
Block block
|
Block block
|
||||||
@@ -409,17 +417,22 @@ export namespace mean_field::preconditioning {
|
|||||||
return PreparedStellarPreconditioner<Problem, Block>{problem, std::move(block)};
|
return PreparedStellarPreconditioner<Problem, Block>{problem, std::move(block)};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Problem, SpecificationBorderBlockType Block>
|
template <
|
||||||
requires (!std::is_lvalue_reference_v<Problem>) &&
|
typename Problem,
|
||||||
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
SpecificationBorderBlockType Block>
|
||||||
EquilibriumCoordinateComponentFor<
|
requires(!std::is_lvalue_reference_v<Problem>) &&
|
||||||
Block,
|
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||||
typename std::remove_cvref_t<Problem>::FormType> &&
|
EquilibriumCoordinateComponentFor<
|
||||||
SpecificationBorderPreparableFor<std::remove_cvref_t<Problem>, Block>
|
Block,
|
||||||
|
typename std::remove_cvref_t<Problem>::FormType> &&
|
||||||
|
SpecificationBorderPreparableFor<
|
||||||
|
std::remove_cvref_t<Problem>,
|
||||||
|
Block>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
Problem &&,
|
Problem &&,
|
||||||
Block
|
Block
|
||||||
) -> PreparedStellarPreconditioner<
|
)
|
||||||
std::remove_cvref_t<Problem>,
|
-> PreparedStellarPreconditioner<
|
||||||
std::remove_cvref_t<Block>> = delete;
|
std::remove_cvref_t<Problem>,
|
||||||
|
std::remove_cvref_t<Block>> = delete;
|
||||||
} // namespace mean_field::preconditioning
|
} // namespace mean_field::preconditioning
|
||||||
|
|||||||
@@ -236,15 +236,13 @@ export namespace mean_field::preconditioning {
|
|||||||
* assembly. The current kernels remain polytropic, but selection no
|
* assembly. The current kernels remain polytropic, but selection no
|
||||||
* longer embeds that closed-world type test in the descriptor concept.
|
* longer embeds that closed-world type test in the descriptor concept.
|
||||||
*/
|
*/
|
||||||
template <typename EquationOfState>
|
template <typename EquationOfState> struct MaterialSurfaceEquationOfStateBackend {
|
||||||
struct MaterialSurfaceEquationOfStateBackend {
|
|
||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <> struct MaterialSurfaceEquationOfStateBackend<eos::Polytrope> {
|
||||||
struct MaterialSurfaceEquationOfStateBackend<eos::Polytrope> {
|
|
||||||
static constexpr bool registered = true;
|
static constexpr bool registered = true;
|
||||||
using CoreType = operators::PreparedStellarEquilibriumOperator;
|
using CoreType = operators::PreparedStellarEquilibriumOperator;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename EquationOfState>
|
template <typename EquationOfState>
|
||||||
@@ -252,8 +250,7 @@ export namespace mean_field::preconditioning {
|
|||||||
{
|
{
|
||||||
MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::registered
|
MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::registered
|
||||||
} -> std::convertible_to<bool>;
|
} -> std::convertible_to<bool>;
|
||||||
requires MaterialSurfaceEquationOfStateBackend<
|
requires MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::registered;
|
||||||
std::remove_cvref_t<EquationOfState>>::registered;
|
|
||||||
typename MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::CoreType;
|
typename MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::CoreType;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -266,13 +263,11 @@ export namespace mean_field::preconditioning {
|
|||||||
* truthful while the current kernels still consume the legacy physical
|
* truthful while the current kernels still consume the legacy physical
|
||||||
* core directly.
|
* core directly.
|
||||||
*/
|
*/
|
||||||
template <typename EquationOfState, typename PhysicalCore>
|
template <typename EquationOfState, typename PhysicalCore> struct MaterialSurfaceExecutableRuntime {
|
||||||
struct MaterialSurfaceExecutableRuntime {
|
|
||||||
static constexpr bool available = false;
|
static constexpr bool available = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <> struct MaterialSurfaceExecutableRuntime<eos::Polytrope, operators::PreparedStellarEquilibriumOperator> {
|
||||||
struct MaterialSurfaceExecutableRuntime<eos::Polytrope, operators::PreparedStellarEquilibriumOperator> {
|
|
||||||
static constexpr bool available = true;
|
static constexpr bool available = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -280,33 +275,28 @@ export namespace mean_field::preconditioning {
|
|||||||
concept ExecutableMaterialSurfaceRuntimeFor = requires {
|
concept ExecutableMaterialSurfaceRuntimeFor = requires {
|
||||||
{
|
{
|
||||||
MaterialSurfaceExecutableRuntime<
|
MaterialSurfaceExecutableRuntime<
|
||||||
std::remove_cvref_t<EquationOfState>,
|
std::remove_cvref_t<EquationOfState>, std::remove_cvref_t<PhysicalCore>>::available
|
||||||
std::remove_cvref_t<PhysicalCore>>::available
|
|
||||||
} -> std::convertible_to<bool>;
|
} -> std::convertible_to<bool>;
|
||||||
requires MaterialSurfaceExecutableRuntime<
|
requires MaterialSurfaceExecutableRuntime<
|
||||||
std::remove_cvref_t<EquationOfState>,
|
std::remove_cvref_t<EquationOfState>, std::remove_cvref_t<PhysicalCore>>::available;
|
||||||
std::remove_cvref_t<PhysicalCore>>::available;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Descriptor>
|
template <typename Descriptor>
|
||||||
concept ImplementedMaterialSurfaceDescriptor =
|
concept ImplementedMaterialSurfaceDescriptor =
|
||||||
MaterialSurfaceDescriptor<Descriptor> &&
|
MaterialSurfaceDescriptor<Descriptor> &&
|
||||||
ImplementedMaterialSurfaceEquationOfState<
|
ImplementedMaterialSurfaceEquationOfState<typename Descriptor::ThermodynamicEquations::EquationOfStateType>;
|
||||||
typename Descriptor::ThermodynamicEquations::EquationOfStateType>;
|
|
||||||
|
|
||||||
template <typename Descriptor, typename PhysicalCore>
|
template <typename Descriptor, typename PhysicalCore>
|
||||||
concept MaterialSurfaceRuntimeFor =
|
concept MaterialSurfaceRuntimeFor = ImplementedMaterialSurfaceDescriptor<Descriptor> && requires {
|
||||||
ImplementedMaterialSurfaceDescriptor<Descriptor> && requires {
|
typename MaterialSurfaceEquationOfStateBackend<
|
||||||
|
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType;
|
||||||
|
requires std::same_as<
|
||||||
|
std::remove_cvref_t<PhysicalCore>,
|
||||||
typename MaterialSurfaceEquationOfStateBackend<
|
typename MaterialSurfaceEquationOfStateBackend<
|
||||||
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType;
|
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType>;
|
||||||
requires std::same_as<
|
requires ExecutableMaterialSurfaceRuntimeFor<
|
||||||
std::remove_cvref_t<PhysicalCore>,
|
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType, PhysicalCore>;
|
||||||
typename MaterialSurfaceEquationOfStateBackend<
|
};
|
||||||
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType>;
|
|
||||||
requires ExecutableMaterialSurfaceRuntimeFor<
|
|
||||||
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType,
|
|
||||||
PhysicalCore>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept MaterialSurfacePreconditionerProblem =
|
concept MaterialSurfacePreconditionerProblem =
|
||||||
@@ -778,14 +768,14 @@ export namespace mean_field::preconditioning {
|
|||||||
fullEnthalpyDirection = enthalpyDirection;
|
fullEnthalpyDirection = enthalpyDirection;
|
||||||
|
|
||||||
m_operation->Mult(m_fullDirection, m_fullAction);
|
m_operation->Mult(m_fullDirection, m_fullAction);
|
||||||
const auto fullActionView = m_operation->GetRootManifest().residualView(m_fullAction);
|
const auto fullActionView = m_operation->GetRootManifest().residualView(m_fullAction);
|
||||||
const auto fullDensityAction = fullActionView.block(utils::blocks::density_field.mass_term);
|
const auto fullDensityAction = fullActionView.block(utils::blocks::density_field.mass_term);
|
||||||
const auto fullSurfaceAction =
|
const auto fullSurfaceAction =
|
||||||
fullActionView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term);
|
fullActionView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term);
|
||||||
const auto fullEnthalpyAction = fullActionView.block(utils::blocks::enthalpy_field.specific_term);
|
const auto fullEnthalpyAction = fullActionView.block(utils::blocks::enthalpy_field.specific_term);
|
||||||
densityAction = fullDensityAction;
|
densityAction = fullDensityAction;
|
||||||
surfaceAction = fullSurfaceAction;
|
surfaceAction = fullSurfaceAction;
|
||||||
enthalpyAction = fullEnthalpyAction;
|
enthalpyAction = fullEnthalpyAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyEnthalpyToDensity(
|
void ApplyEnthalpyToDensity(
|
||||||
@@ -1121,6 +1111,79 @@ export namespace mean_field::preconditioning {
|
|||||||
std::uint64_t regularizedEntries{0};
|
std::uint64_t regularizedEntries{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
[[nodiscard]] inline DiagonalPreparationQuality regularizeMaterialSurfaceDiagonal(
|
||||||
|
mfem::Vector &diagonal,
|
||||||
|
const MaterialSurfaceDiagonalOptions options,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
const int localOptionsAreValid = std::isfinite(options.relativeFloor) && options.relativeFloor >= 0.0 &&
|
||||||
|
std::isfinite(options.absoluteFloor) && options.absoluteFloor > 0.0
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
|
int globalOptionsAreValid = 0;
|
||||||
|
if (MPI_Allreduce(&localOptionsAreValid, &globalOptionsAreValid, 1, MPI_INT, MPI_MIN, communicator) !=
|
||||||
|
MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("Material-surface regularization could not validate its options.");
|
||||||
|
}
|
||||||
|
if (globalOptionsAreValid == 0) {
|
||||||
|
throw std::invalid_argument("Material-surface diagonal floors must be finite and nonnegative.");
|
||||||
|
}
|
||||||
|
|
||||||
|
double localMaximum = 0.0;
|
||||||
|
double localMinimum = std::numeric_limits<double>::infinity();
|
||||||
|
int localEntriesAreFinite = 1;
|
||||||
|
for (int index = 0; index < diagonal.Size(); ++index) {
|
||||||
|
if (!std::isfinite(diagonal(index))) {
|
||||||
|
localEntriesAreFinite = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const double magnitude = std::abs(diagonal(index));
|
||||||
|
localMaximum = std::max(localMaximum, magnitude);
|
||||||
|
localMinimum = std::min(localMinimum, magnitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
int globalEntriesAreFinite = 0;
|
||||||
|
if (MPI_Allreduce(&localEntriesAreFinite, &globalEntriesAreFinite, 1, MPI_INT, MPI_MIN, communicator) !=
|
||||||
|
MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("Material-surface regularization could not validate its diagonal.");
|
||||||
|
}
|
||||||
|
if (globalEntriesAreFinite == 0) {
|
||||||
|
throw std::invalid_argument("A material-surface diagonal contains a non-finite entry.");
|
||||||
|
}
|
||||||
|
|
||||||
|
double globalMaximum = 0.0;
|
||||||
|
double globalMinimum = 0.0;
|
||||||
|
const int maximumStatus =
|
||||||
|
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
||||||
|
const int minimumStatus =
|
||||||
|
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
|
||||||
|
if (maximumStatus != MPI_SUCCESS || minimumStatus != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("Material-surface regularization could not reduce diagonal magnitudes.");
|
||||||
|
}
|
||||||
|
const double floor = std::max(options.absoluteFloor, options.relativeFloor * globalMaximum);
|
||||||
|
std::uint64_t localRegularized = 0;
|
||||||
|
for (int index = 0; index < diagonal.Size(); ++index) {
|
||||||
|
if (std::abs(diagonal(index)) < floor) {
|
||||||
|
diagonal(index) = std::copysign(floor, diagonal(index) == 0.0 ? 1.0 : diagonal(index));
|
||||||
|
++localRegularized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t globalRegularized = 0;
|
||||||
|
if (MPI_Allreduce(&localRegularized, &globalRegularized, 1, MPI_UINT64_T, MPI_SUM, communicator) !=
|
||||||
|
MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("Material-surface regularization could not count regularized entries.");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
.minimumAbsoluteEntryBeforeRegularization = globalMinimum,
|
||||||
|
.maximumAbsoluteEntryBeforeRegularization = globalMaximum,
|
||||||
|
.appliedFloor = floor,
|
||||||
|
.regularizedEntries = globalRegularized
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
struct SurfaceRieszCalibrationReport final {
|
struct SurfaceRieszCalibrationReport final {
|
||||||
SurfaceRieszCalibrationTarget target{SurfaceRieszCalibrationTarget::none};
|
SurfaceRieszCalibrationTarget target{SurfaceRieszCalibrationTarget::none};
|
||||||
int probeCount{0};
|
int probeCount{0};
|
||||||
@@ -1522,40 +1585,7 @@ export namespace mean_field::preconditioning {
|
|||||||
const MaterialSurfaceDiagonalOptions options,
|
const MaterialSurfaceDiagonalOptions options,
|
||||||
const MPI_Comm communicator
|
const MPI_Comm communicator
|
||||||
) {
|
) {
|
||||||
if (!std::isfinite(options.relativeFloor) || options.relativeFloor < 0.0 ||
|
return detail::regularizeMaterialSurfaceDiagonal(diagonal, options, communicator);
|
||||||
!std::isfinite(options.absoluteFloor) || options.absoluteFloor <= 0.0) {
|
|
||||||
throw std::invalid_argument("Material-surface diagonal floors must be finite and nonnegative.");
|
|
||||||
}
|
|
||||||
double localMaximum = 0.0;
|
|
||||||
double localMinimum = std::numeric_limits<double>::infinity();
|
|
||||||
for (int index = 0; index < diagonal.Size(); ++index) {
|
|
||||||
if (!std::isfinite(diagonal(index))) {
|
|
||||||
throw std::invalid_argument("A material-surface diagonal contains a non-finite entry.");
|
|
||||||
}
|
|
||||||
const double magnitude = std::abs(diagonal(index));
|
|
||||||
localMaximum = std::max(localMaximum, magnitude);
|
|
||||||
localMinimum = std::min(localMinimum, magnitude);
|
|
||||||
}
|
|
||||||
double globalMaximum = 0.0;
|
|
||||||
double globalMinimum = 0.0;
|
|
||||||
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
|
||||||
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
|
|
||||||
const double floor = std::max(options.absoluteFloor, options.relativeFloor * globalMaximum);
|
|
||||||
std::uint64_t localRegularized = 0;
|
|
||||||
for (int index = 0; index < diagonal.Size(); ++index) {
|
|
||||||
if (std::abs(diagonal(index)) < floor) {
|
|
||||||
diagonal(index) = std::copysign(floor, diagonal(index) == 0.0 ? 1.0 : diagonal(index));
|
|
||||||
++localRegularized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::uint64_t globalRegularized = 0;
|
|
||||||
MPI_Allreduce(&localRegularized, &globalRegularized, 1, MPI_UINT64_T, MPI_SUM, communicator);
|
|
||||||
return {
|
|
||||||
.minimumAbsoluteEntryBeforeRegularization = globalMinimum,
|
|
||||||
.maximumAbsoluteEntryBeforeRegularization = globalMaximum,
|
|
||||||
.appliedFloor = floor,
|
|
||||||
.regularizedEntries = globalRegularized
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Block m_block;
|
Block m_block;
|
||||||
@@ -2051,40 +2081,7 @@ export namespace mean_field::preconditioning {
|
|||||||
const MaterialSurfaceDiagonalOptions options,
|
const MaterialSurfaceDiagonalOptions options,
|
||||||
const MPI_Comm communicator
|
const MPI_Comm communicator
|
||||||
) {
|
) {
|
||||||
if (!std::isfinite(options.relativeFloor) || options.relativeFloor < 0.0 ||
|
return detail::regularizeMaterialSurfaceDiagonal(diagonal, options, communicator);
|
||||||
!std::isfinite(options.absoluteFloor) || options.absoluteFloor <= 0.0) {
|
|
||||||
throw std::invalid_argument("Material-surface diagonal floors must be finite and nonnegative.");
|
|
||||||
}
|
|
||||||
double localMaximum = 0.0;
|
|
||||||
double localMinimum = std::numeric_limits<double>::infinity();
|
|
||||||
for (int index = 0; index < diagonal.Size(); ++index) {
|
|
||||||
if (!std::isfinite(diagonal(index))) {
|
|
||||||
throw std::invalid_argument("A material-surface diagonal contains a non-finite entry.");
|
|
||||||
}
|
|
||||||
const double magnitude = std::abs(diagonal(index));
|
|
||||||
localMaximum = std::max(localMaximum, magnitude);
|
|
||||||
localMinimum = std::min(localMinimum, magnitude);
|
|
||||||
}
|
|
||||||
double globalMaximum = 0.0;
|
|
||||||
double globalMinimum = 0.0;
|
|
||||||
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
|
||||||
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
|
|
||||||
const double floor = std::max(options.absoluteFloor, options.relativeFloor * globalMaximum);
|
|
||||||
std::uint64_t localRegularized = 0;
|
|
||||||
for (int index = 0; index < diagonal.Size(); ++index) {
|
|
||||||
if (std::abs(diagonal(index)) < floor) {
|
|
||||||
diagonal(index) = std::copysign(floor, diagonal(index) == 0.0 ? 1.0 : diagonal(index));
|
|
||||||
++localRegularized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::uint64_t globalRegularized = 0;
|
|
||||||
MPI_Allreduce(&localRegularized, &globalRegularized, 1, MPI_UINT64_T, MPI_SUM, communicator);
|
|
||||||
return {
|
|
||||||
.minimumAbsoluteEntryBeforeRegularization = globalMinimum,
|
|
||||||
.maximumAbsoluteEntryBeforeRegularization = globalMaximum,
|
|
||||||
.appliedFloor = floor,
|
|
||||||
.regularizedEntries = globalRegularized
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Block m_block;
|
Block m_block;
|
||||||
@@ -2111,7 +2108,9 @@ export namespace mean_field::preconditioning {
|
|||||||
template <
|
template <
|
||||||
MaterialSurfaceDescriptor Descriptor,
|
MaterialSurfaceDescriptor Descriptor,
|
||||||
MaterialSurfaceFactorizationPolicy Policy>
|
MaterialSurfaceFactorizationPolicy Policy>
|
||||||
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
|
requires MaterialSurfaceRuntimeFor<
|
||||||
|
Descriptor,
|
||||||
|
operators::PreparedStellarEquilibriumOperator>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const operators::PreparedStellarEquilibriumOperator &operation,
|
const operators::PreparedStellarEquilibriumOperator &operation,
|
||||||
MaterialSurfaceBlock<
|
MaterialSurfaceBlock<
|
||||||
@@ -2127,12 +2126,17 @@ export namespace mean_field::preconditioning {
|
|||||||
equilibrium::StellarEquilibriumModel Model,
|
equilibrium::StellarEquilibriumModel Model,
|
||||||
equilibrium::StellarDiscretizationType Discretization,
|
equilibrium::StellarDiscretizationType Discretization,
|
||||||
MaterialSurfaceFactorizationPolicy Policy>
|
MaterialSurfaceFactorizationPolicy Policy>
|
||||||
requires MaterialSurfacePreconditionerProblem<
|
requires MaterialSurfacePreconditionerProblem<equilibrium::StellarEquilibriumProblem<
|
||||||
equilibrium::StellarEquilibriumProblem<Model, Discretization>>
|
Model,
|
||||||
|
Discretization>>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
const equilibrium::StellarEquilibriumProblem<
|
||||||
|
Model,
|
||||||
|
Discretization> &problem,
|
||||||
MaterialSurfaceBlock<
|
MaterialSurfaceBlock<
|
||||||
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model, Discretization>>,
|
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<
|
||||||
|
Model,
|
||||||
|
Discretization>>,
|
||||||
backend::Diagonal,
|
backend::Diagonal,
|
||||||
backend::Diagonal,
|
backend::Diagonal,
|
||||||
Policy> block
|
Policy> block
|
||||||
@@ -2144,7 +2148,9 @@ export namespace mean_field::preconditioning {
|
|||||||
MaterialSurfaceDescriptor Descriptor,
|
MaterialSurfaceDescriptor Descriptor,
|
||||||
MaterialSurfaceFactorizationPolicy Policy,
|
MaterialSurfaceFactorizationPolicy Policy,
|
||||||
backend::ApplicationMode Mode>
|
backend::ApplicationMode Mode>
|
||||||
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
|
requires MaterialSurfaceRuntimeFor<
|
||||||
|
Descriptor,
|
||||||
|
operators::PreparedStellarEquilibriumOperator>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const operators::PreparedStellarEquilibriumOperator &operation,
|
const operators::PreparedStellarEquilibriumOperator &operation,
|
||||||
MaterialSurfaceBlock<
|
MaterialSurfaceBlock<
|
||||||
@@ -2162,12 +2168,17 @@ export namespace mean_field::preconditioning {
|
|||||||
equilibrium::StellarDiscretizationType Discretization,
|
equilibrium::StellarDiscretizationType Discretization,
|
||||||
MaterialSurfaceFactorizationPolicy Policy,
|
MaterialSurfaceFactorizationPolicy Policy,
|
||||||
backend::ApplicationMode Mode>
|
backend::ApplicationMode Mode>
|
||||||
requires MaterialSurfacePreconditionerProblem<
|
requires MaterialSurfacePreconditionerProblem<equilibrium::StellarEquilibriumProblem<
|
||||||
equilibrium::StellarEquilibriumProblem<Model, Discretization>>
|
Model,
|
||||||
|
Discretization>>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
const equilibrium::StellarEquilibriumProblem<
|
||||||
|
Model,
|
||||||
|
Discretization> &problem,
|
||||||
MaterialSurfaceBlock<
|
MaterialSurfaceBlock<
|
||||||
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model, Discretization>>,
|
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<
|
||||||
|
Model,
|
||||||
|
Discretization>>,
|
||||||
backend::Diagonal,
|
backend::Diagonal,
|
||||||
backend::HypreBoomerAMG<Mode>,
|
backend::HypreBoomerAMG<Mode>,
|
||||||
Policy,
|
Policy,
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ export import :preconditioning.stellar_equilibrium;
|
|||||||
export import :preconditioning.stellar_structure;
|
export import :preconditioning.stellar_structure;
|
||||||
export import :preconditioning.specification_border;
|
export import :preconditioning.specification_border;
|
||||||
export import :preconditioning.equilibrium_coordinates;
|
export import :preconditioning.equilibrium_coordinates;
|
||||||
|
export import :preconditioning.stellar_recipe;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -63,7 +63,7 @@ export namespace mean_field::preconditioning {
|
|||||||
.geometry = prepared.geometry != current.geometry,
|
.geometry = prepared.geometry != current.geometry,
|
||||||
.equationOfState = prepared.equationOfStateIdentity != current.equationOfStateIdentity,
|
.equationOfState = prepared.equationOfStateIdentity != current.equationOfStateIdentity,
|
||||||
.linearization = prepared.linearization != current.linearization ||
|
.linearization = prepared.linearization != current.linearization ||
|
||||||
prepared.preparedOperatorGeneration != current.preparedOperatorGeneration
|
prepared.preparedOperatorGeneration != current.preparedOperatorGeneration
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,9 +97,7 @@ export namespace mean_field::preconditioning {
|
|||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <
|
template <equilibrium::StellarEquilibriumModel Model, equilibrium::StellarDiscretizationType Discretization>
|
||||||
equilibrium::StellarEquilibriumModel Model,
|
|
||||||
equilibrium::StellarDiscretizationType Discretization>
|
|
||||||
struct StellarEquilibriumProblemTraits<equilibrium::StellarEquilibriumProblem<Model, Discretization>> {
|
struct StellarEquilibriumProblemTraits<equilibrium::StellarEquilibriumProblem<Model, Discretization>> {
|
||||||
using Problem = equilibrium::StellarEquilibriumProblem<Model, Discretization>;
|
using Problem = equilibrium::StellarEquilibriumProblem<Model, Discretization>;
|
||||||
using Form = typename Problem::FormType;
|
using Form = typename Problem::FormType;
|
||||||
@@ -131,10 +129,9 @@ export namespace mean_field::preconditioning {
|
|||||||
[[nodiscard]] static StellarPreconditionerLifecycleSnapshot Snapshot(const Problem &problem) {
|
[[nodiscard]] static StellarPreconditionerLifecycleSnapshot Snapshot(const Problem &problem) {
|
||||||
const operators::StellarEquilibriumDependencies &dependencies = problem.GetLinearizationDependencies();
|
const operators::StellarEquilibriumDependencies &dependencies = problem.GetLinearizationDependencies();
|
||||||
return {
|
return {
|
||||||
.discretization = dependencies.discretization,
|
.discretization = dependencies.discretization,
|
||||||
.geometry = problem.GetGeometryDependency(),
|
.geometry = problem.GetGeometryDependency(),
|
||||||
.equationOfStateIdentity =
|
.equationOfStateIdentity = std::addressof(problem.GetStellarModel().equationOfState()),
|
||||||
std::addressof(problem.GetStellarModel().equationOfState()),
|
|
||||||
.linearization = dependencies,
|
.linearization = dependencies,
|
||||||
.preparedOperatorGeneration = problem.GetPreparationGeneration()
|
.preparedOperatorGeneration = problem.GetPreparationGeneration()
|
||||||
};
|
};
|
||||||
@@ -145,26 +142,20 @@ export namespace mean_field::preconditioning {
|
|||||||
concept StellarPreconditionerProblem = StellarEquilibriumProblemTraits<std::remove_cvref_t<Candidate>>::registered;
|
concept StellarPreconditionerProblem = StellarEquilibriumProblemTraits<std::remove_cvref_t<Candidate>>::registered;
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename Block>
|
template <typename Block> struct IsGeneratedStellarValueBlock : std::false_type { };
|
||||||
struct IsGeneratedStellarValueBlock : std::false_type { };
|
|
||||||
|
|
||||||
template <typename Generated>
|
template <typename Generated>
|
||||||
struct IsGeneratedStellarValueBlock<utils::blocks::generated_value_block<Generated>>
|
struct IsGeneratedStellarValueBlock<utils::blocks::generated_value_block<Generated>> : std::true_type { };
|
||||||
: std::true_type { };
|
|
||||||
|
|
||||||
template <typename Block>
|
template <typename Block> struct IsGeneratedStellarResidualBlock : std::false_type { };
|
||||||
struct IsGeneratedStellarResidualBlock : std::false_type { };
|
|
||||||
|
|
||||||
template <typename Generated>
|
template <typename Generated>
|
||||||
struct IsGeneratedStellarResidualBlock<utils::blocks::generated_residual_block<Generated>>
|
struct IsGeneratedStellarResidualBlock<utils::blocks::generated_residual_block<Generated>> : std::true_type { };
|
||||||
: std::true_type { };
|
|
||||||
|
|
||||||
template <typename Coupling>
|
template <typename Coupling>
|
||||||
inline constexpr bool isPurePhysicalStellarCoupling =
|
inline constexpr bool isPurePhysicalStellarCoupling =
|
||||||
!IsGeneratedStellarValueBlock<
|
!IsGeneratedStellarValueBlock<std::remove_cvref_t<typename Coupling::Value>>::value &&
|
||||||
std::remove_cvref_t<typename Coupling::Value>>::value &&
|
!IsGeneratedStellarResidualBlock<std::remove_cvref_t<typename Coupling::Residual>>::value;
|
||||||
!IsGeneratedStellarResidualBlock<
|
|
||||||
std::remove_cvref_t<typename Coupling::Residual>>::value;
|
|
||||||
|
|
||||||
/* Pure structure contributions owned by a trusted backend are exact
|
/* Pure structure contributions owned by a trusted backend are exact
|
||||||
* (core, specification, coupling) capabilities. Future cores and new
|
* (core, specification, coupling) capabilities. Future cores and new
|
||||||
@@ -172,8 +163,7 @@ export namespace mean_field::preconditioning {
|
|||||||
* be accompanied by an explicit preconditioner decision. Generated-
|
* be accompanied by an explicit preconditioner decision. Generated-
|
||||||
* border terms remain the responsibility of specification-border
|
* border terms remain the responsibility of specification-border
|
||||||
* machinery. */
|
* machinery. */
|
||||||
template <typename PhysicalCore, typename Specification>
|
template <typename PhysicalCore, typename Specification> struct StellarStructureBackendHandledCouplings {
|
||||||
struct StellarStructureBackendHandledCouplings {
|
|
||||||
using Type = utils::blocks::type_list<>;
|
using Type = utils::blocks::type_list<>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -213,17 +203,12 @@ export namespace mean_field::preconditioning {
|
|||||||
struct StellarStructureBackendHandledCouplings<
|
struct StellarStructureBackendHandledCouplings<
|
||||||
operators::PreparedStellarEquilibriumOperator,
|
operators::PreparedStellarEquilibriumOperator,
|
||||||
models::FixedCentralDensity> {
|
models::FixedCentralDensity> {
|
||||||
using Type = utils::blocks::type_list<
|
using Type = utils::blocks::type_list<operators::StellarEquilibriumJacobianCoupling<
|
||||||
operators::StellarEquilibriumJacobianCoupling<
|
utils::blocks::enthalpy::specific::residual,
|
||||||
utils::blocks::enthalpy::specific::residual,
|
utils::blocks::enthalpy::specific::value>>;
|
||||||
utils::blocks::enthalpy::specific::value>>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <
|
template <typename Coupling, typename Model, typename PhysicalCore, typename ModelSpecifications>
|
||||||
typename Coupling,
|
|
||||||
typename Model,
|
|
||||||
typename PhysicalCore,
|
|
||||||
typename ModelSpecifications>
|
|
||||||
struct EveryCouplingContributionHandled;
|
struct EveryCouplingContributionHandled;
|
||||||
|
|
||||||
template <
|
template <
|
||||||
@@ -235,30 +220,22 @@ export namespace mean_field::preconditioning {
|
|||||||
Coupling,
|
Coupling,
|
||||||
Model,
|
Model,
|
||||||
PhysicalCore,
|
PhysicalCore,
|
||||||
models::detail::SpecificationSetStorage<Specifications...>> final {
|
models::detail::SpecificationSetStorage<Specifications...>>
|
||||||
|
final {
|
||||||
private:
|
private:
|
||||||
template <typename Specification>
|
template <typename Specification>
|
||||||
static constexpr bool handled =
|
static constexpr bool handled =
|
||||||
!utils::blocks::contains_type_v<
|
!utils::blocks::contains_type_v<
|
||||||
Coupling,
|
Coupling,
|
||||||
typename operators::StellarEquilibriumSpecificationCompilation<
|
typename operators::StellarEquilibriumSpecificationCompilation<Specification>::JacobianCouplings> ||
|
||||||
Specification>::JacobianCouplings> ||
|
(operators::stellarEquilibriumBackendRuntimeAuthorized<Specification, Model> &&
|
||||||
(operators::stellarEquilibriumBackendRuntimeAuthorized<
|
|
||||||
Specification,
|
|
||||||
Model> &&
|
|
||||||
utils::blocks::contains_type_v<
|
utils::blocks::contains_type_v<
|
||||||
Coupling,
|
Coupling,
|
||||||
typename StellarStructureBackendHandledCouplings<
|
typename StellarStructureBackendHandledCouplings<PhysicalCore, Specification>::Type>) ||
|
||||||
PhysicalCore,
|
operators::stellarEquilibriumSpecificationCouplingIsStructuralZero<Specification, Model, Coupling>;
|
||||||
Specification>::Type>) ||
|
|
||||||
operators::stellarEquilibriumSpecificationCouplingIsStructuralZero<
|
|
||||||
Specification,
|
|
||||||
Model,
|
|
||||||
Coupling>;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr bool value =
|
static constexpr bool value = (handled<Specifications> && ...);
|
||||||
(handled<Specifications> && ...);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <
|
template <
|
||||||
@@ -269,11 +246,7 @@ export namespace mean_field::preconditioning {
|
|||||||
typename Unsupported>
|
typename Unsupported>
|
||||||
struct CollectUnsupportedStellarStructureCouplings;
|
struct CollectUnsupportedStellarStructureCouplings;
|
||||||
|
|
||||||
template <
|
template <typename Model, typename PhysicalCore, typename ModelSpecifications, typename Unsupported>
|
||||||
typename Model,
|
|
||||||
typename PhysicalCore,
|
|
||||||
typename ModelSpecifications,
|
|
||||||
typename Unsupported>
|
|
||||||
struct CollectUnsupportedStellarStructureCouplings<
|
struct CollectUnsupportedStellarStructureCouplings<
|
||||||
utils::blocks::type_list<>,
|
utils::blocks::type_list<>,
|
||||||
Model,
|
Model,
|
||||||
@@ -299,11 +272,7 @@ export namespace mean_field::preconditioning {
|
|||||||
private:
|
private:
|
||||||
static constexpr bool supported =
|
static constexpr bool supported =
|
||||||
!isPurePhysicalStellarCoupling<Head> ||
|
!isPurePhysicalStellarCoupling<Head> ||
|
||||||
EveryCouplingContributionHandled<
|
EveryCouplingContributionHandled<Head, Model, PhysicalCore, ModelSpecifications>::value;
|
||||||
Head,
|
|
||||||
Model,
|
|
||||||
PhysicalCore,
|
|
||||||
ModelSpecifications>::value;
|
|
||||||
using Next = std::conditional_t<
|
using Next = std::conditional_t<
|
||||||
supported,
|
supported,
|
||||||
utils::blocks::type_list<Unsupported...>,
|
utils::blocks::type_list<Unsupported...>,
|
||||||
@@ -318,42 +287,36 @@ export namespace mean_field::preconditioning {
|
|||||||
Next>::Type;
|
Next>::Type;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Candidate, typename = void>
|
template <typename Candidate, typename = void> struct DefaultStellarStructurePhysicalTopologyAudit {
|
||||||
struct DefaultStellarStructurePhysicalTopologyAudit {
|
using ContributionCouplings = utils::blocks::type_list<>;
|
||||||
using ContributionCouplings = utils::blocks::type_list<>;
|
using UnsupportedCouplings = utils::blocks::type_list<>;
|
||||||
using UnsupportedCouplings = utils::blocks::type_list<>;
|
|
||||||
|
|
||||||
static constexpr bool supported = false;
|
static constexpr bool supported = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <model::StellarModelType Model>
|
template <model::StellarModelType Model>
|
||||||
requires(
|
requires(
|
||||||
operators::StellarEquilibriumSystemCompilable<
|
operators::StellarEquilibriumSystemCompilable<std::remove_cvref_t<Model>> &&
|
||||||
std::remove_cvref_t<Model>> &&
|
operators::hasStellarEquilibriumCoreRuntime<std::remove_cvref_t<Model>>
|
||||||
operators::hasStellarEquilibriumCoreRuntime<
|
)
|
||||||
std::remove_cvref_t<Model>>)
|
|
||||||
struct DefaultStellarStructurePhysicalTopologyAudit<
|
struct DefaultStellarStructurePhysicalTopologyAudit<
|
||||||
Model,
|
Model,
|
||||||
std::void_t<
|
std::void_t<
|
||||||
typename operators::CompiledStellarEquilibriumSystem<
|
typename operators::CompiledStellarEquilibriumSystem<
|
||||||
std::remove_cvref_t<Model>>::ContributionJacobianCouplings,
|
std::remove_cvref_t<Model>>::ContributionJacobianCouplings,
|
||||||
operators::StellarEquilibriumPhysicalCoreType<
|
operators::StellarEquilibriumPhysicalCoreType<std::remove_cvref_t<Model>>>> {
|
||||||
std::remove_cvref_t<Model>>>> {
|
|
||||||
private:
|
private:
|
||||||
using Compilation = operators::CompiledStellarEquilibriumSystem<
|
using Compilation = operators::CompiledStellarEquilibriumSystem<std::remove_cvref_t<Model>>;
|
||||||
std::remove_cvref_t<Model>>;
|
using PhysicalCore = operators::StellarEquilibriumPhysicalCoreType<std::remove_cvref_t<Model>>;
|
||||||
using PhysicalCore = operators::StellarEquilibriumPhysicalCoreType<
|
|
||||||
std::remove_cvref_t<Model>>;
|
|
||||||
public:
|
public:
|
||||||
using ContributionCouplings =
|
using ContributionCouplings = typename Compilation::ContributionJacobianCouplings;
|
||||||
typename Compilation::ContributionJacobianCouplings;
|
using UnsupportedCouplings = typename CollectUnsupportedStellarStructureCouplings<
|
||||||
using UnsupportedCouplings =
|
ContributionCouplings,
|
||||||
typename CollectUnsupportedStellarStructureCouplings<
|
std::remove_cvref_t<Model>,
|
||||||
ContributionCouplings,
|
PhysicalCore,
|
||||||
std::remove_cvref_t<Model>,
|
typename std::remove_cvref_t<Model>::SpecificationTypes,
|
||||||
PhysicalCore,
|
utils::blocks::type_list<>>::Type;
|
||||||
typename std::remove_cvref_t<Model>::SpecificationTypes,
|
|
||||||
utils::blocks::type_list<>>::Type;
|
|
||||||
|
|
||||||
static constexpr bool supported = UnsupportedCouplings::size == 0;
|
static constexpr bool supported = UnsupportedCouplings::size == 0;
|
||||||
};
|
};
|
||||||
@@ -369,13 +332,11 @@ export namespace mean_field::preconditioning {
|
|||||||
* detection-safe and therefore suitable for constraining factories. */
|
* detection-safe and therefore suitable for constraining factories. */
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
struct DefaultStellarStructurePhysicalTopologySupport
|
struct DefaultStellarStructurePhysicalTopologySupport
|
||||||
: detail::DefaultStellarStructurePhysicalTopologyAudit<
|
: detail::DefaultStellarStructurePhysicalTopologyAudit<std::remove_cvref_t<Candidate>> { };
|
||||||
std::remove_cvref_t<Candidate>> { };
|
|
||||||
|
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
inline constexpr bool defaultStellarStructurePhysicalTopologySupported =
|
inline constexpr bool defaultStellarStructurePhysicalTopologySupported =
|
||||||
DefaultStellarStructurePhysicalTopologySupport<
|
DefaultStellarStructurePhysicalTopologySupport<std::remove_cvref_t<Candidate>>::supported;
|
||||||
std::remove_cvref_t<Candidate>>::supported;
|
|
||||||
|
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept DefaultStellarStructurePhysicalTopologySupportedFor =
|
concept DefaultStellarStructurePhysicalTopologySupportedFor =
|
||||||
@@ -419,9 +380,8 @@ export namespace mean_field::preconditioning {
|
|||||||
template <typename Form> struct IdentityPlanForForm;
|
template <typename Form> struct IdentityPlanForForm;
|
||||||
|
|
||||||
template <typename... Values, typename... Residuals>
|
template <typename... Values, typename... Residuals>
|
||||||
struct IdentityPlanForForm<utils::blocks::block_form<
|
struct IdentityPlanForForm<
|
||||||
utils::blocks::type_list<Values...>,
|
utils::blocks::block_form<utils::blocks::type_list<Values...>, utils::blocks::type_list<Residuals...>>> {
|
||||||
utils::blocks::type_list<Residuals...>>> {
|
|
||||||
static_assert(sizeof...(Values) == sizeof...(Residuals));
|
static_assert(sizeof...(Values) == sizeof...(Residuals));
|
||||||
using Type = PreconditionerPlan<IdentityBlock<Values, Residuals>...>;
|
using Type = PreconditionerPlan<IdentityBlock<Values, Residuals>...>;
|
||||||
|
|
||||||
|
|||||||
83
libmeanfield/interface/preconditioning/stellar_recipe.cppm
Normal file
83
libmeanfield/interface/preconditioning/stellar_recipe.cppm
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mfem.hpp>
|
||||||
|
|
||||||
|
export module mean_field:preconditioning.stellar_recipe;
|
||||||
|
|
||||||
|
export import :preconditioning.equilibrium_coordinates;
|
||||||
|
|
||||||
|
export namespace mean_field::preconditioning {
|
||||||
|
/*
|
||||||
|
* A stellar-preconditioner prescription is an unbound, owning value. It
|
||||||
|
* may therefore be created before a problem exists and safely moved into
|
||||||
|
* the eventual user-owned solve context. A prepared inverse is deliberately a
|
||||||
|
* separate, problem-bound object with a stable address.
|
||||||
|
*/
|
||||||
|
struct StellarPreconditionerPrescriptionTag { };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept StellarPreconditionerPrescription =
|
||||||
|
std::derived_from<std::remove_cvref_t<Candidate>, StellarPreconditionerPrescriptionTag> &&
|
||||||
|
std::move_constructible<std::remove_cvref_t<Candidate>>;
|
||||||
|
|
||||||
|
struct DefaultStellarPreconditioner final : StellarPreconditionerPrescriptionTag { };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This overload is the user-facing, problem-independent factory. The
|
||||||
|
* existing makePreconditioner(problem) overload remains the low-level
|
||||||
|
* factory for the typed, unprepared block assembled below.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr DefaultStellarPreconditioner makePreconditioner() noexcept {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Candidate, typename Problem>
|
||||||
|
concept PreparedStellarInverseFor =
|
||||||
|
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||||
|
std::derived_from<std::remove_cvref_t<Candidate>, mfem::Solver> &&
|
||||||
|
std::destructible<std::remove_cvref_t<Candidate>> &&
|
||||||
|
requires(std::remove_cvref_t<Candidate> &prepared, const std::remove_cvref_t<Candidate> &constantPrepared) {
|
||||||
|
{ constantPrepared.GetProblem() } -> std::same_as<const std::remove_cvref_t<Problem> &>;
|
||||||
|
{ constantPrepared.IsCurrent() } -> std::same_as<bool>;
|
||||||
|
prepared.Refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Built-in preparation is intentionally policy-first. The same spelling
|
||||||
|
* can be supplied beside a third-party prescription and found by ADL,
|
||||||
|
* without adding that prescription to a central registry or switch.
|
||||||
|
* Preparation requires an already-prepared problem because the current
|
||||||
|
* physical inverse assembles state-dependent numerical data.
|
||||||
|
*/
|
||||||
|
template <DefaultStellarPreconditionerAvailableFor Problem>
|
||||||
|
[[nodiscard]] auto prepareStellarPreconditioner(
|
||||||
|
DefaultStellarPreconditioner,
|
||||||
|
const Problem &problem
|
||||||
|
) {
|
||||||
|
return preconditioning::prepare(problem, preconditioning::makePreconditioner(problem));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Prescription, typename Problem>
|
||||||
|
concept StellarPreconditionerRuntimeAvailableFor =
|
||||||
|
StellarPreconditionerPrescription<Prescription> &&
|
||||||
|
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||||
|
requires(std::remove_cvref_t<Prescription> prescription, const std::remove_cvref_t<Problem> &problem) {
|
||||||
|
requires std::same_as<
|
||||||
|
decltype(prepareStellarPreconditioner(std::move(prescription), problem)),
|
||||||
|
std::remove_cvref_t<decltype(prepareStellarPreconditioner(std::move(prescription), problem))>>;
|
||||||
|
{
|
||||||
|
prepareStellarPreconditioner(std::move(prescription), problem)
|
||||||
|
} -> PreparedStellarInverseFor<std::remove_cvref_t<Problem>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <StellarPreconditionerPrescription Prescription, typename Problem>
|
||||||
|
requires StellarPreconditionerRuntimeAvailableFor<Prescription, Problem>
|
||||||
|
using PreparedStellarInverseType = std::remove_cvref_t<decltype(prepareStellarPreconditioner(
|
||||||
|
std::declval<std::remove_cvref_t<Prescription> &&>(),
|
||||||
|
std::declval<const std::remove_cvref_t<Problem> &>()
|
||||||
|
))>;
|
||||||
|
} // namespace mean_field::preconditioning
|
||||||
@@ -634,13 +634,11 @@ export namespace mean_field::preconditioning {
|
|||||||
* Add future cores here only together with matching cross-coupling and
|
* Add future cores here only together with matching cross-coupling and
|
||||||
* preparation implementations.
|
* preparation implementations.
|
||||||
*/
|
*/
|
||||||
template <typename PhysicalCore>
|
template <typename PhysicalCore> struct StellarStructureExecutableRuntime {
|
||||||
struct StellarStructureExecutableRuntime {
|
|
||||||
static constexpr bool available = false;
|
static constexpr bool available = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <> struct StellarStructureExecutableRuntime<operators::PreparedStellarEquilibriumOperator> {
|
||||||
struct StellarStructureExecutableRuntime<operators::PreparedStellarEquilibriumOperator> {
|
|
||||||
static constexpr bool available = true;
|
static constexpr bool available = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -654,14 +652,12 @@ export namespace mean_field::preconditioning {
|
|||||||
|
|
||||||
template <typename Descriptor, typename PhysicalCore>
|
template <typename Descriptor, typename PhysicalCore>
|
||||||
concept StellarStructureRuntimeFor =
|
concept StellarStructureRuntimeFor =
|
||||||
MaterialSurfaceRuntimeFor<Descriptor, PhysicalCore> &&
|
MaterialSurfaceRuntimeFor<Descriptor, PhysicalCore> && ExecutableStellarStructureRuntimeFor<PhysicalCore>;
|
||||||
ExecutableStellarStructureRuntimeFor<PhysicalCore>;
|
|
||||||
|
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept StellarStructurePreconditionerProblem =
|
concept StellarStructurePreconditionerProblem =
|
||||||
equilibrium::DiscretizedStellarEquilibriumProblem<Candidate> &&
|
equilibrium::DiscretizedStellarEquilibriumProblem<Candidate> &&
|
||||||
DefaultStellarStructurePhysicalTopologySupportedFor<
|
DefaultStellarStructurePhysicalTopologySupportedFor<typename std::remove_cvref_t<Candidate>::ModelType> &&
|
||||||
typename std::remove_cvref_t<Candidate>::ModelType> &&
|
|
||||||
requires {
|
requires {
|
||||||
requires StellarStructureRuntimeFor<
|
requires StellarStructureRuntimeFor<
|
||||||
MaterialSurfaceDescriptorFor<std::remove_cvref_t<Candidate>>,
|
MaterialSurfaceDescriptorFor<std::remove_cvref_t<Candidate>>,
|
||||||
@@ -688,8 +684,7 @@ export namespace mean_field::preconditioning {
|
|||||||
preconditioning::prepare(problem, std::move(materialComponent));
|
preconditioning::prepare(problem, std::move(materialComponent));
|
||||||
preconditioning::prepare(
|
preconditioning::prepare(
|
||||||
problem.GetPhysicalOperator().GetHydrostaticOperator().GetFEM(),
|
problem.GetPhysicalOperator().GetHydrostaticOperator().GetFEM(),
|
||||||
problem.GetPhysicalOperator().GetGravityContext().GetGeometryContext(),
|
problem.GetPhysicalOperator().GetGravityContext().GetGeometryContext(), std::move(gravityComponent)
|
||||||
std::move(gravityComponent)
|
|
||||||
);
|
);
|
||||||
StellarStructureCrossJacobianOperator{problem.GetPhysicalOperator()};
|
StellarStructureCrossJacobianOperator{problem.GetPhysicalOperator()};
|
||||||
};
|
};
|
||||||
@@ -873,19 +868,30 @@ export namespace mean_field::preconditioning {
|
|||||||
GravityFactorizationPolicy GravityPolicy,
|
GravityFactorizationPolicy GravityPolicy,
|
||||||
StellarStructureFactorizationPolicy StructurePolicy>
|
StellarStructureFactorizationPolicy StructurePolicy>
|
||||||
requires StellarStructurePreparableFor<
|
requires StellarStructurePreparableFor<
|
||||||
equilibrium::StellarEquilibriumProblem<Model, Discretization>,
|
equilibrium::StellarEquilibriumProblem<
|
||||||
|
Model,
|
||||||
|
Discretization>,
|
||||||
MaterialComponent,
|
MaterialComponent,
|
||||||
GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>>
|
GravityFieldBlock<
|
||||||
|
GravityMassBackend,
|
||||||
|
backend::HypreBoomerAMG<Mode>,
|
||||||
|
GravityPolicy>>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
const equilibrium::StellarEquilibriumProblem<
|
||||||
|
Model,
|
||||||
|
Discretization> &problem,
|
||||||
StellarStructureBlock<
|
StellarStructureBlock<
|
||||||
MaterialComponent,
|
MaterialComponent,
|
||||||
GravityFieldBlock<
|
GravityFieldBlock<
|
||||||
GravityMassBackend,
|
GravityMassBackend,
|
||||||
backend::HypreBoomerAMG<Mode>,
|
backend::HypreBoomerAMG<Mode>,
|
||||||
GravityPolicy>,
|
GravityPolicy>,
|
||||||
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::FormType,
|
typename equilibrium::StellarEquilibriumProblem<
|
||||||
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::JacobianFormType,
|
Model,
|
||||||
|
Discretization>::FormType,
|
||||||
|
typename equilibrium::StellarEquilibriumProblem<
|
||||||
|
Model,
|
||||||
|
Discretization>::JacobianFormType,
|
||||||
StructurePolicy> structure
|
StructurePolicy> structure
|
||||||
) {
|
) {
|
||||||
return PreparedStellarStructureBlock<
|
return PreparedStellarStructureBlock<
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstddef>
|
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
|
#include <cstddef>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -59,13 +59,14 @@ export namespace mean_field::seed {
|
|||||||
|
|
||||||
/* An explicit opt-in for a specification that leaves a radial seed unchanged. */
|
/* An explicit opt-in for a specification that leaves a radial seed unchanged. */
|
||||||
struct NoStateChange {
|
struct NoStateChange {
|
||||||
static constexpr bool registered = true;
|
static constexpr bool registered = true;
|
||||||
static constexpr bool providesRadialMass = false;
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
template <typename Model>
|
template <typename Model> static constexpr bool supports = true;
|
||||||
static constexpr bool supports = true;
|
|
||||||
|
|
||||||
template <typename Specification, typename Model>
|
template <
|
||||||
|
typename Specification,
|
||||||
|
typename Model>
|
||||||
static void validate(
|
static void validate(
|
||||||
const Specification &,
|
const Specification &,
|
||||||
const Model &,
|
const Model &,
|
||||||
@@ -74,7 +75,9 @@ export namespace mean_field::seed {
|
|||||||
) noexcept {
|
) noexcept {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Specification, typename Model>
|
template <
|
||||||
|
typename Specification,
|
||||||
|
typename Model>
|
||||||
static void initialize(
|
static void initialize(
|
||||||
const Specification &,
|
const Specification &,
|
||||||
const Model &,
|
const Model &,
|
||||||
@@ -115,19 +118,17 @@ export namespace mean_field::seed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct UnavailableRadialProjectionPhysics final {
|
struct UnavailableRadialProjectionPhysics final {
|
||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
static constexpr bool providesRadialMass = false;
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
template <typename Model>
|
template <typename Model> static constexpr bool supports = false;
|
||||||
static constexpr bool supports = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FixedTotalMassRadialProjectionPhysics final {
|
struct FixedTotalMassRadialProjectionPhysics final {
|
||||||
static constexpr bool registered = true;
|
static constexpr bool registered = true;
|
||||||
static constexpr bool providesRadialMass = true;
|
static constexpr bool providesRadialMass = true;
|
||||||
|
|
||||||
template <typename Model>
|
template <typename Model> static constexpr bool supports = true;
|
||||||
static constexpr bool supports = true;
|
|
||||||
|
|
||||||
[[nodiscard]] static dimensions::MassValue targetMass(const models::FixedTotalMass &specification) {
|
[[nodiscard]] static dimensions::MassValue targetMass(const models::FixedTotalMass &specification) {
|
||||||
return specification.targetMass();
|
return specification.targetMass();
|
||||||
@@ -164,10 +165,7 @@ export namespace mean_field::seed {
|
|||||||
static constexpr bool providesRadialMass = false;
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
template <typename Model>
|
template <typename Model>
|
||||||
static constexpr bool supports = requires(
|
static constexpr bool supports = requires(const Model &model, const surface::Isobaric &condition) {
|
||||||
const Model &model,
|
|
||||||
const surface::Isobaric &condition
|
|
||||||
) {
|
|
||||||
{
|
{
|
||||||
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
|
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
|
||||||
model.equationOfState(), condition.targetPressure()
|
model.equationOfState(), condition.targetPressure()
|
||||||
@@ -213,11 +211,10 @@ export namespace mean_field::seed {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct FixedCentralDensityRadialProjectionPhysics final {
|
struct FixedCentralDensityRadialProjectionPhysics final {
|
||||||
static constexpr bool registered = true;
|
static constexpr bool registered = true;
|
||||||
static constexpr bool providesRadialMass = false;
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
template <typename Model>
|
template <typename Model> static constexpr bool supports = true;
|
||||||
static constexpr bool supports = true;
|
|
||||||
|
|
||||||
template <typename Model>
|
template <typename Model>
|
||||||
static void validate(
|
static void validate(
|
||||||
@@ -246,11 +243,10 @@ export namespace mean_field::seed {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct FixedAngularMomentumRadialProjectionPhysics final {
|
struct FixedAngularMomentumRadialProjectionPhysics final {
|
||||||
static constexpr bool registered = true;
|
static constexpr bool registered = true;
|
||||||
static constexpr bool providesRadialMass = false;
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
template <typename Model>
|
template <typename Model> static constexpr bool supports = true;
|
||||||
static constexpr bool supports = true;
|
|
||||||
|
|
||||||
template <typename Model>
|
template <typename Model>
|
||||||
static void validate(
|
static void validate(
|
||||||
@@ -316,12 +312,12 @@ export namespace mean_field::seed {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename Candidate> struct UnwrapRadialProjectionPhysics {
|
template <typename Candidate> struct UnwrapRadialProjectionPhysics {
|
||||||
using Type = UnavailableRadialProjectionPhysics;
|
using Type = UnavailableRadialProjectionPhysics;
|
||||||
static constexpr bool valid = false;
|
static constexpr bool valid = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Physics> struct UnwrapRadialProjectionPhysics<projection::Use<Physics>> {
|
template <typename Physics> struct UnwrapRadialProjectionPhysics<projection::Use<Physics>> {
|
||||||
using Type = Physics;
|
using Type = Physics;
|
||||||
static constexpr bool valid = true;
|
static constexpr bool valid = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -358,7 +354,9 @@ export namespace mean_field::seed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Specification, typename Model>
|
template <
|
||||||
|
typename Specification,
|
||||||
|
typename Model>
|
||||||
[[nodiscard]] consteval bool radialProjectionPhysicsIsComplete() {
|
[[nodiscard]] consteval bool radialProjectionPhysicsIsComplete() {
|
||||||
using Physics = typename SelectRadialProjectionPhysics<Specification>::Type;
|
using Physics = typename SelectRadialProjectionPhysics<Specification>::Type;
|
||||||
if constexpr (!radialProjectionPhysicsRegistered<Physics>()) {
|
if constexpr (!radialProjectionPhysicsRegistered<Physics>()) {
|
||||||
@@ -370,12 +368,9 @@ export namespace mean_field::seed {
|
|||||||
} else if constexpr (!static_cast<bool>(Physics::template supports<Model>)) {
|
} else if constexpr (!static_cast<bool>(Physics::template supports<Model>)) {
|
||||||
return false;
|
return false;
|
||||||
} else if constexpr (!requires(
|
} else if constexpr (!requires(
|
||||||
const Specification &specification,
|
const Specification &specification, const Model &model,
|
||||||
const Model &model,
|
const RadialProfile &profile, const StellarEquilibriumProjectionOptions &options,
|
||||||
const RadialProfile &profile,
|
const RadialProjectionScales &scales, RadialProjectionState &state,
|
||||||
const StellarEquilibriumProjectionOptions &options,
|
|
||||||
const RadialProjectionScales &scales,
|
|
||||||
RadialProjectionState &state,
|
|
||||||
mfem::Vector coordinate
|
mfem::Vector coordinate
|
||||||
) {
|
) {
|
||||||
Physics::validate(specification, model, profile, options);
|
Physics::validate(specification, model, profile, options);
|
||||||
@@ -417,21 +412,24 @@ export namespace mean_field::seed {
|
|||||||
|
|
||||||
static constexpr std::size_t radialMassProviderCount =
|
static constexpr std::size_t radialMassProviderCount =
|
||||||
(std::size_t{0} + ... +
|
(std::size_t{0} + ... +
|
||||||
(radialProjectionPhysicsProvidesMass<
|
(radialProjectionPhysicsProvidesMass<typename SelectRadialProjectionPhysics<Specifications>::Type>()
|
||||||
typename SelectRadialProjectionPhysics<Specifications>::Type>()
|
|
||||||
? std::size_t{1}
|
? std::size_t{1}
|
||||||
: std::size_t{0}));
|
: std::size_t{0}));
|
||||||
static constexpr bool complete = radialMassProviderCount == 1 &&
|
static constexpr bool complete =
|
||||||
(radialProjectionPhysicsIsComplete<Specifications, ModelType>() && ...);
|
radialMassProviderCount == 1 && (radialProjectionPhysicsIsComplete<Specifications, ModelType>() && ...);
|
||||||
|
|
||||||
[[nodiscard]] static dimensions::MassValue targetMass(const ModelType &model) requires complete {
|
[[nodiscard]] static dimensions::MassValue targetMass(const ModelType &model)
|
||||||
|
requires complete
|
||||||
|
{
|
||||||
dimensions::MassValue result{0.0};
|
dimensions::MassValue result{0.0};
|
||||||
([&] {
|
(
|
||||||
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
|
[&] {
|
||||||
if constexpr (radialProjectionPhysicsProvidesMass<Physics>()) {
|
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
|
||||||
result = Physics::targetMass(model.template specification<Specifications>());
|
if constexpr (radialProjectionPhysicsProvidesMass<Physics>()) {
|
||||||
}
|
result = Physics::targetMass(model.template specification<Specifications>());
|
||||||
}(), ...);
|
}
|
||||||
|
}(),
|
||||||
|
...);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,11 +437,15 @@ export namespace mean_field::seed {
|
|||||||
const ModelType &model,
|
const ModelType &model,
|
||||||
const RadialProfile &profile,
|
const RadialProfile &profile,
|
||||||
const StellarEquilibriumProjectionOptions &options
|
const StellarEquilibriumProjectionOptions &options
|
||||||
) requires complete {
|
)
|
||||||
([&] {
|
requires complete
|
||||||
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
|
{
|
||||||
Physics::validate(model.template specification<Specifications>(), model, profile, options);
|
(
|
||||||
}(), ...);
|
[&] {
|
||||||
|
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
|
||||||
|
Physics::validate(model.template specification<Specifications>(), model, profile, options);
|
||||||
|
}(),
|
||||||
|
...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename StateView>
|
template <typename StateView>
|
||||||
@@ -452,27 +454,32 @@ export namespace mean_field::seed {
|
|||||||
const RadialProjectionScales &scales,
|
const RadialProjectionScales &scales,
|
||||||
RadialProjectionState &state,
|
RadialProjectionState &state,
|
||||||
const StateView &stateView
|
const StateView &stateView
|
||||||
) requires complete {
|
)
|
||||||
([&] {
|
requires complete
|
||||||
using Contribution = models::SpecificationContribution<Specifications>;
|
{
|
||||||
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
|
(
|
||||||
if constexpr (Contribution::generatedValueArity == 0) {
|
[&] {
|
||||||
Physics::initialize(
|
using Contribution = models::SpecificationContribution<Specifications>;
|
||||||
model.template specification<Specifications>(), model, scales, state, mfem::Vector{}
|
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
|
||||||
);
|
if constexpr (Contribution::generatedValueArity == 0) {
|
||||||
} else {
|
Physics::initialize(
|
||||||
static_assert(
|
model.template specification<Specifications>(), model, scales, state, mfem::Vector{}
|
||||||
Contribution::generatedValueArity == 1,
|
);
|
||||||
"Radial projection currently requires each specification contribution to generate at "
|
} else {
|
||||||
"most one scalar coordinate."
|
static_assert(
|
||||||
);
|
Contribution::generatedValueArity == 1,
|
||||||
using Term = RadialProjectionCoordinateTerm<Specifications, Contribution::generatedStateKind>;
|
"Radial projection currently requires each specification contribution to generate at "
|
||||||
Physics::initialize(
|
"most one scalar coordinate."
|
||||||
model.template specification<Specifications>(), model, scales, state,
|
);
|
||||||
stateView.block(Term{})
|
using Term =
|
||||||
);
|
RadialProjectionCoordinateTerm<Specifications, Contribution::generatedStateKind>;
|
||||||
}
|
Physics::initialize(
|
||||||
}(), ...);
|
model.template specification<Specifications>(), model, scales, state,
|
||||||
|
stateView.block(Term{})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
...);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -494,21 +501,25 @@ export namespace mean_field::seed {
|
|||||||
concept RadialProfileProjectableModel =
|
concept RadialProfileProjectableModel =
|
||||||
model::StellarModelType<Candidate> && radialProjectionIsCompilable<std::remove_cvref_t<Candidate>>;
|
model::StellarModelType<Candidate> && radialProjectionIsCompilable<std::remove_cvref_t<Candidate>>;
|
||||||
|
|
||||||
template <equilibrium::StellarEquilibriumModel Model, equilibrium::StellarDiscretizationType Discretization>
|
template <
|
||||||
|
equilibrium::StellarEquilibriumModel Model,
|
||||||
|
equilibrium::StellarDiscretizationType Discretization>
|
||||||
requires RadialProfileProjectableModel<Model>
|
requires RadialProfileProjectableModel<Model>
|
||||||
[[nodiscard]] ProjectedEquilibriumState<Model> projectRadialProfile(
|
[[nodiscard]] ProjectedEquilibriumState<Model> projectRadialProfile(
|
||||||
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
equilibrium::StellarEquilibriumProblem<
|
||||||
|
Model,
|
||||||
|
Discretization> &problem,
|
||||||
const RadialProfile &profile,
|
const RadialProfile &profile,
|
||||||
const StellarEquilibriumProjectionOptions &options = {}
|
const StellarEquilibriumProjectionOptions &options = {}
|
||||||
) {
|
) {
|
||||||
using Projection = detail::CompileRadialProjection<
|
using Projection = detail::CompileRadialProjection<
|
||||||
std::remove_cvref_t<Model>,
|
std::remove_cvref_t<Model>, typename std::remove_cvref_t<Model>::SpecificationTypes>;
|
||||||
typename std::remove_cvref_t<Model>::SpecificationTypes>;
|
|
||||||
const auto &stellarModel = problem.GetStellarModel();
|
const auto &stellarModel = problem.GetStellarModel();
|
||||||
Projection::validate(stellarModel, profile, options);
|
Projection::validate(stellarModel, profile, options);
|
||||||
const dimensions::MassValue targetMass = Projection::targetMass(stellarModel);
|
const dimensions::MassValue targetMass = Projection::targetMass(stellarModel);
|
||||||
const detail::ProjectedRadialFields fields = detail::projectRadialFields(
|
const detail::ProjectedRadialFields fields = detail::projectRadialFields(
|
||||||
problem.GetDiscretization().finiteElementModel(), profile, targetMass, options
|
equilibrium::detail::StellarEquilibriumProblemFactory::MutableFiniteElementModelForProjection(problem),
|
||||||
|
profile, targetMass, options
|
||||||
);
|
);
|
||||||
|
|
||||||
mfem::Vector values(problem.StateSize());
|
mfem::Vector values(problem.StateSize());
|
||||||
@@ -563,11 +574,15 @@ export namespace mean_field::seed {
|
|||||||
equilibrium::StellarDiscretizationType Discretization,
|
equilibrium::StellarDiscretizationType Discretization,
|
||||||
typename Strategy>
|
typename Strategy>
|
||||||
requires RadialSeedStrategyFor<
|
requires RadialSeedStrategyFor<
|
||||||
Strategy,
|
Strategy,
|
||||||
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::ModelType> &&
|
typename equilibrium::StellarEquilibriumProblem<
|
||||||
|
Model,
|
||||||
|
Discretization>::ModelType> &&
|
||||||
RadialProfileProjectableModel<Model>
|
RadialProfileProjectableModel<Model>
|
||||||
[[nodiscard]] ProjectedEquilibriumState<Model> makeProjectedEquilibriumState(
|
[[nodiscard]] ProjectedEquilibriumState<Model> makeProjectedEquilibriumState(
|
||||||
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
equilibrium::StellarEquilibriumProblem<
|
||||||
|
Model,
|
||||||
|
Discretization> &problem,
|
||||||
const Strategy &strategy,
|
const Strategy &strategy,
|
||||||
const StellarEquilibriumProjectionOptions &options = {}
|
const StellarEquilibriumProjectionOptions &options = {}
|
||||||
) {
|
) {
|
||||||
|
|||||||
868
libmeanfield/interface/solver/linear_backend.cppm
Normal file
868
libmeanfield/interface/solver/linear_backend.cppm
Normal file
@@ -0,0 +1,868 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <concepts>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mfem.hpp>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
|
export module mean_field:solver.linear_backend;
|
||||||
|
|
||||||
|
export import :preconditioning.backend;
|
||||||
|
|
||||||
|
export namespace mean_field::solver {
|
||||||
|
enum class LinearSolveStatus : std::uint8_t {
|
||||||
|
converged,
|
||||||
|
maximum_iterations,
|
||||||
|
breakdown,
|
||||||
|
non_finite,
|
||||||
|
backend_failure
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LinearSolveControl final {
|
||||||
|
double relativeTolerance{1.0e-8};
|
||||||
|
double absoluteTolerance{0.0};
|
||||||
|
int maximumIterations{100};
|
||||||
|
|
||||||
|
void Validate() const {
|
||||||
|
if (!std::isfinite(relativeTolerance) || relativeTolerance < 0.0) {
|
||||||
|
throw std::invalid_argument("A linear solve requires a finite, non-negative relative tolerance.");
|
||||||
|
}
|
||||||
|
if (!std::isfinite(absoluteTolerance) || absoluteTolerance < 0.0) {
|
||||||
|
throw std::invalid_argument("A linear solve requires a finite, non-negative absolute tolerance.");
|
||||||
|
}
|
||||||
|
if (maximumIterations <= 0) {
|
||||||
|
throw std::invalid_argument("A linear solve requires at least one permitted iteration.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double ConvergenceThreshold(const double globalRightHandSideNorm) const {
|
||||||
|
Validate();
|
||||||
|
if (!std::isfinite(globalRightHandSideNorm) || globalRightHandSideNorm < 0.0) {
|
||||||
|
throw std::invalid_argument("A linear solve requires a finite, non-negative right-hand-side norm.");
|
||||||
|
}
|
||||||
|
const double relativeThreshold = relativeTolerance * globalRightHandSideNorm;
|
||||||
|
if (!std::isfinite(relativeThreshold)) {
|
||||||
|
throw std::invalid_argument("The linear relative convergence threshold must be finite.");
|
||||||
|
}
|
||||||
|
return absoluteTolerance > relativeThreshold ? absoluteTolerance : relativeThreshold;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Numerical termination is data, not an exception. Implementations throw
|
||||||
|
* for invalid controls, configuration, dimensions, or violated lifetime
|
||||||
|
* contracts. All reported norms are communicator-global Euclidean norms.
|
||||||
|
* Solve uses the incoming correction as its initial guess and overwrites
|
||||||
|
* it with the final correction, so initialResidualNorm is ||b - A x_0||.
|
||||||
|
* Convergence remains relative to the right-hand side rather than the
|
||||||
|
* quality of a particular initial guess:
|
||||||
|
*
|
||||||
|
* ||b - A x|| <= max(absoluteTolerance,
|
||||||
|
* relativeTolerance * ||b||).
|
||||||
|
*
|
||||||
|
* For a zero right-hand side, relativeTrueResidualNorm is zero exactly
|
||||||
|
* when the true residual is zero and positive infinity otherwise. The
|
||||||
|
* true-residual fields are distinct from the backend's recurrence so
|
||||||
|
* callers never have to infer one from the other. This is deliberately
|
||||||
|
* fixed-size: recording a history is an optional backend concern whose
|
||||||
|
* storage must be owned and reserved by the prepared runtime, not allocated
|
||||||
|
* while Solve is active.
|
||||||
|
*/
|
||||||
|
struct LinearSolveReport final {
|
||||||
|
LinearSolveStatus status{LinearSolveStatus::backend_failure};
|
||||||
|
LinearSolveControl control{};
|
||||||
|
int iterations{0};
|
||||||
|
int restarts{0};
|
||||||
|
double rightHandSideNorm{0.0};
|
||||||
|
double initialResidualNorm{0.0};
|
||||||
|
double reportedResidualNorm{0.0};
|
||||||
|
double trueResidualNorm{0.0};
|
||||||
|
double relativeTrueResidualNorm{0.0};
|
||||||
|
// Includes MFEM's initial-guess residual application and the
|
||||||
|
// post-solve application used to verify the true residual.
|
||||||
|
std::uint64_t operatorApplications{0};
|
||||||
|
std::uint64_t inversePreconditionerApplications{0};
|
||||||
|
double solveSeconds{0.0};
|
||||||
|
// These totals include only completed applications. Operator time also
|
||||||
|
// includes the post-solve true-residual verification application.
|
||||||
|
double operatorSeconds{0.0};
|
||||||
|
double inversePreconditionerSeconds{0.0};
|
||||||
|
|
||||||
|
[[nodiscard]] bool Converged() const noexcept {
|
||||||
|
return status == LinearSolveStatus::converged;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LinearBackendConfigurationTag { };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept LinearBackendConfiguration =
|
||||||
|
std::derived_from<std::remove_cvref_t<Candidate>, LinearBackendConfigurationTag> &&
|
||||||
|
std::move_constructible<std::remove_cvref_t<Candidate>> && requires {
|
||||||
|
requires std::same_as<
|
||||||
|
std::remove_cv_t<decltype(std::remove_cvref_t<Candidate>::supportedPreconditionerContract)>,
|
||||||
|
preconditioning::ApplicationContract>;
|
||||||
|
typename std::integral_constant<
|
||||||
|
preconditioning::ApplicationContract, std::remove_cvref_t<Candidate>::supportedPreconditionerContract>;
|
||||||
|
requires(
|
||||||
|
std::remove_cvref_t<Candidate>::supportedPreconditionerContract ==
|
||||||
|
preconditioning::ApplicationContract::stationary_linear ||
|
||||||
|
std::remove_cvref_t<Candidate>::supportedPreconditionerContract ==
|
||||||
|
preconditioning::ApplicationContract::flexible
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mean_field::solver
|
||||||
|
|
||||||
|
namespace mean_field::solver::detail {
|
||||||
|
template <typename Candidate>
|
||||||
|
concept StaticPreconditionerContractDeclared = requires { &std::remove_cvref_t<Candidate>::applicationContract; };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept ExactStaticPreconditionerContract = requires {
|
||||||
|
requires std::same_as<
|
||||||
|
std::remove_cv_t<decltype(std::remove_cvref_t<Candidate>::applicationContract)>,
|
||||||
|
preconditioning::ApplicationContract>;
|
||||||
|
typename std::integral_constant<
|
||||||
|
preconditioning::ApplicationContract, std::remove_cvref_t<Candidate>::applicationContract>;
|
||||||
|
requires(
|
||||||
|
std::remove_cvref_t<Candidate>::applicationContract ==
|
||||||
|
preconditioning::ApplicationContract::stationary_linear ||
|
||||||
|
std::remove_cvref_t<Candidate>::applicationContract == preconditioning::ApplicationContract::flexible
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate, typename = void> struct StaticPreconditionerContract {
|
||||||
|
static constexpr bool declared = StaticPreconditionerContractDeclared<Candidate>;
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
static constexpr preconditioning::ApplicationContract value =
|
||||||
|
preconditioning::ApplicationContract::stationary_linear;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
struct StaticPreconditionerContract<Candidate, std::enable_if_t<ExactStaticPreconditionerContract<Candidate>>> {
|
||||||
|
static constexpr bool declared = true;
|
||||||
|
static constexpr auto value = std::remove_cvref_t<Candidate>::applicationContract;
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept BackendPreconditionerContractDeclared = requires { typename std::remove_cvref_t<Candidate>::BackendType; };
|
||||||
|
|
||||||
|
template <typename Backend>
|
||||||
|
concept ExactRegisteredBackendPreconditionerContract = requires {
|
||||||
|
requires preconditioning::backend::Registered<std::remove_cvref_t<Backend>>;
|
||||||
|
requires std::same_as<
|
||||||
|
std::remove_cv_t<
|
||||||
|
decltype(preconditioning::backend::Traits<std::remove_cvref_t<Backend>>::applicationContract)>,
|
||||||
|
preconditioning::ApplicationContract>;
|
||||||
|
typename std::integral_constant<
|
||||||
|
preconditioning::ApplicationContract,
|
||||||
|
preconditioning::backend::Traits<std::remove_cvref_t<Backend>>::applicationContract>;
|
||||||
|
requires(
|
||||||
|
preconditioning::backend::Traits<std::remove_cvref_t<Backend>>::applicationContract ==
|
||||||
|
preconditioning::ApplicationContract::stationary_linear ||
|
||||||
|
preconditioning::backend::Traits<std::remove_cvref_t<Backend>>::applicationContract ==
|
||||||
|
preconditioning::ApplicationContract::flexible
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept ExactBackendPreconditionerContract =
|
||||||
|
BackendPreconditionerContractDeclared<Candidate> &&
|
||||||
|
ExactRegisteredBackendPreconditionerContract<typename std::remove_cvref_t<Candidate>::BackendType>;
|
||||||
|
|
||||||
|
template <typename Candidate, typename = void> struct BackendPreconditionerContract {
|
||||||
|
static constexpr bool declared = BackendPreconditionerContractDeclared<Candidate>;
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
static constexpr preconditioning::ApplicationContract value =
|
||||||
|
preconditioning::ApplicationContract::stationary_linear;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
struct BackendPreconditionerContract<Candidate, std::enable_if_t<ExactBackendPreconditionerContract<Candidate>>> {
|
||||||
|
private:
|
||||||
|
using Backend = typename std::remove_cvref_t<Candidate>::BackendType;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr bool declared = true;
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
static constexpr preconditioning::ApplicationContract value =
|
||||||
|
preconditioning::backend::Traits<std::remove_cvref_t<Backend>>::applicationContract;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate> struct DirectPreconditionerContractAudit final {
|
||||||
|
private:
|
||||||
|
using StaticContract = StaticPreconditionerContract<Candidate>;
|
||||||
|
using BackendContract = BackendPreconditionerContract<Candidate>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr bool declarationsValid = (!StaticContract::declared || StaticContract::registered) &&
|
||||||
|
(!BackendContract::declared || BackendContract::registered);
|
||||||
|
static constexpr bool sourcesAgree = !StaticContract::registered || !BackendContract::registered ||
|
||||||
|
StaticContract::value == BackendContract::value;
|
||||||
|
static constexpr bool registered =
|
||||||
|
declarationsValid && sourcesAgree && (StaticContract::registered || BackendContract::registered);
|
||||||
|
static constexpr preconditioning::ApplicationContract value = [] {
|
||||||
|
if constexpr (StaticContract::registered) {
|
||||||
|
return StaticContract::value;
|
||||||
|
} else if constexpr (BackendContract::registered) {
|
||||||
|
return BackendContract::value;
|
||||||
|
} else {
|
||||||
|
return preconditioning::ApplicationContract::stationary_linear;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept PhysicalInversePreconditionerContractDeclared =
|
||||||
|
requires(const std::remove_cvref_t<Candidate> &candidate) { candidate.GetPhysicalInverse(); };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
using PhysicalInverseType =
|
||||||
|
std::remove_cvref_t<decltype(std::declval<const std::remove_cvref_t<Candidate> &>().GetPhysicalInverse())>;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept ExactPhysicalInversePreconditionerContract =
|
||||||
|
PhysicalInversePreconditionerContractDeclared<Candidate> &&
|
||||||
|
DirectPreconditionerContractAudit<PhysicalInverseType<Candidate>>::registered;
|
||||||
|
|
||||||
|
template <typename Candidate, typename = void> struct PhysicalInversePreconditionerContract {
|
||||||
|
static constexpr bool declared = PhysicalInversePreconditionerContractDeclared<Candidate>;
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
static constexpr preconditioning::ApplicationContract value =
|
||||||
|
preconditioning::ApplicationContract::stationary_linear;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
struct PhysicalInversePreconditionerContract<
|
||||||
|
Candidate,
|
||||||
|
std::enable_if_t<ExactPhysicalInversePreconditionerContract<Candidate>>> {
|
||||||
|
using PhysicalInverse = PhysicalInverseType<Candidate>;
|
||||||
|
using ContractAudit = DirectPreconditionerContractAudit<PhysicalInverse>;
|
||||||
|
|
||||||
|
static constexpr bool declared = true;
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
static constexpr preconditioning::ApplicationContract value = ContractAudit::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate> struct LinearPreconditionerContractAudit final {
|
||||||
|
private:
|
||||||
|
using StaticContract = StaticPreconditionerContract<Candidate>;
|
||||||
|
using BackendContract = BackendPreconditionerContract<Candidate>;
|
||||||
|
using PhysicalContract = PhysicalInversePreconditionerContract<Candidate>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr bool declarationsValid = (!StaticContract::declared || StaticContract::registered) &&
|
||||||
|
(!BackendContract::declared || BackendContract::registered) &&
|
||||||
|
(!PhysicalContract::declared || PhysicalContract::registered);
|
||||||
|
static constexpr bool sourcesAgree = (!StaticContract::registered || !BackendContract::registered ||
|
||||||
|
StaticContract::value == BackendContract::value) &&
|
||||||
|
(!StaticContract::registered || !PhysicalContract::registered ||
|
||||||
|
StaticContract::value == PhysicalContract::value) &&
|
||||||
|
(!BackendContract::registered || !PhysicalContract::registered ||
|
||||||
|
BackendContract::value == PhysicalContract::value);
|
||||||
|
static constexpr bool registered =
|
||||||
|
declarationsValid && sourcesAgree &&
|
||||||
|
(StaticContract::registered || BackendContract::registered || PhysicalContract::registered);
|
||||||
|
static constexpr preconditioning::ApplicationContract value = [] {
|
||||||
|
if constexpr (StaticContract::registered) {
|
||||||
|
return StaticContract::value;
|
||||||
|
} else if constexpr (BackendContract::registered) {
|
||||||
|
return BackendContract::value;
|
||||||
|
} else if constexpr (PhysicalContract::registered) {
|
||||||
|
return PhysicalContract::value;
|
||||||
|
} else {
|
||||||
|
return preconditioning::ApplicationContract::stationary_linear;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
};
|
||||||
|
} // namespace mean_field::solver::detail
|
||||||
|
|
||||||
|
export namespace mean_field::solver {
|
||||||
|
template <typename Candidate>
|
||||||
|
concept LinearPreconditionerApplicationContractAvailable =
|
||||||
|
detail::LinearPreconditionerContractAudit<std::remove_cvref_t<Candidate>>::registered;
|
||||||
|
|
||||||
|
template <LinearPreconditionerApplicationContractAvailable Candidate>
|
||||||
|
inline constexpr preconditioning::ApplicationContract linearPreconditionerApplicationContract =
|
||||||
|
detail::LinearPreconditionerContractAudit<std::remove_cvref_t<Candidate>>::value;
|
||||||
|
|
||||||
|
template <typename Configuration, typename Preconditioner>
|
||||||
|
concept LinearBackendPreconditionerCompatible =
|
||||||
|
LinearBackendConfiguration<Configuration> && LinearPreconditionerApplicationContractAvailable<Preconditioner> &&
|
||||||
|
(std::remove_cvref_t<Configuration>::supportedPreconditionerContract ==
|
||||||
|
preconditioning::ApplicationContract::flexible ||
|
||||||
|
linearPreconditionerApplicationContract<std::remove_cvref_t<Preconditioner>> ==
|
||||||
|
preconditioning::ApplicationContract::stationary_linear);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A prepared backend is bound once to the exact operator and inverse that
|
||||||
|
* its owner keeps at stable addresses and identifies the communicator on
|
||||||
|
* which it operates. The communicator supplied to preparation is borrowed;
|
||||||
|
* a backend may retain it or own a congruent duplicate. The handle returned
|
||||||
|
* by GetCommunicator is borrowed from the backend and must not be freed by
|
||||||
|
* the caller. Every prepared backend must be destroyed before MPI_Finalize.
|
||||||
|
* It owns its numerical workspaces; neither copyability nor movability is
|
||||||
|
* required. Solve treats a caller-provided, correctly sized correction
|
||||||
|
* vector as its initial guess and overwrites it with the final correction.
|
||||||
|
*/
|
||||||
|
template <typename Candidate, typename Operator, typename Preconditioner>
|
||||||
|
concept PreparedLinearBackendFor = std::derived_from<std::remove_cvref_t<Operator>, mfem::Operator> &&
|
||||||
|
std::derived_from<std::remove_cvref_t<Preconditioner>, mfem::Solver> &&
|
||||||
|
std::destructible<std::remove_cvref_t<Candidate>> &&
|
||||||
|
requires(
|
||||||
|
std::remove_cvref_t<Candidate> &prepared,
|
||||||
|
const std::remove_cvref_t<Candidate> &constantPrepared,
|
||||||
|
const mfem::Vector &rightHandSide,
|
||||||
|
mfem::Vector &correction,
|
||||||
|
const LinearSolveControl &control
|
||||||
|
) {
|
||||||
|
{
|
||||||
|
constantPrepared.GetOperator()
|
||||||
|
} -> std::same_as<const std::remove_cvref_t<Operator> &>;
|
||||||
|
{
|
||||||
|
constantPrepared.GetPreconditioner()
|
||||||
|
} -> std::same_as<const std::remove_cvref_t<Preconditioner> &>;
|
||||||
|
{ constantPrepared.GetCommunicator() } -> std::same_as<MPI_Comm>;
|
||||||
|
{ constantPrepared.IsReady() } -> std::same_as<bool>;
|
||||||
|
{ constantPrepared.RightHandSideSize() } -> std::same_as<int>;
|
||||||
|
{ constantPrepared.CorrectionSize() } -> std::same_as<int>;
|
||||||
|
{
|
||||||
|
prepared.Solve(rightHandSide, correction, control)
|
||||||
|
} -> std::same_as<LinearSolveReport>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `prepareLinearBackend` is intentionally unqualified in this detection
|
||||||
|
* boundary. A third-party configuration supplies its overload beside the
|
||||||
|
* configuration type and ADL discovers it without a library registry.
|
||||||
|
*/
|
||||||
|
template <typename Configuration, typename Operator, typename Preconditioner>
|
||||||
|
concept LinearBackendRuntimeAvailableFor =
|
||||||
|
LinearBackendPreconditionerCompatible<Configuration, Preconditioner> &&
|
||||||
|
std::derived_from<std::remove_cvref_t<Operator>, mfem::Operator> &&
|
||||||
|
std::derived_from<std::remove_cvref_t<Preconditioner>, mfem::Solver> &&
|
||||||
|
requires(
|
||||||
|
std::remove_cvref_t<Configuration> configuration,
|
||||||
|
const std::remove_cvref_t<Operator> &operation,
|
||||||
|
std::remove_cvref_t<Preconditioner> &preconditioner,
|
||||||
|
MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
requires std::same_as<
|
||||||
|
decltype(prepareLinearBackend(std::move(configuration), operation, preconditioner, communicator)),
|
||||||
|
std::remove_cvref_t<
|
||||||
|
decltype(prepareLinearBackend(std::move(configuration), operation, preconditioner, communicator))>>;
|
||||||
|
{
|
||||||
|
prepareLinearBackend(std::move(configuration), operation, preconditioner, communicator)
|
||||||
|
} -> PreparedLinearBackendFor<std::remove_cvref_t<Operator>, std::remove_cvref_t<Preconditioner>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <LinearBackendConfiguration Configuration, typename Operator, typename Preconditioner>
|
||||||
|
requires LinearBackendRuntimeAvailableFor<Configuration, Operator, Preconditioner>
|
||||||
|
using PreparedLinearBackendType = std::remove_cvref_t<decltype(prepareLinearBackend(
|
||||||
|
std::declval<std::remove_cvref_t<Configuration> &&>(),
|
||||||
|
std::declval<const std::remove_cvref_t<Operator> &>(),
|
||||||
|
std::declval<std::remove_cvref_t<Preconditioner> &>(),
|
||||||
|
std::declval<MPI_Comm>()
|
||||||
|
))>;
|
||||||
|
} // namespace mean_field::solver
|
||||||
|
|
||||||
|
export namespace mean_field::solver::linear {
|
||||||
|
struct FGMRESOptions final {
|
||||||
|
int restartLength{50};
|
||||||
|
int printLevel{-1};
|
||||||
|
|
||||||
|
void Validate() const {
|
||||||
|
if (restartLength <= 0) {
|
||||||
|
throw std::invalid_argument("MFEM FGMRES requires a positive restart length.");
|
||||||
|
}
|
||||||
|
if (printLevel < -1 || printLevel > 3) {
|
||||||
|
throw std::invalid_argument("MFEM FGMRES print level must be between -1 and 3.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FGMRES final : public LinearBackendConfigurationTag {
|
||||||
|
public:
|
||||||
|
static constexpr preconditioning::ApplicationContract supportedPreconditionerContract =
|
||||||
|
preconditioning::ApplicationContract::flexible;
|
||||||
|
|
||||||
|
FGMRES() = default;
|
||||||
|
|
||||||
|
explicit FGMRES(FGMRESOptions options) : m_options(std::move(options)) {
|
||||||
|
m_options.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const FGMRESOptions &GetOptions() const noexcept {
|
||||||
|
return m_options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FGMRESOptions m_options{};
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename Operation>
|
||||||
|
requires std::derived_from<std::remove_cvref_t<Operation>, mfem::Operator>
|
||||||
|
class CountedOperator final : public mfem::Operator {
|
||||||
|
public:
|
||||||
|
explicit CountedOperator(const Operation &operation)
|
||||||
|
: mfem::Operator(
|
||||||
|
operation.Height(),
|
||||||
|
operation.Width()
|
||||||
|
),
|
||||||
|
m_operation(std::addressof(operation)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mult(
|
||||||
|
const mfem::Vector &input,
|
||||||
|
mfem::Vector &output
|
||||||
|
) const override {
|
||||||
|
const auto start = std::chrono::steady_clock::now();
|
||||||
|
m_operation->Mult(input, output);
|
||||||
|
m_seconds += std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
|
||||||
|
++m_applications;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset() const noexcept {
|
||||||
|
m_applications = 0;
|
||||||
|
m_seconds = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint64_t Applications() const noexcept {
|
||||||
|
return m_applications;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double Seconds() const noexcept {
|
||||||
|
return m_seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Operation *m_operation;
|
||||||
|
mutable std::uint64_t m_applications{0};
|
||||||
|
mutable double m_seconds{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Operation, typename Preconditioner>
|
||||||
|
requires std::derived_from<std::remove_cvref_t<Operation>, mfem::Operator> &&
|
||||||
|
std::derived_from<std::remove_cvref_t<Preconditioner>, mfem::Solver>
|
||||||
|
class CountedPreconditioner final : public mfem::Solver {
|
||||||
|
public:
|
||||||
|
CountedPreconditioner(
|
||||||
|
const Operation &operation,
|
||||||
|
Preconditioner &preconditioner,
|
||||||
|
const CountedOperator<Operation> &countedOperation
|
||||||
|
)
|
||||||
|
: mfem::Solver(
|
||||||
|
preconditioner.Height(),
|
||||||
|
preconditioner.Width(),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
m_operation(std::addressof(operation)),
|
||||||
|
m_preconditioner(std::addressof(preconditioner)),
|
||||||
|
m_countedOperation(std::addressof(countedOperation)) {
|
||||||
|
m_preconditioner->iterative_mode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetOperator(const mfem::Operator &operation) override {
|
||||||
|
if (std::addressof(operation) != m_countedOperation) {
|
||||||
|
throw std::invalid_argument("The MFEM FGMRES preconditioner received an unexpected operator.");
|
||||||
|
}
|
||||||
|
m_preconditioner->SetOperator(*m_operation);
|
||||||
|
if (m_preconditioner->Height() != Height() || m_preconditioner->Width() != Width()) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"The MFEM FGMRES preconditioner changed dimensions while binding its operator."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mult(
|
||||||
|
const mfem::Vector &input,
|
||||||
|
mfem::Vector &output
|
||||||
|
) const override {
|
||||||
|
const auto start = std::chrono::steady_clock::now();
|
||||||
|
m_preconditioner->Mult(input, output);
|
||||||
|
m_seconds += std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
|
||||||
|
++m_applications;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset() const noexcept {
|
||||||
|
m_applications = 0;
|
||||||
|
m_seconds = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint64_t Applications() const noexcept {
|
||||||
|
return m_applications;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double Seconds() const noexcept {
|
||||||
|
return m_seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Operation *m_operation;
|
||||||
|
Preconditioner *m_preconditioner;
|
||||||
|
const CountedOperator<Operation> *m_countedOperation;
|
||||||
|
mutable std::uint64_t m_applications{0};
|
||||||
|
mutable double m_seconds{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool MpiIsUsable() noexcept {
|
||||||
|
int initialized = 0;
|
||||||
|
int finalized = 0;
|
||||||
|
return MPI_Initialized(&initialized) == MPI_SUCCESS && initialized != 0 &&
|
||||||
|
MPI_Finalized(&finalized) == MPI_SUCCESS && finalized == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool AllRanksAgree(
|
||||||
|
const bool localValue,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
int local = localValue ? 1 : 0;
|
||||||
|
int global = 0;
|
||||||
|
if (MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("MFEM FGMRES could not perform a communicator-wide validity check.");
|
||||||
|
}
|
||||||
|
return global != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void RequireCollectivelyIdenticalConfiguration(
|
||||||
|
const FGMRESOptions &options,
|
||||||
|
const LinearSolveControl &control,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
const std::array<double, 2> localRealValues{control.relativeTolerance, control.absoluteTolerance};
|
||||||
|
std::array<double, 2> minimumRealValues{};
|
||||||
|
std::array<double, 2> maximumRealValues{};
|
||||||
|
const std::array<int, 3> localIntegerValues{
|
||||||
|
control.maximumIterations, options.restartLength, options.printLevel
|
||||||
|
};
|
||||||
|
std::array<int, 3> minimumIntegerValues{};
|
||||||
|
std::array<int, 3> maximumIntegerValues{};
|
||||||
|
|
||||||
|
if (MPI_Allreduce(
|
||||||
|
localRealValues.data(), minimumRealValues.data(), static_cast<int>(localRealValues.size()),
|
||||||
|
MPI_DOUBLE, MPI_MIN, communicator
|
||||||
|
) != MPI_SUCCESS ||
|
||||||
|
MPI_Allreduce(
|
||||||
|
localRealValues.data(), maximumRealValues.data(), static_cast<int>(localRealValues.size()),
|
||||||
|
MPI_DOUBLE, MPI_MAX, communicator
|
||||||
|
) != MPI_SUCCESS ||
|
||||||
|
MPI_Allreduce(
|
||||||
|
localIntegerValues.data(), minimumIntegerValues.data(), static_cast<int>(localIntegerValues.size()),
|
||||||
|
MPI_INT, MPI_MIN, communicator
|
||||||
|
) != MPI_SUCCESS ||
|
||||||
|
MPI_Allreduce(
|
||||||
|
localIntegerValues.data(), maximumIntegerValues.data(), static_cast<int>(localIntegerValues.size()),
|
||||||
|
MPI_INT, MPI_MAX, communicator
|
||||||
|
) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("MFEM FGMRES could not validate its distributed configuration.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minimumRealValues != maximumRealValues || minimumIntegerValues != maximumIntegerValues) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"MFEM FGMRES requires identical options and solve controls on every communicator rank."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool LocallyFinite(const mfem::Vector &values) {
|
||||||
|
for (int index = 0; index < values.Size(); ++index) {
|
||||||
|
if (!std::isfinite(values(index))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline double GlobalNorm(
|
||||||
|
const mfem::Vector &values,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
const double localNorm = values.Norml2();
|
||||||
|
const double localNormSquared = localNorm * localNorm;
|
||||||
|
double globalNormSquared = 0.0;
|
||||||
|
if (MPI_Allreduce(&localNormSquared, &globalNormSquared, 1, MPI_DOUBLE, MPI_SUM, communicator) !=
|
||||||
|
MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("MFEM FGMRES could not reduce a global vector norm.");
|
||||||
|
}
|
||||||
|
if (!std::isfinite(globalNormSquared) || globalNormSquared < 0.0) {
|
||||||
|
return std::numeric_limits<double>::quiet_NaN();
|
||||||
|
}
|
||||||
|
return std::sqrt(globalNormSquared);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline double MaximumRankValue(
|
||||||
|
const double localValue,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
double maximumValue = 0.0;
|
||||||
|
if (MPI_Allreduce(&localValue, &maximumValue, 1, MPI_DOUBLE, MPI_MAX, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("MFEM FGMRES could not reduce a communicator-wide timing measurement.");
|
||||||
|
}
|
||||||
|
return maximumValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Candidate> [[nodiscard]] bool RuntimeDependencyIsCurrent(const Candidate &candidate) {
|
||||||
|
if constexpr (requires {
|
||||||
|
{ candidate.IsCurrent() } -> std::same_as<bool>;
|
||||||
|
}) {
|
||||||
|
return candidate.IsCurrent();
|
||||||
|
} else if constexpr (requires {
|
||||||
|
{ candidate.IsPrepared() } -> std::same_as<bool>;
|
||||||
|
}) {
|
||||||
|
return candidate.IsPrepared();
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Operation, typename Preconditioner>
|
||||||
|
requires std::derived_from<std::remove_cvref_t<Operation>, mfem::Operator> &&
|
||||||
|
std::derived_from<std::remove_cvref_t<Preconditioner>, mfem::Solver>
|
||||||
|
class PreparedFGMRES final {
|
||||||
|
private:
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PreparedFGMRES(
|
||||||
|
FGMRESOptions options,
|
||||||
|
const Operation &operation,
|
||||||
|
Preconditioner &preconditioner,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
)
|
||||||
|
: m_options(std::move(options)),
|
||||||
|
m_operation(std::addressof(operation)),
|
||||||
|
m_preconditioner(std::addressof(preconditioner)),
|
||||||
|
m_communicator(communicator),
|
||||||
|
m_countedOperation(operation),
|
||||||
|
m_countedPreconditioner(
|
||||||
|
operation,
|
||||||
|
preconditioner,
|
||||||
|
m_countedOperation
|
||||||
|
),
|
||||||
|
m_solver(communicator),
|
||||||
|
m_rightHandSide(operation.Height()),
|
||||||
|
m_operationAction(operation.Height()),
|
||||||
|
m_trueResidual(operation.Height()) {
|
||||||
|
m_options.Validate();
|
||||||
|
if (!MpiIsUsable()) {
|
||||||
|
throw std::logic_error("MFEM FGMRES requires initialized MPI that has not been finalized.");
|
||||||
|
}
|
||||||
|
if (m_communicator == MPI_COMM_NULL) {
|
||||||
|
throw std::invalid_argument("MFEM FGMRES requires a non-null MPI communicator.");
|
||||||
|
}
|
||||||
|
if (operation.Height() <= 0 || operation.Width() <= 0 || operation.Height() != operation.Width() ||
|
||||||
|
preconditioner.Height() != operation.Width() || preconditioner.Width() != operation.Height()) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"MFEM FGMRES requires compatible square operator and preconditioner dimensions."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_rightHandSide = 0.0;
|
||||||
|
m_operationAction = 0.0;
|
||||||
|
m_trueResidual = 0.0;
|
||||||
|
|
||||||
|
m_solver.SetPreconditioner(m_countedPreconditioner);
|
||||||
|
m_solver.SetOperator(m_countedOperation);
|
||||||
|
m_solver.SetKDim(m_options.restartLength);
|
||||||
|
m_solver.SetPrintLevel(m_options.printLevel);
|
||||||
|
m_solver.iterative_mode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PreparedFGMRES(const PreparedFGMRES &) = delete;
|
||||||
|
PreparedFGMRES &operator=(const PreparedFGMRES &) = delete;
|
||||||
|
PreparedFGMRES(PreparedFGMRES &&) = delete;
|
||||||
|
PreparedFGMRES &operator=(PreparedFGMRES &&) = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] const Operation &GetOperator() const noexcept {
|
||||||
|
return *m_operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const Preconditioner &GetPreconditioner() const noexcept {
|
||||||
|
return *m_preconditioner;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MPI_Comm GetCommunicator() const noexcept {
|
||||||
|
return m_communicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsReady() const {
|
||||||
|
return m_operation != nullptr && m_preconditioner != nullptr && m_communicator != MPI_COMM_NULL &&
|
||||||
|
m_operation->Height() == m_operation->Width() &&
|
||||||
|
m_preconditioner->Height() == m_operation->Width() &&
|
||||||
|
m_preconditioner->Width() == m_operation->Height() &&
|
||||||
|
m_rightHandSide.Size() == m_operation->Height() &&
|
||||||
|
m_operationAction.Size() == m_operation->Height() &&
|
||||||
|
m_trueResidual.Size() == m_operation->Height() && RuntimeDependencyIsCurrent(*m_operation) &&
|
||||||
|
RuntimeDependencyIsCurrent(*m_preconditioner);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int RightHandSideSize() const noexcept {
|
||||||
|
return m_operation->Height();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int CorrectionSize() const noexcept {
|
||||||
|
return m_operation->Width();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] LinearSolveReport Solve(
|
||||||
|
const mfem::Vector &rightHandSide,
|
||||||
|
mfem::Vector &correction,
|
||||||
|
const LinearSolveControl &control
|
||||||
|
) {
|
||||||
|
bool localConfigurationIsValid = true;
|
||||||
|
try {
|
||||||
|
m_options.Validate();
|
||||||
|
control.Validate();
|
||||||
|
} catch (const std::invalid_argument &) {
|
||||||
|
localConfigurationIsValid = false;
|
||||||
|
}
|
||||||
|
if (!AllRanksAgree(localConfigurationIsValid, m_communicator)) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"MFEM FGMRES requires valid options and solve controls on every communicator rank."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!localConfigurationIsValid) {
|
||||||
|
throw std::invalid_argument("MFEM FGMRES received invalid options or solve controls.");
|
||||||
|
}
|
||||||
|
RequireCollectivelyIdenticalConfiguration(m_options, control, m_communicator);
|
||||||
|
if (!AllRanksAgree(IsReady(), m_communicator)) {
|
||||||
|
throw std::logic_error(
|
||||||
|
"MFEM FGMRES requires a complete, current prepared runtime on every communicator rank."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!AllRanksAgree(
|
||||||
|
rightHandSide.Size() == RightHandSideSize() && correction.Size() == CorrectionSize(),
|
||||||
|
m_communicator
|
||||||
|
)) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"MFEM FGMRES received incompatible linear-system vectors on at least one rank."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!AllRanksAgree(LocallyFinite(rightHandSide), m_communicator) ||
|
||||||
|
!AllRanksAgree(LocallyFinite(correction), m_communicator)) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"MFEM FGMRES requires finite right-hand side and initial-guess values."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_rightHandSide = rightHandSide;
|
||||||
|
const double rightHandSideNorm = GlobalNorm(m_rightHandSide, m_communicator);
|
||||||
|
const double threshold = control.ConvergenceThreshold(rightHandSideNorm);
|
||||||
|
|
||||||
|
m_countedOperation.Reset();
|
||||||
|
m_countedPreconditioner.Reset();
|
||||||
|
m_solver.SetRelTol(0.0);
|
||||||
|
m_solver.SetAbsTol(threshold);
|
||||||
|
m_solver.SetMaxIter(control.maximumIterations);
|
||||||
|
|
||||||
|
const Clock::time_point start = Clock::now();
|
||||||
|
m_solver.Mult(m_rightHandSide, correction);
|
||||||
|
const double solveSeconds =
|
||||||
|
MaximumRankValue(std::chrono::duration<double>(Clock::now() - start).count(), m_communicator);
|
||||||
|
|
||||||
|
const bool correctionIsFinite = AllRanksAgree(LocallyFinite(correction), m_communicator);
|
||||||
|
double trueResidualNorm = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
if (correctionIsFinite) {
|
||||||
|
m_countedOperation.Mult(correction, m_operationAction);
|
||||||
|
m_trueResidual = m_rightHandSide;
|
||||||
|
m_trueResidual -= m_operationAction;
|
||||||
|
if (AllRanksAgree(LocallyFinite(m_trueResidual), m_communicator)) {
|
||||||
|
trueResidualNorm = GlobalNorm(m_trueResidual, m_communicator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const double initialResidualNorm = m_solver.GetInitialNorm();
|
||||||
|
const double reportedResidualNorm = m_solver.GetFinalNorm();
|
||||||
|
const std::uint64_t krylovIterationCount = m_countedPreconditioner.Applications();
|
||||||
|
if (krylovIterationCount > static_cast<std::uint64_t>(std::numeric_limits<int>::max())) {
|
||||||
|
throw std::overflow_error("MFEM FGMRES reported more Krylov iterations than can be represented.");
|
||||||
|
}
|
||||||
|
const int iterations = static_cast<int>(krylovIterationCount);
|
||||||
|
// A restart is an additional Krylov cycle entered after the
|
||||||
|
// initial cycle, not the residual check at a cycle boundary.
|
||||||
|
const int restarts = iterations > 0 ? (iterations - 1) / m_options.restartLength : 0;
|
||||||
|
const bool numericalValuesAreFinite = correctionIsFinite && std::isfinite(initialResidualNorm) &&
|
||||||
|
std::isfinite(reportedResidualNorm) &&
|
||||||
|
std::isfinite(trueResidualNorm);
|
||||||
|
|
||||||
|
LinearSolveStatus status = LinearSolveStatus::backend_failure;
|
||||||
|
if (!numericalValuesAreFinite) {
|
||||||
|
status = LinearSolveStatus::non_finite;
|
||||||
|
} else if (trueResidualNorm <= threshold) {
|
||||||
|
status = LinearSolveStatus::converged;
|
||||||
|
} else if (!m_solver.GetConverged() && iterations >= control.maximumIterations) {
|
||||||
|
status = LinearSolveStatus::maximum_iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double relativeTrueResidualNorm =
|
||||||
|
rightHandSideNorm > 0.0 ? trueResidualNorm / rightHandSideNorm
|
||||||
|
: (trueResidualNorm == 0.0 ? 0.0 : std::numeric_limits<double>::infinity());
|
||||||
|
const double operatorSeconds = MaximumRankValue(m_countedOperation.Seconds(), m_communicator);
|
||||||
|
const double inversePreconditionerSeconds =
|
||||||
|
MaximumRankValue(m_countedPreconditioner.Seconds(), m_communicator);
|
||||||
|
|
||||||
|
return {
|
||||||
|
.status = status,
|
||||||
|
.control = control,
|
||||||
|
.iterations = iterations,
|
||||||
|
.restarts = restarts,
|
||||||
|
.rightHandSideNorm = rightHandSideNorm,
|
||||||
|
.initialResidualNorm = initialResidualNorm,
|
||||||
|
.reportedResidualNorm = reportedResidualNorm,
|
||||||
|
.trueResidualNorm = trueResidualNorm,
|
||||||
|
.relativeTrueResidualNorm = relativeTrueResidualNorm,
|
||||||
|
.operatorApplications = m_countedOperation.Applications(),
|
||||||
|
.inversePreconditionerApplications = m_countedPreconditioner.Applications(),
|
||||||
|
.solveSeconds = solveSeconds,
|
||||||
|
.operatorSeconds = operatorSeconds,
|
||||||
|
.inversePreconditionerSeconds = inversePreconditionerSeconds
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FGMRESOptions m_options;
|
||||||
|
const Operation *m_operation;
|
||||||
|
Preconditioner *m_preconditioner;
|
||||||
|
MPI_Comm m_communicator;
|
||||||
|
CountedOperator<Operation> m_countedOperation;
|
||||||
|
CountedPreconditioner<Operation, Preconditioner> m_countedPreconditioner;
|
||||||
|
mfem::FGMRESSolver m_solver;
|
||||||
|
mfem::Vector m_rightHandSide;
|
||||||
|
mfem::Vector m_operationAction;
|
||||||
|
mfem::Vector m_trueResidual;
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Operation,
|
||||||
|
typename Preconditioner>
|
||||||
|
requires std::derived_from<
|
||||||
|
std::remove_cvref_t<Operation>,
|
||||||
|
mfem::Operator> &&
|
||||||
|
std::derived_from<
|
||||||
|
std::remove_cvref_t<Preconditioner>,
|
||||||
|
mfem::Solver>
|
||||||
|
[[nodiscard]] auto prepareLinearBackend(
|
||||||
|
FGMRES configuration,
|
||||||
|
const Operation &operation,
|
||||||
|
Preconditioner &preconditioner,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
return detail::PreparedFGMRES<Operation, Preconditioner>{
|
||||||
|
configuration.GetOptions(), operation, preconditioner, communicator
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} // namespace mean_field::solver::linear
|
||||||
579
libmeanfield/interface/solver/newton.cppm
Normal file
579
libmeanfield/interface/solver/newton.cppm
Normal file
@@ -0,0 +1,579 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <concepts>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <exception>
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mfem.hpp>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
|
export module mean_field:solver.newton;
|
||||||
|
|
||||||
|
export import :solver.linear_backend;
|
||||||
|
|
||||||
|
export namespace mean_field::solver::nonlinear {
|
||||||
|
/*
|
||||||
|
* Backtracking counts the full Newton trial as its first trial. A
|
||||||
|
* contraction is applied only after that candidate has been rejected.
|
||||||
|
*/
|
||||||
|
struct BacktrackingOptions final {
|
||||||
|
double initialStepLength{1.0};
|
||||||
|
double contractionFactor{0.5};
|
||||||
|
double fractionToBoundarySafety{0.9};
|
||||||
|
double sufficientDecrease{1.0e-4};
|
||||||
|
double minimumStepLength{1.0e-8};
|
||||||
|
int maximumTrials{20};
|
||||||
|
|
||||||
|
void Validate() const {
|
||||||
|
if (!std::isfinite(initialStepLength) || initialStepLength <= 0.0) {
|
||||||
|
throw std::invalid_argument("Newton backtracking requires a finite, positive initial step length.");
|
||||||
|
}
|
||||||
|
if (!std::isfinite(contractionFactor) || contractionFactor <= 0.0 || contractionFactor >= 1.0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Newton backtracking requires a finite contraction factor strictly between zero and one."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!std::isfinite(fractionToBoundarySafety) || fractionToBoundarySafety <= 0.0 ||
|
||||||
|
fractionToBoundarySafety >= 1.0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Newton backtracking requires a finite fraction-to-boundary safety factor strictly between zero "
|
||||||
|
"and one."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!std::isfinite(sufficientDecrease) || sufficientDecrease <= 0.0 || sufficientDecrease >= 1.0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Newton backtracking requires a finite sufficient-decrease factor strictly between zero and one."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!std::isfinite(minimumStepLength) || minimumStepLength <= 0.0 ||
|
||||||
|
minimumStepLength > initialStepLength) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Newton backtracking requires a finite, positive minimum step no larger than the initial step."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (maximumTrials <= 0) {
|
||||||
|
throw std::invalid_argument("Newton backtracking requires at least one permitted trial.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NewtonOptions final {
|
||||||
|
double relativeTolerance{1.0e-8};
|
||||||
|
double absoluteTolerance{0.0};
|
||||||
|
int maximumIterations{50};
|
||||||
|
LinearSolveControl linearSolve{};
|
||||||
|
BacktrackingOptions backtracking{};
|
||||||
|
|
||||||
|
void Validate() const {
|
||||||
|
if (!std::isfinite(relativeTolerance) || relativeTolerance < 0.0) {
|
||||||
|
throw std::invalid_argument("A Newton solve requires a finite, non-negative relative tolerance.");
|
||||||
|
}
|
||||||
|
if (!std::isfinite(absoluteTolerance) || absoluteTolerance < 0.0) {
|
||||||
|
throw std::invalid_argument("A Newton solve requires a finite, non-negative absolute tolerance.");
|
||||||
|
}
|
||||||
|
if (maximumIterations <= 0) {
|
||||||
|
throw std::invalid_argument("A Newton solve requires at least one permitted iteration.");
|
||||||
|
}
|
||||||
|
linearSolve.Validate();
|
||||||
|
backtracking.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double ConvergenceThreshold(const double initialResidualNorm) const {
|
||||||
|
Validate();
|
||||||
|
if (!std::isfinite(initialResidualNorm) || initialResidualNorm < 0.0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"A Newton convergence threshold requires a finite, non-negative initial residual norm."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const double relativeThreshold = relativeTolerance * initialResidualNorm;
|
||||||
|
if (!std::isfinite(relativeThreshold)) {
|
||||||
|
throw std::invalid_argument("The Newton relative convergence threshold must be finite.");
|
||||||
|
}
|
||||||
|
return absoluteTolerance > relativeThreshold ? absoluteTolerance : relativeThreshold;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The MVP globalization merit is phi(x) = 0.5 ||F_normalized(x)||^2.
|
||||||
|
* A metric customization receives the solve communicator and must return
|
||||||
|
* communicator-consistent values or throw collectively. The Newton engine
|
||||||
|
* reduces every predicate that drives control flow, but it cannot make a
|
||||||
|
* rank-local exception inside an arbitrary callback collective-safe.
|
||||||
|
*/
|
||||||
|
struct NormalizedResidualMetric final { };
|
||||||
|
|
||||||
|
struct MetricEvaluation final {
|
||||||
|
double residualNorm{0.0};
|
||||||
|
double merit{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline MetricEvaluation getMetric(
|
||||||
|
const NormalizedResidualMetric &,
|
||||||
|
const mfem::Vector &normalizedResidual,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
if (communicator == MPI_COMM_NULL) {
|
||||||
|
throw std::invalid_argument("A nonlinear metric requires a valid communicator.");
|
||||||
|
}
|
||||||
|
|
||||||
|
double localSquaredNorm = 0.0;
|
||||||
|
for (int index = 0; index < normalizedResidual.Size(); ++index) {
|
||||||
|
const double value = static_cast<double>(normalizedResidual(index));
|
||||||
|
localSquaredNorm += value * value;
|
||||||
|
}
|
||||||
|
|
||||||
|
double globalSquaredNorm = 0.0;
|
||||||
|
if (MPI_Allreduce(&localSquaredNorm, &globalSquaredNorm, 1, MPI_DOUBLE, MPI_SUM, communicator) != MPI_SUCCESS) {
|
||||||
|
throw std::runtime_error("The nonlinear metric could not reduce the normalized residual norm.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const double residualNorm = std::sqrt(globalSquaredNorm);
|
||||||
|
return {.residualNorm = residualNorm, .merit = 0.5 * residualNorm * residualNorm};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Metric = NormalizedResidualMetric>
|
||||||
|
requires std::move_constructible<std::remove_cvref_t<Metric>>
|
||||||
|
class Newton final {
|
||||||
|
public:
|
||||||
|
using MetricType = std::remove_cvref_t<Metric>;
|
||||||
|
|
||||||
|
Newton()
|
||||||
|
requires std::default_initializable<MetricType>
|
||||||
|
: Newton(
|
||||||
|
NewtonOptions{},
|
||||||
|
MetricType{}
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit Newton(NewtonOptions options)
|
||||||
|
requires std::default_initializable<MetricType>
|
||||||
|
: Newton(
|
||||||
|
std::move(options),
|
||||||
|
MetricType{}
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Newton(
|
||||||
|
NewtonOptions options,
|
||||||
|
MetricType metric
|
||||||
|
)
|
||||||
|
: m_options(std::move(options)),
|
||||||
|
m_metric(std::move(metric)) {
|
||||||
|
m_options.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const NewtonOptions &options() const noexcept {
|
||||||
|
return m_options;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const MetricType &metric() const noexcept {
|
||||||
|
return m_metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
NewtonOptions m_options;
|
||||||
|
[[no_unique_address]] MetricType m_metric;
|
||||||
|
};
|
||||||
|
|
||||||
|
Newton() -> Newton<NormalizedResidualMetric>;
|
||||||
|
Newton(NewtonOptions) -> Newton<NormalizedResidualMetric>;
|
||||||
|
|
||||||
|
template <typename Metric>
|
||||||
|
Newton(
|
||||||
|
NewtonOptions,
|
||||||
|
Metric
|
||||||
|
) -> Newton<std::remove_cvref_t<Metric>>;
|
||||||
|
|
||||||
|
template <typename Candidate> struct IsNewtonConfiguration : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Metric> struct IsNewtonConfiguration<Newton<Metric>> : std::true_type { };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept NewtonConfiguration = IsNewtonConfiguration<std::remove_cvref_t<Candidate>>::value;
|
||||||
|
|
||||||
|
enum class IterationDisposition : std::uint8_t {
|
||||||
|
unspecified,
|
||||||
|
accepted,
|
||||||
|
converged,
|
||||||
|
inadmissible_state,
|
||||||
|
non_finite_state,
|
||||||
|
non_finite_residual,
|
||||||
|
linear_solve_failure,
|
||||||
|
globalization_failure,
|
||||||
|
stagnation,
|
||||||
|
iteration_limit
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Event spans borrow solver workspaces and are valid only for the duration
|
||||||
|
* of the callback. Copy values that must outlive the callback.
|
||||||
|
*/
|
||||||
|
struct BeforeIteration final {
|
||||||
|
int iteration{0};
|
||||||
|
double initialResidualNorm{0.0};
|
||||||
|
double residualNorm{0.0};
|
||||||
|
double relativeResidualNorm{0.0};
|
||||||
|
double merit{0.0};
|
||||||
|
MPI_Comm communicator{MPI_COMM_NULL};
|
||||||
|
std::span<const mfem::real_t> physicalState{};
|
||||||
|
std::span<const mfem::real_t> normalizedState{};
|
||||||
|
std::span<const mfem::real_t> normalizedResidual{};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class LineSearchTrialDisposition : std::uint8_t {
|
||||||
|
accepted,
|
||||||
|
inadmissible_state,
|
||||||
|
non_finite_state,
|
||||||
|
non_finite_residual,
|
||||||
|
insufficient_decrease
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AfterLineSearchTrial final {
|
||||||
|
int iteration{0};
|
||||||
|
int trial{0};
|
||||||
|
double stepLength{0.0};
|
||||||
|
LineSearchTrialDisposition disposition{LineSearchTrialDisposition::insufficient_decrease};
|
||||||
|
std::string_view rejectionSource{};
|
||||||
|
std::optional<MetricEvaluation> metric{};
|
||||||
|
std::optional<double> minimumJacobianDeterminant{};
|
||||||
|
double preparationSeconds{0.0};
|
||||||
|
double metricSeconds{0.0};
|
||||||
|
MPI_Comm communicator{MPI_COMM_NULL};
|
||||||
|
std::span<const mfem::real_t> candidatePhysicalState{};
|
||||||
|
std::span<const mfem::real_t> candidateNormalizedState{};
|
||||||
|
std::span<const mfem::real_t> candidateNormalizedResidual{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AfterIteration final {
|
||||||
|
int iteration{0};
|
||||||
|
IterationDisposition disposition{IterationDisposition::unspecified};
|
||||||
|
bool stepAccepted{false};
|
||||||
|
double acceptedStepLength{0.0};
|
||||||
|
int lineSearchTrials{0};
|
||||||
|
double initialResidualNorm{0.0};
|
||||||
|
double previousResidualNorm{0.0};
|
||||||
|
double residualNorm{0.0};
|
||||||
|
double relativeResidualNorm{0.0};
|
||||||
|
double merit{0.0};
|
||||||
|
double iterationSeconds{0.0};
|
||||||
|
double lineSearchSeconds{0.0};
|
||||||
|
double trialPreparationSeconds{0.0};
|
||||||
|
double metricEvaluationSeconds{0.0};
|
||||||
|
double preconditionerRefreshSeconds{0.0};
|
||||||
|
double rollbackSeconds{0.0};
|
||||||
|
std::optional<LinearSolveReport> linearSolve{};
|
||||||
|
MPI_Comm communicator{MPI_COMM_NULL};
|
||||||
|
std::span<const mfem::real_t> physicalState{};
|
||||||
|
std::span<const mfem::real_t> normalizedState{};
|
||||||
|
std::span<const mfem::real_t> normalizedResidual{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NoObserver final { };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept BeforeIterationCallback = std::invocable<Candidate &, const BeforeIteration &> &&
|
||||||
|
std::same_as<std::invoke_result_t<Candidate &, const BeforeIteration &>, void>;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept AfterIterationCallback = std::invocable<Candidate &, const AfterIteration &> &&
|
||||||
|
std::same_as<std::invoke_result_t<Candidate &, const AfterIteration &>, void>;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept LineSearchTrialCallback =
|
||||||
|
std::invocable<Candidate &, const AfterLineSearchTrial &> &&
|
||||||
|
std::same_as<std::invoke_result_t<Candidate &, const AfterLineSearchTrial &>, void>;
|
||||||
|
|
||||||
|
template <BeforeIterationCallback BeforeCallback, AfterIterationCallback AfterCallback>
|
||||||
|
class CallbackObserver final {
|
||||||
|
public:
|
||||||
|
CallbackObserver(
|
||||||
|
BeforeCallback before,
|
||||||
|
AfterCallback after
|
||||||
|
)
|
||||||
|
: m_before(std::move(before)),
|
||||||
|
m_after(std::move(after)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void beforeIteration(const BeforeIteration &event) noexcept(std::is_nothrow_invocable_v<
|
||||||
|
BeforeCallback &,
|
||||||
|
const BeforeIteration &>) {
|
||||||
|
std::invoke(m_before, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void afterIteration(const AfterIteration &event) noexcept(std::is_nothrow_invocable_v<
|
||||||
|
AfterCallback &,
|
||||||
|
const AfterIteration &>) {
|
||||||
|
std::invoke(m_after, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[no_unique_address]] BeforeCallback m_before;
|
||||||
|
[[no_unique_address]] AfterCallback m_after;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename BeforeCallback,
|
||||||
|
typename AfterCallback>
|
||||||
|
requires BeforeIterationCallback<std::decay_t<BeforeCallback>> &&
|
||||||
|
AfterIterationCallback<std::decay_t<AfterCallback>> &&
|
||||||
|
std::constructible_from<
|
||||||
|
std::decay_t<BeforeCallback>,
|
||||||
|
BeforeCallback> &&
|
||||||
|
std::constructible_from<
|
||||||
|
std::decay_t<AfterCallback>,
|
||||||
|
AfterCallback>
|
||||||
|
[[nodiscard]] auto makeObserver(
|
||||||
|
BeforeCallback &&before,
|
||||||
|
AfterCallback &&after
|
||||||
|
) {
|
||||||
|
return CallbackObserver<std::decay_t<BeforeCallback>, std::decay_t<AfterCallback>>{
|
||||||
|
std::forward<BeforeCallback>(before), std::forward<AfterCallback>(after)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
BeforeIterationCallback BeforeCallback,
|
||||||
|
LineSearchTrialCallback TrialCallback,
|
||||||
|
AfterIterationCallback AfterCallback>
|
||||||
|
class DetailedCallbackObserver final {
|
||||||
|
public:
|
||||||
|
DetailedCallbackObserver(
|
||||||
|
BeforeCallback before,
|
||||||
|
TrialCallback trial,
|
||||||
|
AfterCallback after
|
||||||
|
)
|
||||||
|
: m_before(std::move(before)),
|
||||||
|
m_trial(std::move(trial)),
|
||||||
|
m_after(std::move(after)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void beforeIteration(const BeforeIteration &event) noexcept(std::is_nothrow_invocable_v<
|
||||||
|
BeforeCallback &,
|
||||||
|
const BeforeIteration &>) {
|
||||||
|
std::invoke(m_before, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void afterLineSearchTrial(const AfterLineSearchTrial &event) noexcept(std::is_nothrow_invocable_v<
|
||||||
|
TrialCallback &,
|
||||||
|
const AfterLineSearchTrial &>) {
|
||||||
|
std::invoke(m_trial, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void afterIteration(const AfterIteration &event) noexcept(std::is_nothrow_invocable_v<
|
||||||
|
AfterCallback &,
|
||||||
|
const AfterIteration &>) {
|
||||||
|
std::invoke(m_after, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[no_unique_address]] BeforeCallback m_before;
|
||||||
|
[[no_unique_address]] TrialCallback m_trial;
|
||||||
|
[[no_unique_address]] AfterCallback m_after;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename BeforeCallback,
|
||||||
|
typename TrialCallback,
|
||||||
|
typename AfterCallback>
|
||||||
|
requires BeforeIterationCallback<std::decay_t<BeforeCallback>> &&
|
||||||
|
LineSearchTrialCallback<std::decay_t<TrialCallback>> &&
|
||||||
|
AfterIterationCallback<std::decay_t<AfterCallback>> &&
|
||||||
|
std::constructible_from<
|
||||||
|
std::decay_t<BeforeCallback>,
|
||||||
|
BeforeCallback> &&
|
||||||
|
std::constructible_from<
|
||||||
|
std::decay_t<TrialCallback>,
|
||||||
|
TrialCallback> &&
|
||||||
|
std::constructible_from<
|
||||||
|
std::decay_t<AfterCallback>,
|
||||||
|
AfterCallback>
|
||||||
|
[[nodiscard]] auto makeObserver(
|
||||||
|
BeforeCallback &&before,
|
||||||
|
TrialCallback &&trial,
|
||||||
|
AfterCallback &&after
|
||||||
|
) {
|
||||||
|
return DetailedCallbackObserver<
|
||||||
|
std::decay_t<BeforeCallback>, std::decay_t<TrialCallback>, std::decay_t<AfterCallback>>{
|
||||||
|
std::forward<BeforeCallback>(before), std::forward<TrialCallback>(trial), std::forward<AfterCallback>(after)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
[[nodiscard]] inline double NextBacktrackingStepLength(
|
||||||
|
const double rejectedStepLength,
|
||||||
|
const double acceptedMinimumJacobianDeterminant,
|
||||||
|
const std::optional<double> rejectedMinimumJacobianDeterminant,
|
||||||
|
const bool rejectedByInvertedGeometry,
|
||||||
|
const BacktrackingOptions &options
|
||||||
|
) noexcept {
|
||||||
|
const double contractedStepLength = rejectedStepLength * options.contractionFactor;
|
||||||
|
if (!rejectedByInvertedGeometry || !rejectedMinimumJacobianDeterminant.has_value() ||
|
||||||
|
!std::isfinite(acceptedMinimumJacobianDeterminant) || acceptedMinimumJacobianDeterminant <= 0.0 ||
|
||||||
|
!std::isfinite(*rejectedMinimumJacobianDeterminant) || *rejectedMinimumJacobianDeterminant > 0.0) {
|
||||||
|
return contractedStepLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double determinantChange = acceptedMinimumJacobianDeterminant - *rejectedMinimumJacobianDeterminant;
|
||||||
|
if (!std::isfinite(determinantChange) || determinantChange <= 0.0) {
|
||||||
|
return contractedStepLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double estimatedBoundaryStep =
|
||||||
|
rejectedStepLength * acceptedMinimumJacobianDeterminant / determinantChange;
|
||||||
|
const double safeguardedStep = options.fractionToBoundarySafety * estimatedBoundaryStep;
|
||||||
|
if (!std::isfinite(safeguardedStep) || safeguardedStep <= 0.0 || safeguardedStep >= rejectedStepLength) {
|
||||||
|
return contractedStepLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keep the configured backtracking ladder intact. The geometry
|
||||||
|
* certificate is used only to skip rungs that its local boundary
|
||||||
|
* estimate says are unsafe; it does not introduce a new trial
|
||||||
|
* length between two rungs. This preserves the candidates that
|
||||||
|
* ordinary backtracking would eventually test while avoiding the
|
||||||
|
* expensive preparation of the skipped, inverted geometries.
|
||||||
|
*/
|
||||||
|
if (contractedStepLength <= safeguardedStep) {
|
||||||
|
return contractedStepLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double rung =
|
||||||
|
std::ceil(std::log(safeguardedStep / rejectedStepLength) / std::log(options.contractionFactor));
|
||||||
|
double skippedStep = rejectedStepLength * std::pow(options.contractionFactor, rung);
|
||||||
|
if (!std::isfinite(skippedStep) || skippedStep <= 0.0 || skippedStep >= rejectedStepLength) {
|
||||||
|
return contractedStepLength;
|
||||||
|
}
|
||||||
|
if (skippedStep > safeguardedStep) {
|
||||||
|
skippedStep *= options.contractionFactor;
|
||||||
|
}
|
||||||
|
return skippedStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Observer>
|
||||||
|
inline constexpr bool isNoObserver = std::same_as<std::remove_cvref_t<Observer>, NoObserver>;
|
||||||
|
|
||||||
|
template <typename Observer>
|
||||||
|
concept ObservesBeforeIteration =
|
||||||
|
!isNoObserver<Observer> &&
|
||||||
|
requires(std::remove_reference_t<Observer> &observer, const BeforeIteration &event) {
|
||||||
|
{ observer.beforeIteration(event) } -> std::same_as<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Observer>
|
||||||
|
concept ObservesAfterIteration =
|
||||||
|
!isNoObserver<Observer> &&
|
||||||
|
requires(std::remove_reference_t<Observer> &observer, const AfterIteration &event) {
|
||||||
|
{ observer.afterIteration(event) } -> std::same_as<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Observer>
|
||||||
|
concept ObservesLineSearchTrial =
|
||||||
|
!isNoObserver<Observer> &&
|
||||||
|
requires(std::remove_reference_t<Observer> &observer, const AfterLineSearchTrial &event) {
|
||||||
|
{ observer.afterLineSearchTrial(event) } -> std::same_as<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Callback>
|
||||||
|
void InvokeObserverHookCollectively(
|
||||||
|
const MPI_Comm communicator,
|
||||||
|
const char *remoteFailureMessage,
|
||||||
|
Callback &&callback
|
||||||
|
) {
|
||||||
|
std::exception_ptr localFailure;
|
||||||
|
try {
|
||||||
|
std::invoke(std::forward<Callback>(callback));
|
||||||
|
} catch (...) {
|
||||||
|
localFailure = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
const int localFailureFlag = localFailure != nullptr ? 1 : 0;
|
||||||
|
int globalFailureFlag = 0;
|
||||||
|
if (MPI_Allreduce(&localFailureFlag, &globalFailureFlag, 1, MPI_INT, MPI_MAX, communicator) !=
|
||||||
|
MPI_SUCCESS) {
|
||||||
|
if (localFailure != nullptr) {
|
||||||
|
std::rethrow_exception(localFailure);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("The nonlinear solver could not synchronize an observer callback.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalFailureFlag != 0) {
|
||||||
|
if (localFailure != nullptr) {
|
||||||
|
std::rethrow_exception(localFailure);
|
||||||
|
}
|
||||||
|
throw std::runtime_error(remoteFailureMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Observer>
|
||||||
|
void InvokeBeforeIteration(
|
||||||
|
Observer &observer,
|
||||||
|
const BeforeIteration &event
|
||||||
|
) {
|
||||||
|
if constexpr (ObservesBeforeIteration<Observer>) {
|
||||||
|
if constexpr (noexcept(observer.beforeIteration(event))) {
|
||||||
|
observer.beforeIteration(event);
|
||||||
|
} else {
|
||||||
|
InvokeObserverHookCollectively(
|
||||||
|
event.communicator, "An observer before-iteration callback failed on another rank.",
|
||||||
|
[&observer, &event] { observer.beforeIteration(event); }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Observer>
|
||||||
|
void InvokeAfterIteration(
|
||||||
|
Observer &observer,
|
||||||
|
const AfterIteration &event
|
||||||
|
) {
|
||||||
|
if constexpr (ObservesAfterIteration<Observer>) {
|
||||||
|
if constexpr (noexcept(observer.afterIteration(event))) {
|
||||||
|
observer.afterIteration(event);
|
||||||
|
} else {
|
||||||
|
InvokeObserverHookCollectively(
|
||||||
|
event.communicator, "An observer after-iteration callback failed on another rank.",
|
||||||
|
[&observer, &event] { observer.afterIteration(event); }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Observer>
|
||||||
|
void InvokeAfterLineSearchTrial(
|
||||||
|
Observer &observer,
|
||||||
|
const AfterLineSearchTrial &event
|
||||||
|
) {
|
||||||
|
if constexpr (ObservesLineSearchTrial<Observer>) {
|
||||||
|
if constexpr (noexcept(observer.afterLineSearchTrial(event))) {
|
||||||
|
observer.afterLineSearchTrial(event);
|
||||||
|
} else {
|
||||||
|
InvokeObserverHookCollectively(
|
||||||
|
event.communicator, "An observer line-search callback failed on another rank.",
|
||||||
|
[&observer, &event] { observer.afterLineSearchTrial(event); }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Observers run synchronously on every solve rank. Ordinary callback
|
||||||
|
* exceptions are synchronized before the solver proceeds, so all ranks can
|
||||||
|
* unwind together; explicitly noexcept callbacks bypass that synchronization.
|
||||||
|
* A callback must still not enter an MPI collective on only a subset of
|
||||||
|
* ranks. A before/after pair is guaranteed for iterations that finish by
|
||||||
|
* returning an evaluation report. Infrastructure exceptions unwind
|
||||||
|
* immediately and do not promise an after callback.
|
||||||
|
*/
|
||||||
|
template <typename Candidate>
|
||||||
|
concept NewtonObserver = detail::isNoObserver<Candidate> || detail::ObservesBeforeIteration<Candidate> ||
|
||||||
|
detail::ObservesLineSearchTrial<Candidate> || detail::ObservesAfterIteration<Candidate>;
|
||||||
|
} // namespace mean_field::solver::nonlinear
|
||||||
1642
libmeanfield/interface/solver/stellar_context.cppm
Normal file
1642
libmeanfield/interface/solver/stellar_context.cppm
Normal file
File diff suppressed because it is too large
Load Diff
4
libmeanfield/interface/solver/stellar_equilibrium.cppm
Normal file
4
libmeanfield/interface/solver/stellar_equilibrium.cppm
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export module mean_field:solver.stellar_equilibrium;
|
||||||
|
|
||||||
|
export import :solver.stellar_structure;
|
||||||
|
export import :solver.stellar_context;
|
||||||
60
libmeanfield/interface/solver/stellar_equilibrium_types.cppm
Normal file
60
libmeanfield/interface/solver/stellar_equilibrium_types.cppm
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
export module mean_field:solver.stellar_equilibrium_types;
|
||||||
|
|
||||||
|
export import :solver.linear_backend;
|
||||||
|
|
||||||
|
export namespace mean_field::solver {
|
||||||
|
enum class StellarEquilibriumFailureReason : std::uint8_t {
|
||||||
|
unspecified,
|
||||||
|
inadmissible_state,
|
||||||
|
non_finite_state,
|
||||||
|
non_finite_residual,
|
||||||
|
linear_solve_failure,
|
||||||
|
globalization_failure,
|
||||||
|
stagnation,
|
||||||
|
iteration_limit
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An owning, backend-neutral account of an expected numerical failure.
|
||||||
|
* Backend-specific measurements may be translated into the message or
|
||||||
|
* future common diagnostics, but are deliberately not part of this stable
|
||||||
|
* result boundary.
|
||||||
|
*/
|
||||||
|
struct StellarEquilibriumFailureReport final {
|
||||||
|
StellarEquilibriumFailureReason reason{StellarEquilibriumFailureReason::unspecified};
|
||||||
|
std::string message;
|
||||||
|
int completedNonlinearIterations{0};
|
||||||
|
std::optional<double> initialResidualNorm;
|
||||||
|
std::optional<double> finalResidualNorm;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fixed-size diagnostics retained by every evaluation report. Detailed
|
||||||
|
* iteration histories belong in an observer so the default solve does not
|
||||||
|
* allocate storage proportional to the iteration count.
|
||||||
|
*/
|
||||||
|
struct StellarEquilibriumEvaluationDiagnostics final {
|
||||||
|
int attemptedNonlinearIterations{0};
|
||||||
|
int acceptedNonlinearIterations{0};
|
||||||
|
int totalLineSearchTrials{0};
|
||||||
|
int inadmissibleLineSearchTrials{0};
|
||||||
|
int nonFiniteLineSearchTrials{0};
|
||||||
|
int insufficientDecreaseTrials{0};
|
||||||
|
double initialResidualNorm{0.0};
|
||||||
|
double finalResidualNorm{0.0};
|
||||||
|
double lastAcceptedStepLength{0.0};
|
||||||
|
double totalLinearSolveSeconds{0.0};
|
||||||
|
double totalLineSearchSeconds{0.0};
|
||||||
|
double totalTrialPreparationSeconds{0.0};
|
||||||
|
double totalMetricEvaluationSeconds{0.0};
|
||||||
|
double totalPreconditionerRefreshSeconds{0.0};
|
||||||
|
double totalRollbackSeconds{0.0};
|
||||||
|
std::optional<LinearSolveReport> lastLinearSolve;
|
||||||
|
};
|
||||||
|
} // namespace mean_field::solver
|
||||||
486
libmeanfield/interface/solver/stellar_structure.cppm
Normal file
486
libmeanfield/interface/solver/stellar_structure.cppm
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mfem.hpp>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
|
export module mean_field:solver.stellar_structure;
|
||||||
|
|
||||||
|
export import :operators.stellar_equilibrium_problem;
|
||||||
|
export import :solver.stellar_equilibrium_types;
|
||||||
|
|
||||||
|
export namespace mean_field::solver {
|
||||||
|
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem> class StellarEquilibriumEvaluationReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace mean_field::solver::detail {
|
||||||
|
enum class StellarViewCertification : std::uint8_t { unavailable, checkpoint, structure };
|
||||||
|
|
||||||
|
template <typename Problem> struct StellarStructureStorage final {
|
||||||
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
|
|
||||||
|
StellarStructureStorage(
|
||||||
|
std::unique_ptr<ProblemType> ownedProblem,
|
||||||
|
std::unique_ptr<mfem::Vector> acceptedPhysicalState,
|
||||||
|
std::unique_ptr<physics::RigidRotation> prescribedRotation
|
||||||
|
)
|
||||||
|
: problem(std::move(ownedProblem)),
|
||||||
|
physicalState(std::move(acceptedPhysicalState)),
|
||||||
|
rotation(std::move(prescribedRotation)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ProblemType> problem;
|
||||||
|
std::unique_ptr<mfem::Vector> physicalState;
|
||||||
|
std::unique_ptr<physics::RigidRotation> rotation;
|
||||||
|
std::uint64_t viewGeneration{0};
|
||||||
|
StellarViewCertification certification{StellarViewCertification::unavailable};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Problem>
|
||||||
|
[[nodiscard]] bool IsCurrentView(
|
||||||
|
const std::weak_ptr<const StellarStructureStorage<Problem>> &candidate,
|
||||||
|
const std::uint64_t generation,
|
||||||
|
const bool requireConverged
|
||||||
|
) noexcept {
|
||||||
|
const auto storage = candidate.lock();
|
||||||
|
if (storage == nullptr || storage->problem == nullptr || storage->physicalState == nullptr ||
|
||||||
|
storage->viewGeneration != generation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (requireConverged) {
|
||||||
|
return storage->certification == StellarViewCertification::structure;
|
||||||
|
}
|
||||||
|
return storage->certification == StellarViewCertification::checkpoint ||
|
||||||
|
storage->certification == StellarViewCertification::structure;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Problem>
|
||||||
|
[[nodiscard]] std::shared_ptr<const StellarStructureStorage<Problem>> RequireCurrentView(
|
||||||
|
const std::weak_ptr<const StellarStructureStorage<Problem>> &candidate,
|
||||||
|
const std::uint64_t generation,
|
||||||
|
const bool requireConverged
|
||||||
|
) {
|
||||||
|
auto storage = candidate.lock();
|
||||||
|
if (storage == nullptr || storage->problem == nullptr || storage->physicalState == nullptr ||
|
||||||
|
storage->viewGeneration != generation ||
|
||||||
|
(requireConverged && storage->certification != StellarViewCertification::structure) ||
|
||||||
|
(!requireConverged && storage->certification != StellarViewCertification::checkpoint &&
|
||||||
|
storage->certification != StellarViewCertification::structure)) {
|
||||||
|
throw std::logic_error("The stellar structure view is stale or is not certified for this result.");
|
||||||
|
}
|
||||||
|
return storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Vector> [[nodiscard]] std::span<const mfem::real_t> ReadOnlySpan(const Vector &values) noexcept {
|
||||||
|
return {values.GetData(), static_cast<std::size_t>(values.Size())};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Problem> struct StellarEvaluationReportAccess;
|
||||||
|
} // namespace mean_field::solver::detail
|
||||||
|
|
||||||
|
export namespace mean_field::equilibrium {
|
||||||
|
/*
|
||||||
|
* These are the future owning, self-contained values. There is no public
|
||||||
|
* construction path until deep capture and its MPI-independent storage
|
||||||
|
* schema are implemented.
|
||||||
|
*/
|
||||||
|
template <DiscretizedStellarEquilibriumProblem Problem> class StellarStructure final {
|
||||||
|
public:
|
||||||
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
|
|
||||||
|
StellarStructure(const StellarStructure &) = delete;
|
||||||
|
StellarStructure &operator=(const StellarStructure &) = delete;
|
||||||
|
StellarStructure(StellarStructure &&) noexcept = default;
|
||||||
|
StellarStructure &operator=(StellarStructure &&) = delete;
|
||||||
|
~StellarStructure() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
StellarStructure() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <DiscretizedStellarEquilibriumProblem Problem> class StellarCheckpoint final {
|
||||||
|
public:
|
||||||
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
|
|
||||||
|
StellarCheckpoint(const StellarCheckpoint &) = delete;
|
||||||
|
StellarCheckpoint &operator=(const StellarCheckpoint &) = delete;
|
||||||
|
StellarCheckpoint(StellarCheckpoint &&) noexcept = default;
|
||||||
|
StellarCheckpoint &operator=(StellarCheckpoint &&) = delete;
|
||||||
|
~StellarCheckpoint() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
StellarCheckpoint() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Result views weakly observe context-owned storage. valid() remains safe
|
||||||
|
* after that context is destroyed. References and spans extracted from a
|
||||||
|
* valid view remain borrowed: the context must outlive their use, and the
|
||||||
|
* next evaluate() call invalidates them along with their originating view.
|
||||||
|
*/
|
||||||
|
template <DiscretizedStellarEquilibriumProblem Problem> class StellarStructureView final {
|
||||||
|
public:
|
||||||
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
|
using ModelType = typename ProblemType::ModelType;
|
||||||
|
|
||||||
|
[[nodiscard]] bool valid() const noexcept {
|
||||||
|
return solver::detail::IsCurrentView(m_storage, m_generation, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const ModelType &model() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
return storage->problem->GetStellarModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const ModelType &model() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] MPI_Comm communicator() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
return storage->problem->GetCommunicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MPI_Comm communicator() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<const mfem::real_t> state() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
return solver::detail::ReadOnlySpan(*storage->physicalState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<const mfem::real_t> state() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<const operators::RootBlockDescriptor> stateDescriptors() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
return storage->problem->GetManifest().valueBlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<const operators::RootBlockDescriptor> stateDescriptors() const && = delete;
|
||||||
|
|
||||||
|
template <typename Term>
|
||||||
|
requires requires(
|
||||||
|
const typename ProblemType::ManifestType &manifest,
|
||||||
|
const mfem::Vector &physicalState,
|
||||||
|
const Term &term
|
||||||
|
) { manifest.stateView(physicalState).block(term); }
|
||||||
|
[[nodiscard]] std::span<const mfem::real_t> stateBlock(const Term &term) const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
const auto block = storage->problem->GetManifest().stateView(*storage->physicalState).block(term);
|
||||||
|
return solver::detail::ReadOnlySpan(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Term> [[nodiscard]] std::span<const mfem::real_t> stateBlock(const Term &) const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<physics::RigidRotation> prescribedRotation() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
if (storage->rotation == nullptr) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return *storage->rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<physics::RigidRotation> prescribedRotation() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] physics::RigidRotation rotation() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
return storage->problem->GetPreparedOperator().GetRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] physics::RigidRotation rotation() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] StellarStructure<ProblemType> capture() const {
|
||||||
|
(void)RequireStorage();
|
||||||
|
throw std::logic_error("Capturing a self-contained StellarStructure is not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <DiscretizedStellarEquilibriumProblem> friend class solver::StellarEquilibriumEvaluationReport;
|
||||||
|
using Storage = solver::detail::StellarStructureStorage<ProblemType>;
|
||||||
|
|
||||||
|
StellarStructureView(
|
||||||
|
std::weak_ptr<const Storage> storage,
|
||||||
|
const std::uint64_t generation
|
||||||
|
) noexcept
|
||||||
|
: m_storage(std::move(storage)),
|
||||||
|
m_generation(generation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::shared_ptr<const Storage> RequireStorage() const {
|
||||||
|
return solver::detail::RequireCurrentView(m_storage, m_generation, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::weak_ptr<const Storage> m_storage;
|
||||||
|
std::uint64_t m_generation;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <DiscretizedStellarEquilibriumProblem Problem> class StellarCheckpointView final {
|
||||||
|
public:
|
||||||
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
|
using ModelType = typename ProblemType::ModelType;
|
||||||
|
|
||||||
|
[[nodiscard]] bool valid() const noexcept {
|
||||||
|
return solver::detail::IsCurrentView(m_storage, m_generation, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const ModelType &model() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
return storage->problem->GetStellarModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const ModelType &model() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] MPI_Comm communicator() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
return storage->problem->GetCommunicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MPI_Comm communicator() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<const mfem::real_t> state() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
return solver::detail::ReadOnlySpan(*storage->physicalState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<const mfem::real_t> state() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<const operators::RootBlockDescriptor> stateDescriptors() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
return storage->problem->GetManifest().valueBlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<const operators::RootBlockDescriptor> stateDescriptors() const && = delete;
|
||||||
|
|
||||||
|
template <typename Term>
|
||||||
|
requires requires(
|
||||||
|
const typename ProblemType::ManifestType &manifest,
|
||||||
|
const mfem::Vector &physicalState,
|
||||||
|
const Term &term
|
||||||
|
) { manifest.stateView(physicalState).block(term); }
|
||||||
|
[[nodiscard]] std::span<const mfem::real_t> stateBlock(const Term &term) const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
const auto block = storage->problem->GetManifest().stateView(*storage->physicalState).block(term);
|
||||||
|
return solver::detail::ReadOnlySpan(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Term> [[nodiscard]] std::span<const mfem::real_t> stateBlock(const Term &) const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<physics::RigidRotation> prescribedRotation() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
if (storage->rotation == nullptr) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return *storage->rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<physics::RigidRotation> prescribedRotation() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] physics::RigidRotation rotation() const & {
|
||||||
|
const auto storage = RequireStorage();
|
||||||
|
return storage->problem->GetPreparedOperator().GetRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] physics::RigidRotation rotation() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] StellarCheckpoint<ProblemType> capture() const {
|
||||||
|
(void)RequireStorage();
|
||||||
|
throw std::logic_error("Capturing a self-contained StellarCheckpoint is not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <DiscretizedStellarEquilibriumProblem> friend class solver::StellarEquilibriumEvaluationReport;
|
||||||
|
using Storage = solver::detail::StellarStructureStorage<ProblemType>;
|
||||||
|
|
||||||
|
StellarCheckpointView(
|
||||||
|
std::weak_ptr<const Storage> storage,
|
||||||
|
const std::uint64_t generation
|
||||||
|
) noexcept
|
||||||
|
: m_storage(std::move(storage)),
|
||||||
|
m_generation(generation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::shared_ptr<const Storage> RequireStorage() const {
|
||||||
|
return solver::detail::RequireCurrentView(m_storage, m_generation, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::weak_ptr<const Storage> m_storage;
|
||||||
|
std::uint64_t m_generation;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <DiscretizedStellarEquilibriumProblem Problem>
|
||||||
|
[[noreturn]] void serialize(
|
||||||
|
const StellarStructure<Problem> &,
|
||||||
|
const std::filesystem::path &
|
||||||
|
) {
|
||||||
|
throw std::logic_error("Serializing a StellarStructure is not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <DiscretizedStellarEquilibriumProblem Problem>
|
||||||
|
[[noreturn]] void serialize(
|
||||||
|
const StellarStructureView<Problem> &view,
|
||||||
|
const std::filesystem::path &
|
||||||
|
) {
|
||||||
|
(void)view.state();
|
||||||
|
throw std::logic_error("Serializing a StellarStructureView is not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <DiscretizedStellarEquilibriumProblem Problem>
|
||||||
|
[[noreturn]] void serialize(
|
||||||
|
const StellarCheckpoint<Problem> &,
|
||||||
|
const std::filesystem::path &
|
||||||
|
) {
|
||||||
|
throw std::logic_error("Serializing a StellarCheckpoint is not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <DiscretizedStellarEquilibriumProblem Problem>
|
||||||
|
[[noreturn]] void serialize(
|
||||||
|
const StellarCheckpointView<Problem> &view,
|
||||||
|
const std::filesystem::path &
|
||||||
|
) {
|
||||||
|
(void)view.state();
|
||||||
|
throw std::logic_error("Serializing a StellarCheckpointView is not implemented.");
|
||||||
|
}
|
||||||
|
} // namespace mean_field::equilibrium
|
||||||
|
|
||||||
|
export namespace mean_field::solver {
|
||||||
|
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
|
||||||
|
class StellarEquilibriumEvaluationReport final {
|
||||||
|
public:
|
||||||
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
|
using StructureView = equilibrium::StellarStructureView<ProblemType>;
|
||||||
|
using CheckpointView = equilibrium::StellarCheckpointView<ProblemType>;
|
||||||
|
|
||||||
|
StellarEquilibriumEvaluationReport(const StellarEquilibriumEvaluationReport &) = default;
|
||||||
|
StellarEquilibriumEvaluationReport &operator=(const StellarEquilibriumEvaluationReport &) = default;
|
||||||
|
StellarEquilibriumEvaluationReport(StellarEquilibriumEvaluationReport &&) noexcept = default;
|
||||||
|
StellarEquilibriumEvaluationReport &operator=(StellarEquilibriumEvaluationReport &&) noexcept = default;
|
||||||
|
~StellarEquilibriumEvaluationReport() = default;
|
||||||
|
|
||||||
|
[[nodiscard]] bool converged() const noexcept {
|
||||||
|
return m_converged;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const StellarEquilibriumEvaluationDiagnostics &diagnostics() const & noexcept {
|
||||||
|
return m_diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const StellarEquilibriumEvaluationDiagnostics &diagnostics() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] int completedNonlinearIterations() const noexcept {
|
||||||
|
return m_diagnostics.acceptedNonlinearIterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double initialResidualNorm() const noexcept {
|
||||||
|
return m_diagnostics.initialResidualNorm;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double finalResidualNorm() const noexcept {
|
||||||
|
return m_diagnostics.finalResidualNorm;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const StellarEquilibriumFailureReport &failure() const & {
|
||||||
|
if (!m_failure.has_value()) {
|
||||||
|
throw std::logic_error("A converged stellar-equilibrium report has no failure record.");
|
||||||
|
}
|
||||||
|
return *m_failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const StellarEquilibriumFailureReport &failure() const && = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] StructureView structureView() const {
|
||||||
|
if (!m_converged) {
|
||||||
|
throw std::logic_error("A failed stellar-equilibrium report cannot certify a structure view.");
|
||||||
|
}
|
||||||
|
StructureView view{m_storage, m_generation};
|
||||||
|
if (!view.valid()) {
|
||||||
|
throw std::logic_error("The stellar-equilibrium structure view has been invalidated.");
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] CheckpointView checkpointView() const {
|
||||||
|
CheckpointView view{m_storage, m_generation};
|
||||||
|
if (!view.valid()) {
|
||||||
|
throw std::logic_error("The stellar-equilibrium checkpoint view has been invalidated.");
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] CheckpointView lastAcceptedCheckpointView() const {
|
||||||
|
return checkpointView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct detail::StellarEvaluationReportAccess<ProblemType>;
|
||||||
|
using Storage = detail::StellarStructureStorage<ProblemType>;
|
||||||
|
|
||||||
|
StellarEquilibriumEvaluationReport(
|
||||||
|
const bool converged,
|
||||||
|
StellarEquilibriumEvaluationDiagnostics diagnostics,
|
||||||
|
std::optional<StellarEquilibriumFailureReport> failure,
|
||||||
|
std::weak_ptr<const Storage> storage,
|
||||||
|
const std::uint64_t generation
|
||||||
|
)
|
||||||
|
: m_converged(converged),
|
||||||
|
m_diagnostics(std::move(diagnostics)),
|
||||||
|
m_failure(std::move(failure)),
|
||||||
|
m_storage(std::move(storage)),
|
||||||
|
m_generation(generation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool m_converged;
|
||||||
|
StellarEquilibriumEvaluationDiagnostics m_diagnostics;
|
||||||
|
std::optional<StellarEquilibriumFailureReport> m_failure;
|
||||||
|
std::weak_ptr<const Storage> m_storage;
|
||||||
|
std::uint64_t m_generation;
|
||||||
|
};
|
||||||
|
} // namespace mean_field::solver
|
||||||
|
|
||||||
|
namespace mean_field::solver::detail {
|
||||||
|
template <typename Problem> struct StellarEvaluationReportAccess final {
|
||||||
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
|
using Report = StellarEquilibriumEvaluationReport<ProblemType>;
|
||||||
|
using Storage = StellarStructureStorage<ProblemType>;
|
||||||
|
|
||||||
|
[[nodiscard]] static Report Success(
|
||||||
|
const std::shared_ptr<Storage> &storage,
|
||||||
|
StellarEquilibriumEvaluationDiagnostics diagnostics
|
||||||
|
) {
|
||||||
|
if (storage == nullptr) {
|
||||||
|
throw std::invalid_argument("A stellar-equilibrium report requires owned result storage.");
|
||||||
|
}
|
||||||
|
storage->certification = StellarViewCertification::structure;
|
||||||
|
return Report{true, std::move(diagnostics), std::nullopt, storage, storage->viewGeneration};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static Report Failure(
|
||||||
|
const std::shared_ptr<Storage> &storage,
|
||||||
|
StellarEquilibriumEvaluationDiagnostics diagnostics,
|
||||||
|
const StellarEquilibriumFailureReason reason,
|
||||||
|
std::string message
|
||||||
|
) {
|
||||||
|
if (storage == nullptr) {
|
||||||
|
throw std::invalid_argument("A stellar-equilibrium report requires owned result storage.");
|
||||||
|
}
|
||||||
|
storage->certification = StellarViewCertification::checkpoint;
|
||||||
|
StellarEquilibriumFailureReport failure{
|
||||||
|
.reason = reason,
|
||||||
|
.message = std::move(message),
|
||||||
|
.completedNonlinearIterations = diagnostics.acceptedNonlinearIterations,
|
||||||
|
.initialResidualNorm = diagnostics.initialResidualNorm,
|
||||||
|
.finalResidualNorm = diagnostics.finalResidualNorm
|
||||||
|
};
|
||||||
|
return Report{
|
||||||
|
false, std::move(diagnostics), std::optional<StellarEquilibriumFailureReport>{std::move(failure)},
|
||||||
|
storage, storage->viewGeneration
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace mean_field::solver::detail
|
||||||
236
sandbox.cpp
Normal file
236
sandbox.cpp
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <exception>
|
||||||
|
#include <iostream>
|
||||||
|
#include <numbers>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mfem.hpp>
|
||||||
|
|
||||||
|
import mean_field;
|
||||||
|
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
[[nodiscard]] const char *trialDispositionName(
|
||||||
|
const solver::nonlinear::LineSearchTrialDisposition disposition
|
||||||
|
) noexcept {
|
||||||
|
using Disposition = solver::nonlinear::LineSearchTrialDisposition;
|
||||||
|
switch (disposition) {
|
||||||
|
case Disposition::accepted:
|
||||||
|
return "accepted";
|
||||||
|
case Disposition::inadmissible_state:
|
||||||
|
return "inadmissible state";
|
||||||
|
case Disposition::non_finite_state:
|
||||||
|
return "non-finite state";
|
||||||
|
case Disposition::non_finite_residual:
|
||||||
|
return "non-finite residual";
|
||||||
|
case Disposition::insufficient_decrease:
|
||||||
|
default:
|
||||||
|
return "insufficient decrease";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const char *linearStatusName(const solver::LinearSolveStatus status) noexcept {
|
||||||
|
switch (status) {
|
||||||
|
case solver::LinearSolveStatus::converged:
|
||||||
|
return "converged";
|
||||||
|
case solver::LinearSolveStatus::maximum_iterations:
|
||||||
|
return "maximum iterations";
|
||||||
|
case solver::LinearSolveStatus::breakdown:
|
||||||
|
return "breakdown";
|
||||||
|
case solver::LinearSolveStatus::non_finite:
|
||||||
|
return "non-finite";
|
||||||
|
case solver::LinearSolveStatus::backend_failure:
|
||||||
|
default:
|
||||||
|
return "backend failure";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(
|
||||||
|
int argc,
|
||||||
|
char **argv
|
||||||
|
) {
|
||||||
|
mfem::Mpi::Init(argc, argv);
|
||||||
|
int exitCode = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
mfem::Device device("cpu");
|
||||||
|
int rank = 0;
|
||||||
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||||
|
|
||||||
|
utils::Args arguments;
|
||||||
|
arguments.mesh_file = "sandbox.smesh";
|
||||||
|
arguments.p.rtol = 1.0e-12;
|
||||||
|
arguments.p.atol = 1.0e-12;
|
||||||
|
const auto discretizationStart = std::chrono::steady_clock::now();
|
||||||
|
auto finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
if (!finiteElements.okay()) {
|
||||||
|
throw std::runtime_error("The sandbox could not construct its finite-element discretization.");
|
||||||
|
}
|
||||||
|
if (rank == 0) {
|
||||||
|
std::cout << "Finite-element setup: "
|
||||||
|
<< std::chrono::duration<double>(std::chrono::steady_clock::now() - discretizationStart).count()
|
||||||
|
<< " s\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr double radius = utils::RADIUS;
|
||||||
|
constexpr double mass = utils::MASS;
|
||||||
|
constexpr double angularMomentum = 0.05;
|
||||||
|
constexpr double gravitationalConstant = utils::G;
|
||||||
|
const double polytropicConstant =
|
||||||
|
2.0 * gravitationalConstant * radius * radius / std::numbers::pi_v<double>;
|
||||||
|
const double centralDensity =
|
||||||
|
std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
||||||
|
|
||||||
|
auto stellarModel = model::StellarModel(
|
||||||
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
|
integral::FixedAngularMomentum({
|
||||||
|
.Jtotal = dimensions::AngularMomentumValue{angularMomentum},
|
||||||
|
.axis = {0.0, 0.0, 1.0},
|
||||||
|
.center = {0.0, 0.0, 0.0}
|
||||||
|
}),
|
||||||
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||||
|
);
|
||||||
|
|
||||||
|
auto discretization = equilibrium::makeStellarDiscretization(
|
||||||
|
std::move(finiteElements),
|
||||||
|
normalization::PhysicalRieszDiagonal{
|
||||||
|
dimensions::LengthValue{radius},
|
||||||
|
gravitationalConstant
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
auto preconditioner = preconditioning::makePreconditioner();
|
||||||
|
auto linearSolver = solver::linear::FGMRES({.restartLength = 40, .printLevel = -1});
|
||||||
|
const auto contextStart = std::chrono::steady_clock::now();
|
||||||
|
auto context = solver::makeContext(
|
||||||
|
std::move(stellarModel), std::move(discretization), std::move(preconditioner), std::move(linearSolver)
|
||||||
|
);
|
||||||
|
if (rank == 0) {
|
||||||
|
std::cout << "Solver context setup: "
|
||||||
|
<< std::chrono::duration<double>(std::chrono::steady_clock::now() - contextStart).count()
|
||||||
|
<< " s\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto observer = solver::nonlinear::makeObserver(
|
||||||
|
[](const solver::nonlinear::BeforeIteration &event) {
|
||||||
|
int rank = 0;
|
||||||
|
MPI_Comm_rank(event.communicator, &rank);
|
||||||
|
if (rank == 0) {
|
||||||
|
std::cout << "Newton " << event.iteration << ": |F| = " << event.residualNorm << '\n';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[](const solver::nonlinear::AfterLineSearchTrial &event) {
|
||||||
|
int rank = 0;
|
||||||
|
MPI_Comm_rank(event.communicator, &rank);
|
||||||
|
if (rank != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << " trial " << event.trial + 1 << ": step = " << event.stepLength
|
||||||
|
<< ", outcome = " << trialDispositionName(event.disposition);
|
||||||
|
if (!event.rejectionSource.empty()) {
|
||||||
|
std::cout << ", source = " << event.rejectionSource;
|
||||||
|
}
|
||||||
|
if (event.metric.has_value()) {
|
||||||
|
std::cout << ", |F| = " << event.metric->residualNorm;
|
||||||
|
}
|
||||||
|
if (event.minimumJacobianDeterminant.has_value()) {
|
||||||
|
std::cout << ", min(det J_map) = " << *event.minimumJacobianDeterminant;
|
||||||
|
}
|
||||||
|
std::cout << ", prepare = " << event.preparationSeconds << " s"
|
||||||
|
<< ", metric = " << event.metricSeconds << " s\n";
|
||||||
|
},
|
||||||
|
[](const solver::nonlinear::AfterIteration &event) {
|
||||||
|
int rank = 0;
|
||||||
|
MPI_Comm_rank(event.communicator, &rank);
|
||||||
|
if (rank == 0) {
|
||||||
|
std::cout << " step = " << event.acceptedStepLength
|
||||||
|
<< ", trials = " << event.lineSearchTrials
|
||||||
|
<< ", |F| = " << event.residualNorm
|
||||||
|
<< ", iteration = " << event.iterationSeconds << " s"
|
||||||
|
<< ", line search = " << event.lineSearchSeconds << " s"
|
||||||
|
<< ", trial preparation = " << event.trialPreparationSeconds << " s"
|
||||||
|
<< ", accepted refresh = " << event.preconditionerRefreshSeconds << " s\n";
|
||||||
|
if (event.rollbackSeconds > 0.0) {
|
||||||
|
std::cout << " rollback = " << event.rollbackSeconds << " s\n";
|
||||||
|
}
|
||||||
|
if (event.linearSolve.has_value()) {
|
||||||
|
const auto &linear = *event.linearSolve;
|
||||||
|
std::cout << " FGMRES: " << linearStatusName(linear.status)
|
||||||
|
<< ", iterations = " << linear.iterations
|
||||||
|
<< ", restarts = " << linear.restarts
|
||||||
|
<< ", initial/|b| = "
|
||||||
|
<< (linear.rightHandSideNorm > 0.0
|
||||||
|
? linear.initialResidualNorm / linear.rightHandSideNorm
|
||||||
|
: 0.0)
|
||||||
|
<< ", true/|b| = " << linear.relativeTrueResidualNorm
|
||||||
|
<< ", J calls = " << linear.operatorApplications
|
||||||
|
<< ", P^-1 calls = " << linear.inversePreconditionerApplications
|
||||||
|
<< ", solve = " << linear.solveSeconds << " s"
|
||||||
|
<< ", J time = " << linear.operatorSeconds << " s"
|
||||||
|
<< ", P^-1 time = " << linear.inversePreconditionerSeconds << " s\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
auto nonlinearSolver = solver::nonlinear::Newton(solver::nonlinear::NewtonOptions{
|
||||||
|
.relativeTolerance = 1.0e-8,
|
||||||
|
.absoluteTolerance = 0.0,
|
||||||
|
.maximumIterations = 30,
|
||||||
|
.linearSolve = {
|
||||||
|
.relativeTolerance = 3.0e-2,
|
||||||
|
.absoluteTolerance = 0.0,
|
||||||
|
.maximumIterations = 200
|
||||||
|
},
|
||||||
|
.backtracking = {}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto equilibriumSolver = solver::make(context, nonlinearSolver, observer);
|
||||||
|
const auto evaluateStart = std::chrono::steady_clock::now();
|
||||||
|
auto report = equilibriumSolver.evaluate();
|
||||||
|
if (rank == 0) {
|
||||||
|
std::cout << "Evaluation: "
|
||||||
|
<< std::chrono::duration<double>(std::chrono::steady_clock::now() - evaluateStart).count()
|
||||||
|
<< " s\n";
|
||||||
|
const auto &diagnostics = report.diagnostics();
|
||||||
|
std::cout << "Totals: linear = " << diagnostics.totalLinearSolveSeconds
|
||||||
|
<< " s, line search = " << diagnostics.totalLineSearchSeconds
|
||||||
|
<< " s, trial preparation = " << diagnostics.totalTrialPreparationSeconds
|
||||||
|
<< " s, accepted refresh = " << diagnostics.totalPreconditionerRefreshSeconds
|
||||||
|
<< " s, rollback = " << diagnostics.totalRollbackSeconds
|
||||||
|
<< " s, inadmissible trials = " << diagnostics.inadmissibleLineSearchTrials
|
||||||
|
<< ", non-finite trials = " << diagnostics.nonFiniteLineSearchTrials
|
||||||
|
<< ", insufficient-decrease trials = " << diagnostics.insufficientDecreaseTrials << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.converged()) {
|
||||||
|
auto structureView = report.structureView();
|
||||||
|
std::cout << "Converged with " << structureView.state().size() << " state values.\n";
|
||||||
|
|
||||||
|
// These are the eventual persistence APIs. Both deliberately
|
||||||
|
// throw "not implemented" until the checkpoint schema is chosen:
|
||||||
|
// auto structure = structureView.capture();
|
||||||
|
// equilibrium::serialize(structureView, "structure.checkpoint");
|
||||||
|
} else {
|
||||||
|
auto checkpointView = report.lastAcceptedCheckpointView();
|
||||||
|
std::cerr << "Solve stopped after " << report.completedNonlinearIterations()
|
||||||
|
<< " accepted steps: " << report.failure().message << '\n'
|
||||||
|
<< "The last checkpoint has " << checkpointView.state().size() << " state values.\n";
|
||||||
|
// auto checkpoint = checkpointView.capture();
|
||||||
|
// equilibrium::serialize(checkpointView, "failed-step.checkpoint");
|
||||||
|
exitCode = 1;
|
||||||
|
}
|
||||||
|
} catch (const std::exception &error) {
|
||||||
|
std::cerr << "sandbox failure: " << error.what() << '\n';
|
||||||
|
exitCode = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
mfem::Mpi::Finalize();
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -141,55 +141,50 @@ TEST_CASE(
|
|||||||
tags::model_specification_type_contract
|
tags::model_specification_type_contract
|
||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
using Request = models::FixedAngularMomentumLayoutRequest;
|
using Request = models::FixedAngularMomentumLayoutRequest;
|
||||||
using Form = operators::CompiledStellarEquilibriumForm<AngularMomentumPolytropicMassModel>;
|
using Form = operators::CompiledStellarEquilibriumForm<AngularMomentumPolytropicMassModel>;
|
||||||
using Jacobian = operators::CompiledStellarEquilibriumJacobianForm<AngularMomentumPolytropicMassModel>;
|
using Jacobian = operators::CompiledStellarEquilibriumJacobianForm<AngularMomentumPolytropicMassModel>;
|
||||||
using AngularValue = utils::blocks::fixed_angular_momentum::angular_velocity::value;
|
using AngularValue = utils::blocks::fixed_angular_momentum::angular_velocity::value;
|
||||||
using AngularResidual = utils::blocks::fixed_angular_momentum::angular_velocity::residual;
|
using AngularResidual = utils::blocks::fixed_angular_momentum::angular_velocity::residual;
|
||||||
|
|
||||||
STATIC_CHECK(models::ConstraintLayoutRequestType<Request>);
|
STATIC_CHECK(models::ConstraintLayoutRequestType<Request>);
|
||||||
STATIC_CHECK(models::CompiledConstraint<models::CompiledFixedAngularMomentum>);
|
STATIC_CHECK(models::CompiledConstraint<models::CompiledFixedAngularMomentum>);
|
||||||
STATIC_CHECK(std::same_as<
|
STATIC_CHECK(
|
||||||
typename Request::GeneratedValueType,
|
std::same_as<typename Request::GeneratedValueType, models::PhysicalCoordinateFor<models::FixedAngularMomentum>>
|
||||||
models::PhysicalCoordinateFor<models::FixedAngularMomentum>>);
|
);
|
||||||
STATIC_CHECK(std::same_as<
|
STATIC_CHECK(
|
||||||
typename AngularValue::GeneratedType,
|
std::same_as<typename AngularValue::GeneratedType, models::PhysicalCoordinateFor<models::FixedAngularMomentum>>
|
||||||
models::PhysicalCoordinateFor<models::FixedAngularMomentum>>);
|
);
|
||||||
STATIC_CHECK(std::same_as<
|
STATIC_CHECK(
|
||||||
typename models::CompiledFixedAngularMomentum::AngularVelocityField,
|
std::same_as<typename models::CompiledFixedAngularMomentum::AngularVelocityField, field::AngularVelocity>
|
||||||
field::AngularVelocity>);
|
);
|
||||||
STATIC_CHECK(Form::value_block_count == 7);
|
STATIC_CHECK(Form::value_block_count == 7);
|
||||||
STATIC_CHECK(Form::residual_block_count == 7);
|
STATIC_CHECK(Form::residual_block_count == 7);
|
||||||
STATIC_CHECK(Request::valueBlock<Form>().index == 6);
|
STATIC_CHECK(Request::valueBlock<Form>().index == 6);
|
||||||
STATIC_CHECK(Request::residualBlock<Form>().index == 6);
|
STATIC_CHECK(Request::residualBlock<Form>().index == 6);
|
||||||
STATIC_CHECK(utils::blocks::valid_jacobian_form<Form, Jacobian>);
|
STATIC_CHECK(utils::blocks::valid_jacobian_form<Form, Jacobian>);
|
||||||
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
|
STATIC_CHECK(
|
||||||
AngularResidual,
|
utils::blocks::has_jacobian_coupling_v<AngularResidual, utils::blocks::density::mass::value, Jacobian>
|
||||||
utils::blocks::density::mass::value,
|
);
|
||||||
Jacobian>);
|
STATIC_CHECK(
|
||||||
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
|
utils::blocks::has_jacobian_coupling_v<
|
||||||
AngularResidual,
|
AngularResidual, utils::blocks::surface_deformation::parameters::value, Jacobian>
|
||||||
utils::blocks::surface_deformation::parameters::value,
|
);
|
||||||
Jacobian>);
|
|
||||||
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<AngularResidual, AngularValue, Jacobian>);
|
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<AngularResidual, AngularValue, Jacobian>);
|
||||||
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
|
STATIC_CHECK(
|
||||||
utils::blocks::surface_deformation::shape_equilibrium::residual,
|
utils::blocks::has_jacobian_coupling_v<
|
||||||
AngularValue,
|
utils::blocks::surface_deformation::shape_equilibrium::residual, AngularValue, Jacobian>
|
||||||
Jacobian>);
|
);
|
||||||
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
|
STATIC_CHECK(
|
||||||
utils::blocks::enthalpy::specific::residual,
|
utils::blocks::has_jacobian_coupling_v<utils::blocks::enthalpy::specific::residual, AngularValue, Jacobian>
|
||||||
AngularValue,
|
);
|
||||||
Jacobian>);
|
STATIC_CHECK_FALSE(
|
||||||
STATIC_CHECK_FALSE(utils::blocks::has_jacobian_coupling_v<
|
utils::blocks::has_jacobian_coupling_v<utils::blocks::gravity::poisson::residual, AngularValue, Jacobian>
|
||||||
utils::blocks::gravity::poisson::residual,
|
);
|
||||||
AngularValue,
|
|
||||||
Jacobian>);
|
|
||||||
|
|
||||||
const integral::FixedAngularMomentum specification({
|
const integral::FixedAngularMomentum specification(
|
||||||
.Jtotal = dimensions::AngularMomentumValue{2.75},
|
{.Jtotal = dimensions::AngularMomentumValue{2.75}, .axis = {0.0, 3.0, 4.0}, .center = {0.25, -0.5, 0.75}}
|
||||||
.axis = {0.0, 3.0, 4.0},
|
);
|
||||||
.center = {0.25, -0.5, 0.75}
|
|
||||||
});
|
|
||||||
const models::CompiledFixedAngularMomentum compiled = models::compileConstraint(specification);
|
const models::CompiledFixedAngularMomentum compiled = models::compileConstraint(specification);
|
||||||
CHECK(compiled.targetAngularMomentum() == dimensions::AngularMomentumValue{2.75});
|
CHECK(compiled.targetAngularMomentum() == dimensions::AngularMomentumValue{2.75});
|
||||||
CHECK(std::abs(compiled.specification().axis()[0]) < 1.0e-15);
|
CHECK(std::abs(compiled.specification().axis()[0]) < 1.0e-15);
|
||||||
@@ -281,9 +276,7 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
const mean_field::models::FixedTotalMass mass{mean_field::dimensions::MassValue{1.25}};
|
const mean_field::models::FixedTotalMass mass{mean_field::dimensions::MassValue{1.25}};
|
||||||
const mean_field::models::FixedCentralDensity centralDensity{mean_field::eos::DensityValue{2.5}};
|
const mean_field::models::FixedCentralDensity centralDensity{mean_field::eos::DensityValue{2.5}};
|
||||||
const mean_field::models::FixedAngularMomentum angularMomentum{
|
const mean_field::models::FixedAngularMomentum angularMomentum{mean_field::dimensions::AngularMomentumValue{0.75}};
|
||||||
mean_field::dimensions::AngularMomentumValue{0.75}
|
|
||||||
};
|
|
||||||
|
|
||||||
CHECK(mass.targetMass() == mean_field::dimensions::MassValue{1.25});
|
CHECK(mass.targetMass() == mean_field::dimensions::MassValue{1.25});
|
||||||
CHECK(centralDensity.targetDensity() == mean_field::eos::DensityValue{2.5});
|
CHECK(centralDensity.targetDensity() == mean_field::eos::DensityValue{2.5});
|
||||||
@@ -308,18 +301,17 @@ TEST_CASE(
|
|||||||
std::invalid_argument
|
std::invalid_argument
|
||||||
);
|
);
|
||||||
CHECK_THROWS_AS(
|
CHECK_THROWS_AS(
|
||||||
mean_field::models::FixedAngularMomentum({
|
mean_field::models::FixedAngularMomentum(
|
||||||
.Jtotal = mean_field::dimensions::AngularMomentumValue{1.0},
|
{.Jtotal = mean_field::dimensions::AngularMomentumValue{1.0}, .axis = {0.0, 0.0, 0.0}}
|
||||||
.axis = {0.0, 0.0, 0.0}
|
),
|
||||||
}),
|
|
||||||
std::invalid_argument
|
std::invalid_argument
|
||||||
);
|
);
|
||||||
CHECK_THROWS_AS(
|
CHECK_THROWS_AS(
|
||||||
mean_field::models::FixedAngularMomentum({
|
mean_field::models::FixedAngularMomentum(
|
||||||
.Jtotal = mean_field::dimensions::AngularMomentumValue{1.0},
|
{.Jtotal = mean_field::dimensions::AngularMomentumValue{1.0},
|
||||||
.axis = {0.0, 0.0, 1.0},
|
.axis = {0.0, 0.0, 1.0},
|
||||||
.center = {0.0, std::numeric_limits<double>::quiet_NaN(), 0.0}
|
.center = {0.0, std::numeric_limits<double>::quiet_NaN(), 0.0}}
|
||||||
}),
|
),
|
||||||
std::invalid_argument
|
std::invalid_argument
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -27,16 +27,15 @@ namespace {
|
|||||||
int marker;
|
int marker;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ModelDefinition =
|
using ModelDefinition = mean_field::eos::ConstitutiveLaw<MoveOnlyEquationOfState, "MoveOnlyEquationOfState">;
|
||||||
mean_field::eos::ConstitutiveLaw<MoveOnlyEquationOfState, "MoveOnlyEquationOfState">;
|
|
||||||
|
|
||||||
explicit MoveOnlyEquationOfState(const Parameters parameters)
|
explicit MoveOnlyEquationOfState(const Parameters parameters)
|
||||||
: m_marker(std::make_unique<int>(parameters.marker)) {
|
: m_marker(std::make_unique<int>(parameters.marker)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveOnlyEquationOfState(const MoveOnlyEquationOfState &) = delete;
|
MoveOnlyEquationOfState(const MoveOnlyEquationOfState &) = delete;
|
||||||
MoveOnlyEquationOfState &operator=(const MoveOnlyEquationOfState &) = delete;
|
MoveOnlyEquationOfState &operator=(const MoveOnlyEquationOfState &) = delete;
|
||||||
MoveOnlyEquationOfState(MoveOnlyEquationOfState &&) noexcept = default;
|
MoveOnlyEquationOfState(MoveOnlyEquationOfState &&) noexcept = default;
|
||||||
MoveOnlyEquationOfState &operator=(MoveOnlyEquationOfState &&) noexcept = default;
|
MoveOnlyEquationOfState &operator=(MoveOnlyEquationOfState &&) noexcept = default;
|
||||||
|
|
||||||
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
|
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
|
||||||
@@ -60,9 +59,9 @@ namespace {
|
|||||||
: m_marker(std::make_unique<int>(parameters.marker)) {
|
: m_marker(std::make_unique<int>(parameters.marker)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveOnlySurfaceCondition(const MoveOnlySurfaceCondition &) = delete;
|
MoveOnlySurfaceCondition(const MoveOnlySurfaceCondition &) = delete;
|
||||||
MoveOnlySurfaceCondition &operator=(const MoveOnlySurfaceCondition &) = delete;
|
MoveOnlySurfaceCondition &operator=(const MoveOnlySurfaceCondition &) = delete;
|
||||||
MoveOnlySurfaceCondition(MoveOnlySurfaceCondition &&) noexcept = default;
|
MoveOnlySurfaceCondition(MoveOnlySurfaceCondition &&) noexcept = default;
|
||||||
MoveOnlySurfaceCondition &operator=(MoveOnlySurfaceCondition &&) noexcept = default;
|
MoveOnlySurfaceCondition &operator=(MoveOnlySurfaceCondition &&) noexcept = default;
|
||||||
|
|
||||||
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
|
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
|
||||||
@@ -87,7 +86,7 @@ namespace {
|
|||||||
"C_move",
|
"C_move",
|
||||||
"move_only_integral.residual",
|
"move_only_integral.residual",
|
||||||
"R_move">;
|
"R_move">;
|
||||||
using TargetValue = typename ScalarDescription::TargetValue;
|
using TargetValue = typename ScalarDescription::TargetValue;
|
||||||
using ModelDefinition = mean_field::integral::FixedScalarWithMultiplier<
|
using ModelDefinition = mean_field::integral::FixedScalarWithMultiplier<
|
||||||
MoveOnlyIntegralConstraint,
|
MoveOnlyIntegralConstraint,
|
||||||
"MoveOnlyIntegralConstraint",
|
"MoveOnlyIntegralConstraint",
|
||||||
@@ -99,9 +98,9 @@ namespace {
|
|||||||
: m_marker(std::make_unique<int>(parameters.marker)) {
|
: m_marker(std::make_unique<int>(parameters.marker)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveOnlyIntegralConstraint(const MoveOnlyIntegralConstraint &) = delete;
|
MoveOnlyIntegralConstraint(const MoveOnlyIntegralConstraint &) = delete;
|
||||||
MoveOnlyIntegralConstraint &operator=(const MoveOnlyIntegralConstraint &) = delete;
|
MoveOnlyIntegralConstraint &operator=(const MoveOnlyIntegralConstraint &) = delete;
|
||||||
MoveOnlyIntegralConstraint(MoveOnlyIntegralConstraint &&) noexcept = default;
|
MoveOnlyIntegralConstraint(MoveOnlyIntegralConstraint &&) noexcept = default;
|
||||||
MoveOnlyIntegralConstraint &operator=(MoveOnlyIntegralConstraint &&) noexcept = default;
|
MoveOnlyIntegralConstraint &operator=(MoveOnlyIntegralConstraint &&) noexcept = default;
|
||||||
|
|
||||||
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
|
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
|
||||||
@@ -167,20 +166,15 @@ TEST_CASE(
|
|||||||
tags::stellar_model_specification_api
|
tags::stellar_model_specification_api
|
||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
using Qualified = models::SpecificationSet<
|
using Qualified =
|
||||||
const eos::Polytrope &,
|
models::SpecificationSet<const eos::Polytrope &, volatile surface::Isobaric &&, const integral::FixedTotalMass>;
|
||||||
volatile surface::Isobaric &&,
|
using Plain = models::SpecificationSet<eos::Polytrope, surface::Isobaric, integral::FixedTotalMass>;
|
||||||
const integral::FixedTotalMass>;
|
|
||||||
using Plain = models::SpecificationSet<
|
|
||||||
eos::Polytrope,
|
|
||||||
surface::Isobaric,
|
|
||||||
integral::FixedTotalMass>;
|
|
||||||
|
|
||||||
STATIC_CHECK(std::same_as<Qualified, Plain>);
|
STATIC_CHECK(std::same_as<Qualified, Plain>);
|
||||||
STATIC_CHECK(models::ValidModelSpecificationPack<
|
STATIC_CHECK(
|
||||||
const eos::Polytrope &,
|
models::ValidModelSpecificationPack<
|
||||||
volatile surface::Isobaric &&,
|
const eos::Polytrope &, volatile surface::Isobaric &&, const integral::FixedTotalMass>
|
||||||
const integral::FixedTotalMass>);
|
);
|
||||||
STATIC_CHECK(model::StellarModelType<model::StellarModel<Qualified>>);
|
STATIC_CHECK(model::StellarModelType<model::StellarModel<Qualified>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,23 +220,20 @@ TEST_CASE(
|
|||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
auto stellarModel = model::StellarModel(
|
auto stellarModel = model::StellarModel(
|
||||||
MoveOnlyIntegralConstraint({.marker = 307}),
|
MoveOnlyIntegralConstraint({.marker = 307}), MoveOnlySurfaceCondition({.marker = 211}),
|
||||||
MoveOnlySurfaceCondition({.marker = 211}),
|
|
||||||
MoveOnlyEquationOfState({.marker = 101})
|
MoveOnlyEquationOfState({.marker = 101})
|
||||||
);
|
);
|
||||||
using Model = std::remove_cvref_t<decltype(stellarModel)>;
|
using Model = std::remove_cvref_t<decltype(stellarModel)>;
|
||||||
using Expected = model::StellarModel<models::SpecificationSet<
|
using Expected = model::StellarModel<
|
||||||
MoveOnlyEquationOfState,
|
models::SpecificationSet<MoveOnlyEquationOfState, MoveOnlySurfaceCondition, MoveOnlyIntegralConstraint>>;
|
||||||
MoveOnlySurfaceCondition,
|
|
||||||
MoveOnlyIntegralConstraint>>;
|
|
||||||
|
|
||||||
STATIC_CHECK(std::same_as<Model, Expected>);
|
STATIC_CHECK(std::same_as<Model, Expected>);
|
||||||
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlyEquationOfState>);
|
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlyEquationOfState>);
|
||||||
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlySurfaceCondition>);
|
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlySurfaceCondition>);
|
||||||
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlyIntegralConstraint>);
|
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlyIntegralConstraint>);
|
||||||
|
|
||||||
const auto &equationOfState = stellarModel.specification<MoveOnlyEquationOfState>();
|
const auto &equationOfState = stellarModel.specification<MoveOnlyEquationOfState>();
|
||||||
const auto &surfaceCondition = stellarModel.specification<MoveOnlySurfaceCondition>();
|
const auto &surfaceCondition = stellarModel.specification<MoveOnlySurfaceCondition>();
|
||||||
const auto &integralConstraint = stellarModel.specification<MoveOnlyIntegralConstraint>();
|
const auto &integralConstraint = stellarModel.specification<MoveOnlyIntegralConstraint>();
|
||||||
|
|
||||||
REQUIRE(equationOfState.marker() != nullptr);
|
REQUIRE(equationOfState.marker() != nullptr);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -15,34 +15,28 @@ import mean_field;
|
|||||||
import test_helpers;
|
import test_helpers;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
namespace blocks = mean_field::utils::blocks;
|
namespace blocks = mean_field::utils::blocks;
|
||||||
namespace normalization = mean_field::normalization;
|
namespace normalization = mean_field::normalization;
|
||||||
namespace models = mean_field::models;
|
namespace models = mean_field::models;
|
||||||
|
|
||||||
struct ModelWithoutFixedTotalMass final { };
|
struct ModelWithoutFixedTotalMass final { };
|
||||||
|
|
||||||
template <typename Model>
|
template <typename Model>
|
||||||
concept SupportsModelDerivedStellarScales = requires(
|
concept SupportsModelDerivedStellarScales =
|
||||||
const normalization::PhysicalRieszDiagonal<> &policy,
|
requires(const normalization::PhysicalRieszDiagonal<> &policy, const Model &model) {
|
||||||
const Model &model
|
{
|
||||||
) {
|
normalization::deriveStellarCharacteristicScales(policy, model)
|
||||||
{
|
} -> std::same_as<normalization::StellarCharacteristicScales>;
|
||||||
normalization::deriveStellarCharacteristicScales(policy, model)
|
};
|
||||||
} -> std::same_as<normalization::StellarCharacteristicScales>;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TestValue final : blocks::value_block_base { };
|
struct TestValue final : blocks::value_block_base { };
|
||||||
struct TestResidual final : blocks::residual_block_base { };
|
struct TestResidual final : blocks::residual_block_base { };
|
||||||
using TestForm = blocks::block_form<
|
using TestForm = blocks::block_form<blocks::type_list<TestValue>, blocks::type_list<TestResidual>>;
|
||||||
blocks::type_list<TestValue>,
|
|
||||||
blocks::type_list<TestResidual>>;
|
|
||||||
|
|
||||||
using GlobalSpecificEnergyNormalization = models::CoordinateNormalization<
|
using GlobalSpecificEnergyNormalization = models::
|
||||||
models::RieszTopology::global_scalar,
|
CoordinateNormalization<models::RieszTopology::global_scalar, models::PhysicalScaleLaw::specific_energy>;
|
||||||
models::PhysicalScaleLaw::specific_energy>;
|
using VolumeSpecificEnergyNormalization = models::
|
||||||
using VolumeSpecificEnergyNormalization = models::CoordinateNormalization<
|
CoordinateNormalization<models::RieszTopology::scalar_volume_l2, models::PhysicalScaleLaw::specific_energy>;
|
||||||
models::RieszTopology::scalar_volume_l2,
|
|
||||||
models::PhysicalScaleLaw::specific_energy>;
|
|
||||||
|
|
||||||
class SelfDescribingMagneticSpecificEnergy final {
|
class SelfDescribingMagneticSpecificEnergy final {
|
||||||
public:
|
public:
|
||||||
@@ -98,9 +92,7 @@ namespace {
|
|||||||
"NormalizationMockVolumeCoordinate",
|
"NormalizationMockVolumeCoordinate",
|
||||||
models::DependsOn<blocks::density::mass::value>,
|
models::DependsOn<blocks::density::mass::value>,
|
||||||
models::Affects<blocks::enthalpy::specific::residual>,
|
models::Affects<blocks::enthalpy::specific::residual>,
|
||||||
models::GeneratedNormalization<
|
models::GeneratedNormalization<VolumeSpecificEnergyNormalization, GlobalSpecificEnergyNormalization>>;
|
||||||
VolumeSpecificEnergyNormalization,
|
|
||||||
GlobalSpecificEnergyNormalization>>;
|
|
||||||
|
|
||||||
explicit constexpr GeneratedVolumeCoordinateWithoutMetricSource(const Parameters parameters) noexcept
|
explicit constexpr GeneratedVolumeCoordinateWithoutMetricSource(const Parameters parameters) noexcept
|
||||||
: m_target(parameters.target) {
|
: m_target(parameters.target) {
|
||||||
@@ -134,39 +126,36 @@ namespace {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename Specification>
|
template <typename Specification>
|
||||||
using GeneratedValueBlock = blocks::generated_value_block<
|
using GeneratedValueBlock = blocks::generated_value_block<models::PhysicalCoordinateFor<Specification>>;
|
||||||
models::PhysicalCoordinateFor<Specification>>;
|
|
||||||
|
|
||||||
template <typename Specification>
|
template <typename Specification>
|
||||||
using GeneratedMultiplierBlock = blocks::generated_value_block<
|
using GeneratedMultiplierBlock = blocks::generated_value_block<models::MultiplierFor<Specification>>;
|
||||||
models::MultiplierFor<Specification>>;
|
|
||||||
|
|
||||||
template <typename Specification>
|
template <typename Specification>
|
||||||
using GeneratedResidualBlock = blocks::generated_residual_block<
|
using GeneratedResidualBlock = blocks::generated_residual_block<models::ResidualFor<Specification>>;
|
||||||
models::ResidualFor<Specification>>;
|
|
||||||
|
|
||||||
using SelfDescribingValue = GeneratedValueBlock<SelfDescribingMagneticSpecificEnergy>;
|
using SelfDescribingValue = GeneratedValueBlock<SelfDescribingMagneticSpecificEnergy>;
|
||||||
using SelfDescribingResidual = GeneratedResidualBlock<SelfDescribingMagneticSpecificEnergy>;
|
using SelfDescribingResidual = GeneratedResidualBlock<SelfDescribingMagneticSpecificEnergy>;
|
||||||
using SelfDescribingForm = blocks::block_form<
|
using SelfDescribingForm =
|
||||||
blocks::type_list<SelfDescribingValue>,
|
blocks::block_form<blocks::type_list<SelfDescribingValue>, blocks::type_list<SelfDescribingResidual>>;
|
||||||
blocks::type_list<SelfDescribingResidual>>;
|
|
||||||
|
|
||||||
using MissingValue = GeneratedMultiplierBlock<MissingGeneratedNormalization>;
|
using MissingValue = GeneratedMultiplierBlock<MissingGeneratedNormalization>;
|
||||||
using MissingResidual = GeneratedResidualBlock<MissingGeneratedNormalization>;
|
using MissingResidual = GeneratedResidualBlock<MissingGeneratedNormalization>;
|
||||||
using MissingNormalizationForm = blocks::block_form<
|
using MissingNormalizationForm =
|
||||||
blocks::type_list<MissingValue>,
|
blocks::block_form<blocks::type_list<MissingValue>, blocks::type_list<MissingResidual>>;
|
||||||
blocks::type_list<MissingResidual>>;
|
|
||||||
|
|
||||||
using UnpreparedVolumeValue = GeneratedValueBlock<GeneratedVolumeCoordinateWithoutMetricSource>;
|
using UnpreparedVolumeValue = GeneratedValueBlock<GeneratedVolumeCoordinateWithoutMetricSource>;
|
||||||
using UnpreparedVolumeResidual = GeneratedResidualBlock<GeneratedVolumeCoordinateWithoutMetricSource>;
|
using UnpreparedVolumeResidual = GeneratedResidualBlock<GeneratedVolumeCoordinateWithoutMetricSource>;
|
||||||
using UnpreparedVolumeForm = blocks::block_form<
|
using UnpreparedVolumeForm =
|
||||||
blocks::type_list<UnpreparedVolumeValue>,
|
blocks::block_form<blocks::type_list<UnpreparedVolumeValue>, blocks::type_list<UnpreparedVolumeResidual>>;
|
||||||
blocks::type_list<UnpreparedVolumeResidual>>;
|
|
||||||
|
|
||||||
class DenseOperator final : public mfem::Operator {
|
class DenseOperator final : public mfem::Operator {
|
||||||
public:
|
public:
|
||||||
explicit DenseOperator(const mfem::DenseMatrix &matrix)
|
explicit DenseOperator(const mfem::DenseMatrix &matrix)
|
||||||
: mfem::Operator(matrix.Height(), matrix.Width()),
|
: mfem::Operator(
|
||||||
|
matrix.Height(),
|
||||||
|
matrix.Width()
|
||||||
|
),
|
||||||
m_matrix(matrix) {
|
m_matrix(matrix) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +173,10 @@ namespace {
|
|||||||
class DenseInverseSolver final : public mfem::Solver {
|
class DenseInverseSolver final : public mfem::Solver {
|
||||||
public:
|
public:
|
||||||
explicit DenseInverseSolver(const mfem::DenseMatrix &inverse)
|
explicit DenseInverseSolver(const mfem::DenseMatrix &inverse)
|
||||||
: mfem::Solver(inverse.Height(), inverse.Width()),
|
: mfem::Solver(
|
||||||
|
inverse.Height(),
|
||||||
|
inverse.Width()
|
||||||
|
),
|
||||||
m_inverse(inverse) {
|
m_inverse(inverse) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,75 +239,54 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
using Map = normalization::DiagonalNormalization;
|
using Map = normalization::DiagonalNormalization;
|
||||||
|
|
||||||
STATIC_CHECK(std::constructible_from<
|
STATIC_CHECK(std::constructible_from<normalization::ScaledJacobianOperator, const DenseOperator &, const Map &>);
|
||||||
normalization::ScaledJacobianOperator,
|
STATIC_CHECK_FALSE(std::constructible_from<normalization::ScaledJacobianOperator, DenseOperator &&, const Map &>);
|
||||||
const DenseOperator &,
|
STATIC_CHECK_FALSE(
|
||||||
const Map &>);
|
std::constructible_from<normalization::ScaledJacobianOperator, const DenseOperator &&, const Map &>
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
);
|
||||||
normalization::ScaledJacobianOperator,
|
STATIC_CHECK_FALSE(std::constructible_from<normalization::ScaledJacobianOperator, const DenseOperator &, Map &&>);
|
||||||
DenseOperator &&,
|
|
||||||
const Map &>);
|
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
|
||||||
normalization::ScaledJacobianOperator,
|
|
||||||
const DenseOperator &&,
|
|
||||||
const Map &>);
|
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
|
||||||
normalization::ScaledJacobianOperator,
|
|
||||||
const DenseOperator &,
|
|
||||||
Map &&>);
|
|
||||||
|
|
||||||
STATIC_CHECK(std::constructible_from<
|
STATIC_CHECK(std::constructible_from<normalization::ScaledInverseOperator, const DenseOperator &, const Map &>);
|
||||||
normalization::ScaledInverseOperator,
|
STATIC_CHECK_FALSE(std::constructible_from<normalization::ScaledInverseOperator, DenseOperator &&, const Map &>);
|
||||||
const DenseOperator &,
|
STATIC_CHECK_FALSE(std::constructible_from<normalization::ScaledInverseOperator, const DenseOperator &, Map &&>);
|
||||||
const Map &>);
|
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
|
||||||
normalization::ScaledInverseOperator,
|
|
||||||
DenseOperator &&,
|
|
||||||
const Map &>);
|
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
|
||||||
normalization::ScaledInverseOperator,
|
|
||||||
const DenseOperator &,
|
|
||||||
Map &&>);
|
|
||||||
|
|
||||||
STATIC_CHECK(std::constructible_from<
|
STATIC_CHECK(
|
||||||
normalization::ScaledPreconditioner,
|
std::constructible_from<
|
||||||
DenseInverseSolver &,
|
normalization::ScaledPreconditioner, DenseInverseSolver &, const DenseOperator &, const DenseOperator &,
|
||||||
const DenseOperator &,
|
const Map &>
|
||||||
const DenseOperator &,
|
);
|
||||||
const Map &>);
|
STATIC_CHECK_FALSE(
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
std::constructible_from<
|
||||||
normalization::ScaledPreconditioner,
|
normalization::ScaledPreconditioner, DenseInverseSolver &&, const DenseOperator &, const DenseOperator &,
|
||||||
DenseInverseSolver &&,
|
const Map &>
|
||||||
const DenseOperator &,
|
);
|
||||||
const DenseOperator &,
|
STATIC_CHECK_FALSE(
|
||||||
const Map &>);
|
std::constructible_from<
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
normalization::ScaledPreconditioner, DenseInverseSolver &, DenseOperator &&, const DenseOperator &,
|
||||||
normalization::ScaledPreconditioner,
|
const Map &>
|
||||||
DenseInverseSolver &,
|
);
|
||||||
DenseOperator &&,
|
STATIC_CHECK_FALSE(
|
||||||
const DenseOperator &,
|
std::constructible_from<
|
||||||
const Map &>);
|
normalization::ScaledPreconditioner, DenseInverseSolver &, const DenseOperator &, DenseOperator &&,
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
const Map &>
|
||||||
normalization::ScaledPreconditioner,
|
);
|
||||||
DenseInverseSolver &,
|
STATIC_CHECK_FALSE(
|
||||||
const DenseOperator &,
|
std::constructible_from<
|
||||||
DenseOperator &&,
|
normalization::ScaledPreconditioner, DenseInverseSolver &, const DenseOperator &, const DenseOperator &,
|
||||||
const Map &>);
|
Map &&>
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
);
|
||||||
normalization::ScaledPreconditioner,
|
|
||||||
DenseInverseSolver &,
|
|
||||||
const DenseOperator &,
|
|
||||||
const DenseOperator &,
|
|
||||||
Map &&>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Characteristic Stellar Scales Satisfy Gravity Virial And Rotation Identities", "[normalization][physics]") {
|
TEST_CASE(
|
||||||
|
"Characteristic Stellar Scales Satisfy Gravity Virial And Rotation Identities",
|
||||||
|
"[normalization][physics]"
|
||||||
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
constexpr double mass = 7.0;
|
constexpr double mass = 7.0;
|
||||||
constexpr double radius = 3.0;
|
constexpr double radius = 3.0;
|
||||||
constexpr double gravity = 5.0;
|
constexpr double gravity = 5.0;
|
||||||
const auto scales = normalization::deriveStellarCharacteristicScales(
|
const auto scales = normalization::deriveStellarCharacteristicScales(
|
||||||
dimensions::MassValue{mass}, dimensions::LengthValue{radius}, gravity
|
dimensions::MassValue{mass}, dimensions::LengthValue{radius}, gravity
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -332,66 +303,87 @@ TEST_CASE("Characteristic Stellar Scales Satisfy Gravity Virial And Rotation Ide
|
|||||||
CHECK(virial == Catch::Approx(scales.force * radius).epsilon(2.0e-15));
|
CHECK(virial == Catch::Approx(scales.force * radius).epsilon(2.0e-15));
|
||||||
|
|
||||||
// Omega_0 is the Kepler/break-up scale and J_0 = M R^2 Omega_0.
|
// Omega_0 is the Kepler/break-up scale and J_0 = M R^2 Omega_0.
|
||||||
CHECK(scales.angularVelocity * scales.angularVelocity * radius ==
|
CHECK(
|
||||||
Catch::Approx(scales.acceleration).epsilon(2.0e-15));
|
scales.angularVelocity * scales.angularVelocity * radius == Catch::Approx(scales.acceleration).epsilon(2.0e-15)
|
||||||
CHECK(scales.angularMomentum ==
|
);
|
||||||
Catch::Approx(mass * radius * radius * scales.angularVelocity).epsilon(2.0e-15));
|
CHECK(scales.angularMomentum == Catch::Approx(mass * radius * radius * scales.angularVelocity).epsilon(2.0e-15));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Characteristic Scales Obey The Expected Stellar Homology Exponents", "[normalization][physics]") {
|
TEST_CASE(
|
||||||
|
"Characteristic Scales Obey The Expected Stellar Homology Exponents",
|
||||||
|
"[normalization][physics]"
|
||||||
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
const auto reference = normalization::deriveStellarCharacteristicScales(
|
const auto reference =
|
||||||
dimensions::MassValue{2.5}, dimensions::LengthValue{4.0}, 3.0
|
normalization::deriveStellarCharacteristicScales(dimensions::MassValue{2.5}, dimensions::LengthValue{4.0}, 3.0);
|
||||||
);
|
constexpr double massFactor = 11.0;
|
||||||
constexpr double massFactor = 11.0;
|
constexpr double radiusFactor = 0.2;
|
||||||
constexpr double radiusFactor = 0.2;
|
|
||||||
constexpr double gravityFactor = 7.0;
|
constexpr double gravityFactor = 7.0;
|
||||||
const auto transformed = normalization::deriveStellarCharacteristicScales(
|
const auto transformed = normalization::deriveStellarCharacteristicScales(
|
||||||
dimensions::MassValue{2.5 * massFactor},
|
dimensions::MassValue{2.5 * massFactor}, dimensions::LengthValue{4.0 * radiusFactor}, 3.0 * gravityFactor
|
||||||
dimensions::LengthValue{4.0 * radiusFactor},
|
|
||||||
3.0 * gravityFactor
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CHECK(transformed.density / reference.density ==
|
CHECK(
|
||||||
Catch::Approx(massFactor / std::pow(radiusFactor, 3)).epsilon(4.0e-15));
|
transformed.density / reference.density ==
|
||||||
CHECK(transformed.acceleration / reference.acceleration ==
|
Catch::Approx(massFactor / std::pow(radiusFactor, 3)).epsilon(4.0e-15)
|
||||||
Catch::Approx(gravityFactor * massFactor / std::pow(radiusFactor, 2)).epsilon(4.0e-15));
|
);
|
||||||
CHECK(transformed.inverseTimeSquared / reference.inverseTimeSquared ==
|
CHECK(
|
||||||
Catch::Approx(gravityFactor * massFactor / std::pow(radiusFactor, 3)).epsilon(4.0e-15));
|
transformed.acceleration / reference.acceleration ==
|
||||||
CHECK(transformed.specificEnergy / reference.specificEnergy ==
|
Catch::Approx(gravityFactor * massFactor / std::pow(radiusFactor, 2)).epsilon(4.0e-15)
|
||||||
Catch::Approx(gravityFactor * massFactor / radiusFactor).epsilon(4.0e-15));
|
);
|
||||||
CHECK(transformed.pressure / reference.pressure ==
|
CHECK(
|
||||||
Catch::Approx(gravityFactor * massFactor * massFactor / std::pow(radiusFactor, 4)).epsilon(4.0e-15));
|
transformed.inverseTimeSquared / reference.inverseTimeSquared ==
|
||||||
CHECK(transformed.angularVelocity / reference.angularVelocity == Catch::Approx(
|
Catch::Approx(gravityFactor * massFactor / std::pow(radiusFactor, 3)).epsilon(4.0e-15)
|
||||||
std::sqrt(gravityFactor * massFactor / std::pow(radiusFactor, 3))
|
);
|
||||||
).epsilon(4.0e-15));
|
CHECK(
|
||||||
CHECK(transformed.angularMomentum / reference.angularMomentum == Catch::Approx(
|
transformed.specificEnergy / reference.specificEnergy ==
|
||||||
massFactor * std::sqrt(gravityFactor * massFactor * radiusFactor)
|
Catch::Approx(gravityFactor * massFactor / radiusFactor).epsilon(4.0e-15)
|
||||||
).epsilon(4.0e-15));
|
);
|
||||||
|
CHECK(
|
||||||
|
transformed.pressure / reference.pressure ==
|
||||||
|
Catch::Approx(gravityFactor * massFactor * massFactor / std::pow(radiusFactor, 4)).epsilon(4.0e-15)
|
||||||
|
);
|
||||||
|
CHECK(
|
||||||
|
transformed.angularVelocity / reference.angularVelocity ==
|
||||||
|
Catch::Approx(std::sqrt(gravityFactor * massFactor / std::pow(radiusFactor, 3))).epsilon(4.0e-15)
|
||||||
|
);
|
||||||
|
CHECK(
|
||||||
|
transformed.angularMomentum / reference.angularMomentum ==
|
||||||
|
Catch::Approx(massFactor * std::sqrt(gravityFactor * massFactor * radiusFactor)).epsilon(4.0e-15)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Physical Block Scales Distinguish Invariants From Numerical Phase Conditions", "[normalization][physics]") {
|
TEST_CASE(
|
||||||
|
"Physical Block Scales Distinguish Invariants From Numerical Phase Conditions",
|
||||||
|
"[normalization][physics]"
|
||||||
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
const auto scales = normalization::deriveStellarCharacteristicScales(
|
const auto scales =
|
||||||
dimensions::MassValue{9.0}, dimensions::LengthValue{2.0}, 4.0
|
normalization::deriveStellarCharacteristicScales(dimensions::MassValue{9.0}, dimensions::LengthValue{2.0}, 4.0);
|
||||||
);
|
|
||||||
CHECK(normalization::physicalScale<blocks::density::mass::value>(scales) == scales.density);
|
CHECK(normalization::physicalScale<blocks::density::mass::value>(scales) == scales.density);
|
||||||
CHECK(normalization::physicalScale<blocks::gravity::gradient::value>(scales) == scales.acceleration);
|
CHECK(normalization::physicalScale<blocks::gravity::gradient::value>(scales) == scales.acceleration);
|
||||||
CHECK(normalization::physicalScale<blocks::gravity::poisson::residual>(scales) == scales.inverseTimeSquared);
|
CHECK(normalization::physicalScale<blocks::gravity::poisson::residual>(scales) == scales.inverseTimeSquared);
|
||||||
CHECK(normalization::physicalScale<blocks::fixed_total_mass::mass_normalization::residual>(scales) == 9.0);
|
CHECK(normalization::physicalScale<blocks::fixed_total_mass::mass_normalization::residual>(scales) == 9.0);
|
||||||
CHECK(normalization::physicalScale<blocks::fixed_angular_momentum::angular_velocity::value>(scales) ==
|
CHECK(
|
||||||
scales.angularVelocity);
|
normalization::physicalScale<blocks::fixed_angular_momentum::angular_velocity::value>(scales) ==
|
||||||
CHECK(normalization::physicalScale<blocks::fixed_angular_momentum::angular_velocity::residual>(scales) ==
|
scales.angularVelocity
|
||||||
scales.angularMomentum);
|
);
|
||||||
|
CHECK(
|
||||||
|
normalization::physicalScale<blocks::fixed_angular_momentum::angular_velocity::residual>(scales) ==
|
||||||
|
scales.angularMomentum
|
||||||
|
);
|
||||||
|
|
||||||
// The central-density condition is implemented as h(0)-h_target, so its residual scale is energy/mass,
|
// The central-density condition is implemented as h(0)-h_target, so its residual scale is energy/mass,
|
||||||
// despite the physical target being expressed as a density.
|
// despite the physical target being expressed as a density.
|
||||||
CHECK(normalization::physicalScale<blocks::fixed_central_density::central_value::residual>(scales) ==
|
CHECK(
|
||||||
scales.specificEnergy);
|
normalization::physicalScale<blocks::fixed_central_density::central_value::residual>(scales) ==
|
||||||
CHECK(normalization::physicalScale<blocks::fixed_central_density::central_value::residual>(scales) !=
|
scales.specificEnergy
|
||||||
scales.density);
|
);
|
||||||
|
CHECK(
|
||||||
|
normalization::physicalScale<blocks::fixed_central_density::central_value::residual>(scales) != scales.density
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
@@ -399,39 +391,40 @@ TEST_CASE(
|
|||||||
"[normalization][type][extension]"
|
"[normalization][type][extension]"
|
||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
using ValueTraits = normalization::PhysicalRieszBlockTraits<SelfDescribingValue>;
|
using ValueTraits = normalization::PhysicalRieszBlockTraits<SelfDescribingValue>;
|
||||||
using ResidualTraits = normalization::PhysicalRieszBlockTraits<SelfDescribingResidual>;
|
using ResidualTraits = normalization::PhysicalRieszBlockTraits<SelfDescribingResidual>;
|
||||||
|
|
||||||
STATIC_CHECK(models::SelfDescribingModelSpecification<SelfDescribingMagneticSpecificEnergy>);
|
STATIC_CHECK(models::SelfDescribingModelSpecification<SelfDescribingMagneticSpecificEnergy>);
|
||||||
STATIC_CHECK(models::CompleteGeneratedNormalizationFor<SelfDescribingMagneticSpecificEnergy>);
|
STATIC_CHECK(models::CompleteGeneratedNormalizationFor<SelfDescribingMagneticSpecificEnergy>);
|
||||||
STATIC_CHECK(operators::StellarEquilibriumSpecificationCompilable<SelfDescribingMagneticSpecificEnergy>);
|
STATIC_CHECK(operators::StellarEquilibriumSpecificationCompilable<SelfDescribingMagneticSpecificEnergy>);
|
||||||
STATIC_CHECK(normalization::GeneratedValuePhysicalRieszNormalizable<
|
STATIC_CHECK(
|
||||||
models::PhysicalCoordinateFor<SelfDescribingMagneticSpecificEnergy>>);
|
normalization::GeneratedValuePhysicalRieszNormalizable<
|
||||||
STATIC_CHECK(normalization::GeneratedResidualPhysicalRieszNormalizable<
|
models::PhysicalCoordinateFor<SelfDescribingMagneticSpecificEnergy>>
|
||||||
models::ResidualFor<SelfDescribingMagneticSpecificEnergy>>);
|
);
|
||||||
STATIC_CHECK(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
|
STATIC_CHECK(
|
||||||
SelfDescribingMagneticSpecificEnergy>);
|
normalization::GeneratedResidualPhysicalRieszNormalizable<
|
||||||
STATIC_CHECK(normalization::CompilableNormalizationFor<
|
models::ResidualFor<SelfDescribingMagneticSpecificEnergy>>
|
||||||
normalization::PhysicalRieszDiagonal<>,
|
);
|
||||||
SelfDescribingForm>);
|
STATIC_CHECK(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<SelfDescribingMagneticSpecificEnergy>);
|
||||||
STATIC_CHECK(normalization::RegisteredStellarSpecificationNormalization<
|
STATIC_CHECK(normalization::CompilableNormalizationFor<normalization::PhysicalRieszDiagonal<>, SelfDescribingForm>);
|
||||||
SelfDescribingMagneticSpecificEnergy>);
|
STATIC_CHECK(normalization::RegisteredStellarSpecificationNormalization<SelfDescribingMagneticSpecificEnergy>);
|
||||||
STATIC_CHECK(normalization::CompleteStellarSpecificationNormalizationFor<
|
STATIC_CHECK(
|
||||||
SelfDescribingMagneticSpecificEnergy,
|
normalization::CompleteStellarSpecificationNormalizationFor<
|
||||||
SelfDescribingForm>);
|
SelfDescribingMagneticSpecificEnergy, SelfDescribingForm>
|
||||||
|
);
|
||||||
|
|
||||||
STATIC_CHECK(ValueTraits::Method::topology == normalization::RieszTopology::global_scalar);
|
STATIC_CHECK(ValueTraits::Method::topology == normalization::RieszTopology::global_scalar);
|
||||||
STATIC_CHECK(ValueTraits::Method::scale == normalization::PhysicalScaleKind::dimensionless);
|
STATIC_CHECK(ValueTraits::Method::scale == normalization::PhysicalScaleKind::dimensionless);
|
||||||
STATIC_CHECK(ResidualTraits::Method::topology == normalization::RieszTopology::global_scalar);
|
STATIC_CHECK(ResidualTraits::Method::topology == normalization::RieszTopology::global_scalar);
|
||||||
STATIC_CHECK(ResidualTraits::Method::scale == normalization::PhysicalScaleKind::specific_energy);
|
STATIC_CHECK(ResidualTraits::Method::scale == normalization::PhysicalScaleKind::specific_energy);
|
||||||
|
|
||||||
const auto scales = normalization::deriveStellarCharacteristicScales(
|
const auto scales =
|
||||||
dimensions::MassValue{9.0}, dimensions::LengthValue{2.0}, 4.0
|
normalization::deriveStellarCharacteristicScales(dimensions::MassValue{9.0}, dimensions::LengthValue{2.0}, 4.0);
|
||||||
);
|
|
||||||
const blocks::form_layout<SelfDescribingForm> layout({1}, {1});
|
const blocks::form_layout<SelfDescribingForm> layout({1}, {1});
|
||||||
normalization::DiagonalNormalizationBuilder<SelfDescribingForm> builder(layout);
|
normalization::DiagonalNormalizationBuilder<SelfDescribingForm> builder(layout);
|
||||||
normalization::StellarSpecificationNormalizationContribution<
|
normalization::StellarSpecificationNormalizationContribution<SelfDescribingMagneticSpecificEnergy>::Apply(
|
||||||
SelfDescribingMagneticSpecificEnergy>::Apply(builder, scales);
|
builder, scales
|
||||||
|
);
|
||||||
const normalization::DiagonalNormalization map = std::move(builder).Build();
|
const normalization::DiagonalNormalization map = std::move(builder).Build();
|
||||||
|
|
||||||
REQUIRE(map.StateFactors().Size() == 1);
|
REQUIRE(map.StateFactors().Size() == 1);
|
||||||
@@ -452,47 +445,51 @@ TEST_CASE(
|
|||||||
|
|
||||||
STATIC_CHECK(models::ModelSpecification<MissingGeneratedNormalization>);
|
STATIC_CHECK(models::ModelSpecification<MissingGeneratedNormalization>);
|
||||||
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<MissingGeneratedNormalization>);
|
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<MissingGeneratedNormalization>);
|
||||||
STATIC_CHECK_FALSE(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
|
STATIC_CHECK_FALSE(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<MissingGeneratedNormalization>);
|
||||||
MissingGeneratedNormalization>);
|
STATIC_CHECK_FALSE(
|
||||||
STATIC_CHECK_FALSE(normalization::CompilableNormalizationFor<
|
normalization::CompilableNormalizationFor<normalization::PhysicalRieszDiagonal<>, MissingNormalizationForm>
|
||||||
normalization::PhysicalRieszDiagonal<>,
|
);
|
||||||
MissingNormalizationForm>);
|
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<MissingGeneratedNormalization>);
|
||||||
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<
|
STATIC_CHECK_FALSE(
|
||||||
MissingGeneratedNormalization>);
|
normalization::CompleteStellarSpecificationNormalizationFor<
|
||||||
STATIC_CHECK_FALSE(normalization::CompleteStellarSpecificationNormalizationFor<
|
MissingGeneratedNormalization, MissingNormalizationForm>
|
||||||
MissingGeneratedNormalization,
|
);
|
||||||
MissingNormalizationForm>);
|
|
||||||
|
|
||||||
STATIC_CHECK(models::ModelSpecification<MalformedGeneratedNormalizationConstraint>);
|
STATIC_CHECK(models::ModelSpecification<MalformedGeneratedNormalizationConstraint>);
|
||||||
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<
|
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<MalformedGeneratedNormalizationConstraint>);
|
||||||
MalformedGeneratedNormalizationConstraint>);
|
STATIC_CHECK_FALSE(
|
||||||
STATIC_CHECK_FALSE(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
|
normalization::CompleteGeneratedPhysicalRieszNormalizationFor<MalformedGeneratedNormalizationConstraint>
|
||||||
MalformedGeneratedNormalizationConstraint>);
|
);
|
||||||
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<
|
STATIC_CHECK_FALSE(
|
||||||
MalformedGeneratedNormalizationConstraint>);
|
normalization::RegisteredStellarSpecificationNormalization<MalformedGeneratedNormalizationConstraint>
|
||||||
|
);
|
||||||
|
|
||||||
// The declaration itself is a valid Riesz law, but runtime stellar
|
// The declaration itself is a valid Riesz law, but runtime stellar
|
||||||
// preparation has no finite-element Gram source for a generated volume
|
// preparation has no finite-element Gram source for a generated volume
|
||||||
// field. The stronger runtime concept must therefore reject it.
|
// field. The stronger runtime concept must therefore reject it.
|
||||||
STATIC_CHECK(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
|
STATIC_CHECK(
|
||||||
GeneratedVolumeCoordinateWithoutMetricSource>);
|
normalization::CompleteGeneratedPhysicalRieszNormalizationFor<GeneratedVolumeCoordinateWithoutMetricSource>
|
||||||
STATIC_CHECK(normalization::CompilableNormalizationFor<
|
);
|
||||||
normalization::PhysicalRieszDiagonal<>,
|
STATIC_CHECK(
|
||||||
UnpreparedVolumeForm>);
|
normalization::CompilableNormalizationFor<normalization::PhysicalRieszDiagonal<>, UnpreparedVolumeForm>
|
||||||
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<
|
);
|
||||||
GeneratedVolumeCoordinateWithoutMetricSource>);
|
STATIC_CHECK_FALSE(
|
||||||
STATIC_CHECK_FALSE(normalization::CompleteStellarSpecificationNormalizationFor<
|
normalization::RegisteredStellarSpecificationNormalization<GeneratedVolumeCoordinateWithoutMetricSource>
|
||||||
GeneratedVolumeCoordinateWithoutMetricSource,
|
);
|
||||||
UnpreparedVolumeForm>);
|
STATIC_CHECK_FALSE(
|
||||||
|
normalization::CompleteStellarSpecificationNormalizationFor<
|
||||||
|
GeneratedVolumeCoordinateWithoutMetricSource, UnpreparedVolumeForm>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Characteristic Scale Construction Rejects Invalid Or Overflowing References", "[normalization][validation]") {
|
TEST_CASE(
|
||||||
|
"Characteristic Scale Construction Rejects Invalid Or Overflowing References",
|
||||||
|
"[normalization][validation]"
|
||||||
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
CHECK_THROWS_AS(
|
CHECK_THROWS_AS(
|
||||||
normalization::deriveStellarCharacteristicScales(
|
normalization::deriveStellarCharacteristicScales(dimensions::MassValue{0.0}, dimensions::LengthValue{1.0}, 1.0),
|
||||||
dimensions::MassValue{0.0}, dimensions::LengthValue{1.0}, 1.0
|
|
||||||
),
|
|
||||||
std::invalid_argument
|
std::invalid_argument
|
||||||
);
|
);
|
||||||
CHECK_THROWS_AS(
|
CHECK_THROWS_AS(
|
||||||
@@ -502,8 +499,7 @@ TEST_CASE("Characteristic Scale Construction Rejects Invalid Or Overflowing Refe
|
|||||||
std::invalid_argument
|
std::invalid_argument
|
||||||
);
|
);
|
||||||
CHECK_THROWS_AS(
|
CHECK_THROWS_AS(
|
||||||
(normalization::PhysicalRieszDiagonal{dimensions::LengthValue{1.0},
|
(normalization::PhysicalRieszDiagonal{dimensions::LengthValue{1.0}, std::numeric_limits<double>::quiet_NaN()}),
|
||||||
std::numeric_limits<double>::quiet_NaN()}),
|
|
||||||
std::invalid_argument
|
std::invalid_argument
|
||||||
);
|
);
|
||||||
CHECK_THROWS_AS(
|
CHECK_THROWS_AS(
|
||||||
@@ -514,25 +510,26 @@ TEST_CASE("Characteristic Scale Construction Rejects Invalid Or Overflowing Refe
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Diagonal Riesz Maps Reproduce Primal And Dual Norms Across Extreme Metrics", "[normalization][math]") {
|
TEST_CASE(
|
||||||
|
"Diagonal Riesz Maps Reproduce Primal And Dual Norms Across Extreme Metrics",
|
||||||
|
"[normalization][math]"
|
||||||
|
) {
|
||||||
const blocks::form_layout<TestForm> layout({3}, {3});
|
const blocks::form_layout<TestForm> layout({3}, {3});
|
||||||
normalization::DiagonalNormalizationBuilder<TestForm> builder(layout);
|
normalization::DiagonalNormalizationBuilder<TestForm> builder(layout);
|
||||||
const mfem::Vector gram = vector({1.0e-20, 4.0, 9.0e20});
|
const mfem::Vector gram = vector({1.0e-20, 4.0, 9.0e20});
|
||||||
constexpr double stateScale = 10.0;
|
constexpr double stateScale = 10.0;
|
||||||
constexpr double residualScale = 0.25;
|
constexpr double residualScale = 0.25;
|
||||||
builder.SetValueBlock<TestValue>(stateScale, gram);
|
builder.SetValueBlock<TestValue>(stateScale, gram);
|
||||||
builder.SetResidualBlock<TestResidual>(residualScale, gram);
|
builder.SetResidualBlock<TestResidual>(residualScale, gram);
|
||||||
const normalization::DiagonalNormalization map = std::move(builder).Build();
|
const normalization::DiagonalNormalization map = std::move(builder).Build();
|
||||||
|
|
||||||
const mfem::Vector state = vector({3.0e10, -2.0, 4.0e-10});
|
const mfem::Vector state = vector({3.0e10, -2.0, 4.0e-10});
|
||||||
const mfem::Vector residual = vector({2.0e-10, -3.0, 5.0e10});
|
const mfem::Vector residual = vector({2.0e-10, -3.0, 5.0e10});
|
||||||
double expectedPrimalNormSquared = 0.0;
|
double expectedPrimalNormSquared = 0.0;
|
||||||
double expectedDualNormSquared = 0.0;
|
double expectedDualNormSquared = 0.0;
|
||||||
for (int index = 0; index < gram.Size(); ++index) {
|
for (int index = 0; index < gram.Size(); ++index) {
|
||||||
expectedPrimalNormSquared += gram(index) * state(index) * state(index) /
|
expectedPrimalNormSquared += gram(index) * state(index) * state(index) / (stateScale * stateScale);
|
||||||
(stateScale * stateScale);
|
expectedDualNormSquared += residual(index) * residual(index) / (gram(index) * residualScale * residualScale);
|
||||||
expectedDualNormSquared += residual(index) * residual(index) /
|
|
||||||
(gram(index) * residualScale * residualScale);
|
|
||||||
}
|
}
|
||||||
CHECK(map.LocalStateNormSquared(state) == Catch::Approx(expectedPrimalNormSquared).epsilon(3.0e-15));
|
CHECK(map.LocalStateNormSquared(state) == Catch::Approx(expectedPrimalNormSquared).epsilon(3.0e-15));
|
||||||
CHECK(map.LocalResidualNormSquared(residual) == Catch::Approx(expectedDualNormSquared).epsilon(3.0e-15));
|
CHECK(map.LocalResidualNormSquared(residual) == Catch::Approx(expectedDualNormSquared).epsilon(3.0e-15));
|
||||||
@@ -549,10 +546,12 @@ TEST_CASE("Diagonal Riesz Maps Reproduce Primal And Dual Norms Across Extreme Me
|
|||||||
checkVector(recoveredResidual, residual, 3.0e-15);
|
checkVector(recoveredResidual, residual, 3.0e-15);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Hybrid Riesz Rows Replace Missing Volume Metrics With Point Metrics", "[normalization][math]") {
|
TEST_CASE(
|
||||||
|
"Hybrid Riesz Rows Replace Missing Volume Metrics With Point Metrics",
|
||||||
|
"[normalization][math]"
|
||||||
|
) {
|
||||||
using HybridForm = blocks::block_form<
|
using HybridForm = blocks::block_form<
|
||||||
blocks::type_list<blocks::enthalpy::specific::value>,
|
blocks::type_list<blocks::enthalpy::specific::value>, blocks::type_list<blocks::enthalpy::specific::residual>>;
|
||||||
blocks::type_list<blocks::enthalpy::specific::residual>>;
|
|
||||||
const blocks::form_layout<HybridForm> layout({4}, {4});
|
const blocks::form_layout<HybridForm> layout({4}, {4});
|
||||||
normalization::DiagonalNormalizationBuilder<HybridForm> builder(layout);
|
normalization::DiagonalNormalizationBuilder<HybridForm> builder(layout);
|
||||||
builder.SetValueBlock<blocks::enthalpy::specific::value>(2.0, vector({2.0, 3.0, 5.0, 7.0}));
|
builder.SetValueBlock<blocks::enthalpy::specific::value>(2.0, vector({2.0, 3.0, 5.0, 7.0}));
|
||||||
@@ -580,7 +579,10 @@ TEST_CASE("Hybrid Riesz Rows Replace Missing Volume Metrics With Point Metrics",
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Runtime Normalization Assembly Rejects Missing Duplicate And Invalid Data", "[normalization][validation]") {
|
TEST_CASE(
|
||||||
|
"Runtime Normalization Assembly Rejects Missing Duplicate And Invalid Data",
|
||||||
|
"[normalization][validation]"
|
||||||
|
) {
|
||||||
const blocks::form_layout<TestForm> layout({2}, {2});
|
const blocks::form_layout<TestForm> layout({2}, {2});
|
||||||
|
|
||||||
normalization::DiagonalNormalizationBuilder<TestForm> missing(layout);
|
normalization::DiagonalNormalizationBuilder<TestForm> missing(layout);
|
||||||
@@ -603,8 +605,11 @@ TEST_CASE("Runtime Normalization Assembly Rejects Missing Duplicate And Invalid
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Scaled Jacobian And Inverse Implement The Exact Coordinate Change", "[normalization][linear-algebra]") {
|
TEST_CASE(
|
||||||
const mfem::Vector stateFactors = vector({1.0e-9, 2.0e7});
|
"Scaled Jacobian And Inverse Implement The Exact Coordinate Change",
|
||||||
|
"[normalization][linear-algebra]"
|
||||||
|
) {
|
||||||
|
const mfem::Vector stateFactors = vector({1.0e-9, 2.0e7});
|
||||||
const mfem::Vector residualFactors = vector({5.0e8, 3.0e-6});
|
const mfem::Vector residualFactors = vector({5.0e8, 3.0e-6});
|
||||||
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
|
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
|
||||||
|
|
||||||
@@ -617,10 +622,8 @@ TEST_CASE("Scaled Jacobian And Inverse Implement The Exact Coordinate Change", "
|
|||||||
mfem::DenseMatrix inverse(2);
|
mfem::DenseMatrix inverse(2);
|
||||||
for (int row = 0; row < 2; ++row) {
|
for (int row = 0; row < 2; ++row) {
|
||||||
for (int column = 0; column < 2; ++column) {
|
for (int column = 0; column < 2; ++column) {
|
||||||
matrix(row, column) = normalizedMatrix[row][column] * stateFactors(column) /
|
matrix(row, column) = normalizedMatrix[row][column] * stateFactors(column) / residualFactors(row);
|
||||||
residualFactors(row);
|
inverse(row, column) = normalizedInverse[row][column] * residualFactors(column) / stateFactors(row);
|
||||||
inverse(row, column) = normalizedInverse[row][column] * residualFactors(column) /
|
|
||||||
stateFactors(row);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const DenseOperator physicalJacobian(matrix);
|
const DenseOperator physicalJacobian(matrix);
|
||||||
@@ -641,8 +644,11 @@ TEST_CASE("Scaled Jacobian And Inverse Implement The Exact Coordinate Change", "
|
|||||||
checkVector(recovered, direction, 2.0e-13);
|
checkVector(recovered, direction, 2.0e-13);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Scaled Preconditioning Routes An Exact Physical Inverse Through FGMRES", "[normalization][solver]") {
|
TEST_CASE(
|
||||||
const mfem::Vector stateFactors = vector({1.0e-9, 2.0e7});
|
"Scaled Preconditioning Routes An Exact Physical Inverse Through FGMRES",
|
||||||
|
"[normalization][solver]"
|
||||||
|
) {
|
||||||
|
const mfem::Vector stateFactors = vector({1.0e-9, 2.0e7});
|
||||||
const mfem::Vector residualFactors = vector({5.0e8, 3.0e-6});
|
const mfem::Vector residualFactors = vector({5.0e8, 3.0e-6});
|
||||||
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
|
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
|
||||||
|
|
||||||
@@ -652,19 +658,16 @@ TEST_CASE("Scaled Preconditioning Routes An Exact Physical Inverse Through FGMRE
|
|||||||
mfem::DenseMatrix physicalInverseMatrix(2);
|
mfem::DenseMatrix physicalInverseMatrix(2);
|
||||||
for (int row = 0; row < 2; ++row) {
|
for (int row = 0; row < 2; ++row) {
|
||||||
for (int column = 0; column < 2; ++column) {
|
for (int column = 0; column < 2; ++column) {
|
||||||
physicalMatrix(row, column) = normalizedMatrix[row][column] * stateFactors(column) /
|
physicalMatrix(row, column) = normalizedMatrix[row][column] * stateFactors(column) / residualFactors(row);
|
||||||
residualFactors(row);
|
physicalInverseMatrix(row, column) =
|
||||||
physicalInverseMatrix(row, column) = normalizedInverse[row][column] * residualFactors(column) /
|
normalizedInverse[row][column] * residualFactors(column) / stateFactors(row);
|
||||||
stateFactors(row);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DenseOperator physicalJacobian(physicalMatrix);
|
const DenseOperator physicalJacobian(physicalMatrix);
|
||||||
const normalization::ScaledJacobianOperator scaledJacobian(physicalJacobian, map);
|
const normalization::ScaledJacobianOperator scaledJacobian(physicalJacobian, map);
|
||||||
DenseInverseSolver physicalInverse(physicalInverseMatrix);
|
DenseInverseSolver physicalInverse(physicalInverseMatrix);
|
||||||
normalization::ScaledPreconditioner scaledPreconditioner(
|
normalization::ScaledPreconditioner scaledPreconditioner(physicalInverse, physicalJacobian, scaledJacobian, map);
|
||||||
physicalInverse, physicalJacobian, scaledJacobian, map
|
|
||||||
);
|
|
||||||
|
|
||||||
CHECK(physicalInverse.BoundOperator() == &physicalJacobian);
|
CHECK(physicalInverse.BoundOperator() == &physicalJacobian);
|
||||||
CHECK(&scaledPreconditioner.GetPhysicalJacobian() == &physicalJacobian);
|
CHECK(&scaledPreconditioner.GetPhysicalJacobian() == &physicalJacobian);
|
||||||
@@ -697,18 +700,18 @@ TEST_CASE("Scaled Preconditioning Routes An Exact Physical Inverse Through FGMRE
|
|||||||
CHECK(scaledPreconditioner.GetStatistics().applications >= 2);
|
CHECK(scaledPreconditioner.GetStatistics().applications >= 2);
|
||||||
|
|
||||||
mfem::IdentityOperator differentNormalizedJacobian(2);
|
mfem::IdentityOperator differentNormalizedJacobian(2);
|
||||||
CHECK_THROWS_AS(
|
CHECK_THROWS_AS(scaledPreconditioner.SetOperator(differentNormalizedJacobian), std::invalid_argument);
|
||||||
scaledPreconditioner.SetOperator(differentNormalizedJacobian),
|
|
||||||
std::invalid_argument
|
|
||||||
);
|
|
||||||
mfem::IdentityOperator wrongSize(3);
|
mfem::IdentityOperator wrongSize(3);
|
||||||
CHECK_THROWS_AS(scaledPreconditioner.SetOperator(wrongSize), std::invalid_argument);
|
CHECK_THROWS_AS(scaledPreconditioner.SetOperator(wrongSize), std::invalid_argument);
|
||||||
mfem::Vector wrongCorrection(1);
|
mfem::Vector wrongCorrection(1);
|
||||||
CHECK_THROWS_AS(scaledPreconditioner.Mult(rightHandSide, wrongCorrection), std::invalid_argument);
|
CHECK_THROWS_AS(scaledPreconditioner.Mult(rightHandSide, wrongCorrection), std::invalid_argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Physical Riesz Scaling Collapses A Forty-Eight-Decade Diagonal Imbalance", "[normalization][numerics]") {
|
TEST_CASE(
|
||||||
const mfem::Vector stateFactors = vector({1.0e-12, 1.0, 1.0e12});
|
"Physical Riesz Scaling Collapses A Forty-Eight-Decade Diagonal Imbalance",
|
||||||
|
"[normalization][numerics]"
|
||||||
|
) {
|
||||||
|
const mfem::Vector stateFactors = vector({1.0e-12, 1.0, 1.0e12});
|
||||||
const mfem::Vector residualFactors = vector({1.0e12, 1.0, 1.0e-12});
|
const mfem::Vector residualFactors = vector({1.0e12, 1.0, 1.0e-12});
|
||||||
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
|
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
|
||||||
|
|
||||||
@@ -727,38 +730,38 @@ TEST_CASE("Physical Riesz Scaling Collapses A Forty-Eight-Decade Diagonal Imbala
|
|||||||
checkVector(action, direction, 4.0e-15);
|
checkVector(action, direction, 4.0e-15);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("A Compiled Stellar Problem Prepares Reference Physical Riesz Coordinates", "[normalization][integration]") {
|
TEST_CASE(
|
||||||
|
"A Compiled Stellar Problem Prepares Reference Physical Riesz Coordinates",
|
||||||
|
"[normalization][integration]"
|
||||||
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
utils::Args args = test_utils::setup_args();
|
utils::Args args = test_utils::setup_args();
|
||||||
fem::FEM finiteElements = fem::setup_fem(args.mesh_file, args, 0);
|
fem::FEM finiteElements = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
REQUIRE(finiteElements.okay());
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
constexpr double targetMass = 2.0;
|
constexpr double targetMass = 2.0;
|
||||||
constexpr double referenceRadius = 1.25;
|
constexpr double referenceRadius = 1.25;
|
||||||
constexpr double gravitationalConstant = 3.0;
|
constexpr double gravitationalConstant = 3.0;
|
||||||
const normalization::PhysicalRieszDiagonal policy{
|
const normalization::PhysicalRieszDiagonal policy{dimensions::LengthValue{referenceRadius}, gravitationalConstant};
|
||||||
dimensions::LengthValue{referenceRadius}, gravitationalConstant
|
auto discretization = equilibrium::makeStellarDiscretization(std::move(finiteElements), policy);
|
||||||
};
|
auto problem = equilibrium::discretize(
|
||||||
const auto discretization = equilibrium::makeStellarDiscretization(finiteElements, policy);
|
|
||||||
auto problem = equilibrium::discretize(
|
|
||||||
model::StellarModel(
|
model::StellarModel(
|
||||||
eos::Polytrope({.n = 3.0, .K = 0.25}),
|
eos::Polytrope({.n = 3.0, .K = 0.25}), surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
|
||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
||||||
),
|
),
|
||||||
discretization
|
std::move(discretization)
|
||||||
);
|
);
|
||||||
|
|
||||||
using ProblemType = std::remove_cvref_t<decltype(problem)>;
|
using ProblemType = std::remove_cvref_t<decltype(problem)>;
|
||||||
using Form = typename ProblemType::FormType;
|
using Form = typename ProblemType::FormType;
|
||||||
using ModelType = std::remove_cvref_t<decltype(problem.GetStellarModel())>;
|
using ModelType = std::remove_cvref_t<decltype(problem.GetStellarModel())>;
|
||||||
STATIC_CHECK(SupportsModelDerivedStellarScales<ModelType>);
|
STATIC_CHECK(SupportsModelDerivedStellarScales<ModelType>);
|
||||||
STATIC_CHECK_FALSE(SupportsModelDerivedStellarScales<ModelWithoutFixedTotalMass>);
|
STATIC_CHECK_FALSE(SupportsModelDerivedStellarScales<ModelWithoutFixedTotalMass>);
|
||||||
STATIC_CHECK(std::same_as<
|
STATIC_CHECK(
|
||||||
typename ProblemType::NormalizationPrescriptionType,
|
std::same_as<typename ProblemType::NormalizationPrescriptionType, std::remove_cvref_t<decltype(policy)>>
|
||||||
std::remove_cvref_t<decltype(policy)>>);
|
);
|
||||||
CHECK(problem.GetNormalizationPrescription().referenceRadius() == dimensions::LengthValue{referenceRadius});
|
CHECK(problem.GetNormalizationPrescription().referenceRadius() == dimensions::LengthValue{referenceRadius});
|
||||||
|
|
||||||
const normalization::DiagonalNormalization map = normalization::prepareNormalization(problem);
|
const normalization::DiagonalNormalization map = normalization::prepareNormalization(problem);
|
||||||
@@ -773,39 +776,41 @@ TEST_CASE("A Compiled Stellar Problem Prepares Reference Physical Riesz Coordina
|
|||||||
CHECK(map.ResidualFactors()(index) > 0.0);
|
CHECK(map.ResidualFactors()(index) > 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto scales = normalization::deriveStellarCharacteristicScales(policy, problem.GetStellarModel());
|
const auto scales = normalization::deriveStellarCharacteristicScales(policy, problem.GetStellarModel());
|
||||||
const auto &layout = problem.GetManifest().layout();
|
const auto &layout = problem.GetManifest().layout();
|
||||||
constexpr int massValueBlock = blocks::type_index_v<
|
constexpr int massValueBlock =
|
||||||
blocks::fixed_total_mass::mass_normalization::value,
|
blocks::type_index_v<blocks::fixed_total_mass::mass_normalization::value, typename Form::value_blocks>;
|
||||||
typename Form::value_blocks>;
|
constexpr int massResidualBlock =
|
||||||
constexpr int massResidualBlock = blocks::type_index_v<
|
blocks::type_index_v<blocks::fixed_total_mass::mass_normalization::residual, typename Form::residual_blocks>;
|
||||||
blocks::fixed_total_mass::mass_normalization::residual,
|
constexpr int phaseValueBlock =
|
||||||
typename Form::residual_blocks>;
|
blocks::type_index_v<blocks::fixed_central_density::central_value::value, typename Form::value_blocks>;
|
||||||
constexpr int phaseValueBlock = blocks::type_index_v<
|
constexpr int phaseResidualBlock =
|
||||||
blocks::fixed_central_density::central_value::value,
|
blocks::type_index_v<blocks::fixed_central_density::central_value::residual, typename Form::residual_blocks>;
|
||||||
typename Form::value_blocks>;
|
constexpr int enthalpyResidualBlock =
|
||||||
constexpr int phaseResidualBlock = blocks::type_index_v<
|
blocks::type_index_v<blocks::enthalpy::specific::residual, typename Form::residual_blocks>;
|
||||||
blocks::fixed_central_density::central_value::residual,
|
|
||||||
typename Form::residual_blocks>;
|
|
||||||
constexpr int enthalpyResidualBlock = blocks::type_index_v<
|
|
||||||
blocks::enthalpy::specific::residual,
|
|
||||||
typename Form::residual_blocks>;
|
|
||||||
|
|
||||||
CHECK(map.StateFactors()(layout.value_offsets()[massValueBlock]) ==
|
CHECK(
|
||||||
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
|
map.StateFactors()(layout.value_offsets()[massValueBlock]) ==
|
||||||
CHECK(map.ResidualFactors()(layout.residual_offsets()[massResidualBlock]) ==
|
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15)
|
||||||
Catch::Approx(1.0 / targetMass).epsilon(2.0e-15));
|
);
|
||||||
CHECK(map.StateFactors()(layout.value_offsets()[phaseValueBlock]) ==
|
CHECK(
|
||||||
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
|
map.ResidualFactors()(layout.residual_offsets()[massResidualBlock]) ==
|
||||||
CHECK(map.ResidualFactors()(layout.residual_offsets()[phaseResidualBlock]) ==
|
Catch::Approx(1.0 / targetMass).epsilon(2.0e-15)
|
||||||
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
|
);
|
||||||
|
CHECK(
|
||||||
|
map.StateFactors()(layout.value_offsets()[phaseValueBlock]) ==
|
||||||
|
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15)
|
||||||
|
);
|
||||||
|
CHECK(
|
||||||
|
map.ResidualFactors()(layout.residual_offsets()[phaseResidualBlock]) ==
|
||||||
|
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15)
|
||||||
|
);
|
||||||
|
|
||||||
const auto &surfaceRows = problem.GetPressureSurfaceRows().reduced_dofs();
|
const auto &surfaceRows = problem.GetPressureSurfaceRows().reduced_dofs();
|
||||||
REQUIRE(surfaceRows.Size() > 0);
|
REQUIRE(surfaceRows.Size() > 0);
|
||||||
for (const int row : surfaceRows) {
|
for (const int row : surfaceRows) {
|
||||||
const int rootRow = layout.residual_offsets()[enthalpyResidualBlock] + row;
|
const int rootRow = layout.residual_offsets()[enthalpyResidualBlock] + row;
|
||||||
CHECK(map.ResidualFactors()(rootRow) ==
|
CHECK(map.ResidualFactors()(rootRow) == Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
|
||||||
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mfem::Vector physicalState(problem.StateSize());
|
mfem::Vector physicalState(problem.StateSize());
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
import mean_field;
|
import mean_field;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
namespace blocks = mean_field::utils::blocks;
|
namespace blocks = mean_field::utils::blocks;
|
||||||
namespace normalization = mean_field::normalization;
|
namespace normalization = mean_field::normalization;
|
||||||
|
|
||||||
using PhysicalForm = blocks::surface_deformed_stellar_equilibrium_form;
|
using PhysicalForm = blocks::surface_deformed_stellar_equilibrium_form;
|
||||||
using PhaseForm = blocks::central_density_bordered_stellar_equilibrium_form;
|
using PhaseForm = blocks::central_density_bordered_stellar_equilibrium_form;
|
||||||
using PhysicalPlan = normalization::PhysicalRieszNormalizationPlanFor<PhysicalForm>;
|
using PhysicalPlan = normalization::PhysicalRieszNormalizationPlanFor<PhysicalForm>;
|
||||||
using PhasePlan = normalization::PhysicalRieszNormalizationPlanFor<PhaseForm>;
|
using PhasePlan = normalization::PhysicalRieszNormalizationPlanFor<PhaseForm>;
|
||||||
|
|
||||||
struct ValueA final : blocks::value_block_base { };
|
struct ValueA final : blocks::value_block_base { };
|
||||||
struct ValueB final : blocks::value_block_base { };
|
struct ValueB final : blocks::value_block_base { };
|
||||||
@@ -22,9 +22,7 @@ namespace {
|
|||||||
struct ForeignValue final : blocks::value_block_base { };
|
struct ForeignValue final : blocks::value_block_base { };
|
||||||
struct ForeignResidual final : blocks::residual_block_base { };
|
struct ForeignResidual final : blocks::residual_block_base { };
|
||||||
|
|
||||||
using SmallForm = blocks::block_form<
|
using SmallForm = blocks::block_form<blocks::type_list<ValueA, ValueB>, blocks::type_list<ResidualA, ResidualB>>;
|
||||||
blocks::type_list<ValueA, ValueB>,
|
|
||||||
blocks::type_list<ResidualA, ResidualB>>;
|
|
||||||
using ValueAIdentity = normalization::CoordinateComponent<
|
using ValueAIdentity = normalization::CoordinateComponent<
|
||||||
normalization::CoordinateKind::value,
|
normalization::CoordinateKind::value,
|
||||||
blocks::type_list<ValueA>,
|
blocks::type_list<ValueA>,
|
||||||
@@ -50,21 +48,11 @@ namespace {
|
|||||||
blocks::type_list<ForeignResidual>,
|
blocks::type_list<ForeignResidual>,
|
||||||
normalization::IdentityCoordinate>;
|
normalization::IdentityCoordinate>;
|
||||||
|
|
||||||
using CompleteSmallPlan = normalization::NormalizationPlan<
|
using CompleteSmallPlan =
|
||||||
ValueAIdentity,
|
normalization::NormalizationPlan<ValueAIdentity, ValueBIdentity, ResidualAIdentity, ResidualBIdentity>;
|
||||||
ValueBIdentity,
|
using MissingSmallPlan = normalization::NormalizationPlan<ValueAIdentity, ResidualAIdentity, ResidualBIdentity>;
|
||||||
ResidualAIdentity,
|
using DuplicateSmallPlan = normalization::
|
||||||
ResidualBIdentity>;
|
NormalizationPlan<ValueAIdentity, ValueAIdentity, ValueBIdentity, ResidualAIdentity, ResidualBIdentity>;
|
||||||
using MissingSmallPlan = normalization::NormalizationPlan<
|
|
||||||
ValueAIdentity,
|
|
||||||
ResidualAIdentity,
|
|
||||||
ResidualBIdentity>;
|
|
||||||
using DuplicateSmallPlan = normalization::NormalizationPlan<
|
|
||||||
ValueAIdentity,
|
|
||||||
ValueAIdentity,
|
|
||||||
ValueBIdentity,
|
|
||||||
ResidualAIdentity,
|
|
||||||
ResidualBIdentity>;
|
|
||||||
using ForeignSmallPlan = normalization::NormalizationPlan<
|
using ForeignSmallPlan = normalization::NormalizationPlan<
|
||||||
ValueAIdentity,
|
ValueAIdentity,
|
||||||
ValueBIdentity,
|
ValueBIdentity,
|
||||||
@@ -79,11 +67,11 @@ namespace {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct IncoherentComponent final {
|
struct IncoherentComponent final {
|
||||||
using Blocks = blocks::type_list<ValueA>;
|
using Blocks = blocks::type_list<ValueA>;
|
||||||
using Method = normalization::IdentityCoordinate;
|
using Method = normalization::IdentityCoordinate;
|
||||||
using ValueBlocks = blocks::type_list<ValueB>;
|
using ValueBlocks = blocks::type_list<ValueB>;
|
||||||
using ResidualBlocks = blocks::type_list<>;
|
using ResidualBlocks = blocks::type_list<>;
|
||||||
static constexpr auto kind = normalization::CoordinateKind::value;
|
static constexpr auto kind = normalization::CoordinateKind::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
using WrongDensityTopology = normalization::CoordinateComponent<
|
using WrongDensityTopology = normalization::CoordinateComponent<
|
||||||
@@ -95,40 +83,34 @@ namespace {
|
|||||||
|
|
||||||
struct FutureInvariantValue final : blocks::value_block_base { };
|
struct FutureInvariantValue final : blocks::value_block_base { };
|
||||||
struct FutureInvariantResidual final : blocks::residual_block_base { };
|
struct FutureInvariantResidual final : blocks::residual_block_base { };
|
||||||
using UnregisteredFutureForm = blocks::block_form<
|
using UnregisteredFutureForm =
|
||||||
blocks::type_list<FutureInvariantValue>,
|
blocks::block_form<blocks::type_list<FutureInvariantValue>, blocks::type_list<FutureInvariantResidual>>;
|
||||||
blocks::type_list<FutureInvariantResidual>>;
|
|
||||||
|
|
||||||
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::integral::FixedTotalMass>>;
|
mean_field::integral::FixedTotalMass>>;
|
||||||
using RieszPolicy = normalization::PhysicalRieszDiagonal<>;
|
using RieszPolicy = normalization::PhysicalRieszDiagonal<>;
|
||||||
using RieszDiscretization = mean_field::equilibrium::StellarDiscretizationFor<RieszPolicy>;
|
using RieszDiscretization = mean_field::equilibrium::StellarDiscretizationFor<RieszPolicy>;
|
||||||
using BaselineProblem = mean_field::equilibrium::StellarEquilibriumProblem<BaseModel>;
|
using BaselineProblem = mean_field::equilibrium::StellarEquilibriumProblem<BaseModel>;
|
||||||
using RieszProblem = mean_field::equilibrium::StellarEquilibriumProblem<BaseModel, RieszDiscretization>;
|
using RieszProblem = mean_field::equilibrium::StellarEquilibriumProblem<BaseModel, RieszDiscretization>;
|
||||||
using AngularModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using AngularModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::integral::FixedTotalMass,
|
mean_field::integral::FixedTotalMass,
|
||||||
mean_field::integral::FixedAngularMomentum>>;
|
mean_field::integral::FixedAngularMomentum>>;
|
||||||
using AngularForm = mean_field::operators::CompiledStellarEquilibriumForm<AngularModel>;
|
using AngularForm = mean_field::operators::CompiledStellarEquilibriumForm<AngularModel>;
|
||||||
|
|
||||||
template <typename Mapper>
|
template <typename FiniteElements>
|
||||||
concept CanMakeRieszDiscretization = requires(
|
concept CanMakeRieszDiscretization = requires(FiniteElements &&finiteElements, RieszPolicy policy) {
|
||||||
mean_field::fem::FEM &finiteElements,
|
mean_field::equilibrium::makeStellarDiscretization(std::forward<FiniteElements>(finiteElements), policy);
|
||||||
Mapper &&mapper,
|
|
||||||
RieszPolicy policy
|
|
||||||
) {
|
|
||||||
mean_field::equilibrium::makeStellarDiscretization(
|
|
||||||
finiteElements,
|
|
||||||
std::forward<Mapper>(mapper),
|
|
||||||
policy
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("Normalization Plans Prove Exact Ownership Of Every Compiled Coordinate", "[normalization][type]") {
|
TEST_CASE(
|
||||||
|
"Normalization Plans Prove Exact Ownership Of Every Compiled Coordinate",
|
||||||
|
"[normalization][type]"
|
||||||
|
) {
|
||||||
STATIC_CHECK(normalization::NormalizationPlanType<PhysicalPlan>);
|
STATIC_CHECK(normalization::NormalizationPlanType<PhysicalPlan>);
|
||||||
STATIC_CHECK(normalization::CompleteNormalizationFor<PhysicalPlan, PhysicalForm>);
|
STATIC_CHECK(normalization::CompleteNormalizationFor<PhysicalPlan, PhysicalForm>);
|
||||||
STATIC_CHECK(normalization::CompleteNormalizationFor<PhasePlan, PhaseForm>);
|
STATIC_CHECK(normalization::CompleteNormalizationFor<PhasePlan, PhaseForm>);
|
||||||
@@ -136,17 +118,19 @@ TEST_CASE("Normalization Plans Prove Exact Ownership Of Every Compiled Coordinat
|
|||||||
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, PhysicalForm>);
|
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, PhysicalForm>);
|
||||||
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, PhaseForm>);
|
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, PhaseForm>);
|
||||||
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, AngularForm>);
|
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, AngularForm>);
|
||||||
STATIC_CHECK(normalization::StellarSpecificationNormalizationContribution<
|
STATIC_CHECK(
|
||||||
mean_field::integral::FixedAngularMomentum>::registered);
|
normalization::StellarSpecificationNormalizationContribution<
|
||||||
|
mean_field::integral::FixedAngularMomentum>::registered
|
||||||
|
);
|
||||||
|
|
||||||
STATIC_CHECK(normalization::CompleteNormalizationFor<CompleteSmallPlan, SmallForm>);
|
STATIC_CHECK(normalization::CompleteNormalizationFor<CompleteSmallPlan, SmallForm>);
|
||||||
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<MissingSmallPlan, SmallForm>);
|
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<MissingSmallPlan, SmallForm>);
|
||||||
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<DuplicateSmallPlan, SmallForm>);
|
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<DuplicateSmallPlan, SmallForm>);
|
||||||
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<ForeignSmallPlan, SmallForm>);
|
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<ForeignSmallPlan, SmallForm>);
|
||||||
|
|
||||||
using Missing = normalization::NormalizationCoverage<SmallForm, MissingSmallPlan>;
|
using Missing = normalization::NormalizationCoverage<SmallForm, MissingSmallPlan>;
|
||||||
using Duplicate = normalization::NormalizationCoverage<SmallForm, DuplicateSmallPlan>;
|
using Duplicate = normalization::NormalizationCoverage<SmallForm, DuplicateSmallPlan>;
|
||||||
using Foreign = normalization::NormalizationCoverage<SmallForm, ForeignSmallPlan>;
|
using Foreign = normalization::NormalizationCoverage<SmallForm, ForeignSmallPlan>;
|
||||||
STATIC_CHECK(Missing::MissingValueBlocks::size == 1);
|
STATIC_CHECK(Missing::MissingValueBlocks::size == 1);
|
||||||
STATIC_CHECK(blocks::contains_type_v<ValueB, typename Missing::MissingValueBlocks>);
|
STATIC_CHECK(blocks::contains_type_v<ValueB, typename Missing::MissingValueBlocks>);
|
||||||
STATIC_CHECK(Duplicate::RepeatedValueBlocks::size == 1);
|
STATIC_CHECK(Duplicate::RepeatedValueBlocks::size == 1);
|
||||||
@@ -155,72 +139,54 @@ TEST_CASE("Normalization Plans Prove Exact Ownership Of Every Compiled Coordinat
|
|||||||
STATIC_CHECK(Foreign::UnexpectedResidualBlocks::size == 1);
|
STATIC_CHECK(Foreign::UnexpectedResidualBlocks::size == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Physical Riesz Methods Reject Incompatible Or Unregistered Field Topologies", "[normalization][type]") {
|
TEST_CASE(
|
||||||
|
"Physical Riesz Methods Reject Incompatible Or Unregistered Field Topologies",
|
||||||
|
"[normalization][type]"
|
||||||
|
) {
|
||||||
STATIC_CHECK_FALSE(normalization::NormalizationComponent<MalformedComponent>);
|
STATIC_CHECK_FALSE(normalization::NormalizationComponent<MalformedComponent>);
|
||||||
STATIC_CHECK_FALSE(normalization::NormalizationComponent<IncoherentComponent>);
|
STATIC_CHECK_FALSE(normalization::NormalizationComponent<IncoherentComponent>);
|
||||||
STATIC_CHECK_FALSE(normalization::NormalizationComponent<WrongDensityTopology>);
|
STATIC_CHECK_FALSE(normalization::NormalizationComponent<WrongDensityTopology>);
|
||||||
STATIC_CHECK_FALSE(normalization::CompilableNormalizationFor<RieszPolicy, UnregisteredFutureForm>);
|
STATIC_CHECK_FALSE(normalization::CompilableNormalizationFor<RieszPolicy, UnregisteredFutureForm>);
|
||||||
STATIC_CHECK(normalization::CompilableNormalizationFor<normalization::Unnormalized, UnregisteredFutureForm>);
|
STATIC_CHECK(normalization::CompilableNormalizationFor<normalization::Unnormalized, UnregisteredFutureForm>);
|
||||||
|
|
||||||
using Density = normalization::PhysicalRieszBlockTraits<blocks::density::mass::value>;
|
using Density = normalization::PhysicalRieszBlockTraits<blocks::density::mass::value>;
|
||||||
using Gravity = normalization::PhysicalRieszBlockTraits<blocks::gravity::gradient::value>;
|
using Gravity = normalization::PhysicalRieszBlockTraits<blocks::gravity::gradient::value>;
|
||||||
using Surface = normalization::PhysicalRieszBlockTraits<blocks::surface_deformation::parameters::value>;
|
using Surface = normalization::PhysicalRieszBlockTraits<blocks::surface_deformation::parameters::value>;
|
||||||
using EnthalpyResidual = normalization::PhysicalRieszBlockTraits<blocks::enthalpy::specific::residual>;
|
using EnthalpyResidual = normalization::PhysicalRieszBlockTraits<blocks::enthalpy::specific::residual>;
|
||||||
using MassResidual = normalization::PhysicalRieszBlockTraits<
|
using MassResidual =
|
||||||
blocks::fixed_total_mass::mass_normalization::residual>;
|
normalization::PhysicalRieszBlockTraits<blocks::fixed_total_mass::mass_normalization::residual>;
|
||||||
|
|
||||||
STATIC_CHECK(Density::Method::topology == normalization::RieszTopology::scalar_volume_l2);
|
STATIC_CHECK(Density::Method::topology == normalization::RieszTopology::scalar_volume_l2);
|
||||||
STATIC_CHECK(Gravity::Method::topology == normalization::RieszTopology::vector_volume_l2);
|
STATIC_CHECK(Gravity::Method::topology == normalization::RieszTopology::vector_volume_l2);
|
||||||
STATIC_CHECK(Surface::Method::topology == normalization::RieszTopology::scalar_boundary_l2);
|
STATIC_CHECK(Surface::Method::topology == normalization::RieszTopology::scalar_boundary_l2);
|
||||||
STATIC_CHECK(
|
STATIC_CHECK(EnthalpyResidual::Method::topology == normalization::RieszTopology::hybrid_scalar_volume_point_rows);
|
||||||
EnthalpyResidual::Method::topology == normalization::RieszTopology::hybrid_scalar_volume_point_rows
|
|
||||||
);
|
|
||||||
STATIC_CHECK(MassResidual::Method::topology == normalization::RieszTopology::global_scalar);
|
STATIC_CHECK(MassResidual::Method::topology == normalization::RieszTopology::global_scalar);
|
||||||
STATIC_CHECK(MassResidual::Method::scale == normalization::PhysicalScaleKind::mass);
|
STATIC_CHECK(MassResidual::Method::scale == normalization::PhysicalScaleKind::mass);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Normalization Is Part Of The Compile-Time Discretization And Problem Type", "[normalization][type]") {
|
TEST_CASE(
|
||||||
|
"Normalization Is Part Of The Compile-Time Discretization And Problem Type",
|
||||||
|
"[normalization][type]"
|
||||||
|
) {
|
||||||
STATIC_CHECK(mean_field::equilibrium::StellarDiscretizationType<RieszDiscretization>);
|
STATIC_CHECK(mean_field::equilibrium::StellarDiscretizationType<RieszDiscretization>);
|
||||||
STATIC_CHECK_FALSE(std::same_as<RieszDiscretization, mean_field::equilibrium::StellarDiscretization>);
|
STATIC_CHECK_FALSE(std::same_as<RieszDiscretization, mean_field::equilibrium::StellarDiscretization>);
|
||||||
STATIC_CHECK_FALSE(std::same_as<RieszProblem, BaselineProblem>);
|
STATIC_CHECK_FALSE(std::same_as<RieszProblem, BaselineProblem>);
|
||||||
STATIC_CHECK(std::same_as<typename BaselineProblem::NormalizationPrescriptionType, normalization::Unnormalized>);
|
STATIC_CHECK(std::same_as<typename BaselineProblem::NormalizationPrescriptionType, normalization::Unnormalized>);
|
||||||
STATIC_CHECK(std::same_as<typename RieszProblem::NormalizationPrescriptionType, RieszPolicy>);
|
STATIC_CHECK(std::same_as<typename RieszProblem::NormalizationPrescriptionType, RieszPolicy>);
|
||||||
STATIC_CHECK(mean_field::equilibrium::DiscretizedStellarEquilibriumProblem<RieszProblem>);
|
STATIC_CHECK(mean_field::equilibrium::DiscretizedStellarEquilibriumProblem<RieszProblem>);
|
||||||
STATIC_CHECK(std::constructible_from<
|
STATIC_CHECK(std::constructible_from<RieszDiscretization, mean_field::fem::FEM &&, RieszPolicy>);
|
||||||
RieszDiscretization,
|
STATIC_CHECK_FALSE(std::constructible_from<RieszDiscretization, mean_field::fem::FEM &, RieszPolicy>);
|
||||||
mean_field::fem::FEM &,
|
STATIC_CHECK_FALSE(std::constructible_from<RieszDiscretization, const mean_field::fem::FEM &, RieszPolicy>);
|
||||||
const mean_field::mapping::DomainMapper &,
|
STATIC_CHECK(std::constructible_from<mean_field::equilibrium::StellarDiscretization, mean_field::fem::FEM &&>);
|
||||||
RieszPolicy>);
|
STATIC_CHECK_FALSE(std::constructible_from<mean_field::equilibrium::StellarDiscretization, mean_field::fem::FEM &>);
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
STATIC_CHECK_FALSE(
|
||||||
RieszDiscretization,
|
std::constructible_from<mean_field::equilibrium::StellarDiscretization, const mean_field::fem::FEM &>
|
||||||
mean_field::fem::FEM &,
|
);
|
||||||
mean_field::mapping::DomainMapper &&,
|
STATIC_CHECK(CanMakeRieszDiscretization<mean_field::fem::FEM>);
|
||||||
RieszPolicy>);
|
STATIC_CHECK_FALSE(CanMakeRieszDiscretization<mean_field::fem::FEM &>);
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
STATIC_CHECK_FALSE(CanMakeRieszDiscretization<const mean_field::fem::FEM &>);
|
||||||
RieszDiscretization,
|
|
||||||
mean_field::fem::FEM &,
|
|
||||||
const mean_field::mapping::DomainMapper &&,
|
|
||||||
RieszPolicy>);
|
|
||||||
STATIC_CHECK(std::constructible_from<
|
|
||||||
mean_field::equilibrium::StellarDiscretization,
|
|
||||||
mean_field::fem::FEM &,
|
|
||||||
const mean_field::mapping::DomainMapper &>);
|
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
|
||||||
mean_field::equilibrium::StellarDiscretization,
|
|
||||||
mean_field::fem::FEM &,
|
|
||||||
mean_field::mapping::DomainMapper &&>);
|
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<
|
|
||||||
mean_field::equilibrium::StellarDiscretization,
|
|
||||||
mean_field::fem::FEM &,
|
|
||||||
const mean_field::mapping::DomainMapper &&>);
|
|
||||||
STATIC_CHECK(CanMakeRieszDiscretization<
|
|
||||||
mean_field::mapping::DomainMapper &>);
|
|
||||||
STATIC_CHECK_FALSE(CanMakeRieszDiscretization<
|
|
||||||
mean_field::mapping::DomainMapper>);
|
|
||||||
STATIC_CHECK_FALSE(CanMakeRieszDiscretization<
|
|
||||||
const mean_field::mapping::DomainMapper>);
|
|
||||||
|
|
||||||
using SmallLayout = blocks::form_layout<SmallForm>;
|
using SmallLayout = blocks::form_layout<SmallForm>;
|
||||||
using SmallBuilder = normalization::DiagonalNormalizationBuilder<SmallForm>;
|
using SmallBuilder = normalization::DiagonalNormalizationBuilder<SmallForm>;
|
||||||
STATIC_CHECK(std::constructible_from<SmallBuilder, const SmallLayout &>);
|
STATIC_CHECK(std::constructible_from<SmallBuilder, const SmallLayout &>);
|
||||||
STATIC_CHECK_FALSE(std::constructible_from<SmallBuilder, SmallLayout &&>);
|
STATIC_CHECK_FALSE(std::constructible_from<SmallBuilder, SmallLayout &&>);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,28 @@ using namespace mean_field;
|
|||||||
namespace prepared_test = gravity_prepared_test_utils;
|
namespace prepared_test = gravity_prepared_test_utils;
|
||||||
namespace gravity_context = operators::context::gravity_field;
|
namespace gravity_context = operators::context::gravity_field;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
[[nodiscard]] mfem::Vector makeAffineDisplacement(
|
||||||
|
const fem::FEM &finiteElements,
|
||||||
|
const double scale
|
||||||
|
) {
|
||||||
|
mfem::ParGridFunction field(finiteElements.displacementFes.get());
|
||||||
|
mfem::VectorFunctionCoefficient coefficient(
|
||||||
|
finiteElements.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||||
|
value.SetSize(position.Size());
|
||||||
|
for (int component = 0; component < position.Size(); ++component) {
|
||||||
|
value(component) = scale * position(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
field.ProjectCoefficient(coefficient);
|
||||||
|
|
||||||
|
mfem::Vector result;
|
||||||
|
field.GetTrueDofs(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Gravity Field Linearization Context Applies Selective Invalidation",
|
"Gravity Field Linearization Context Applies Selective Invalidation",
|
||||||
tags::gravity_context
|
tags::gravity_context
|
||||||
@@ -125,6 +147,70 @@ TEST_CASE(
|
|||||||
CHECK(context.GetGeometryContext().GetSourceOperator().GetPreparationCount() == 1);
|
CHECK(context.GetGeometryContext().GetSourceOperator().GetPreparationCount() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Gravity Field Contexts Invalidate And Recover After Candidate Rejection",
|
||||||
|
tags::gravity_context &tags::geometry &tags::unit
|
||||||
|
) {
|
||||||
|
auto args = test_utils::setup_args();
|
||||||
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
gravity_context::GravityFieldGeometryContext geometryContext(f, *f.domainMapperStateless);
|
||||||
|
const mfem::Vector zeroDisplacement = geometryContext.GetDisplacementMap().gather(makeAffineDisplacement(f, 0.0));
|
||||||
|
const mfem::Vector foldedDisplacement =
|
||||||
|
geometryContext.GetDisplacementMap().gather(makeAffineDisplacement(f, -2.0));
|
||||||
|
|
||||||
|
REQUIRE(geometryContext.TryPrepare(zeroDisplacement, {.value = 0}, {.value = 0}).has_value());
|
||||||
|
REQUIRE(geometryContext.IsPrepared());
|
||||||
|
|
||||||
|
const auto geometryRejection = geometryContext.TryPrepare(foldedDisplacement, {.value = 0}, {.value = 1});
|
||||||
|
REQUIRE_FALSE(geometryRejection.has_value());
|
||||||
|
CHECK(geometryRejection.error().reason == gravity_context::GravityFieldPreparationRejectionReason::invalid_mapping);
|
||||||
|
CHECK(geometryRejection.error().mappingStatus == mapping::MappingStatus::non_positive_determinant);
|
||||||
|
CHECK_FALSE(geometryContext.IsPrepared());
|
||||||
|
|
||||||
|
const auto geometryRecovery = geometryContext.TryPrepare(zeroDisplacement, {.value = 0}, {.value = 1});
|
||||||
|
REQUIRE(geometryRecovery.has_value());
|
||||||
|
CHECK(geometryRecovery->reconstructed_operators);
|
||||||
|
CHECK(geometryContext.IsPrepared());
|
||||||
|
|
||||||
|
gravity_context::GravityFieldLinearizationContext linearizationContext(f, *f.domainMapperStateless);
|
||||||
|
mfem::Vector density =
|
||||||
|
prepared_test::make_deterministic_vector(linearizationContext.GetDensityMap().reduced_size(), 0.17);
|
||||||
|
mfem::Vector gravityGradient =
|
||||||
|
prepared_test::make_deterministic_vector(linearizationContext.GetGravityGradientMap().reduced_size(), 0.41);
|
||||||
|
mfem::Vector gravityPotential =
|
||||||
|
prepared_test::make_deterministic_vector(linearizationContext.GetGravityPotentialMap().reduced_size(), 0.73);
|
||||||
|
gravity_context::GravityFieldRevisions revisions;
|
||||||
|
|
||||||
|
const auto makeState = [&](const mfem::Vector &displacement) {
|
||||||
|
return gravity_context::GravityFieldStateView{
|
||||||
|
.density = density,
|
||||||
|
.displacement = displacement,
|
||||||
|
.gravity_gradient = gravityGradient,
|
||||||
|
.gravity_potential = gravityPotential
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
REQUIRE(linearizationContext.TryPrepare(makeState(zeroDisplacement), revisions).has_value());
|
||||||
|
REQUIRE(linearizationContext.IsPrepared());
|
||||||
|
|
||||||
|
revisions.displacement.value = 1;
|
||||||
|
const auto linearizationRejection = linearizationContext.TryPrepare(makeState(foldedDisplacement), revisions);
|
||||||
|
REQUIRE_FALSE(linearizationRejection.has_value());
|
||||||
|
CHECK(
|
||||||
|
linearizationRejection.error().reason ==
|
||||||
|
gravity_context::GravityFieldPreparationRejectionReason::invalid_mapping
|
||||||
|
);
|
||||||
|
CHECK(linearizationRejection.error().mappingStatus == mapping::MappingStatus::non_positive_determinant);
|
||||||
|
CHECK_FALSE(linearizationContext.IsPrepared());
|
||||||
|
|
||||||
|
const auto linearizationRecovery = linearizationContext.TryPrepare(makeState(zeroDisplacement), revisions);
|
||||||
|
REQUIRE(linearizationRecovery.has_value());
|
||||||
|
CHECK(linearizationRecovery->geometry.reconstructed_operators);
|
||||||
|
CHECK(linearizationContext.IsPrepared());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Gravity Field Geometry Context Distinguishes Primal And Linearization Preparation",
|
"Gravity Field Geometry Context Distinguishes Primal And Linearization Preparation",
|
||||||
tags::gravity_context
|
tags::gravity_context
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
@@ -177,6 +178,26 @@ namespace gravity_displacement_force_test_utils {
|
|||||||
return direction;
|
return direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mfem::Vector make_affine_displacement(
|
||||||
|
const mean_field::fem::FEM &f,
|
||||||
|
const double scale
|
||||||
|
) {
|
||||||
|
mfem::ParGridFunction field(f.displacementFes.get());
|
||||||
|
mfem::VectorFunctionCoefficient coefficient(
|
||||||
|
f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||||
|
value.SetSize(position.Size());
|
||||||
|
for (int dimension = 0; dimension < position.Size(); ++dimension) {
|
||||||
|
value(dimension) = scale * position(dimension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
field.ProjectCoefficient(coefficient);
|
||||||
|
|
||||||
|
mfem::Vector result;
|
||||||
|
field.GetTrueDofs(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] mfem::Vector make_vacuum_only_density(const mean_field::fem::FEM &f) {
|
[[nodiscard]] mfem::Vector make_vacuum_only_density(const mean_field::fem::FEM &f) {
|
||||||
mfem::ParGridFunction densityField(f.densityFes.get());
|
mfem::ParGridFunction densityField(f.densityFes.get());
|
||||||
densityField = 0.0;
|
densityField = 0.0;
|
||||||
@@ -422,6 +443,85 @@ TEST_CASE(
|
|||||||
CHECK(gravity_prepared_test_utils::global_norm(vacuumResidual, f.mesh->GetComm()) == 0.0);
|
CHECK(gravity_prepared_test_utils::global_norm(vacuumResidual, f.mesh->GetComm()) == 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Gravity Displacement Force Reports Candidate Mapping And Arithmetic Rejections",
|
||||||
|
tags::gravity_unit
|
||||||
|
) {
|
||||||
|
mean_field::utils::Args args = test_utils::setup_args();
|
||||||
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
mfem::Vector density = gravity_displacement_force_test_utils::make_density(f, 0.21);
|
||||||
|
mfem::Vector gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.37);
|
||||||
|
mfem::Vector displacement = gravity_displacement_force_test_utils::make_affine_displacement(f, -2.0);
|
||||||
|
mfem::Vector residual;
|
||||||
|
|
||||||
|
const auto invalidMapping = mean_field::operators::kernels::try_apply_gravity_displacement_force_residual(
|
||||||
|
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
|
||||||
|
);
|
||||||
|
REQUIRE_FALSE(invalidMapping.has_value());
|
||||||
|
CHECK(
|
||||||
|
invalidMapping.error().reason ==
|
||||||
|
mean_field::operators::kernels::GravityDisplacementForceRejectionReason::invalid_mapping
|
||||||
|
);
|
||||||
|
CHECK(invalidMapping.error().mappingStatus == mean_field::mapping::MappingStatus::non_positive_determinant);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
|
||||||
|
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
|
||||||
|
),
|
||||||
|
std::domain_error
|
||||||
|
);
|
||||||
|
|
||||||
|
displacement = 0.0;
|
||||||
|
density = 1.0e200;
|
||||||
|
gravityGradient = 1.0e200;
|
||||||
|
const auto nonFiniteArithmetic = mean_field::operators::kernels::try_apply_gravity_displacement_force_residual(
|
||||||
|
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
|
||||||
|
);
|
||||||
|
REQUIRE_FALSE(nonFiniteArithmetic.has_value());
|
||||||
|
CHECK(
|
||||||
|
nonFiniteArithmetic.error().reason ==
|
||||||
|
mean_field::operators::kernels::GravityDisplacementForceRejectionReason::non_finite_arithmetic
|
||||||
|
);
|
||||||
|
|
||||||
|
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
|
||||||
|
gravityPotential = 0.0;
|
||||||
|
auto revisions = gravity_displacement_force_test_utils::make_revisions();
|
||||||
|
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
|
f, *f.domainMapperStateless
|
||||||
|
);
|
||||||
|
gravity_displacement_force_test_utils::prepare_gravity_context(
|
||||||
|
gravityContext, density, displacement, gravityGradient, gravityPotential, revisions
|
||||||
|
);
|
||||||
|
mean_field::operators::PreparedGravityDisplacementForceOperator preparedOperator(
|
||||||
|
f, *f.domainMapperStateless, gravityContext
|
||||||
|
);
|
||||||
|
const auto preparedRejection = preparedOperator.TryPrepare();
|
||||||
|
REQUIRE_FALSE(preparedRejection.has_value());
|
||||||
|
CHECK(
|
||||||
|
preparedRejection.error().reason ==
|
||||||
|
mean_field::operators::kernels::GravityDisplacementForceRejectionReason::non_finite_arithmetic
|
||||||
|
);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(preparedOperator.Prepare(), std::domain_error);
|
||||||
|
|
||||||
|
density = gravity_displacement_force_test_utils::make_density(f, 0.21);
|
||||||
|
gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.37);
|
||||||
|
++revisions.density.value;
|
||||||
|
++revisions.gravity_gradient.value;
|
||||||
|
gravity_displacement_force_test_utils::prepare_gravity_context(
|
||||||
|
gravityContext, density, displacement, gravityGradient, gravityPotential, revisions
|
||||||
|
);
|
||||||
|
const auto preparedAccepted = preparedOperator.TryPrepare();
|
||||||
|
REQUIRE(preparedAccepted.has_value());
|
||||||
|
CHECK(preparedOperator.IsPrepared());
|
||||||
|
|
||||||
|
const auto accepted = mean_field::operators::kernels::try_apply_gravity_displacement_force_residual(
|
||||||
|
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
|
||||||
|
);
|
||||||
|
CHECK(accepted.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Prepared Gravity Displacement Force Reuses Shared Gravity Revisions",
|
"Prepared Gravity Displacement Force Reuses Shared Gravity Revisions",
|
||||||
tags::gravity_prepared
|
tags::gravity_prepared
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
#include <catch2/catch_approx.hpp>
|
#include <catch2/catch_approx.hpp>
|
||||||
@@ -16,22 +17,22 @@ namespace angular_momentum_test_utils {
|
|||||||
[[nodiscard]] mean_field::operators::AngularMomentumDependencies makeDependencies() {
|
[[nodiscard]] mean_field::operators::AngularMomentumDependencies makeDependencies() {
|
||||||
return {
|
return {
|
||||||
.discretization = {.identity = 15013, .revision = 3},
|
.discretization = {.identity = 15013, .revision = 3},
|
||||||
.density = {.identity = 15017, .revision = 5},
|
.density = {.identity = 15017, .revision = 5},
|
||||||
.displacement = {.identity = 15031, .revision = 7},
|
.displacement = {.identity = 15031, .revision = 7},
|
||||||
.rotation = {.identity = 15053, .revision = 11}
|
.rotation = {.identity = 15053, .revision = 11}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions makeGravityRevisions(
|
[[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions makeGravityRevisions(
|
||||||
const mean_field::operators::AngularMomentumDependencies &dependencies,
|
const mean_field::operators::AngularMomentumDependencies &dependencies,
|
||||||
const std::uint64_t gravityGradientRevision = 13,
|
const std::uint64_t gravityGradientRevision = 13,
|
||||||
const std::uint64_t gravityPotentialRevision = 17
|
const std::uint64_t gravityPotentialRevision = 17
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
.discretization = {.value = dependencies.discretization.revision},
|
.discretization = {.value = dependencies.discretization.revision},
|
||||||
.displacement = {.value = dependencies.displacement.revision},
|
.displacement = {.value = dependencies.displacement.revision},
|
||||||
.density = {.value = dependencies.density.revision},
|
.density = {.value = dependencies.density.revision},
|
||||||
.gravity_gradient = {.value = gravityGradientRevision},
|
.gravity_gradient = {.value = gravityGradientRevision},
|
||||||
.gravity_potential = {.value = gravityPotentialRevision}
|
.gravity_potential = {.value = gravityPotentialRevision}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -42,17 +43,17 @@ namespace angular_momentum_test_utils {
|
|||||||
const mfem::Vector &density,
|
const mfem::Vector &density,
|
||||||
const mfem::Vector &displacement,
|
const mfem::Vector &displacement,
|
||||||
const mean_field::operators::AngularMomentumDependencies &dependencies,
|
const mean_field::operators::AngularMomentumDependencies &dependencies,
|
||||||
const std::uint64_t gravityGradientRevision = 13,
|
const std::uint64_t gravityGradientRevision = 13,
|
||||||
const std::uint64_t gravityPotentialRevision = 17
|
const std::uint64_t gravityPotentialRevision = 17
|
||||||
) {
|
) {
|
||||||
mfem::Vector gravityGradient(finiteElements.gravityFluxFes->GetTrueVSize());
|
mfem::Vector gravityGradient(finiteElements.gravityFluxFes->GetTrueVSize());
|
||||||
mfem::Vector gravityPotential(finiteElements.gravityPotentialFes->GetTrueVSize());
|
mfem::Vector gravityPotential(finiteElements.gravityPotentialFes->GetTrueVSize());
|
||||||
gravityGradient = 0.0;
|
gravityGradient = 0.0;
|
||||||
gravityPotential = 0.0;
|
gravityPotential = 0.0;
|
||||||
context.Prepare(
|
context.Prepare(
|
||||||
{.density = context.GetDensityMap().gather(density),
|
{.density = context.GetDensityMap().gather(density),
|
||||||
.displacement = context.GetDisplacementMap().gather(displacement),
|
.displacement = context.GetDisplacementMap().gather(displacement),
|
||||||
.gravity_gradient = context.GetGravityGradientMap().gather(gravityGradient),
|
.gravity_gradient = context.GetGravityGradientMap().gather(gravityGradient),
|
||||||
.gravity_potential = context.GetGravityPotentialMap().gather(gravityPotential)},
|
.gravity_potential = context.GetGravityPotentialMap().gather(gravityPotential)},
|
||||||
makeGravityRevisions(dependencies, gravityGradientRevision, gravityPotentialRevision)
|
makeGravityRevisions(dependencies, gravityGradientRevision, gravityPotentialRevision)
|
||||||
);
|
);
|
||||||
@@ -64,8 +65,8 @@ namespace angular_momentum_test_utils {
|
|||||||
) {
|
) {
|
||||||
mfem::ParGridFunction field(finiteElements.densityFes.get());
|
mfem::ParGridFunction field(finiteElements.densityFes.get());
|
||||||
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
|
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
|
||||||
return 0.94 + 0.08 * std::sin(0.71 * position(0) + phase) +
|
return 0.94 + 0.08 * std::sin(0.71 * position(0) + phase) + 0.05 * std::cos(0.63 * position(1) - phase) +
|
||||||
0.05 * std::cos(0.63 * position(1) - phase) + 0.03 * position(2) * position(2);
|
0.03 * position(2) * position(2);
|
||||||
});
|
});
|
||||||
field.ProjectCoefficient(coefficient);
|
field.ProjectCoefficient(coefficient);
|
||||||
mfem::Vector result;
|
mfem::Vector result;
|
||||||
@@ -79,8 +80,8 @@ namespace angular_momentum_test_utils {
|
|||||||
) {
|
) {
|
||||||
mfem::ParGridFunction field(finiteElements.densityFes.get());
|
mfem::ParGridFunction field(finiteElements.densityFes.get());
|
||||||
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
|
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
|
||||||
return 0.17 * std::sin(0.83 * position(0) + phase) -
|
return 0.17 * std::sin(0.83 * position(0) + phase) - 0.12 * std::cos(0.79 * position(1) - phase) +
|
||||||
0.12 * std::cos(0.79 * position(1) - phase) + 0.06 * position(2);
|
0.06 * position(2);
|
||||||
});
|
});
|
||||||
field.ProjectCoefficient(coefficient);
|
field.ProjectCoefficient(coefficient);
|
||||||
mfem::Vector result;
|
mfem::Vector result;
|
||||||
@@ -110,8 +111,7 @@ namespace angular_momentum_test_utils {
|
|||||||
) {
|
) {
|
||||||
mfem::ParGridFunction field(finiteElements.displacementFes.get());
|
mfem::ParGridFunction field(finiteElements.displacementFes.get());
|
||||||
mfem::VectorFunctionCoefficient coefficient(
|
mfem::VectorFunctionCoefficient coefficient(
|
||||||
finiteElements.mesh->Dimension(),
|
finiteElements.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||||
[scale](const mfem::Vector &position, mfem::Vector &value) {
|
|
||||||
value.SetSize(position.Size());
|
value.SetSize(position.Size());
|
||||||
for (int component = 0; component < position.Size(); ++component) {
|
for (int component = 0; component < position.Size(); ++component) {
|
||||||
value(component) = scale * position(component);
|
value(component) = scale * position(component);
|
||||||
@@ -130,8 +130,7 @@ namespace angular_momentum_test_utils {
|
|||||||
) {
|
) {
|
||||||
mfem::ParGridFunction field(finiteElements.displacementFes.get());
|
mfem::ParGridFunction field(finiteElements.displacementFes.get());
|
||||||
mfem::VectorFunctionCoefficient coefficient(
|
mfem::VectorFunctionCoefficient coefficient(
|
||||||
finiteElements.mesh->Dimension(),
|
finiteElements.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||||
[scale](const mfem::Vector &position, mfem::Vector &value) {
|
|
||||||
value.SetSize(3);
|
value.SetSize(3);
|
||||||
value(0) = scale * (0.07 * position(0) + 0.018 * position(1) * position(2));
|
value(0) = scale * (0.07 * position(0) + 0.018 * position(1) * position(2));
|
||||||
value(1) = scale * (-0.05 * position(1) + 0.013 * position(0) * position(2));
|
value(1) = scale * (-0.05 * position(1) + 0.013 * position(0) * position(2));
|
||||||
@@ -151,7 +150,10 @@ namespace angular_momentum_test_utils {
|
|||||||
return value(0);
|
return value(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] double relativeError(const double actual, const double expected) {
|
[[nodiscard]] double relativeError(
|
||||||
|
const double actual,
|
||||||
|
const double expected
|
||||||
|
) {
|
||||||
return std::abs(actual - expected) /
|
return std::abs(actual - expected) /
|
||||||
std::max({std::abs(actual), std::abs(expected), 100.0 * std::numeric_limits<double>::epsilon()});
|
std::max({std::abs(actual), std::abs(expected), 100.0 * std::numeric_limits<double>::epsilon()});
|
||||||
}
|
}
|
||||||
@@ -168,48 +170,34 @@ TEST_CASE(
|
|||||||
STATIC_CHECK_FALSE(std::is_copy_constructible_v<Operator>);
|
STATIC_CHECK_FALSE(std::is_copy_constructible_v<Operator>);
|
||||||
STATIC_CHECK_FALSE(std::is_move_constructible_v<Operator>);
|
STATIC_CHECK_FALSE(std::is_move_constructible_v<Operator>);
|
||||||
|
|
||||||
utils::Args arguments = test_utils::setup_args();
|
utils::Args arguments = test_utils::setup_args();
|
||||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
REQUIRE(finiteElements.okay());
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
constexpr double densityValue = 1.37;
|
constexpr double densityValue = 1.37;
|
||||||
constexpr double angularVelocity = 0.73;
|
constexpr double angularVelocity = 0.73;
|
||||||
constexpr double targetAngularMomentum = 0.41;
|
constexpr double targetAngularMomentum = 0.41;
|
||||||
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
|
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
|
||||||
const mfem::Vector density = angular_momentum_test_utils::projectConstantDensity(
|
const mfem::Vector density =
|
||||||
finiteElements,
|
angular_momentum_test_utils::projectConstantDensity(finiteElements, densityValue, &densityField);
|
||||||
densityValue,
|
|
||||||
&densityField
|
|
||||||
);
|
|
||||||
mfem::Vector displacement(finiteElements.displacementFes->GetTrueVSize());
|
mfem::Vector displacement(finiteElements.displacementFes->GetTrueVSize());
|
||||||
displacement = 0.0;
|
displacement = 0.0;
|
||||||
finiteElements.displacement->SetFromTrueDofs(displacement);
|
finiteElements.displacement->SetFromTrueDofs(displacement);
|
||||||
|
|
||||||
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
||||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
finiteElements,
|
finiteElements, *finiteElements.domainMapperStateless
|
||||||
*finiteElements.domainMapperStateless
|
|
||||||
);
|
);
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, density, displacement, dependencies
|
||||||
finiteElements,
|
|
||||||
density,
|
|
||||||
displacement,
|
|
||||||
dependencies
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const models::CompiledFixedAngularMomentum originConstraint = models::compileConstraint(
|
const models::CompiledFixedAngularMomentum originConstraint = models::compileConstraint(
|
||||||
integral::FixedAngularMomentum({
|
integral::FixedAngularMomentum(
|
||||||
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
|
{.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum}, .axis = {0.0, 0.0, 4.0}}
|
||||||
.axis = {0.0, 0.0, 4.0}
|
)
|
||||||
})
|
|
||||||
);
|
|
||||||
Operator origin(
|
|
||||||
finiteElements,
|
|
||||||
*finiteElements.domainMapperStateless,
|
|
||||||
gravityContext,
|
|
||||||
originConstraint
|
|
||||||
);
|
);
|
||||||
|
Operator origin(finiteElements, *finiteElements.domainMapperStateless, gravityContext, originConstraint);
|
||||||
const auto initial = origin.Prepare(angularVelocity, dependencies);
|
const auto initial = origin.Prepare(angularVelocity, dependencies);
|
||||||
CHECK(initial.rebuiltStaticPlan);
|
CHECK(initial.rebuiltStaticPlan);
|
||||||
CHECK(initial.refreshedGeometry);
|
CHECK(initial.refreshedGeometry);
|
||||||
@@ -219,10 +207,11 @@ TEST_CASE(
|
|||||||
|
|
||||||
const double independentMoment = analysis::get_moment_of_inertia(finiteElements, densityField);
|
const double independentMoment = analysis::get_moment_of_inertia(finiteElements, densityField);
|
||||||
CHECK(angular_momentum_test_utils::relativeError(origin.GetMomentOfInertia(), independentMoment) < 2.0e-13);
|
CHECK(angular_momentum_test_utils::relativeError(origin.GetMomentOfInertia(), independentMoment) < 2.0e-13);
|
||||||
CHECK(origin.GetCurrentAngularMomentum() ==
|
CHECK(origin.GetCurrentAngularMomentum() == Approx(angularVelocity * origin.GetMomentOfInertia()).epsilon(2.0e-15));
|
||||||
Approx(angularVelocity * origin.GetMomentOfInertia()).epsilon(2.0e-15));
|
CHECK(
|
||||||
CHECK(angular_momentum_test_utils::residual(origin) ==
|
angular_momentum_test_utils::residual(origin) ==
|
||||||
Approx(angularVelocity * origin.GetMomentOfInertia() - targetAngularMomentum).epsilon(2.0e-15));
|
Approx(angularVelocity * origin.GetMomentOfInertia() - targetAngularMomentum).epsilon(2.0e-15)
|
||||||
|
);
|
||||||
|
|
||||||
const auto report = origin.GetConstraintReport();
|
const auto report = origin.GetConstraintReport();
|
||||||
CHECK(report.targetAngularMomentum == targetAngularMomentum);
|
CHECK(report.targetAngularMomentum == targetAngularMomentum);
|
||||||
@@ -239,11 +228,7 @@ TEST_CASE(
|
|||||||
angular_momentum_test_utils::projectAffineDisplacement(finiteElements, affineScale);
|
angular_momentum_test_utils::projectAffineDisplacement(finiteElements, affineScale);
|
||||||
++dependencies.displacement.revision;
|
++dependencies.displacement.revision;
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, density, affineDisplacement, dependencies
|
||||||
finiteElements,
|
|
||||||
density,
|
|
||||||
affineDisplacement,
|
|
||||||
dependencies
|
|
||||||
);
|
);
|
||||||
const auto affine = origin.Prepare(angularVelocity, dependencies);
|
const auto affine = origin.Prepare(angularVelocity, dependencies);
|
||||||
CHECK(affine.refreshedGeometry);
|
CHECK(affine.refreshedGeometry);
|
||||||
@@ -257,38 +242,30 @@ TEST_CASE(
|
|||||||
displacement = 0.0;
|
displacement = 0.0;
|
||||||
++dependencies.displacement.revision;
|
++dependencies.displacement.revision;
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, density, displacement, dependencies
|
||||||
finiteElements,
|
|
||||||
density,
|
|
||||||
displacement,
|
|
||||||
dependencies
|
|
||||||
);
|
);
|
||||||
origin.Prepare(angularVelocity, dependencies);
|
origin.Prepare(angularVelocity, dependencies);
|
||||||
constexpr std::array<double, 3> shiftedCenter{0.27, -0.19, 0.31};
|
constexpr std::array<double, 3> shiftedCenter{0.27, -0.19, 0.31};
|
||||||
Operator shifted(
|
Operator shifted(
|
||||||
finiteElements,
|
finiteElements, *finiteElements.domainMapperStateless, gravityContext,
|
||||||
*finiteElements.domainMapperStateless,
|
models::compileConstraint(
|
||||||
gravityContext,
|
integral::FixedAngularMomentum(
|
||||||
models::compileConstraint(integral::FixedAngularMomentum({
|
{.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
|
||||||
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
|
.axis = {0.0, 0.0, 1.0},
|
||||||
.axis = {0.0, 0.0, 1.0},
|
.center = shiftedCenter}
|
||||||
.center = shiftedCenter
|
)
|
||||||
}))
|
)
|
||||||
);
|
);
|
||||||
shifted.Prepare(angularVelocity, dependencies);
|
shifted.Prepare(angularVelocity, dependencies);
|
||||||
|
|
||||||
const double mass = analysis::domain_integrate_grid_function(
|
const double mass = analysis::domain_integrate_grid_function(
|
||||||
finiteElements,
|
finiteElements, densityField, utils::DOMAINS::STELLAR, mapping::COORDINATE_SPACE::PHYSICAL
|
||||||
densityField,
|
|
||||||
utils::DOMAINS::STELLAR,
|
|
||||||
mapping::COORDINATE_SPACE::PHYSICAL
|
|
||||||
);
|
);
|
||||||
const mfem::Vector centerOfMass = analysis::get_com(finiteElements, densityField);
|
const mfem::Vector centerOfMass = analysis::get_com(finiteElements, densityField);
|
||||||
const double expectedShiftedMoment = origin.GetMomentOfInertia() +
|
const double expectedShiftedMoment =
|
||||||
mass * (shiftedCenter[0] * shiftedCenter[0] +
|
origin.GetMomentOfInertia() +
|
||||||
shiftedCenter[1] * shiftedCenter[1]) -
|
mass * (shiftedCenter[0] * shiftedCenter[0] + shiftedCenter[1] * shiftedCenter[1]) -
|
||||||
2.0 * mass * (shiftedCenter[0] * centerOfMass(0) +
|
2.0 * mass * (shiftedCenter[0] * centerOfMass(0) + shiftedCenter[1] * centerOfMass(1));
|
||||||
shiftedCenter[1] * centerOfMass(1));
|
|
||||||
CHECK(angular_momentum_test_utils::relativeError(shifted.GetMomentOfInertia(), expectedShiftedMoment) < 3.0e-13);
|
CHECK(angular_momentum_test_utils::relativeError(shifted.GetMomentOfInertia(), expectedShiftedMoment) < 3.0e-13);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,45 +275,33 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
utils::Args arguments = test_utils::setup_args();
|
utils::Args arguments = test_utils::setup_args();
|
||||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
REQUIRE(finiteElements.okay());
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
const mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.31);
|
const mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.31);
|
||||||
const mfem::Vector densityDirection =
|
const mfem::Vector densityDirection = angular_momentum_test_utils::projectDensityDirection(finiteElements, 0.67);
|
||||||
angular_momentum_test_utils::projectDensityDirection(finiteElements, 0.67);
|
const mfem::Vector displacement = angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.43);
|
||||||
const mfem::Vector displacement =
|
|
||||||
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.43);
|
|
||||||
const mfem::Vector displacementDirection =
|
const mfem::Vector displacementDirection =
|
||||||
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, -0.79);
|
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, -0.79);
|
||||||
constexpr double angularVelocity = 0.63;
|
constexpr double angularVelocity = 0.63;
|
||||||
constexpr double angularVelocityDirection = -0.37;
|
constexpr double angularVelocityDirection = -0.37;
|
||||||
|
|
||||||
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
||||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
finiteElements,
|
finiteElements, *finiteElements.domainMapperStateless
|
||||||
*finiteElements.domainMapperStateless
|
|
||||||
);
|
);
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, density, displacement, dependencies
|
||||||
finiteElements,
|
|
||||||
density,
|
|
||||||
displacement,
|
|
||||||
dependencies
|
|
||||||
);
|
);
|
||||||
operators::PreparedAngularMomentumOperator operation(
|
operators::PreparedAngularMomentumOperator operation(
|
||||||
finiteElements,
|
finiteElements, *finiteElements.domainMapperStateless, gravityContext,
|
||||||
*finiteElements.domainMapperStateless,
|
models::compileConstraint(integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.81}}))
|
||||||
gravityContext,
|
|
||||||
models::compileConstraint(
|
|
||||||
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.81}})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
operation.Prepare(angularVelocity, dependencies);
|
operation.Prepare(angularVelocity, dependencies);
|
||||||
|
|
||||||
const mfem::Vector reducedDensityDirection = gravityContext.GetDensityMap().gather(densityDirection);
|
const mfem::Vector reducedDensityDirection = gravityContext.GetDensityMap().gather(densityDirection);
|
||||||
const mfem::Vector reducedDisplacementDirection =
|
const mfem::Vector reducedDisplacementDirection = gravityContext.GetDisplacementMap().gather(displacementDirection);
|
||||||
gravityContext.GetDisplacementMap().gather(displacementDirection);
|
|
||||||
mfem::Vector densityAction;
|
mfem::Vector densityAction;
|
||||||
mfem::Vector geometryAction;
|
mfem::Vector geometryAction;
|
||||||
mfem::Vector angularVelocityAction;
|
mfem::Vector angularVelocityAction;
|
||||||
@@ -345,17 +310,17 @@ TEST_CASE(
|
|||||||
operation.ApplyDisplacementJacobianAction(reducedDisplacementDirection, geometryAction);
|
operation.ApplyDisplacementJacobianAction(reducedDisplacementDirection, geometryAction);
|
||||||
operation.ApplyAngularVelocityJacobianAction(angularVelocityDirection, angularVelocityAction);
|
operation.ApplyAngularVelocityJacobianAction(angularVelocityDirection, angularVelocityAction);
|
||||||
operation.ApplyCompleteJacobianAction(
|
operation.ApplyCompleteJacobianAction(
|
||||||
reducedDensityDirection,
|
reducedDensityDirection, reducedDisplacementDirection, angularVelocityDirection, completeAction
|
||||||
reducedDisplacementDirection,
|
);
|
||||||
angularVelocityDirection,
|
CHECK(
|
||||||
completeAction
|
angular_momentum_test_utils::relativeError(
|
||||||
|
completeAction(0), densityAction(0) + geometryAction(0) + angularVelocityAction(0)
|
||||||
|
) < 3.0e-15
|
||||||
|
);
|
||||||
|
CHECK(
|
||||||
|
angularVelocityAction(0) ==
|
||||||
|
Catch::Approx(operation.GetMomentOfInertia() * angularVelocityDirection).epsilon(2.0e-15)
|
||||||
);
|
);
|
||||||
CHECK(angular_momentum_test_utils::relativeError(
|
|
||||||
completeAction(0),
|
|
||||||
densityAction(0) + geometryAction(0) + angularVelocityAction(0)
|
|
||||||
) < 3.0e-15);
|
|
||||||
CHECK(angularVelocityAction(0) ==
|
|
||||||
Catch::Approx(operation.GetMomentOfInertia() * angularVelocityDirection).epsilon(2.0e-15));
|
|
||||||
|
|
||||||
constexpr double angularStep = 1.0e-6;
|
constexpr double angularStep = 1.0e-6;
|
||||||
++dependencies.rotation.revision;
|
++dependencies.rotation.revision;
|
||||||
@@ -363,7 +328,7 @@ TEST_CASE(
|
|||||||
const double angularPlus = angular_momentum_test_utils::residual(operation);
|
const double angularPlus = angular_momentum_test_utils::residual(operation);
|
||||||
++dependencies.rotation.revision;
|
++dependencies.rotation.revision;
|
||||||
operation.Prepare(angularVelocity - angularStep * angularVelocityDirection, dependencies);
|
operation.Prepare(angularVelocity - angularStep * angularVelocityDirection, dependencies);
|
||||||
const double angularMinus = angular_momentum_test_utils::residual(operation);
|
const double angularMinus = angular_momentum_test_utils::residual(operation);
|
||||||
const double angularDifference = (angularPlus - angularMinus) / (2.0 * angularStep);
|
const double angularDifference = (angularPlus - angularMinus) / (2.0 * angularStep);
|
||||||
CHECK(angular_momentum_test_utils::relativeError(angularVelocityAction(0), angularDifference) < 2.0e-10);
|
CHECK(angular_momentum_test_utils::relativeError(angularVelocityAction(0), angularDifference) < 2.0e-10);
|
||||||
|
|
||||||
@@ -372,11 +337,7 @@ TEST_CASE(
|
|||||||
densityPlus.Add(densityStep, densityDirection);
|
densityPlus.Add(densityStep, densityDirection);
|
||||||
++dependencies.density.revision;
|
++dependencies.density.revision;
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, densityPlus, displacement, dependencies
|
||||||
finiteElements,
|
|
||||||
densityPlus,
|
|
||||||
displacement,
|
|
||||||
dependencies
|
|
||||||
);
|
);
|
||||||
operation.Prepare(angularVelocity, dependencies);
|
operation.Prepare(angularVelocity, dependencies);
|
||||||
const double densityPlusResidual = angular_momentum_test_utils::residual(operation);
|
const double densityPlusResidual = angular_momentum_test_utils::residual(operation);
|
||||||
@@ -384,15 +345,11 @@ TEST_CASE(
|
|||||||
densityMinus.Add(-densityStep, densityDirection);
|
densityMinus.Add(-densityStep, densityDirection);
|
||||||
++dependencies.density.revision;
|
++dependencies.density.revision;
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, densityMinus, displacement, dependencies
|
||||||
finiteElements,
|
|
||||||
densityMinus,
|
|
||||||
displacement,
|
|
||||||
dependencies
|
|
||||||
);
|
);
|
||||||
operation.Prepare(angularVelocity, dependencies);
|
operation.Prepare(angularVelocity, dependencies);
|
||||||
const double densityMinusResidual = angular_momentum_test_utils::residual(operation);
|
const double densityMinusResidual = angular_momentum_test_utils::residual(operation);
|
||||||
const double densityDifference = (densityPlusResidual - densityMinusResidual) / (2.0 * densityStep);
|
const double densityDifference = (densityPlusResidual - densityMinusResidual) / (2.0 * densityStep);
|
||||||
CHECK(angular_momentum_test_utils::relativeError(densityAction(0), densityDifference) < 4.0e-8);
|
CHECK(angular_momentum_test_utils::relativeError(densityAction(0), densityDifference) < 4.0e-8);
|
||||||
|
|
||||||
constexpr double geometryStep = 1.0e-6;
|
constexpr double geometryStep = 1.0e-6;
|
||||||
@@ -401,11 +358,7 @@ TEST_CASE(
|
|||||||
++dependencies.density.revision;
|
++dependencies.density.revision;
|
||||||
++dependencies.displacement.revision;
|
++dependencies.displacement.revision;
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, density, displacementPlus, dependencies
|
||||||
finiteElements,
|
|
||||||
density,
|
|
||||||
displacementPlus,
|
|
||||||
dependencies
|
|
||||||
);
|
);
|
||||||
operation.Prepare(angularVelocity, dependencies);
|
operation.Prepare(angularVelocity, dependencies);
|
||||||
const double geometryPlusResidual = angular_momentum_test_utils::residual(operation);
|
const double geometryPlusResidual = angular_momentum_test_utils::residual(operation);
|
||||||
@@ -413,19 +366,19 @@ TEST_CASE(
|
|||||||
displacementMinus.Add(-geometryStep, displacementDirection);
|
displacementMinus.Add(-geometryStep, displacementDirection);
|
||||||
++dependencies.displacement.revision;
|
++dependencies.displacement.revision;
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, density, displacementMinus, dependencies
|
||||||
finiteElements,
|
|
||||||
density,
|
|
||||||
displacementMinus,
|
|
||||||
dependencies
|
|
||||||
);
|
);
|
||||||
operation.Prepare(angularVelocity, dependencies);
|
operation.Prepare(angularVelocity, dependencies);
|
||||||
const double geometryMinusResidual = angular_momentum_test_utils::residual(operation);
|
const double geometryMinusResidual = angular_momentum_test_utils::residual(operation);
|
||||||
const double geometryDifference = (geometryPlusResidual - geometryMinusResidual) / (2.0 * geometryStep);
|
const double geometryDifference = (geometryPlusResidual - geometryMinusResidual) / (2.0 * geometryStep);
|
||||||
INFO("Density angular-momentum derivative error = " <<
|
INFO(
|
||||||
angular_momentum_test_utils::relativeError(densityAction(0), densityDifference));
|
"Density angular-momentum derivative error = "
|
||||||
INFO("Geometry angular-momentum derivative error = " <<
|
<< angular_momentum_test_utils::relativeError(densityAction(0), densityDifference)
|
||||||
angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference));
|
);
|
||||||
|
INFO(
|
||||||
|
"Geometry angular-momentum derivative error = "
|
||||||
|
<< angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference)
|
||||||
|
);
|
||||||
CHECK(angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference) < 4.0e-7);
|
CHECK(angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference) < 4.0e-7);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,53 +388,35 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
utils::Args arguments = test_utils::setup_args();
|
utils::Args arguments = test_utils::setup_args();
|
||||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
REQUIRE(finiteElements.okay());
|
REQUIRE(finiteElements.okay());
|
||||||
mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.29);
|
mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.29);
|
||||||
mfem::Vector displacement =
|
mfem::Vector displacement = angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.41);
|
||||||
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.41);
|
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
||||||
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
|
||||||
std::uint64_t gravityPotentialRevision = 17;
|
std::uint64_t gravityPotentialRevision = 17;
|
||||||
|
|
||||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
finiteElements,
|
finiteElements, *finiteElements.domainMapperStateless
|
||||||
*finiteElements.domainMapperStateless
|
|
||||||
);
|
);
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, density, displacement, dependencies, 13, gravityPotentialRevision
|
||||||
finiteElements,
|
|
||||||
density,
|
|
||||||
displacement,
|
|
||||||
dependencies,
|
|
||||||
13,
|
|
||||||
gravityPotentialRevision
|
|
||||||
);
|
);
|
||||||
operators::PreparedAngularMomentumOperator operation(
|
operators::PreparedAngularMomentumOperator operation(
|
||||||
finiteElements,
|
finiteElements, *finiteElements.domainMapperStateless, gravityContext,
|
||||||
*finiteElements.domainMapperStateless,
|
models::compileConstraint(integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.71}}))
|
||||||
gravityContext,
|
|
||||||
models::compileConstraint(
|
|
||||||
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.71}})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
operation.Prepare(0.52, dependencies);
|
operation.Prepare(0.52, dependencies);
|
||||||
const auto preparationCount = operation.GetPreparationCount();
|
const auto preparationCount = operation.GetPreparationCount();
|
||||||
const double moment = operation.GetMomentOfInertia();
|
const double moment = operation.GetMomentOfInertia();
|
||||||
|
|
||||||
const auto repeated = operation.Prepare(0.52, dependencies);
|
const auto repeated = operation.Prepare(0.52, dependencies);
|
||||||
CHECK_FALSE(repeated.DidAnyWork());
|
CHECK_FALSE(repeated.DidAnyWork());
|
||||||
CHECK(operation.GetPreparationCount() == preparationCount);
|
CHECK(operation.GetPreparationCount() == preparationCount);
|
||||||
|
|
||||||
++gravityPotentialRevision;
|
++gravityPotentialRevision;
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, density, displacement, dependencies, 13, gravityPotentialRevision
|
||||||
finiteElements,
|
|
||||||
density,
|
|
||||||
displacement,
|
|
||||||
dependencies,
|
|
||||||
13,
|
|
||||||
gravityPotentialRevision
|
|
||||||
);
|
);
|
||||||
const auto unrelatedPotential = operation.Prepare(0.52, dependencies);
|
const auto unrelatedPotential = operation.Prepare(0.52, dependencies);
|
||||||
CHECK_FALSE(unrelatedPotential.DidAnyWork());
|
CHECK_FALSE(unrelatedPotential.DidAnyWork());
|
||||||
@@ -494,19 +429,15 @@ TEST_CASE(
|
|||||||
CHECK_FALSE(rotationOnly.refreshedDensity);
|
CHECK_FALSE(rotationOnly.refreshedDensity);
|
||||||
CHECK_FALSE(rotationOnly.refreshedGeometry);
|
CHECK_FALSE(rotationOnly.refreshedGeometry);
|
||||||
CHECK(operation.GetMomentOfInertia() == moment);
|
CHECK(operation.GetMomentOfInertia() == moment);
|
||||||
CHECK(angular_momentum_test_utils::residual(operation) - residualBeforeRotation ==
|
CHECK(
|
||||||
Catch::Approx((0.81 - 0.52) * moment).epsilon(3.0e-15));
|
angular_momentum_test_utils::residual(operation) - residualBeforeRotation ==
|
||||||
|
Catch::Approx((0.81 - 0.52) * moment).epsilon(3.0e-15)
|
||||||
|
);
|
||||||
|
|
||||||
density = angular_momentum_test_utils::projectDensity(finiteElements, 0.83);
|
density = angular_momentum_test_utils::projectDensity(finiteElements, 0.83);
|
||||||
++dependencies.density.revision;
|
++dependencies.density.revision;
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, density, displacement, dependencies, 13, gravityPotentialRevision
|
||||||
finiteElements,
|
|
||||||
density,
|
|
||||||
displacement,
|
|
||||||
dependencies,
|
|
||||||
13,
|
|
||||||
gravityPotentialRevision
|
|
||||||
);
|
);
|
||||||
const auto densityOnly = operation.Prepare(0.81, dependencies);
|
const auto densityOnly = operation.Prepare(0.81, dependencies);
|
||||||
CHECK(densityOnly.refreshedDensity);
|
CHECK(densityOnly.refreshedDensity);
|
||||||
@@ -516,16 +447,84 @@ TEST_CASE(
|
|||||||
displacement = angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.87);
|
displacement = angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.87);
|
||||||
++dependencies.displacement.revision;
|
++dependencies.displacement.revision;
|
||||||
angular_momentum_test_utils::prepareGravityContext(
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
gravityContext,
|
gravityContext, finiteElements, density, displacement, dependencies, 13, gravityPotentialRevision
|
||||||
finiteElements,
|
|
||||||
density,
|
|
||||||
displacement,
|
|
||||||
dependencies,
|
|
||||||
13,
|
|
||||||
gravityPotentialRevision
|
|
||||||
);
|
);
|
||||||
const auto geometryOnly = operation.Prepare(0.81, dependencies);
|
const auto geometryOnly = operation.Prepare(0.81, dependencies);
|
||||||
CHECK(geometryOnly.refreshedGeometry);
|
CHECK(geometryOnly.refreshedGeometry);
|
||||||
CHECK_FALSE(geometryOnly.refreshedDensity);
|
CHECK_FALSE(geometryOnly.refreshedDensity);
|
||||||
CHECK_FALSE(geometryOnly.updatedAngularVelocity);
|
CHECK_FALSE(geometryOnly.updatedAngularVelocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Angular Momentum Returns Explicit Candidate Rejections And Recovers",
|
||||||
|
"[fixed-angular-momentum][prepared][trial-outcome]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
STATIC_CHECK(std::is_trivially_copyable_v<operators::AngularMomentumPreparationRejection>);
|
||||||
|
|
||||||
|
utils::Args arguments = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
|
constexpr double angularVelocity = 0.52;
|
||||||
|
const mfem::Vector positiveDensity = angular_momentum_test_utils::projectConstantDensity(finiteElements, 0.91);
|
||||||
|
const mfem::Vector negativeDensity = angular_momentum_test_utils::projectConstantDensity(finiteElements, -0.91);
|
||||||
|
mfem::Vector displacement(finiteElements.displacementFes->GetTrueVSize());
|
||||||
|
displacement = 0.0;
|
||||||
|
|
||||||
|
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
||||||
|
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
|
finiteElements, *finiteElements.domainMapperStateless
|
||||||
|
);
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext, finiteElements, positiveDensity, displacement, dependencies
|
||||||
|
);
|
||||||
|
operators::PreparedAngularMomentumOperator operation(
|
||||||
|
finiteElements, *finiteElements.domainMapperStateless, gravityContext,
|
||||||
|
models::compileConstraint(integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.71}}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto initial = operation.TryPrepare(angularVelocity, dependencies);
|
||||||
|
REQUIRE(initial.has_value());
|
||||||
|
const std::uint64_t initialPreparationCount = operation.GetPreparationCount();
|
||||||
|
|
||||||
|
const std::uint64_t successfulPreparations = initialPreparationCount;
|
||||||
|
|
||||||
|
++dependencies.density.revision;
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext, finiteElements, negativeDensity, displacement, dependencies
|
||||||
|
);
|
||||||
|
const auto negativeMoment = operation.TryPrepare(angularVelocity, dependencies);
|
||||||
|
REQUIRE_FALSE(negativeMoment.has_value());
|
||||||
|
CHECK(
|
||||||
|
negativeMoment.error().reason ==
|
||||||
|
operators::AngularMomentumPreparationRejectionReason::negative_moment_of_inertia
|
||||||
|
);
|
||||||
|
CHECK(negativeMoment.error().momentOfInertia < 0.0);
|
||||||
|
CHECK(operation.GetPreparationCount() == successfulPreparations);
|
||||||
|
CHECK_FALSE(operation.IsPrepared());
|
||||||
|
REQUIRE_THROWS_AS(operation.Prepare(angularVelocity, dependencies), std::domain_error);
|
||||||
|
|
||||||
|
++dependencies.density.revision;
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext, finiteElements, positiveDensity, displacement, dependencies
|
||||||
|
);
|
||||||
|
const auto recovered = operation.TryPrepare(angularVelocity, dependencies);
|
||||||
|
REQUIRE(recovered.has_value());
|
||||||
|
CHECK(operation.IsPrepared());
|
||||||
|
|
||||||
|
++dependencies.rotation.revision;
|
||||||
|
const auto nonFiniteAngularVelocity = operation.TryPrepare(std::numeric_limits<double>::quiet_NaN(), dependencies);
|
||||||
|
REQUIRE_FALSE(nonFiniteAngularVelocity.has_value());
|
||||||
|
CHECK(
|
||||||
|
nonFiniteAngularVelocity.error().reason ==
|
||||||
|
operators::AngularMomentumPreparationRejectionReason::non_finite_angular_velocity
|
||||||
|
);
|
||||||
|
CHECK_FALSE(operation.IsPrepared());
|
||||||
|
REQUIRE_THROWS_AS(operation.Prepare(std::numeric_limits<double>::quiet_NaN(), dependencies), std::domain_error);
|
||||||
|
|
||||||
|
++dependencies.rotation.revision;
|
||||||
|
REQUIRE(operation.TryPrepare(angularVelocity, dependencies).has_value());
|
||||||
|
CHECK(operation.IsPrepared());
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
import mean_field;
|
import mean_field;
|
||||||
@@ -101,6 +102,22 @@ namespace prepared_barotropic_closure_test_utils {
|
|||||||
return project_scalar(finiteElementSpace, coefficient);
|
return project_scalar(finiteElementSpace, coefficient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mfem::Vector make_folding_displacement(const mean_field::fem::FEM &f) {
|
||||||
|
mfem::ParGridFunction fieldValue(f.displacementFes.get());
|
||||||
|
mfem::VectorFunctionCoefficient coefficient(
|
||||||
|
f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) {
|
||||||
|
value.SetSize(position.Size());
|
||||||
|
value = 0.0;
|
||||||
|
value(0) = -2.0 * position(0);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
fieldValue.ProjectCoefficient(coefficient);
|
||||||
|
|
||||||
|
mfem::Vector result;
|
||||||
|
fieldValue.GetTrueDofs(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] mfem::Vector reduce(
|
[[nodiscard]] mfem::Vector reduce(
|
||||||
const field::FieldDofMap &map,
|
const field::FieldDofMap &map,
|
||||||
const mfem::Vector &full
|
const mfem::Vector &full
|
||||||
@@ -263,6 +280,7 @@ namespace prepared_barotropic_closure_test_utils {
|
|||||||
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<Operator>);
|
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<Operator>);
|
||||||
STATIC_REQUIRE_FALSE(std::is_move_constructible_v<Operator>);
|
STATIC_REQUIRE_FALSE(std::is_move_constructible_v<Operator>);
|
||||||
STATIC_REQUIRE_FALSE(std::is_move_assignable_v<Operator>);
|
STATIC_REQUIRE_FALSE(std::is_move_assignable_v<Operator>);
|
||||||
|
STATIC_REQUIRE(std::is_trivially_copyable_v<mean_field::operators::BarotropicClosurePreparationRejection>);
|
||||||
|
|
||||||
auto args = test_utils::setup_args();
|
auto args = test_utils::setup_args();
|
||||||
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
@@ -303,6 +321,115 @@ namespace prepared_barotropic_closure_test_utils {
|
|||||||
CHECK(globalEnthalpyReduced < globalEnthalpyFull);
|
CHECK(globalEnthalpyReduced < globalEnthalpyFull);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Barotropic Closure Reports Expected EOS Rejections Without Unwinding",
|
||||||
|
tags::barotrope &tags::closure &tags::prepared &tags::unit
|
||||||
|
) {
|
||||||
|
auto args = test_utils::setup_args();
|
||||||
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
const Maps maps(f);
|
||||||
|
const mean_field::eos::Polytrope equationOfState(3.0, 1.5);
|
||||||
|
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
|
||||||
|
f, *f.domainMapperStateless, equationOfState
|
||||||
|
);
|
||||||
|
|
||||||
|
mfem::Vector density(maps.density.reduced_size());
|
||||||
|
mfem::Vector enthalpy(maps.enthalpy.reduced_size());
|
||||||
|
mfem::Vector displacement(maps.displacement.reduced_size());
|
||||||
|
density = 0.0;
|
||||||
|
enthalpy = -1.0;
|
||||||
|
displacement = 0.0;
|
||||||
|
auto dependencies = make_dependencies();
|
||||||
|
|
||||||
|
const auto outsideDomain =
|
||||||
|
preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies);
|
||||||
|
REQUIRE_FALSE(outsideDomain.has_value());
|
||||||
|
CHECK(
|
||||||
|
outsideDomain.error().reason ==
|
||||||
|
mean_field::operators::BarotropicClosurePreparationRejectionReason::equation_of_state
|
||||||
|
);
|
||||||
|
CHECK(outsideDomain.error().equationOfStateError == mean_field::eos::EvaluationErrorCode::outside_domain);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
try {
|
||||||
|
(void)preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies);
|
||||||
|
FAIL("The compatibility Prepare overload accepted an out-of-domain EOS input.");
|
||||||
|
} catch (const mean_field::eos::EvaluationError &error) {
|
||||||
|
CHECK(error.code() == mean_field::eos::EvaluationErrorCode::outside_domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the interpolated input finite while forcing the n = 3
|
||||||
|
// polytropic density evaluation to overflow.
|
||||||
|
enthalpy = 1.0e150;
|
||||||
|
++dependencies.enthalpy.revision;
|
||||||
|
|
||||||
|
const auto rejected =
|
||||||
|
preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies);
|
||||||
|
REQUIRE_FALSE(rejected.has_value());
|
||||||
|
CHECK(
|
||||||
|
rejected.error().reason ==
|
||||||
|
mean_field::operators::BarotropicClosurePreparationRejectionReason::equation_of_state
|
||||||
|
);
|
||||||
|
CHECK(rejected.error().equationOfStateError == mean_field::eos::EvaluationErrorCode::nonfinite_result);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
try {
|
||||||
|
(void)preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies);
|
||||||
|
FAIL("The compatibility Prepare overload accepted a non-finite EOS result.");
|
||||||
|
} catch (const mean_field::eos::EvaluationError &error) {
|
||||||
|
CHECK(error.code() == mean_field::eos::EvaluationErrorCode::nonfinite_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
enthalpy = 1.0;
|
||||||
|
++dependencies.enthalpy.revision;
|
||||||
|
const auto accepted =
|
||||||
|
preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies);
|
||||||
|
REQUIRE(accepted.has_value());
|
||||||
|
CHECK(preparedOperator.IsPrepared());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Barotropic Closure Reports Invalid Candidate Geometry Without Unwinding",
|
||||||
|
tags::barotrope &tags::closure &tags::prepared &tags::geometry &tags::unit
|
||||||
|
) {
|
||||||
|
auto args = test_utils::setup_args();
|
||||||
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
const Maps maps(f);
|
||||||
|
const mean_field::eos::Polytrope equationOfState(3.0, 1.5);
|
||||||
|
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
|
||||||
|
f, *f.domainMapperStateless, equationOfState
|
||||||
|
);
|
||||||
|
|
||||||
|
mfem::Vector density(maps.density.reduced_size());
|
||||||
|
mfem::Vector enthalpy(maps.enthalpy.reduced_size());
|
||||||
|
density = 1.0;
|
||||||
|
enthalpy = 1.0;
|
||||||
|
mfem::Vector displacement = reduce(maps.displacement, make_folding_displacement(f));
|
||||||
|
auto dependencies = make_dependencies();
|
||||||
|
|
||||||
|
const auto rejected =
|
||||||
|
preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies);
|
||||||
|
REQUIRE_FALSE(rejected.has_value());
|
||||||
|
CHECK(
|
||||||
|
rejected.error().reason ==
|
||||||
|
mean_field::operators::BarotropicClosurePreparationRejectionReason::mapping_failure
|
||||||
|
);
|
||||||
|
CHECK(rejected.error().mappingStatus == mean_field::mapping::MappingStatus::non_positive_determinant);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies), std::domain_error
|
||||||
|
);
|
||||||
|
|
||||||
|
displacement = 0.0;
|
||||||
|
++dependencies.displacement.revision;
|
||||||
|
const auto accepted =
|
||||||
|
preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies);
|
||||||
|
REQUIRE(accepted.has_value());
|
||||||
|
CHECK(preparedOperator.IsPrepared());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Prepared Barotropic Closure Matches Full Stateless Kernels Through FieldDof Restriction",
|
"Prepared Barotropic Closure Matches Full Stateless Kernels Through FieldDof Restriction",
|
||||||
tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::field &tags::integration
|
tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::field &tags::integration
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
@@ -49,24 +52,30 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
utils::Args args = test_utils::setup_args();
|
utils::Args args = test_utils::setup_args();
|
||||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
fem::FEM physicalFiniteElements = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
REQUIRE(f.okay());
|
REQUIRE(physicalFiniteElements.okay());
|
||||||
|
|
||||||
|
fem::FEM borderedFiniteElements = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(borderedFiniteElements.okay());
|
||||||
|
|
||||||
models::StellarModel stellarModel{
|
models::StellarModel stellarModel{
|
||||||
models::structure::PolytropicStructure{eos::Polytrope{3.0, 0.25}, 1.0},
|
models::structure::PolytropicStructure{eos::Polytrope{3.0, 0.25}, 1.0},
|
||||||
surface::ConstantPressureSurface{dimensions::PressureValue{0.0}}
|
surface::ConstantPressureSurface{dimensions::PressureValue{0.0}}
|
||||||
};
|
};
|
||||||
operators::PreparedStellarEquilibriumOperator physicalOperator(f, *f.domainMapperStateless, stellarModel);
|
operators::PreparedStellarEquilibriumOperator physicalOperator(
|
||||||
|
physicalFiniteElements, *physicalFiniteElements.domainMapperStateless, stellarModel
|
||||||
|
);
|
||||||
auto equilibriumProblem = equilibrium::discretize(
|
auto equilibriumProblem = equilibrium::discretize(
|
||||||
model::StellarModel(
|
model::StellarModel(
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}}),
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}}),
|
||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
||||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}), eos::Polytrope({.n = 3.0, .K = 0.25})
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}), eos::Polytrope({.n = 3.0, .K = 0.25})
|
||||||
),
|
),
|
||||||
equilibrium::StellarDiscretization{f, *f.domainMapperStateless}
|
std::move(borderedFiniteElements)
|
||||||
);
|
);
|
||||||
auto &borderedOperator = equilibriumProblem.GetPreparedOperator();
|
auto &borderedOperator = equilibriumProblem.GetPreparedOperator();
|
||||||
|
const MPI_Comm communicator = equilibriumProblem.GetCommunicator();
|
||||||
|
|
||||||
STATIC_CHECK(
|
STATIC_CHECK(
|
||||||
std::same_as<
|
std::same_as<
|
||||||
@@ -87,8 +96,7 @@ TEST_CASE(
|
|||||||
CHECK(borderedOperator.GetRootManifest().constraints().size() == 3);
|
CHECK(borderedOperator.GetRootManifest().constraints().size() == 3);
|
||||||
CHECK(borderedOperator.GetRootManifest().specificationDescriptors().size() == 4);
|
CHECK(borderedOperator.GetRootManifest().specificationDescriptors().size() == 4);
|
||||||
|
|
||||||
const auto ¢ralDescriptor =
|
const auto ¢ralDescriptor = borderedOperator.GetRootManifest().specification<constraint::FixedCentralDensity>();
|
||||||
borderedOperator.GetRootManifest().specification<constraint::FixedCentralDensity>();
|
|
||||||
CHECK(centralDescriptor.stableId == "FixedCentralDensity");
|
CHECK(centralDescriptor.stableId == "FixedCentralDensity");
|
||||||
CHECK(centralDescriptor.role == models::SpecificationRole::phase_condition);
|
CHECK(centralDescriptor.role == models::SpecificationRole::phase_condition);
|
||||||
CHECK(centralDescriptor.columnPolicy == operators::RootColumnPolicy::solver_border);
|
CHECK(centralDescriptor.columnPolicy == operators::RootColumnPolicy::solver_border);
|
||||||
@@ -99,10 +107,9 @@ TEST_CASE(
|
|||||||
CHECK(centralDescriptor.residualUnits == "specific_enthalpy");
|
CHECK(centralDescriptor.residualUnits == "specific_enthalpy");
|
||||||
|
|
||||||
mfem::Vector physicalState(physicalOperator.Width());
|
mfem::Vector physicalState(physicalOperator.Width());
|
||||||
physicalState = 0.0;
|
physicalState = 0.0;
|
||||||
const auto physicalStateView =
|
const auto physicalStateView = physicalOperator.GetRootManifest().stateView(physicalState);
|
||||||
physicalOperator.GetRootManifest().stateView(physicalState);
|
physicalStateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||||
physicalStateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
|
||||||
physicalStateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
physicalStateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||||
|
|
||||||
mfem::Vector borderedState(borderedOperator.Width());
|
mfem::Vector borderedState(borderedOperator.Width());
|
||||||
@@ -152,7 +159,7 @@ TEST_CASE(
|
|||||||
localCenterDirection += enthalpyDirection(centerDof);
|
localCenterDirection += enthalpyDirection(centerDof);
|
||||||
}
|
}
|
||||||
double globalCenterDirection = 0.0;
|
double globalCenterDirection = 0.0;
|
||||||
MPI_Allreduce(&localCenterDirection, &globalCenterDirection, 1, MPI_DOUBLE, MPI_SUM, f.mesh->GetComm());
|
MPI_Allreduce(&localCenterDirection, &globalCenterDirection, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
||||||
CHECK(borderedAction(borderedAction.Size() - 1) == globalCenterDirection);
|
CHECK(borderedAction(borderedAction.Size() - 1) == globalCenterDirection);
|
||||||
|
|
||||||
const auto repeatedReport = borderedOperator.Prepare(borderedState, dependencies, rotation);
|
const auto repeatedReport = borderedOperator.Prepare(borderedState, dependencies, rotation);
|
||||||
@@ -181,6 +188,60 @@ TEST_CASE(
|
|||||||
localBorderEntry += enthalpyAction(centerDof);
|
localBorderEntry += enthalpyAction(centerDof);
|
||||||
}
|
}
|
||||||
double globalBorderEntry = 0.0;
|
double globalBorderEntry = 0.0;
|
||||||
MPI_Allreduce(&localBorderEntry, &globalBorderEntry, 1, MPI_DOUBLE, MPI_SUM, f.mesh->GetComm());
|
MPI_Allreduce(&localBorderEntry, &globalBorderEntry, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
||||||
CHECK(globalBorderEntry == -0.625);
|
CHECK(globalBorderEntry == -0.625);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Central Density Variadic Preparation Rejects A Non-Finite Phase Coordinate Without Unwinding",
|
||||||
|
tags::central_density_phase_integration
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
utils::Args args = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
|
auto equilibriumProblem = equilibrium::discretize(
|
||||||
|
model::StellarModel(
|
||||||
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}), eos::Polytrope({.n = 3.0, .K = 0.25})
|
||||||
|
),
|
||||||
|
std::move(finiteElements)
|
||||||
|
);
|
||||||
|
auto &preparedOperator = equilibriumProblem.GetPreparedOperator();
|
||||||
|
|
||||||
|
mfem::Vector state(preparedOperator.Width());
|
||||||
|
state = 0.0;
|
||||||
|
const auto stateView = preparedOperator.GetRootManifest().stateView(state);
|
||||||
|
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||||
|
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||||
|
|
||||||
|
const operators::StellarEquilibriumDependencies dependencies = make_dependencies();
|
||||||
|
const physics::RigidRotation rotation = make_zero_rotation();
|
||||||
|
REQUIRE(equilibriumProblem.TryPrepare(state, dependencies, rotation).has_value());
|
||||||
|
|
||||||
|
int rank = 0;
|
||||||
|
REQUIRE(MPI_Comm_rank(equilibriumProblem.GetCommunicator(), &rank) == MPI_SUCCESS);
|
||||||
|
auto phaseCoordinate = preparedOperator.GetRootManifest().stateView(state).block(
|
||||||
|
utils::blocks::fixed_central_density_phase.central_value_term
|
||||||
|
);
|
||||||
|
REQUIRE(phaseCoordinate.Size() == 1);
|
||||||
|
if (rank == 0) {
|
||||||
|
phaseCoordinate(0) = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
phaseCoordinate.SyncAliasMemory(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto rejected = equilibriumProblem.TryPrepare(state, dependencies, rotation);
|
||||||
|
REQUIRE_FALSE(rejected.has_value());
|
||||||
|
CHECK(rejected.error().reason == operators::StellarEquilibriumPreparationRejectionReason::non_finite_physics);
|
||||||
|
CHECK(rejected.error().stage == operators::StellarEquilibriumPreparationStage::model_specification);
|
||||||
|
CHECK_FALSE(equilibriumProblem.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(equilibriumProblem.Prepare(state, dependencies, rotation), std::domain_error);
|
||||||
|
|
||||||
|
phaseCoordinate(0) = 0.0;
|
||||||
|
phaseCoordinate.SyncAliasMemory(state);
|
||||||
|
REQUIRE(equilibriumProblem.TryPrepare(state, dependencies, rotation).has_value());
|
||||||
|
CHECK(equilibriumProblem.IsPrepared());
|
||||||
|
}
|
||||||
|
|||||||
@@ -415,6 +415,57 @@ TEST_CASE(
|
|||||||
CHECK(preparedOperator.IsPrepared());
|
CHECK(preparedOperator.IsPrepared());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Displacement Residual Preserves Pressure Rejection Details",
|
||||||
|
tags::barotrope_prepared
|
||||||
|
) {
|
||||||
|
mean_field::utils::Args args = test_utils::setup_args();
|
||||||
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
const mfem::Vector density = prepared_displacement_residual_test_utils::make_density(f, 0.31);
|
||||||
|
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.67);
|
||||||
|
const mfem::Vector gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.47);
|
||||||
|
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
|
||||||
|
gravityPotential = 0.0;
|
||||||
|
|
||||||
|
auto dependencies = prepared_displacement_residual_test_utils::make_dependencies();
|
||||||
|
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
|
f, *f.domainMapperStateless
|
||||||
|
);
|
||||||
|
prepared_displacement_residual_test_utils::prepare_gravity_context(
|
||||||
|
gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, 19
|
||||||
|
);
|
||||||
|
|
||||||
|
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
|
||||||
|
const mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.83);
|
||||||
|
mean_field::operators::PreparedDisplacementResidualOperator preparedOperator(
|
||||||
|
f, *f.domainMapperStateless, equationOfState, gravityContext
|
||||||
|
);
|
||||||
|
|
||||||
|
mfem::Vector enthalpy(prepared_displacement_residual_test_utils::make_enthalpy_map(f).reduced_size());
|
||||||
|
enthalpy = std::numeric_limits<double>::max();
|
||||||
|
|
||||||
|
const auto rejected = preparedOperator.TryPrepare({.enthalpy = enthalpy}, dependencies, rotation);
|
||||||
|
REQUIRE_FALSE(rejected.has_value());
|
||||||
|
CHECK(rejected.error().source == mean_field::operators::DisplacementResidualPreparationRejectionSource::pressure);
|
||||||
|
CHECK(
|
||||||
|
rejected.error().reason ==
|
||||||
|
mean_field::operators::DisplacementResidualPreparationRejectionReason::equation_of_state
|
||||||
|
);
|
||||||
|
CHECK(rejected.error().equationOfStateCode == mean_field::eos::EvaluationErrorCode::nonfinite_result);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation), mean_field::eos::EvaluationError
|
||||||
|
);
|
||||||
|
|
||||||
|
enthalpy = 1.0;
|
||||||
|
++dependencies.enthalpy.revision;
|
||||||
|
const auto accepted = preparedOperator.TryPrepare({.enthalpy = enthalpy}, dependencies, rotation);
|
||||||
|
REQUIRE(accepted.has_value());
|
||||||
|
CHECK(preparedOperator.IsPrepared());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Prepared Displacement Residual Selectively Orchestrates Its Children",
|
"Prepared Displacement Residual Selectively Orchestrates Its Children",
|
||||||
tags::barotrope_context_integration
|
tags::barotrope_context_integration
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
import mean_field;
|
import mean_field;
|
||||||
import test_helpers;
|
import test_helpers;
|
||||||
@@ -9,6 +10,56 @@ using namespace mean_field;
|
|||||||
using Catch::Matchers::WithinAbs;
|
using Catch::Matchers::WithinAbs;
|
||||||
namespace prepared_test = gravity_prepared_test_utils;
|
namespace prepared_test = gravity_prepared_test_utils;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
[[nodiscard]] mfem::Vector make_folding_displacement(const mean_field::fem::FEM &f) {
|
||||||
|
mfem::ParGridFunction field(f.displacementFes.get());
|
||||||
|
mfem::VectorFunctionCoefficient coefficient(
|
||||||
|
f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) {
|
||||||
|
value.SetSize(position.Size());
|
||||||
|
for (int dimension = 0; dimension < position.Size(); ++dimension) {
|
||||||
|
value(dimension) = -2.0 * position(dimension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
field.ProjectCoefficient(coefficient);
|
||||||
|
|
||||||
|
mfem::Vector displacementTrue;
|
||||||
|
field.GetTrueDofs(displacementTrue);
|
||||||
|
return displacementTrue;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Mapped Gravity Source Reports Invalid Candidate Geometry Without Unwinding",
|
||||||
|
tags::gravity_prepared_unit &tags::geometry
|
||||||
|
) {
|
||||||
|
auto args = test_utils::setup_args();
|
||||||
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
operators::PreparedMappedGravitySourceOperator preparedOperator(f, *f.domainMapperStateless);
|
||||||
|
mfem::Vector displacement = preparedOperator.GetDisplacementMap().gather(make_folding_displacement(f));
|
||||||
|
|
||||||
|
const auto rejected = preparedOperator.TryPrepare(displacement);
|
||||||
|
REQUIRE_FALSE(rejected.has_value());
|
||||||
|
CHECK(rejected.error().reason == operators::GravitySourcePreparationRejectionReason::invalid_mapping);
|
||||||
|
CHECK(rejected.error().mappingStatus == mapping::MappingStatus::non_positive_determinant);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK_FALSE(preparedOperator.HasVariationData());
|
||||||
|
CHECK(preparedOperator.GetPreparationCount() == 0);
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(preparedOperator.Prepare(displacement), std::domain_error);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK(preparedOperator.GetPreparationCount() == 0);
|
||||||
|
|
||||||
|
displacement = 0.0;
|
||||||
|
const auto recovered = preparedOperator.TryPrepare(displacement);
|
||||||
|
REQUIRE(recovered.has_value());
|
||||||
|
CHECK(preparedOperator.IsPrepared());
|
||||||
|
CHECK(preparedOperator.HasVariationData());
|
||||||
|
CHECK(preparedOperator.GetPreparationCount() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Prepared Mapped Gravity Source Matches Stateless Kernel",
|
"Prepared Mapped Gravity Source Matches Stateless Kernel",
|
||||||
tags::gravity_prepared
|
tags::gravity_prepared
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
import mean_field;
|
import mean_field;
|
||||||
import test_helpers;
|
import test_helpers;
|
||||||
@@ -10,6 +11,56 @@ using namespace mean_field;
|
|||||||
using Catch::Matchers::WithinAbs;
|
using Catch::Matchers::WithinAbs;
|
||||||
namespace prepared_test = gravity_prepared_test_utils;
|
namespace prepared_test = gravity_prepared_test_utils;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
[[nodiscard]] mfem::Vector make_folding_displacement(const mean_field::fem::FEM &f) {
|
||||||
|
mfem::ParGridFunction field(f.displacementFes.get());
|
||||||
|
mfem::VectorFunctionCoefficient coefficient(
|
||||||
|
f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) {
|
||||||
|
value.SetSize(position.Size());
|
||||||
|
for (int dimension = 0; dimension < position.Size(); ++dimension) {
|
||||||
|
value(dimension) = -2.0 * position(dimension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
field.ProjectCoefficient(coefficient);
|
||||||
|
|
||||||
|
mfem::Vector displacementTrue;
|
||||||
|
field.GetTrueDofs(displacementTrue);
|
||||||
|
return displacementTrue;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Mapped Hdiv Mass Reports Invalid Candidate Geometry Without Unwinding",
|
||||||
|
tags::gravity_prepared_unit &tags::geometry
|
||||||
|
) {
|
||||||
|
auto args = test_utils::setup_args();
|
||||||
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
operators::PreparedMappedHDivMassOperator preparedOperator(f, *f.domainMapperStateless);
|
||||||
|
mfem::Vector displacement = preparedOperator.GetDisplacementMap().gather(make_folding_displacement(f));
|
||||||
|
|
||||||
|
const auto rejected = preparedOperator.TryPrepare(displacement);
|
||||||
|
REQUIRE_FALSE(rejected.has_value());
|
||||||
|
CHECK(rejected.error().reason == operators::HDivMassPreparationRejectionReason::invalid_mapping);
|
||||||
|
CHECK(rejected.error().mappingStatus == mapping::MappingStatus::non_positive_determinant);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK_FALSE(preparedOperator.HasVariationData());
|
||||||
|
CHECK(preparedOperator.GetPreparationCount() == 0);
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(preparedOperator.Prepare(displacement), std::domain_error);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK(preparedOperator.GetPreparationCount() == 0);
|
||||||
|
|
||||||
|
displacement = 0.0;
|
||||||
|
const auto recovered = preparedOperator.TryPrepare(displacement);
|
||||||
|
REQUIRE(recovered.has_value());
|
||||||
|
CHECK(preparedOperator.IsPrepared());
|
||||||
|
CHECK(preparedOperator.HasVariationData());
|
||||||
|
CHECK(preparedOperator.GetPreparationCount() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Prepared Mapped Hdiv Mass Matches Stateless Kernel",
|
"Prepared Mapped Hdiv Mass Matches Stateless Kernel",
|
||||||
tags::gravity_prepared
|
tags::gravity_prepared
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
import mean_field;
|
import mean_field;
|
||||||
import test_helpers;
|
import test_helpers;
|
||||||
@@ -288,3 +289,75 @@ TEST_CASE(
|
|||||||
|
|
||||||
CHECK(displacementEffect > 1.0e-8);
|
CHECK(displacementEffect > 1.0e-8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Hydrostatic Equilibrium Reports Candidate Mapping And Arithmetic Failures Without Unwinding",
|
||||||
|
tags::barotrope_hydrostatic_prepared_residual &tags::unit
|
||||||
|
) {
|
||||||
|
auto args = test_utils::setup_args();
|
||||||
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
|
||||||
|
|
||||||
|
mfem::Vector enthalpy = prepared_hydrostatic_test_utils::make_enthalpy(f);
|
||||||
|
mfem::Vector gravityPotential = prepared_hydrostatic_test_utils::make_gravity_potential(f);
|
||||||
|
mfem::Vector displacement = field_dof_test_utils::make_supported_displacement(f, 0.73);
|
||||||
|
displacement *= 1.0e200;
|
||||||
|
|
||||||
|
auto dependencies = prepared_hydrostatic_test_utils::make_dependencies();
|
||||||
|
const auto rotation = prepared_hydrostatic_test_utils::make_rotation();
|
||||||
|
|
||||||
|
const auto mappingRejection = preparedOperator.TryPrepare(
|
||||||
|
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, 0.41), dependencies,
|
||||||
|
rotation
|
||||||
|
);
|
||||||
|
REQUIRE_FALSE(mappingRejection.has_value());
|
||||||
|
CHECK(
|
||||||
|
(mappingRejection.error().reason ==
|
||||||
|
mean_field::operators::HydrostaticEquilibriumPreparationRejectionReason::inverted_geometry ||
|
||||||
|
mappingRejection.error().reason ==
|
||||||
|
mean_field::operators::HydrostaticEquilibriumPreparationRejectionReason::non_finite_geometry)
|
||||||
|
);
|
||||||
|
CHECK(mappingRejection.error().mappingStatus != mean_field::mapping::MappingStatus::valid);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
preparedOperator.Prepare(
|
||||||
|
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, 0.41), dependencies,
|
||||||
|
rotation
|
||||||
|
),
|
||||||
|
std::domain_error
|
||||||
|
);
|
||||||
|
|
||||||
|
displacement = 0.0;
|
||||||
|
++dependencies.displacement.revision;
|
||||||
|
++dependencies.rotation.revision;
|
||||||
|
|
||||||
|
const auto enormousRotation = prepared_hydrostatic_test_utils::make_rotation(1.0e200);
|
||||||
|
const auto arithmeticRejection = preparedOperator.TryPrepare(
|
||||||
|
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, 0.41), dependencies,
|
||||||
|
enormousRotation
|
||||||
|
);
|
||||||
|
REQUIRE_FALSE(arithmeticRejection.has_value());
|
||||||
|
CHECK(
|
||||||
|
arithmeticRejection.error().reason ==
|
||||||
|
mean_field::operators::HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual
|
||||||
|
);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
preparedOperator.Prepare(
|
||||||
|
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, 0.41), dependencies,
|
||||||
|
enormousRotation
|
||||||
|
),
|
||||||
|
std::domain_error
|
||||||
|
);
|
||||||
|
|
||||||
|
++dependencies.rotation.revision;
|
||||||
|
const auto recovered = preparedOperator.TryPrepare(
|
||||||
|
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, 0.41), dependencies,
|
||||||
|
rotation
|
||||||
|
);
|
||||||
|
REQUIRE(recovered.has_value());
|
||||||
|
CHECK(recovered->preparedResidual);
|
||||||
|
CHECK(preparedOperator.IsPrepared());
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
@@ -205,6 +206,7 @@ TEST_CASE(
|
|||||||
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<Operator>);
|
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<Operator>);
|
||||||
STATIC_REQUIRE_FALSE(std::is_move_constructible_v<Operator>);
|
STATIC_REQUIRE_FALSE(std::is_move_constructible_v<Operator>);
|
||||||
STATIC_REQUIRE_FALSE(std::is_move_assignable_v<Operator>);
|
STATIC_REQUIRE_FALSE(std::is_move_assignable_v<Operator>);
|
||||||
|
STATIC_REQUIRE(std::is_trivially_copyable_v<mean_field::operators::MassNormalizationPreparationRejection>);
|
||||||
|
|
||||||
mean_field::utils::Args args = test_utils::setup_args();
|
mean_field::utils::Args args = test_utils::setup_args();
|
||||||
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
@@ -271,6 +273,49 @@ TEST_CASE(
|
|||||||
CHECK(mass_normalization_test_utils::relative_error(measuredScale, expectedScale) < 5e-7);
|
CHECK(mass_normalization_test_utils::relative_error(measuredScale, expectedScale) < 5e-7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Mass Normalization Reports Non-Finite Density Interpolation Without Unwinding",
|
||||||
|
tags::barotrope_mass_normalization_context &tags::unit
|
||||||
|
) {
|
||||||
|
using namespace mass_normalization_test_utils;
|
||||||
|
|
||||||
|
mean_field::utils::Args args = test_utils::setup_args();
|
||||||
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
mfem::Vector density = make_constant_density(f, -0.5 * std::numeric_limits<double>::max());
|
||||||
|
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
|
||||||
|
displacement = 0.0;
|
||||||
|
auto dependencies = make_dependencies();
|
||||||
|
|
||||||
|
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
|
f, *f.domainMapperStateless
|
||||||
|
);
|
||||||
|
prepare_gravity_context(gravityContext, f, density, displacement, dependencies);
|
||||||
|
|
||||||
|
mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext);
|
||||||
|
|
||||||
|
const auto rejected = massOperator.TryPrepare({.targetMass = std::numeric_limits<double>::max()}, dependencies);
|
||||||
|
REQUIRE_FALSE(rejected.has_value());
|
||||||
|
CHECK(
|
||||||
|
rejected.error().reason ==
|
||||||
|
mean_field::operators::MassNormalizationPreparationRejectionReason::non_finite_density_interpolation
|
||||||
|
);
|
||||||
|
CHECK_FALSE(massOperator.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
massOperator.Prepare({.targetMass = std::numeric_limits<double>::max()}, dependencies), std::domain_error
|
||||||
|
);
|
||||||
|
|
||||||
|
density = make_constant_density(f, 1.0);
|
||||||
|
++dependencies.density.revision;
|
||||||
|
++dependencies.targetMass.revision;
|
||||||
|
prepare_gravity_context(gravityContext, f, density, displacement, dependencies);
|
||||||
|
|
||||||
|
const auto accepted = massOperator.TryPrepare({.targetMass = 1.0}, dependencies);
|
||||||
|
REQUIRE(accepted.has_value());
|
||||||
|
CHECK(massOperator.IsPrepared());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Prepared Mass Normalization Density Jacobian Matches Centered Difference",
|
"Prepared Mass Normalization Density Jacobian Matches Centered Difference",
|
||||||
tags::barotrope_mass_normalization_jacobian &tags::accuracy
|
tags::barotrope_mass_normalization_jacobian &tags::accuracy
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
@@ -51,6 +52,26 @@ namespace prepared_pressure_force_test_utils {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] mfem::Vector make_affine_displacement(
|
||||||
|
const mean_field::fem::FEM &f,
|
||||||
|
const double scale
|
||||||
|
) {
|
||||||
|
mfem::ParGridFunction field(f.displacementFes.get());
|
||||||
|
mfem::VectorFunctionCoefficient coefficient(
|
||||||
|
f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||||
|
value.SetSize(position.Size());
|
||||||
|
for (int dimension = 0; dimension < position.Size(); ++dimension) {
|
||||||
|
value(dimension) = scale * position(dimension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
field.ProjectCoefficient(coefficient);
|
||||||
|
|
||||||
|
mfem::Vector result;
|
||||||
|
field.GetTrueDofs(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
mfem::Vector make_positive_enthalpy_true(
|
mfem::Vector make_positive_enthalpy_true(
|
||||||
const mean_field::fem::FEM &f,
|
const mean_field::fem::FEM &f,
|
||||||
@@ -265,6 +286,97 @@ TEST_CASE(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Pressure Force Reports Expected EOS Rejections Without Unwinding",
|
||||||
|
tags::barotrope &tags::pressure &tags::prepared &tags::unit
|
||||||
|
) {
|
||||||
|
mean_field::utils::Args args = test_utils::setup_args();
|
||||||
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
const prepared_pressure_force_test_utils::Maps maps(f);
|
||||||
|
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
|
||||||
|
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
|
||||||
|
|
||||||
|
mfem::Vector enthalpy(maps.enthalpy.reduced_size());
|
||||||
|
mfem::Vector displacement(maps.displacement.reduced_size());
|
||||||
|
enthalpy = -1.0;
|
||||||
|
displacement = 0.0;
|
||||||
|
auto dependencies = prepared_pressure_force_test_utils::make_dependencies();
|
||||||
|
|
||||||
|
const auto outsideDomain =
|
||||||
|
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
|
||||||
|
REQUIRE_FALSE(outsideDomain.has_value());
|
||||||
|
CHECK(
|
||||||
|
outsideDomain.error().reason ==
|
||||||
|
mean_field::operators::PressureForcePreparationRejectionReason::equation_of_state
|
||||||
|
);
|
||||||
|
CHECK(outsideDomain.error().equationOfStateCode == mean_field::eos::EvaluationErrorCode::outside_domain);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
try {
|
||||||
|
(void)preparedOperator.Prepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
|
||||||
|
FAIL("The compatibility Prepare overload accepted an out-of-domain EOS input.");
|
||||||
|
} catch (const mean_field::eos::EvaluationError &error) {
|
||||||
|
CHECK(error.code() == mean_field::eos::EvaluationErrorCode::outside_domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
enthalpy = std::numeric_limits<double>::max();
|
||||||
|
++dependencies.enthalpy.revision;
|
||||||
|
const auto rejected =
|
||||||
|
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
|
||||||
|
REQUIRE_FALSE(rejected.has_value());
|
||||||
|
CHECK(rejected.error().reason == mean_field::operators::PressureForcePreparationRejectionReason::equation_of_state);
|
||||||
|
CHECK(rejected.error().equationOfStateCode == mean_field::eos::EvaluationErrorCode::nonfinite_result);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
preparedOperator.Prepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies),
|
||||||
|
mean_field::eos::EvaluationError
|
||||||
|
);
|
||||||
|
|
||||||
|
enthalpy = 1.0;
|
||||||
|
++dependencies.enthalpy.revision;
|
||||||
|
const auto accepted =
|
||||||
|
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
|
||||||
|
REQUIRE(accepted.has_value());
|
||||||
|
CHECK(preparedOperator.IsPrepared());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Pressure Force Reports Invalid Candidate Geometry Without Unwinding",
|
||||||
|
tags::barotrope &tags::pressure &tags::prepared &tags::geometry &tags::unit
|
||||||
|
) {
|
||||||
|
mean_field::utils::Args args = test_utils::setup_args();
|
||||||
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
const prepared_pressure_force_test_utils::Maps maps(f);
|
||||||
|
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
|
||||||
|
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
|
||||||
|
|
||||||
|
mfem::Vector enthalpy(maps.enthalpy.reduced_size());
|
||||||
|
enthalpy = 1.0;
|
||||||
|
mfem::Vector displacement =
|
||||||
|
maps.displacement.gather(prepared_pressure_force_test_utils::make_affine_displacement(f, -2.0));
|
||||||
|
auto dependencies = prepared_pressure_force_test_utils::make_dependencies();
|
||||||
|
|
||||||
|
const auto rejected =
|
||||||
|
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
|
||||||
|
REQUIRE_FALSE(rejected.has_value());
|
||||||
|
CHECK(rejected.error().reason == mean_field::operators::PressureForcePreparationRejectionReason::invalid_mapping);
|
||||||
|
CHECK(rejected.error().mappingStatus == mean_field::mapping::MappingStatus::non_positive_determinant);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
preparedOperator.Prepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies), std::domain_error
|
||||||
|
);
|
||||||
|
|
||||||
|
displacement = 0.0;
|
||||||
|
++dependencies.displacement.revision;
|
||||||
|
const auto accepted =
|
||||||
|
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
|
||||||
|
REQUIRE(accepted.has_value());
|
||||||
|
CHECK(preparedOperator.IsPrepared());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Prepared Pressure Force Jacobian Matches Full Stateless Columns "
|
"Prepared Pressure Force Jacobian Matches Full Stateless Columns "
|
||||||
"Through FieldDof Restriction",
|
"Through FieldDof Restriction",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
@@ -127,6 +128,26 @@ namespace rotational_displacement_force_test_utils {
|
|||||||
return direction;
|
return direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mfem::Vector make_affine_displacement(
|
||||||
|
const mean_field::fem::FEM &f,
|
||||||
|
const double scale
|
||||||
|
) {
|
||||||
|
mfem::ParGridFunction field(f.displacementFes.get());
|
||||||
|
mfem::VectorFunctionCoefficient coefficient(
|
||||||
|
f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||||
|
value.SetSize(position.Size());
|
||||||
|
for (int dimension = 0; dimension < position.Size(); ++dimension) {
|
||||||
|
value(dimension) = scale * position(dimension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
field.ProjectCoefficient(coefficient);
|
||||||
|
|
||||||
|
mfem::Vector result;
|
||||||
|
field.GetTrueDofs(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
|
[[nodiscard]] mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
|
||||||
mfem::Vector angularVelocity(3);
|
mfem::Vector angularVelocity(3);
|
||||||
angularVelocity(0) = scale * 0.17;
|
angularVelocity(0) = scale * 0.17;
|
||||||
@@ -377,6 +398,85 @@ TEST_CASE(
|
|||||||
CHECK(rotational_displacement_force_test_utils::global_norm(zeroRotationResidual, f.mesh->GetComm()) == 0.0);
|
CHECK(rotational_displacement_force_test_utils::global_norm(zeroRotationResidual, f.mesh->GetComm()) == 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Rotational Displacement Force Reports Candidate Mapping And Arithmetic Rejections",
|
||||||
|
tags::rotation_prepared_unit
|
||||||
|
) {
|
||||||
|
mean_field::utils::Args args = test_utils::setup_args();
|
||||||
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.29);
|
||||||
|
mfem::Vector displacement = rotational_displacement_force_test_utils::make_affine_displacement(f, -2.0);
|
||||||
|
const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.83);
|
||||||
|
mfem::Vector residual;
|
||||||
|
|
||||||
|
const auto invalidMapping = mean_field::operators::kernels::try_apply_rotational_displacement_force_residual(
|
||||||
|
f, *f.domainMapperStateless, rotation, density, displacement, residual
|
||||||
|
);
|
||||||
|
REQUIRE_FALSE(invalidMapping.has_value());
|
||||||
|
CHECK(
|
||||||
|
invalidMapping.error().reason ==
|
||||||
|
mean_field::operators::kernels::RotationalDisplacementForceRejectionReason::invalid_mapping
|
||||||
|
);
|
||||||
|
CHECK(invalidMapping.error().mappingStatus == mean_field::mapping::MappingStatus::non_positive_determinant);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
|
||||||
|
f, *f.domainMapperStateless, rotation, density, displacement, residual
|
||||||
|
),
|
||||||
|
std::domain_error
|
||||||
|
);
|
||||||
|
|
||||||
|
displacement = 0.0;
|
||||||
|
density = 1.0e200;
|
||||||
|
const mean_field::physics::RigidRotation extremeRotation =
|
||||||
|
rotational_displacement_force_test_utils::make_rotation(1.0e100);
|
||||||
|
const auto nonFiniteArithmetic = mean_field::operators::kernels::try_apply_rotational_displacement_force_residual(
|
||||||
|
f, *f.domainMapperStateless, extremeRotation, density, displacement, residual
|
||||||
|
);
|
||||||
|
REQUIRE_FALSE(nonFiniteArithmetic.has_value());
|
||||||
|
CHECK(
|
||||||
|
nonFiniteArithmetic.error().reason ==
|
||||||
|
mean_field::operators::kernels::RotationalDisplacementForceRejectionReason::non_finite_arithmetic
|
||||||
|
);
|
||||||
|
|
||||||
|
mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless);
|
||||||
|
const auto &context = preparedOperator.GetContext();
|
||||||
|
auto dependencies = rotational_displacement_force_test_utils::make_dependencies();
|
||||||
|
mfem::Vector reducedDensity = context.GetDensityMap().gather(density);
|
||||||
|
const mfem::Vector reducedDisplacement = context.GetDisplacementMap().gather(displacement);
|
||||||
|
const auto preparedRejection = preparedOperator.TryPrepare(
|
||||||
|
{.density = reducedDensity, .displacement = reducedDisplacement}, dependencies, extremeRotation
|
||||||
|
);
|
||||||
|
REQUIRE_FALSE(preparedRejection.has_value());
|
||||||
|
CHECK(
|
||||||
|
preparedRejection.error().reason ==
|
||||||
|
mean_field::operators::kernels::RotationalDisplacementForceRejectionReason::non_finite_arithmetic
|
||||||
|
);
|
||||||
|
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
preparedOperator.Prepare(
|
||||||
|
{.density = reducedDensity, .displacement = reducedDisplacement}, dependencies, extremeRotation
|
||||||
|
),
|
||||||
|
std::domain_error
|
||||||
|
);
|
||||||
|
|
||||||
|
density = rotational_displacement_force_test_utils::make_density(f, 0.29);
|
||||||
|
reducedDensity = context.GetDensityMap().gather(density);
|
||||||
|
++dependencies.density.revision;
|
||||||
|
++dependencies.rotation.revision;
|
||||||
|
const auto preparedAccepted = preparedOperator.TryPrepare(
|
||||||
|
{.density = reducedDensity, .displacement = reducedDisplacement}, dependencies, rotation
|
||||||
|
);
|
||||||
|
REQUIRE(preparedAccepted.has_value());
|
||||||
|
CHECK(preparedOperator.IsPrepared());
|
||||||
|
|
||||||
|
const auto accepted = mean_field::operators::kernels::try_apply_rotational_displacement_force_residual(
|
||||||
|
f, *f.domainMapperStateless, rotation, density, displacement, residual
|
||||||
|
);
|
||||||
|
CHECK(accepted.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Prepared Rotational Displacement Force Reprepares Selectively",
|
"Prepared Rotational Displacement Force Reprepares Selectively",
|
||||||
tags::rotation_prepared
|
tags::rotation_prepared
|
||||||
|
|||||||
@@ -1505,6 +1505,69 @@ TEST_CASE(
|
|||||||
CHECK(targetReport.assembledResidual);
|
CHECK(targetReport.assembledResidual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Stellar Trial Preparation Reports Folded Geometry Without Unwinding",
|
||||||
|
tags::reduced_stellar_geometry &tags::prepared &tags::geometry &tags::unit
|
||||||
|
) {
|
||||||
|
mean_field::utils::Args args = test_utils::setup_args();
|
||||||
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(f.okay());
|
||||||
|
|
||||||
|
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
|
||||||
|
const auto stellarModel = stellar_equilibrium_test_utils::make_stellar_model(barotrope, 1.15);
|
||||||
|
const auto parameterGeometry = stellarModel.compileDomainDeformation(f);
|
||||||
|
const auto &surface = parameterGeometry.surfaceDeformationPrescription();
|
||||||
|
|
||||||
|
mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator(
|
||||||
|
f, *f.domainMapperStateless, stellarModel
|
||||||
|
);
|
||||||
|
const auto &layout = stellarOperator.GetLayout();
|
||||||
|
mfem::Vector state = stellar_equilibrium_test_utils::make_state(f, layout);
|
||||||
|
auto dependencies = stellar_equilibrium_test_utils::make_dependencies();
|
||||||
|
const auto rotation = stellar_equilibrium_test_utils::make_zero_rotation();
|
||||||
|
|
||||||
|
const double finiteStateValue = state(0);
|
||||||
|
state(0) = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
const auto nonFiniteState = stellarOperator.TryPrepare(state, dependencies, rotation);
|
||||||
|
REQUIRE_FALSE(nonFiniteState.has_value());
|
||||||
|
CHECK(
|
||||||
|
nonFiniteState.error().reason ==
|
||||||
|
mean_field::operators::StellarEquilibriumPreparationRejectionReason::non_finite_physics
|
||||||
|
);
|
||||||
|
CHECK_FALSE(stellarOperator.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(stellarOperator.Prepare(state, dependencies, rotation), std::domain_error);
|
||||||
|
state(0) = finiteStateValue;
|
||||||
|
|
||||||
|
mfem::Vector foldingParameters(surface.parameterCount());
|
||||||
|
for (int parameter = 0; parameter < foldingParameters.Size(); ++parameter) {
|
||||||
|
foldingParameters(parameter) = -1.5 * surface.referenceRadius(parameter);
|
||||||
|
}
|
||||||
|
stellar_equilibrium_test_utils::assign_value_block(
|
||||||
|
state, layout, stellar_equilibrium_test_utils::displacementValue, foldingParameters
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto rejected = stellarOperator.TryPrepare(state, dependencies, rotation);
|
||||||
|
REQUIRE_FALSE(rejected.has_value());
|
||||||
|
CHECK(
|
||||||
|
rejected.error().reason ==
|
||||||
|
mean_field::operators::StellarEquilibriumPreparationRejectionReason::inverted_geometry
|
||||||
|
);
|
||||||
|
CHECK(rejected.error().stage == mean_field::operators::StellarEquilibriumPreparationStage::generated_geometry);
|
||||||
|
CHECK(std::isfinite(rejected.error().minimumJacobianDeterminant));
|
||||||
|
CHECK(rejected.error().minimumJacobianDeterminant <= 0.0);
|
||||||
|
CHECK_FALSE(stellarOperator.IsPrepared());
|
||||||
|
CHECK_THROWS_AS(stellarOperator.Prepare(state, dependencies, rotation), std::domain_error);
|
||||||
|
|
||||||
|
foldingParameters = 0.0;
|
||||||
|
stellar_equilibrium_test_utils::assign_value_block(
|
||||||
|
state, layout, stellar_equilibrium_test_utils::displacementValue, foldingParameters
|
||||||
|
);
|
||||||
|
const auto accepted = stellarOperator.TryPrepare(state, dependencies, rotation);
|
||||||
|
REQUIRE(accepted.has_value());
|
||||||
|
CHECK(accepted->generatedGeometry.isOrientationPreserving());
|
||||||
|
CHECK(stellarOperator.IsPrepared());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Accepted Reduced Geometries Remain Valid Across Prepared Stellar Physics Quadrature Rules",
|
"Accepted Reduced Geometries Remain Valid Across Prepared Stellar Physics Quadrature Rules",
|
||||||
tags::reduced_stellar_geometry &tags::prepared &tags::geometry &tags::self_consistency
|
tags::reduced_stellar_geometry &tags::prepared &tags::geometry &tags::self_consistency
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
|
#include <limits>
|
||||||
#include <numbers>
|
#include <numbers>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -12,8 +13,7 @@ import mean_field;
|
|||||||
import test_helpers;
|
import test_helpers;
|
||||||
|
|
||||||
namespace outer_manifest_report_test {
|
namespace outer_manifest_report_test {
|
||||||
template <mean_field::model::StellarModelType Model>
|
template <mean_field::model::StellarModelType Model> class PreparedEarlierMultiplier;
|
||||||
class PreparedEarlierMultiplier;
|
|
||||||
|
|
||||||
class EarlierMultiplier final {
|
class EarlierMultiplier final {
|
||||||
public:
|
public:
|
||||||
@@ -36,12 +36,9 @@ namespace outer_manifest_report_test {
|
|||||||
"R_a",
|
"R_a",
|
||||||
"specific_energy",
|
"specific_energy",
|
||||||
"specific_energy">>;
|
"specific_energy">>;
|
||||||
using EquilibriumPhysics =
|
using EquilibriumPhysics = mean_field::operators::SpecificationEquilibriumPhysics<PreparedEarlierMultiplier>;
|
||||||
mean_field::operators::SpecificationEquilibriumPhysics<
|
|
||||||
PreparedEarlierMultiplier>;
|
|
||||||
|
|
||||||
explicit EarlierMultiplier(const Parameters parameters) noexcept
|
explicit EarlierMultiplier(const Parameters parameters) noexcept : m_target(parameters.target) {
|
||||||
: m_target(parameters.target) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] mean_field::dimensions::SpecificEnergyValue target() const noexcept {
|
[[nodiscard]] mean_field::dimensions::SpecificEnergyValue target() const noexcept {
|
||||||
@@ -52,20 +49,20 @@ namespace outer_manifest_report_test {
|
|||||||
mean_field::dimensions::SpecificEnergyValue m_target;
|
mean_field::dimensions::SpecificEnergyValue m_target;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <mean_field::model::StellarModelType Model>
|
template <mean_field::model::StellarModelType Model> class PreparedEarlierMultiplier final {
|
||||||
class PreparedEarlierMultiplier final {
|
|
||||||
public:
|
public:
|
||||||
using Report = mean_field::operators::EmptySpecificationPreparationReport;
|
using Report = mean_field::operators::EmptySpecificationPreparationReport;
|
||||||
|
|
||||||
explicit PreparedEarlierMultiplier(const EarlierMultiplier &) noexcept {
|
explicit PreparedEarlierMultiplier(const EarlierMultiplier &) noexcept {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename StateView>
|
template <typename StateView> [[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
|
||||||
[[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Equation, typename Row>
|
template <
|
||||||
|
typename Equation,
|
||||||
|
typename Row>
|
||||||
[[nodiscard]] mean_field::stellar::StructuralZero AddResidual(
|
[[nodiscard]] mean_field::stellar::StructuralZero AddResidual(
|
||||||
Equation,
|
Equation,
|
||||||
Row &
|
Row &
|
||||||
@@ -73,9 +70,15 @@ namespace outer_manifest_report_test {
|
|||||||
return mean_field::stellar::structuralZero;
|
return mean_field::stellar::structuralZero;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Equation, typename State, typename Direction, typename Row>
|
template <
|
||||||
|
typename Equation,
|
||||||
|
typename State,
|
||||||
|
typename Direction,
|
||||||
|
typename Row>
|
||||||
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||||
mean_field::stellar::Derivative<Equation, State>,
|
mean_field::stellar::Derivative<
|
||||||
|
Equation,
|
||||||
|
State>,
|
||||||
const Direction &,
|
const Direction &,
|
||||||
Row &
|
Row &
|
||||||
) const noexcept {
|
) const noexcept {
|
||||||
@@ -89,40 +92,38 @@ namespace outer_manifest_report_test {
|
|||||||
} // namespace outer_manifest_report_test
|
} // namespace outer_manifest_report_test
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::integral::FixedTotalMass>>;
|
mean_field::integral::FixedTotalMass>>;
|
||||||
|
|
||||||
using CentralDensityModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using CentralDensityModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::integral::FixedTotalMass,
|
mean_field::integral::FixedTotalMass,
|
||||||
mean_field::constraint::FixedCentralDensity>>;
|
mean_field::constraint::FixedCentralDensity>>;
|
||||||
|
|
||||||
using AngularMomentumModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using AngularMomentumModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::integral::FixedTotalMass,
|
mean_field::integral::FixedTotalMass,
|
||||||
mean_field::integral::FixedAngularMomentum>>;
|
mean_field::integral::FixedAngularMomentum>>;
|
||||||
|
|
||||||
using AngularMomentumCentralDensityModel =
|
using AngularMomentumCentralDensityModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
mean_field::eos::Polytrope,
|
||||||
mean_field::eos::Polytrope,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::integral::FixedTotalMass,
|
||||||
mean_field::integral::FixedTotalMass,
|
mean_field::integral::FixedAngularMomentum,
|
||||||
mean_field::integral::FixedAngularMomentum,
|
mean_field::constraint::FixedCentralDensity>>;
|
||||||
mean_field::constraint::FixedCentralDensity>>;
|
|
||||||
|
|
||||||
using IncompleteModel =
|
using IncompleteModel =
|
||||||
mean_field::model::StellarModel<mean_field::models::SpecificationSet<mean_field::eos::Polytrope>>;
|
mean_field::model::StellarModel<mean_field::models::SpecificationSet<mean_field::eos::Polytrope>>;
|
||||||
|
|
||||||
using EarlierMultiplierModel =
|
using EarlierMultiplierModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
mean_field::eos::Polytrope,
|
||||||
mean_field::eos::Polytrope,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::surface::Isobaric,
|
outer_manifest_report_test::EarlierMultiplier,
|
||||||
outer_manifest_report_test::EarlierMultiplier,
|
mean_field::integral::FixedTotalMass>>;
|
||||||
mean_field::integral::FixedTotalMass>>;
|
|
||||||
|
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept HasLegacyNumericalModelAdapter = requires { typename Candidate::NumericalModelAdapter; };
|
concept HasLegacyNumericalModelAdapter = requires { typename Candidate::NumericalModelAdapter; };
|
||||||
@@ -166,8 +167,8 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
using BaseProblem = equilibrium::StellarEquilibriumProblem<BaseModel>;
|
using BaseProblem = equilibrium::StellarEquilibriumProblem<BaseModel>;
|
||||||
using CentralDensityProblem = equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
|
using CentralDensityProblem = equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
|
||||||
using AngularMomentumProblem = equilibrium::StellarEquilibriumProblem<AngularMomentumModel>;
|
using AngularMomentumProblem = equilibrium::StellarEquilibriumProblem<AngularMomentumModel>;
|
||||||
using AngularMomentumCentralDensityProblem =
|
using AngularMomentumCentralDensityProblem =
|
||||||
equilibrium::StellarEquilibriumProblem<AngularMomentumCentralDensityModel>;
|
equilibrium::StellarEquilibriumProblem<AngularMomentumCentralDensityModel>;
|
||||||
@@ -178,14 +179,17 @@ TEST_CASE(
|
|||||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<AngularMomentumCentralDensityModel>);
|
STATIC_CHECK(equilibrium::StellarEquilibriumModel<AngularMomentumCentralDensityModel>);
|
||||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<EarlierMultiplierModel>);
|
STATIC_CHECK(equilibrium::StellarEquilibriumModel<EarlierMultiplierModel>);
|
||||||
STATIC_CHECK_FALSE(equilibrium::StellarEquilibriumModel<IncompleteModel>);
|
STATIC_CHECK_FALSE(equilibrium::StellarEquilibriumModel<IncompleteModel>);
|
||||||
STATIC_CHECK_FALSE(operators::StellarEquilibriumRuntimeContribution<
|
STATIC_CHECK_FALSE(
|
||||||
outer_manifest_report_test::EarlierMultiplier>::registered);
|
operators::StellarEquilibriumRuntimeContribution<outer_manifest_report_test::EarlierMultiplier>::registered
|
||||||
STATIC_CHECK_FALSE(operators::stellarEquilibriumBackendRuntimeAuthorized<
|
);
|
||||||
outer_manifest_report_test::EarlierMultiplier,
|
STATIC_CHECK_FALSE(
|
||||||
EarlierMultiplierModel>);
|
operators::stellarEquilibriumBackendRuntimeAuthorized<
|
||||||
STATIC_CHECK(operators::StellarEquilibriumPhysicsAvailableFor<
|
outer_manifest_report_test::EarlierMultiplier, EarlierMultiplierModel>
|
||||||
outer_manifest_report_test::EarlierMultiplier,
|
);
|
||||||
EarlierMultiplierModel>);
|
STATIC_CHECK(
|
||||||
|
operators::StellarEquilibriumPhysicsAvailableFor<
|
||||||
|
outer_manifest_report_test::EarlierMultiplier, EarlierMultiplierModel>
|
||||||
|
);
|
||||||
STATIC_CHECK_FALSE(std::same_as<BaseProblem, CentralDensityProblem>);
|
STATIC_CHECK_FALSE(std::same_as<BaseProblem, CentralDensityProblem>);
|
||||||
STATIC_CHECK(BaseProblem::symbolicallySquare);
|
STATIC_CHECK(BaseProblem::symbolicallySquare);
|
||||||
STATIC_CHECK(CentralDensityProblem::symbolicallySquare);
|
STATIC_CHECK(CentralDensityProblem::symbolicallySquare);
|
||||||
@@ -213,20 +217,23 @@ TEST_CASE(
|
|||||||
typename AngularMomentumProblem::PreparedOperatorType,
|
typename AngularMomentumProblem::PreparedOperatorType,
|
||||||
operators::PreparedVariadicStellarEquilibriumOperator<AngularMomentumModel>>
|
operators::PreparedVariadicStellarEquilibriumOperator<AngularMomentumModel>>
|
||||||
);
|
);
|
||||||
STATIC_CHECK_FALSE(std::same_as<
|
STATIC_CHECK_FALSE(
|
||||||
typename BaseProblem::PreparedOperatorType,
|
std::same_as<typename BaseProblem::PreparedOperatorType, typename CentralDensityProblem::PreparedOperatorType>
|
||||||
typename CentralDensityProblem::PreparedOperatorType>);
|
);
|
||||||
STATIC_CHECK_FALSE(std::same_as<
|
STATIC_CHECK_FALSE(
|
||||||
typename AngularMomentumProblem::PreparedOperatorType,
|
std::same_as<
|
||||||
typename AngularMomentumCentralDensityProblem::PreparedOperatorType>);
|
typename AngularMomentumProblem::PreparedOperatorType,
|
||||||
|
typename AngularMomentumCentralDensityProblem::PreparedOperatorType>
|
||||||
|
);
|
||||||
STATIC_CHECK(AngularMomentumProblem::FormType::value_block_count == 7);
|
STATIC_CHECK(AngularMomentumProblem::FormType::value_block_count == 7);
|
||||||
STATIC_CHECK(AngularMomentumCentralDensityProblem::FormType::value_block_count == 8);
|
STATIC_CHECK(AngularMomentumCentralDensityProblem::FormType::value_block_count == 8);
|
||||||
STATIC_CHECK(std::same_as<
|
STATIC_CHECK(
|
||||||
typename BaseProblem::FormType,
|
std::same_as<typename BaseProblem::FormType, utils::blocks::surface_deformed_stellar_equilibrium_form>
|
||||||
utils::blocks::surface_deformed_stellar_equilibrium_form>);
|
);
|
||||||
STATIC_CHECK(std::same_as<
|
STATIC_CHECK(
|
||||||
typename CentralDensityProblem::FormType,
|
std::same_as<
|
||||||
utils::blocks::central_density_bordered_stellar_equilibrium_form>);
|
typename CentralDensityProblem::FormType, utils::blocks::central_density_bordered_stellar_equilibrium_form>
|
||||||
|
);
|
||||||
STATIC_CHECK(
|
STATIC_CHECK(
|
||||||
std::same_as<
|
std::same_as<
|
||||||
typename BaseProblem::CompiledSurfaceConstraintType,
|
typename BaseProblem::CompiledSurfaceConstraintType,
|
||||||
@@ -242,34 +249,29 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
utils::Args arguments = test_utils::setup_args();
|
utils::Args arguments = test_utils::setup_args();
|
||||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
REQUIRE(finiteElements.okay());
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
constexpr double radius = utils::RADIUS;
|
constexpr double radius = utils::RADIUS;
|
||||||
constexpr double mass = utils::MASS;
|
constexpr double mass = utils::MASS;
|
||||||
constexpr double targetAngularMomentum = 0.1;
|
constexpr double targetAngularMomentum = 0.1;
|
||||||
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
||||||
const double seedCentralDensity =
|
const double seedCentralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
||||||
std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
auto model = model::StellarModel(
|
||||||
auto model = model::StellarModel(
|
|
||||||
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
integral::FixedAngularMomentum({
|
integral::FixedAngularMomentum(
|
||||||
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
|
{.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum}, .axis = {0.0, 0.0, 3.0}}
|
||||||
.axis = {0.0, 0.0, 3.0}
|
)
|
||||||
})
|
|
||||||
);
|
);
|
||||||
auto problem = equilibrium::discretize(model, finiteElements);
|
auto problem = equilibrium::discretize(model, std::move(finiteElements));
|
||||||
auto projected = seed::makeProjectedEquilibriumState(
|
auto projected = seed::makeProjectedEquilibriumState(
|
||||||
problem,
|
problem,
|
||||||
seed::LaneEmden({
|
seed::LaneEmden({.centralDensity = dimensions::DensityValue{seedCentralDensity}, .radialSampleCount = 1024})
|
||||||
.centralDensity = dimensions::DensityValue{seedCentralDensity},
|
|
||||||
.radialSampleCount = 1024
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
auto dependencies = make_dependencies();
|
auto dependencies = make_dependencies();
|
||||||
|
|
||||||
const auto preparation = problem.Prepare(projected.values, dependencies);
|
const auto preparation = problem.Prepare(projected.values, dependencies);
|
||||||
CHECK(preparation.generatedPhysicalControl);
|
CHECK(preparation.generatedPhysicalControl);
|
||||||
@@ -284,7 +286,7 @@ TEST_CASE(
|
|||||||
CHECK(std::abs(angularReport.scaledResidual) < 7.0e-4);
|
CHECK(std::abs(angularReport.scaledResidual) < 7.0e-4);
|
||||||
|
|
||||||
mfem::Vector direction(problem.StateSize());
|
mfem::Vector direction(problem.StateSize());
|
||||||
direction = 0.0;
|
direction = 0.0;
|
||||||
mfem::Vector angularVelocityDirection = problem.GetManifest().stateView(direction).block(
|
mfem::Vector angularVelocityDirection = problem.GetManifest().stateView(direction).block(
|
||||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||||
);
|
);
|
||||||
@@ -309,15 +311,15 @@ TEST_CASE(
|
|||||||
plusResidual -= minusResidual;
|
plusResidual -= minusResidual;
|
||||||
plusResidual /= 2.0 * step;
|
plusResidual /= 2.0 * step;
|
||||||
|
|
||||||
auto analyticView = problem.GetManifest().residualView(analyticAction);
|
auto analyticView = problem.GetManifest().residualView(analyticAction);
|
||||||
auto differenceView = problem.GetManifest().residualView(plusResidual);
|
auto differenceView = problem.GetManifest().residualView(plusResidual);
|
||||||
const auto blockError = [&](const auto &term) {
|
const auto blockError = [&](const auto &term) {
|
||||||
const mfem::Vector analytic = analyticView.block(term);
|
const mfem::Vector analytic = analyticView.block(term);
|
||||||
const mfem::Vector difference = differenceView.block(term);
|
const mfem::Vector difference = differenceView.block(term);
|
||||||
return relative_difference(analytic, difference);
|
return relative_difference(analytic, difference);
|
||||||
};
|
};
|
||||||
|
|
||||||
const double surfaceError = blockError(utils::blocks::surface_deformation_field.shape_equilibrium_term);
|
const double surfaceError = blockError(utils::blocks::surface_deformation_field.shape_equilibrium_term);
|
||||||
const double enthalpyError = blockError(utils::blocks::enthalpy_field.specific_term);
|
const double enthalpyError = blockError(utils::blocks::enthalpy_field.specific_term);
|
||||||
const double angularMomentumError =
|
const double angularMomentumError =
|
||||||
blockError(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term);
|
blockError(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term);
|
||||||
@@ -335,13 +337,48 @@ TEST_CASE(
|
|||||||
CHECK(analyticView.block(utils::blocks::density_field.mass_term).Norml2() == 0.0);
|
CHECK(analyticView.block(utils::blocks::density_field.mass_term).Norml2() == 0.0);
|
||||||
CHECK(analyticView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term).Norml2() == 0.0);
|
CHECK(analyticView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term).Norml2() == 0.0);
|
||||||
|
|
||||||
|
mfem::Vector nonFiniteAngularVelocityState(projected.values);
|
||||||
|
auto nonFiniteAngularVelocity = problem.GetManifest()
|
||||||
|
.stateView(nonFiniteAngularVelocityState)
|
||||||
|
.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term);
|
||||||
|
REQUIRE(nonFiniteAngularVelocity.Size() == 1);
|
||||||
|
nonFiniteAngularVelocity(0) = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
nonFiniteAngularVelocity.SyncAliasMemory(nonFiniteAngularVelocityState);
|
||||||
|
const auto nonFiniteControl = problem.TryPrepare(nonFiniteAngularVelocityState, dependencies);
|
||||||
|
REQUIRE_FALSE(nonFiniteControl.has_value());
|
||||||
|
CHECK(
|
||||||
|
nonFiniteControl.error().reason == operators::StellarEquilibriumPreparationRejectionReason::non_finite_physics
|
||||||
|
);
|
||||||
|
CHECK_FALSE(problem.IsPrepared());
|
||||||
|
REQUIRE(problem.TryPrepare(projected.values, dependencies).has_value());
|
||||||
|
|
||||||
|
mfem::Vector negativeDensityState(projected.values);
|
||||||
|
auto negativeDensity =
|
||||||
|
problem.GetManifest().stateView(negativeDensityState).block(utils::blocks::density_field.mass_term);
|
||||||
|
negativeDensity *= -1.0;
|
||||||
|
negativeDensity.SyncAliasMemory(negativeDensityState);
|
||||||
|
auto negativeDensityDependencies = dependencies;
|
||||||
|
++negativeDensityDependencies.density.revision;
|
||||||
|
const auto inadmissibleMoment = problem.TryPrepare(negativeDensityState, negativeDensityDependencies);
|
||||||
|
REQUIRE_FALSE(inadmissibleMoment.has_value());
|
||||||
|
CHECK(
|
||||||
|
inadmissibleMoment.error().reason ==
|
||||||
|
operators::StellarEquilibriumPreparationRejectionReason::inadmissible_physics
|
||||||
|
);
|
||||||
|
CHECK_FALSE(problem.IsPrepared());
|
||||||
|
++negativeDensityDependencies.density.revision;
|
||||||
|
REQUIRE(problem.TryPrepare(projected.values, negativeDensityDependencies).has_value());
|
||||||
|
CHECK(problem.IsPrepared());
|
||||||
|
|
||||||
auto zeroModel = model::StellarModel(
|
auto zeroModel = model::StellarModel(
|
||||||
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.0}})
|
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.0}})
|
||||||
);
|
);
|
||||||
auto zeroProblem = equilibrium::discretize(zeroModel, finiteElements);
|
fem::FEM zeroFiniteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
REQUIRE(zeroFiniteElements.okay());
|
||||||
|
auto zeroProblem = equilibrium::discretize(zeroModel, std::move(zeroFiniteElements));
|
||||||
mfem::Vector zeroState(projected.values);
|
mfem::Vector zeroState(projected.values);
|
||||||
zeroProblem.GetManifest().stateView(zeroState).block(
|
zeroProblem.GetManifest().stateView(zeroState).block(
|
||||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||||
@@ -361,47 +398,37 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
using Form = operators::CompiledStellarEquilibriumForm<EarlierMultiplierModel>;
|
using Form = operators::CompiledStellarEquilibriumForm<EarlierMultiplierModel>;
|
||||||
using EarlierValue = utils::blocks::generated_value_block<
|
using EarlierValue =
|
||||||
models::MultiplierFor<outer_manifest_report_test::EarlierMultiplier>>;
|
utils::blocks::generated_value_block<models::MultiplierFor<outer_manifest_report_test::EarlierMultiplier>>;
|
||||||
using MassValue = utils::blocks::fixed_total_mass::mass_normalization::value;
|
using MassValue = utils::blocks::fixed_total_mass::mass_normalization::value;
|
||||||
|
|
||||||
STATIC_CHECK(utils::blocks::type_index_v<
|
STATIC_CHECK(utils::blocks::type_index_v<EarlierValue, typename Form::value_blocks> == 5);
|
||||||
EarlierValue,
|
STATIC_CHECK(utils::blocks::type_index_v<MassValue, typename Form::value_blocks> == 6);
|
||||||
typename Form::value_blocks> == 5);
|
|
||||||
STATIC_CHECK(utils::blocks::type_index_v<
|
|
||||||
MassValue,
|
|
||||||
typename Form::value_blocks> == 6);
|
|
||||||
|
|
||||||
utils::Args arguments = test_utils::setup_args();
|
utils::Args arguments = test_utils::setup_args();
|
||||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
REQUIRE(finiteElements.okay());
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
auto model = model::StellarModel(
|
auto model = model::StellarModel(
|
||||||
eos::Polytrope({.n = 1.0, .K = 0.25}),
|
eos::Polytrope({.n = 1.0, .K = 0.25}), surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
outer_manifest_report_test::EarlierMultiplier({.target = dimensions::SpecificEnergyValue{0.75}}),
|
||||||
outer_manifest_report_test::EarlierMultiplier({
|
|
||||||
.target = dimensions::SpecificEnergyValue{0.75}}),
|
|
||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.25}})
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.25}})
|
||||||
);
|
);
|
||||||
auto problem = equilibrium::discretize(model, finiteElements);
|
auto problem = equilibrium::discretize(model, std::move(finiteElements));
|
||||||
|
|
||||||
mfem::Vector state(problem.StateSize());
|
mfem::Vector state(problem.StateSize());
|
||||||
state = 0.0;
|
state = 0.0;
|
||||||
const auto stateView = problem.GetManifest().stateView(state);
|
const auto stateView = problem.GetManifest().stateView(state);
|
||||||
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||||
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||||
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term) = 0.25;
|
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term) = 0.25;
|
||||||
|
|
||||||
const auto preparation = problem.Prepare(
|
const auto preparation = problem.TryPrepare(state, make_dependencies(), make_zero_rotation());
|
||||||
state,
|
REQUIRE(preparation.has_value());
|
||||||
make_dependencies(),
|
REQUIRE(preparation->physical.DidAnyWork());
|
||||||
make_zero_rotation()
|
|
||||||
);
|
|
||||||
REQUIRE(preparation.physical.DidAnyWork());
|
|
||||||
|
|
||||||
const auto report = problem.GetPreparedOperator().GetFixedMassReport();
|
const auto report = problem.GetPreparedOperator().GetFixedMassReport();
|
||||||
const auto &outerDescriptor =
|
const auto &outerDescriptor = problem.GetManifest().template specification<models::FixedTotalMass>();
|
||||||
problem.GetManifest().template specification<models::FixedTotalMass>();
|
|
||||||
CHECK(report.descriptor.stableId == outerDescriptor.stableId);
|
CHECK(report.descriptor.stableId == outerDescriptor.stableId);
|
||||||
CHECK(report.descriptor.valueBlock == outerDescriptor.valueBlock);
|
CHECK(report.descriptor.valueBlock == outerDescriptor.valueBlock);
|
||||||
CHECK(report.descriptor.residualBlock == outerDescriptor.residualBlock);
|
CHECK(report.descriptor.residualBlock == outerDescriptor.residualBlock);
|
||||||
@@ -409,8 +436,7 @@ TEST_CASE(
|
|||||||
CHECK(report.descriptor.residualBlock == 6);
|
CHECK(report.descriptor.residualBlock == 6);
|
||||||
CHECK(report.descriptor.target == 1.25);
|
CHECK(report.descriptor.target == 1.25);
|
||||||
CHECK(report.dimensionalResidual == report.achieved - report.descriptor.target);
|
CHECK(report.dimensionalResidual == report.achieved - report.descriptor.target);
|
||||||
CHECK(report.scaledResidual ==
|
CHECK(report.scaledResidual == report.dimensionalResidual / report.descriptor.residualScale);
|
||||||
report.dimensionalResidual / report.descriptor.residualScale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
@@ -419,32 +445,38 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
utils::Args args = test_utils::setup_args();
|
utils::Args args = test_utils::setup_args();
|
||||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
fem::FEM legacyFiniteElements = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
REQUIRE(f.okay());
|
REQUIRE(legacyFiniteElements.okay());
|
||||||
|
|
||||||
|
fem::FEM modelDrivenFiniteElements = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(modelDrivenFiniteElements.okay());
|
||||||
|
const MPI_Comm modelDrivenCommunicator = modelDrivenFiniteElements.mesh->GetComm();
|
||||||
|
const mapping::DomainMapper *modelDrivenMapper = modelDrivenFiniteElements.domainMapperStateless.get();
|
||||||
|
|
||||||
models::StellarModel legacyModel{
|
models::StellarModel legacyModel{
|
||||||
models::structure::PolytropicStructure{eos::Polytrope{3.0, 0.25}, 1.25},
|
models::structure::PolytropicStructure{eos::Polytrope{3.0, 0.25}, 1.25},
|
||||||
surface::ConstantPressureSurface{eos::PressureValue{0.0}}
|
surface::ConstantPressureSurface{eos::PressureValue{0.0}}
|
||||||
};
|
};
|
||||||
operators::PreparedStellarEquilibriumOperator legacyOperator(f, *f.domainMapperStateless, legacyModel);
|
operators::PreparedStellarEquilibriumOperator legacyOperator(
|
||||||
|
legacyFiniteElements, *legacyFiniteElements.domainMapperStateless, legacyModel
|
||||||
|
);
|
||||||
|
|
||||||
const equilibrium::StellarDiscretization discretization{f, *f.domainMapperStateless};
|
|
||||||
auto equilibriumProblem = equilibrium::discretize(
|
auto equilibriumProblem = equilibrium::discretize(
|
||||||
model::StellarModel(
|
model::StellarModel(
|
||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.25}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.25}}),
|
||||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}), eos::Polytrope({.n = 3.0, .K = 0.25})
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}), eos::Polytrope({.n = 3.0, .K = 0.25})
|
||||||
),
|
),
|
||||||
discretization
|
std::move(modelDrivenFiniteElements)
|
||||||
);
|
);
|
||||||
auto &modelDrivenOperator = equilibriumProblem.GetPreparedOperator();
|
auto &modelDrivenOperator = equilibriumProblem.GetPreparedOperator();
|
||||||
const auto &physicalOperator = equilibriumProblem.GetPhysicalOperator();
|
const auto &physicalOperator = equilibriumProblem.GetPhysicalOperator();
|
||||||
|
|
||||||
CHECK(equilibriumProblem.StateSize() == legacyOperator.Width());
|
CHECK(equilibriumProblem.StateSize() == legacyOperator.Width());
|
||||||
CHECK(equilibriumProblem.EquationSize() == legacyOperator.Height());
|
CHECK(equilibriumProblem.EquationSize() == legacyOperator.Height());
|
||||||
CHECK(equilibriumProblem.StateSize() == equilibriumProblem.EquationSize());
|
CHECK(equilibriumProblem.StateSize() == equilibriumProblem.EquationSize());
|
||||||
CHECK(&equilibriumProblem.GetDiscretization().finiteElementModel() == &f);
|
CHECK(equilibriumProblem.GetCommunicator() == modelDrivenCommunicator);
|
||||||
CHECK(&equilibriumProblem.GetDiscretization().domainMapper() == f.domainMapperStateless.get());
|
CHECK(&equilibriumProblem.GetDiscretization().domainMapper() == modelDrivenMapper);
|
||||||
CHECK(equilibriumProblem.GetDiscretization().isCurrent());
|
CHECK(equilibriumProblem.GetDiscretization().isCurrent());
|
||||||
CHECK(physicalOperator.GetTargetMass() == 1.25);
|
CHECK(physicalOperator.GetTargetMass() == 1.25);
|
||||||
CHECK(physicalOperator.GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure == 0.0);
|
CHECK(physicalOperator.GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure == 0.0);
|
||||||
@@ -455,7 +487,7 @@ TEST_CASE(
|
|||||||
|
|
||||||
mfem::Vector state(legacyOperator.Width());
|
mfem::Vector state(legacyOperator.Width());
|
||||||
state = 0.0;
|
state = 0.0;
|
||||||
const auto stateView = legacyOperator.GetRootManifest().stateView(state);
|
const auto stateView = legacyOperator.GetRootManifest().stateView(state);
|
||||||
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||||
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||||
|
|
||||||
@@ -487,29 +519,28 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
utils::Args arguments = test_utils::setup_args();
|
utils::Args arguments = test_utils::setup_args();
|
||||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
REQUIRE(finiteElements.okay());
|
REQUIRE(finiteElements.okay());
|
||||||
auto model = model::StellarModel(
|
auto model = model::StellarModel(
|
||||||
eos::Polytrope({.n = 1.0, .K = 0.25}),
|
eos::Polytrope({.n = 1.0, .K = 0.25}), surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
|
||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
||||||
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.2}}),
|
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.2}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
||||||
);
|
);
|
||||||
auto problem = equilibrium::discretize(model, finiteElements);
|
auto problem = equilibrium::discretize(model, std::move(finiteElements));
|
||||||
using Problem = std::remove_cvref_t<decltype(problem)>;
|
using Problem = std::remove_cvref_t<decltype(problem)>;
|
||||||
STATIC_CHECK(Problem::FormType::value_block_count == 8);
|
STATIC_CHECK(Problem::FormType::value_block_count == 8);
|
||||||
STATIC_CHECK(Problem::FormType::residual_block_count == 8);
|
STATIC_CHECK(Problem::FormType::residual_block_count == 8);
|
||||||
|
|
||||||
mfem::Vector state(problem.StateSize());
|
mfem::Vector state(problem.StateSize());
|
||||||
state = 0.0;
|
state = 0.0;
|
||||||
const auto stateView = problem.GetManifest().stateView(state);
|
const auto stateView = problem.GetManifest().stateView(state);
|
||||||
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||||
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||||
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term) = 0.25;
|
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term) = 0.25;
|
||||||
stateView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term) = 0.4;
|
stateView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term) = 0.4;
|
||||||
stateView.block(utils::blocks::fixed_central_density_phase.central_value_term) = 0.03;
|
stateView.block(utils::blocks::fixed_central_density_phase.central_value_term) = 0.03;
|
||||||
|
|
||||||
const auto report = problem.Prepare(state, make_dependencies());
|
const auto report = problem.Prepare(state, make_dependencies());
|
||||||
CHECK(report.template specification<models::FixedCentralDensity>().constraint.DidAnyWork());
|
CHECK(report.template specification<models::FixedCentralDensity>().constraint.DidAnyWork());
|
||||||
@@ -517,18 +548,18 @@ TEST_CASE(
|
|||||||
CHECK(problem.IsPrepared());
|
CHECK(problem.IsPrepared());
|
||||||
CHECK(problem.StateSize() == problem.GetPhysicalOperator().Width() + 2);
|
CHECK(problem.StateSize() == problem.GetPhysicalOperator().Width() + 2);
|
||||||
REQUIRE(problem.GetManifest().constraints().size() == 4);
|
REQUIRE(problem.GetManifest().constraints().size() == 4);
|
||||||
CHECK(problem.GetManifest().template specification<models::FixedAngularMomentum>().stableId ==
|
CHECK(
|
||||||
"FixedAngularMomentum");
|
problem.GetManifest().template specification<models::FixedAngularMomentum>().stableId == "FixedAngularMomentum"
|
||||||
CHECK(problem.GetManifest().template specification<models::FixedCentralDensity>().stableId ==
|
);
|
||||||
"FixedCentralDensity");
|
CHECK(
|
||||||
|
problem.GetManifest().template specification<models::FixedCentralDensity>().stableId == "FixedCentralDensity"
|
||||||
|
);
|
||||||
|
|
||||||
mfem::Vector residual;
|
mfem::Vector residual;
|
||||||
problem.BuildResidual(residual);
|
problem.BuildResidual(residual);
|
||||||
REQUIRE(residual.Size() == problem.EquationSize());
|
REQUIRE(residual.Size() == problem.EquationSize());
|
||||||
const auto residualView = problem.GetManifest().residualView(residual);
|
const auto residualView = problem.GetManifest().residualView(residual);
|
||||||
CHECK(std::isfinite(
|
CHECK(std::isfinite(residualView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term)(0)));
|
||||||
residualView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term)(0)
|
|
||||||
));
|
|
||||||
CHECK(std::isfinite(residualView.block(utils::blocks::fixed_central_density_phase.central_value_term)(0)));
|
CHECK(std::isfinite(residualView.block(utils::blocks::fixed_central_density_phase.central_value_term)(0)));
|
||||||
|
|
||||||
mfem::Vector direction(problem.StateSize());
|
mfem::Vector direction(problem.StateSize());
|
||||||
|
|||||||
@@ -57,33 +57,26 @@ namespace {
|
|||||||
blocks::type_list<>,
|
blocks::type_list<>,
|
||||||
preconditioning::IdentityOperatorCharacteristics,
|
preconditioning::IdentityOperatorCharacteristics,
|
||||||
preconditioning::backend::Identity>;
|
preconditioning::backend::Identity>;
|
||||||
using LifetimeModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using LifetimeModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::models::FixedTotalMass,
|
mean_field::models::FixedTotalMass,
|
||||||
mean_field::models::FixedCentralDensity>>;
|
mean_field::models::FixedCentralDensity>>;
|
||||||
using LifetimeProblem = mean_field::equilibrium::StellarEquilibriumProblem<LifetimeModel>;
|
using LifetimeProblem = mean_field::equilibrium::StellarEquilibriumProblem<LifetimeModel>;
|
||||||
using LifetimeBlock = decltype(preconditioning::makePreconditioner(
|
using LifetimeBlock = decltype(preconditioning::makePreconditioner(std::declval<const LifetimeProblem &>()));
|
||||||
std::declval<const LifetimeProblem &>()
|
using LifetimePrepared = preconditioning::PreparedStellarPreconditioner<LifetimeProblem, LifetimeBlock>;
|
||||||
));
|
|
||||||
using LifetimePrepared = preconditioning::PreparedStellarPreconditioner<
|
|
||||||
LifetimeProblem,
|
|
||||||
LifetimeBlock>;
|
|
||||||
|
|
||||||
template <typename Problem, typename Block>
|
template <typename Problem, typename Block>
|
||||||
concept CanPrepareStellarPreconditioner = requires(const Problem &problem, Block block) {
|
concept CanPrepareStellarPreconditioner =
|
||||||
preconditioning::prepare(problem, std::move(block));
|
requires(const Problem &problem, Block block) { preconditioning::prepare(problem, std::move(block)); };
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Problem, typename Block>
|
template <typename Problem, typename Block>
|
||||||
concept CanPrepareStellarPreconditionerFromTemporary = requires(Block block) {
|
concept CanPrepareStellarPreconditionerFromTemporary =
|
||||||
preconditioning::prepare(std::declval<Problem &&>(), std::move(block));
|
requires(Block block) { preconditioning::prepare(std::declval<Problem &&>(), std::move(block)); };
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Problem, typename Block>
|
template <typename Problem, typename Block>
|
||||||
concept CanPrepareStellarPreconditionerFromConstTemporary = requires(Block block) {
|
concept CanPrepareStellarPreconditionerFromConstTemporary =
|
||||||
preconditioning::prepare(std::declval<const Problem &&>(), std::move(block));
|
requires(Block block) { preconditioning::prepare(std::declval<const Problem &&>(), std::move(block)); };
|
||||||
};
|
|
||||||
|
|
||||||
[[nodiscard]] blocks::form_layout<Form> makeUnevenLayout() {
|
[[nodiscard]] blocks::form_layout<Form> makeUnevenLayout() {
|
||||||
return {
|
return {
|
||||||
@@ -221,7 +214,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
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 = 512}));
|
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
|
||||||
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <numbers>
|
#include <numbers>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <catch2/catch_approx.hpp>
|
#include <catch2/catch_approx.hpp>
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
@@ -18,12 +19,12 @@ namespace material_surface_runtime_contract_test {
|
|||||||
struct RegisteredAlternateEquationOfState final {
|
struct RegisteredAlternateEquationOfState final {
|
||||||
struct Parameters final { };
|
struct Parameters final { };
|
||||||
|
|
||||||
using ModelDefinition = mean_field::models::ConstitutiveLaw<
|
using ModelDefinition = mean_field::models::
|
||||||
RegisteredAlternateEquationOfState,
|
ConstitutiveLaw<RegisteredAlternateEquationOfState, "RegisteredAlternateMaterialSurfaceEquationOfState">;
|
||||||
"RegisteredAlternateMaterialSurfaceEquationOfState">;
|
|
||||||
using Relations = mean_field::eos::RelationCatalog<mean_field::eos::SpecificEnthalpyFromPressure>;
|
using Relations = mean_field::eos::RelationCatalog<mean_field::eos::SpecificEnthalpyFromPressure>;
|
||||||
|
|
||||||
explicit RegisteredAlternateEquationOfState(Parameters) noexcept { }
|
explicit RegisteredAlternateEquationOfState(Parameters) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] mean_field::dimensions::SpecificEnthalpyValue evaluate(
|
[[nodiscard]] mean_field::dimensions::SpecificEnthalpyValue evaluate(
|
||||||
mean_field::eos::SpecificEnthalpyFromPressure,
|
mean_field::eos::SpecificEnthalpyFromPressure,
|
||||||
@@ -40,7 +41,10 @@ namespace material_surface_runtime_contract_test {
|
|||||||
|
|
||||||
using mfem::Operator::Operator;
|
using mfem::Operator::Operator;
|
||||||
|
|
||||||
void Mult(const mfem::Vector &, mfem::Vector &) const override;
|
void Mult(
|
||||||
|
const mfem::Vector &,
|
||||||
|
mfem::Vector &
|
||||||
|
) const override;
|
||||||
|
|
||||||
[[nodiscard]] const mean_field::operators::StellarEquilibriumLayout &GetLayout() const noexcept;
|
[[nodiscard]] const mean_field::operators::StellarEquilibriumLayout &GetLayout() const noexcept;
|
||||||
[[nodiscard]] mean_field::operators::PreparedStellarEquilibriumReport Prepare(
|
[[nodiscard]] mean_field::operators::PreparedStellarEquilibriumReport Prepare(
|
||||||
@@ -58,8 +62,7 @@ namespace material_surface_runtime_contract_test {
|
|||||||
GetSurfaceConstraintOperator() const;
|
GetSurfaceConstraintOperator() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <mean_field::model::StellarModelType Model>
|
template <mean_field::model::StellarModelType Model> class AlternateEquationOfStateRuntime final {
|
||||||
class AlternateEquationOfStateRuntime final {
|
|
||||||
public:
|
public:
|
||||||
using Report = mean_field::operators::EmptySpecificationPreparationReport;
|
using Report = mean_field::operators::EmptySpecificationPreparationReport;
|
||||||
|
|
||||||
@@ -68,10 +71,17 @@ namespace material_surface_runtime_contract_test {
|
|||||||
const mean_field::mapping::DomainMapper &,
|
const mean_field::mapping::DomainMapper &,
|
||||||
AlternatePhysicalCore &,
|
AlternatePhysicalCore &,
|
||||||
const Model &
|
const Model &
|
||||||
) noexcept { }
|
) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
template <typename StateView, typename Controls>
|
template <
|
||||||
void ReadPhysicalControls(const StateView &, Controls &) noexcept { }
|
typename StateView,
|
||||||
|
typename Controls>
|
||||||
|
void ReadPhysicalControls(
|
||||||
|
const StateView &,
|
||||||
|
Controls &
|
||||||
|
) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
template <typename StateView>
|
template <typename StateView>
|
||||||
[[nodiscard]] Report PrepareAfterPhysical(
|
[[nodiscard]] Report PrepareAfterPhysical(
|
||||||
@@ -82,15 +92,18 @@ namespace material_surface_runtime_contract_test {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ResidualView>
|
template <typename ResidualView> void AddResidual(const ResidualView &) const noexcept {
|
||||||
void AddResidual(const ResidualView &) const noexcept { }
|
}
|
||||||
|
|
||||||
template <typename DirectionView, typename ActionView>
|
template <
|
||||||
|
typename DirectionView,
|
||||||
|
typename ActionView>
|
||||||
void AddJacobianAction(
|
void AddJacobianAction(
|
||||||
const DirectionView &,
|
const DirectionView &,
|
||||||
const ActionView &,
|
const ActionView &,
|
||||||
const AlternatePhysicalCore &
|
const AlternatePhysicalCore &
|
||||||
) const noexcept { }
|
) const noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr bool IsPrepared() const noexcept {
|
[[nodiscard]] constexpr bool IsPrepared() const noexcept {
|
||||||
return true;
|
return true;
|
||||||
@@ -100,10 +113,9 @@ namespace material_surface_runtime_contract_test {
|
|||||||
|
|
||||||
namespace mean_field::operators {
|
namespace mean_field::operators {
|
||||||
template <>
|
template <>
|
||||||
struct StellarEquilibriumCoreRuntime<
|
struct StellarEquilibriumCoreRuntime<material_surface_runtime_contract_test::RegisteredAlternateEquationOfState> {
|
||||||
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState> {
|
|
||||||
static constexpr bool registered = true;
|
static constexpr bool registered = true;
|
||||||
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
|
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
|
||||||
|
|
||||||
[[nodiscard]] static std::unique_ptr<CoreType> Make(
|
[[nodiscard]] static std::unique_ptr<CoreType> Make(
|
||||||
fem::FEM &,
|
fem::FEM &,
|
||||||
@@ -134,7 +146,7 @@ namespace mean_field::preconditioning {
|
|||||||
struct MaterialSurfaceEquationOfStateBackend<
|
struct MaterialSurfaceEquationOfStateBackend<
|
||||||
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState> {
|
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState> {
|
||||||
static constexpr bool registered = true;
|
static constexpr bool registered = true;
|
||||||
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
|
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
|
||||||
};
|
};
|
||||||
} // namespace mean_field::preconditioning
|
} // namespace mean_field::preconditioning
|
||||||
|
|
||||||
@@ -150,12 +162,12 @@ namespace {
|
|||||||
mean_field::constraint::FixedCentralDensity>>;
|
mean_field::constraint::FixedCentralDensity>>;
|
||||||
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
|
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
|
||||||
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
|
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
|
||||||
using RegisteredAlternateModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using RegisteredAlternateModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState,
|
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::integral::FixedTotalMass>>;
|
mean_field::integral::FixedTotalMass>>;
|
||||||
using RegisteredAlternateProblem = mean_field::equilibrium::StellarEquilibriumProblem<RegisteredAlternateModel>;
|
using RegisteredAlternateProblem = mean_field::equilibrium::StellarEquilibriumProblem<RegisteredAlternateModel>;
|
||||||
using MaterialSurfaceDiagonal = preconditioning::MaterialSurfaceBlock<
|
using MaterialSurfaceDiagonal = preconditioning::MaterialSurfaceBlock<
|
||||||
PolytropicMaterialSurfaceDescriptor,
|
PolytropicMaterialSurfaceDescriptor,
|
||||||
backend::Diagonal,
|
backend::Diagonal,
|
||||||
backend::Diagonal,
|
backend::Diagonal,
|
||||||
@@ -174,10 +186,8 @@ namespace {
|
|||||||
PolytropicMaterialSurfaceDescriptor,
|
PolytropicMaterialSurfaceDescriptor,
|
||||||
preconditioning::ApproximateMaterialSurfaceLDU,
|
preconditioning::ApproximateMaterialSurfaceLDU,
|
||||||
backend::FixedCycles>;
|
backend::FixedCycles>;
|
||||||
using RegisteredAlternateGravityComponent = preconditioning::GravityFieldBlock<
|
using RegisteredAlternateGravityComponent =
|
||||||
backend::Diagonal,
|
preconditioning::GravityFieldBlock<backend::Diagonal, FixedCycleAMG, preconditioning::GravityApproximateLDU>;
|
||||||
FixedCycleAMG,
|
|
||||||
preconditioning::GravityApproximateLDU>;
|
|
||||||
|
|
||||||
using RegisteredAlternateMaterialSurfaceDescriptor =
|
using RegisteredAlternateMaterialSurfaceDescriptor =
|
||||||
preconditioning::MaterialSurfaceDescriptorFor<RegisteredAlternateProblem>;
|
preconditioning::MaterialSurfaceDescriptorFor<RegisteredAlternateProblem>;
|
||||||
@@ -185,9 +195,8 @@ namespace {
|
|||||||
struct DistinctPhysicalCore final { };
|
struct DistinctPhysicalCore final { };
|
||||||
|
|
||||||
template <typename Problem>
|
template <typename Problem>
|
||||||
concept CanMakeDefaultMaterialSurface = requires(const Problem &problem) {
|
concept CanMakeDefaultMaterialSurface =
|
||||||
preconditioning::materialSurfaceBlock(problem);
|
requires(const Problem &problem) { preconditioning::materialSurfaceBlock(problem); };
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Problem>
|
template <typename Problem>
|
||||||
concept CanPrepareDefaultMaterialSurface = requires(const Problem &problem) {
|
concept CanPrepareDefaultMaterialSurface = requires(const Problem &problem) {
|
||||||
@@ -195,9 +204,8 @@ namespace {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename Problem>
|
template <typename Problem>
|
||||||
concept CanMakeDefaultStellarStructure = requires(const Problem &problem) {
|
concept CanMakeDefaultStellarStructure =
|
||||||
preconditioning::stellarStructureBlock(problem);
|
requires(const Problem &problem) { preconditioning::stellarStructureBlock(problem); };
|
||||||
};
|
|
||||||
|
|
||||||
class KnownCouplings final {
|
class KnownCouplings final {
|
||||||
public:
|
public:
|
||||||
@@ -315,41 +323,46 @@ TEST_CASE(
|
|||||||
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceDiagonal>);
|
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceDiagonal>);
|
||||||
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceH1>);
|
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceH1>);
|
||||||
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<PolytropicMaterialSurfaceDescriptor>);
|
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<PolytropicMaterialSurfaceDescriptor>);
|
||||||
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceEquationOfState<
|
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceEquationOfState<mean_field::eos::Polytrope>);
|
||||||
mean_field::eos::Polytrope>);
|
STATIC_CHECK_FALSE(preconditioning::ImplementedMaterialSurfaceEquationOfState<int>);
|
||||||
|
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceDescriptor<PolytropicMaterialSurfaceDescriptor>);
|
||||||
|
STATIC_CHECK(
|
||||||
|
preconditioning::ExecutableMaterialSurfaceRuntimeFor<
|
||||||
|
mean_field::eos::Polytrope, mean_field::operators::PreparedStellarEquilibriumOperator>
|
||||||
|
);
|
||||||
|
STATIC_CHECK(
|
||||||
|
std::same_as<
|
||||||
|
typename preconditioning::MaterialSurfaceEquationOfStateBackend<mean_field::eos::Polytrope>::CoreType,
|
||||||
|
mean_field::operators::PreparedStellarEquilibriumOperator>
|
||||||
|
);
|
||||||
|
STATIC_CHECK(
|
||||||
|
preconditioning::MaterialSurfaceRuntimeFor<
|
||||||
|
PolytropicMaterialSurfaceDescriptor, mean_field::operators::PreparedStellarEquilibriumOperator>
|
||||||
|
);
|
||||||
STATIC_CHECK_FALSE(
|
STATIC_CHECK_FALSE(
|
||||||
preconditioning::ImplementedMaterialSurfaceEquationOfState<int>);
|
preconditioning::MaterialSurfaceRuntimeFor<PolytropicMaterialSurfaceDescriptor, DistinctPhysicalCore>
|
||||||
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceDescriptor<
|
);
|
||||||
PolytropicMaterialSurfaceDescriptor>);
|
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<RegisteredAlternateMaterialSurfaceDescriptor>);
|
||||||
STATIC_CHECK(preconditioning::ExecutableMaterialSurfaceRuntimeFor<
|
|
||||||
mean_field::eos::Polytrope,
|
|
||||||
mean_field::operators::PreparedStellarEquilibriumOperator>);
|
|
||||||
STATIC_CHECK(std::same_as<
|
|
||||||
typename preconditioning::MaterialSurfaceEquationOfStateBackend<
|
|
||||||
mean_field::eos::Polytrope>::CoreType,
|
|
||||||
mean_field::operators::PreparedStellarEquilibriumOperator>);
|
|
||||||
STATIC_CHECK(preconditioning::MaterialSurfaceRuntimeFor<
|
|
||||||
PolytropicMaterialSurfaceDescriptor,
|
|
||||||
mean_field::operators::PreparedStellarEquilibriumOperator>);
|
|
||||||
STATIC_CHECK_FALSE(preconditioning::MaterialSurfaceRuntimeFor<
|
|
||||||
PolytropicMaterialSurfaceDescriptor,
|
|
||||||
DistinctPhysicalCore>);
|
|
||||||
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<
|
|
||||||
RegisteredAlternateMaterialSurfaceDescriptor>);
|
|
||||||
STATIC_CHECK(mean_field::equilibrium::StellarEquilibriumModel<RegisteredAlternateModel>);
|
STATIC_CHECK(mean_field::equilibrium::StellarEquilibriumModel<RegisteredAlternateModel>);
|
||||||
STATIC_CHECK(std::same_as<
|
STATIC_CHECK(
|
||||||
typename RegisteredAlternateProblem::PhysicalCoreType,
|
std::same_as<
|
||||||
material_surface_runtime_contract_test::AlternatePhysicalCore>);
|
typename RegisteredAlternateProblem::PhysicalCoreType,
|
||||||
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceEquationOfState<
|
material_surface_runtime_contract_test::AlternatePhysicalCore>
|
||||||
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState>);
|
);
|
||||||
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceDescriptor<
|
STATIC_CHECK(
|
||||||
RegisteredAlternateMaterialSurfaceDescriptor>);
|
preconditioning::ImplementedMaterialSurfaceEquationOfState<
|
||||||
STATIC_CHECK_FALSE(preconditioning::ExecutableMaterialSurfaceRuntimeFor<
|
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState>
|
||||||
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState,
|
);
|
||||||
material_surface_runtime_contract_test::AlternatePhysicalCore>);
|
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceDescriptor<RegisteredAlternateMaterialSurfaceDescriptor>);
|
||||||
STATIC_CHECK_FALSE(preconditioning::MaterialSurfaceRuntimeFor<
|
STATIC_CHECK_FALSE(
|
||||||
RegisteredAlternateMaterialSurfaceDescriptor,
|
preconditioning::ExecutableMaterialSurfaceRuntimeFor<
|
||||||
material_surface_runtime_contract_test::AlternatePhysicalCore>);
|
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState,
|
||||||
|
material_surface_runtime_contract_test::AlternatePhysicalCore>
|
||||||
|
);
|
||||||
|
STATIC_CHECK_FALSE(
|
||||||
|
preconditioning::MaterialSurfaceRuntimeFor<
|
||||||
|
RegisteredAlternateMaterialSurfaceDescriptor, material_surface_runtime_contract_test::AlternatePhysicalCore>
|
||||||
|
);
|
||||||
STATIC_CHECK(preconditioning::MaterialSurfacePreconditionerProblem<PolytropicProblem>);
|
STATIC_CHECK(preconditioning::MaterialSurfacePreconditionerProblem<PolytropicProblem>);
|
||||||
STATIC_CHECK_FALSE(preconditioning::MaterialSurfacePreconditionerProblem<RegisteredAlternateProblem>);
|
STATIC_CHECK_FALSE(preconditioning::MaterialSurfacePreconditionerProblem<RegisteredAlternateProblem>);
|
||||||
STATIC_CHECK(CanMakeDefaultMaterialSurface<PolytropicProblem>);
|
STATIC_CHECK(CanMakeDefaultMaterialSurface<PolytropicProblem>);
|
||||||
@@ -357,10 +370,10 @@ TEST_CASE(
|
|||||||
STATIC_CHECK(CanPrepareDefaultMaterialSurface<PolytropicProblem>);
|
STATIC_CHECK(CanPrepareDefaultMaterialSurface<PolytropicProblem>);
|
||||||
STATIC_CHECK_FALSE(CanPrepareDefaultMaterialSurface<RegisteredAlternateProblem>);
|
STATIC_CHECK_FALSE(CanPrepareDefaultMaterialSurface<RegisteredAlternateProblem>);
|
||||||
STATIC_CHECK_FALSE(preconditioning::StellarStructurePreconditionerProblem<RegisteredAlternateProblem>);
|
STATIC_CHECK_FALSE(preconditioning::StellarStructurePreconditionerProblem<RegisteredAlternateProblem>);
|
||||||
STATIC_CHECK_FALSE(preconditioning::StellarStructurePreparableFor<
|
STATIC_CHECK_FALSE(
|
||||||
RegisteredAlternateProblem,
|
preconditioning::StellarStructurePreparableFor<
|
||||||
MaterialSurfaceDiagonal,
|
RegisteredAlternateProblem, MaterialSurfaceDiagonal, RegisteredAlternateGravityComponent>
|
||||||
RegisteredAlternateGravityComponent>);
|
);
|
||||||
STATIC_CHECK_FALSE(CanMakeDefaultStellarStructure<RegisteredAlternateProblem>);
|
STATIC_CHECK_FALSE(CanMakeDefaultStellarStructure<RegisteredAlternateProblem>);
|
||||||
STATIC_CHECK(
|
STATIC_CHECK(
|
||||||
mean_field::material::CompiledThermodynamicEquations<typename PolytropicProblem::ThermodynamicEquationsType>
|
mean_field::material::CompiledThermodynamicEquations<typename PolytropicProblem::ThermodynamicEquationsType>
|
||||||
@@ -572,9 +585,10 @@ TEST_CASE(
|
|||||||
const utils::Args arguments = test_utils::setup_args();
|
const utils::Args arguments = test_utils::setup_args();
|
||||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
REQUIRE(finiteElements.okay());
|
REQUIRE(finiteElements.okay());
|
||||||
|
const mfem::ParFiniteElementSpace *surfaceDeformationSpace = finiteElements.surfaceDeformationFes.get();
|
||||||
|
|
||||||
constexpr double radius = utils::RADIUS;
|
constexpr double radius = utils::RADIUS;
|
||||||
constexpr double mass = utils::MASS;
|
constexpr double mass = utils::MASS;
|
||||||
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
||||||
const double centralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
const double centralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
||||||
const auto stellarModel = model::StellarModel(
|
const auto stellarModel = model::StellarModel(
|
||||||
@@ -583,7 +597,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||||
);
|
);
|
||||||
auto problem = equilibrium::discretize(stellarModel, finiteElements);
|
auto problem = equilibrium::discretize(stellarModel, std::move(finiteElements));
|
||||||
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
|
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
|
||||||
const auto rotation = zeroRotation();
|
const auto rotation = zeroRotation();
|
||||||
problem.Prepare(projected.values, makeDependencies(), rotation);
|
problem.Prepare(projected.values, makeDependencies(), rotation);
|
||||||
@@ -711,7 +725,7 @@ TEST_CASE(
|
|||||||
CHECK(surfaceFit.relativeGramDeterminant > 1.0e-12);
|
CHECK(surfaceFit.relativeGramDeterminant > 1.0e-12);
|
||||||
CHECK(surfaceFit.normalEquations.targetTarget > 0.0);
|
CHECK(surfaceFit.normalEquations.targetTarget > 0.0);
|
||||||
CHECK(frequencyAware.GetSurfaceInverse().Height() == physical.GetDomainDeformation().parameterCount());
|
CHECK(frequencyAware.GetSurfaceInverse().Height() == physical.GetDomainDeformation().parameterCount());
|
||||||
CHECK(frequencyAware.GetSurfaceSurrogateMatrix().Height() == finiteElements.surfaceDeformationFes->GetTrueVSize());
|
CHECK(frequencyAware.GetSurfaceSurrogateMatrix().Height() == surfaceDeformationSpace->GetTrueVSize());
|
||||||
CHECK(frequencyAware.GetSurfaceBackend().GetStatistics().setups == 1);
|
CHECK(frequencyAware.GetSurfaceBackend().GetStatistics().setups == 1);
|
||||||
CHECK(frequencyAware.GetStatistics().surfaceJacobianProbes == 4);
|
CHECK(frequencyAware.GetStatistics().surfaceJacobianProbes == 4);
|
||||||
CHECK(frequencyAware.GetStatistics().surfaceH1Assemblies == 3);
|
CHECK(frequencyAware.GetStatistics().surfaceH1Assemblies == 3);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -122,7 +122,7 @@ namespace unsupported_physical_preconditioner_test {
|
|||||||
mean_field::dimensions::SpecificEnthalpyValue target;
|
mean_field::dimensions::SpecificEnthalpyValue target;
|
||||||
};
|
};
|
||||||
|
|
||||||
using TargetValue = mean_field::dimensions::SpecificEnthalpyValue;
|
using TargetValue = mean_field::dimensions::SpecificEnthalpyValue;
|
||||||
using ScalarDescription = mean_field::stellar::ScalarConstraint<
|
using ScalarDescription = mean_field::stellar::ScalarConstraint<
|
||||||
mean_field::dimensions::quantity::SpecificEnthalpy,
|
mean_field::dimensions::quantity::SpecificEnthalpy,
|
||||||
mean_field::dimensions::quantity::Dimensionless,
|
mean_field::dimensions::quantity::Dimensionless,
|
||||||
@@ -134,18 +134,13 @@ namespace unsupported_physical_preconditioner_test {
|
|||||||
using ModelDefinition = mean_field::constraint::ScalarPhaseCondition<
|
using ModelDefinition = mean_field::constraint::ScalarPhaseCondition<
|
||||||
Constraint,
|
Constraint,
|
||||||
"UnsupportedPhysicalPreconditionerEdge",
|
"UnsupportedPhysicalPreconditionerEdge",
|
||||||
mean_field::stellar::Reads<
|
mean_field::stellar::
|
||||||
mean_field::stellar::state::SpecificEnthalpy,
|
Reads<mean_field::stellar::state::SpecificEnthalpy, mean_field::stellar::state::OwnGeneratedCoordinate>,
|
||||||
mean_field::stellar::state::OwnGeneratedCoordinate>,
|
mean_field::stellar::Changes<mean_field::stellar::equation::PoissonEquation>,
|
||||||
mean_field::stellar::Changes<
|
|
||||||
mean_field::stellar::equation::PoissonEquation>,
|
|
||||||
ScalarDescription>;
|
ScalarDescription>;
|
||||||
using EquilibriumPhysics =
|
using EquilibriumPhysics = mean_field::operators::LocalSpecificationEquilibriumPhysics<PreparedConstraint>;
|
||||||
mean_field::operators::LocalSpecificationEquilibriumPhysics<
|
|
||||||
PreparedConstraint>;
|
|
||||||
|
|
||||||
explicit constexpr Constraint(const Parameters parameters) noexcept
|
explicit constexpr Constraint(const Parameters parameters) noexcept : m_target(parameters.target) {
|
||||||
: m_target(parameters.target) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr TargetValue target() const noexcept {
|
[[nodiscard]] constexpr TargetValue target() const noexcept {
|
||||||
@@ -167,8 +162,7 @@ namespace unsupported_physical_preconditioner_test {
|
|||||||
explicit PreparedConstraint(const Constraint &) noexcept {
|
explicit PreparedConstraint(const Constraint &) noexcept {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename StateView>
|
template <typename StateView> [[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
|
||||||
[[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
|
|
||||||
m_isPrepared = true;
|
m_isPrepared = true;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -189,7 +183,9 @@ namespace unsupported_physical_preconditioner_test {
|
|||||||
return mean_field::stellar::structuralZero;
|
return mean_field::stellar::structuralZero;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Direction, typename Row>
|
template <
|
||||||
|
typename Direction,
|
||||||
|
typename Row>
|
||||||
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||||
mean_field::stellar::Derivative<
|
mean_field::stellar::Derivative<
|
||||||
mean_field::stellar::equation::OwnConstraint,
|
mean_field::stellar::equation::OwnConstraint,
|
||||||
@@ -200,7 +196,9 @@ namespace unsupported_physical_preconditioner_test {
|
|||||||
return mean_field::stellar::zeroDerivative;
|
return mean_field::stellar::zeroDerivative;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Direction, typename Row>
|
template <
|
||||||
|
typename Direction,
|
||||||
|
typename Row>
|
||||||
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||||
mean_field::stellar::Derivative<
|
mean_field::stellar::Derivative<
|
||||||
mean_field::stellar::equation::OwnConstraint,
|
mean_field::stellar::equation::OwnConstraint,
|
||||||
@@ -211,7 +209,9 @@ namespace unsupported_physical_preconditioner_test {
|
|||||||
return mean_field::stellar::zeroDerivative;
|
return mean_field::stellar::zeroDerivative;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Direction, typename Row>
|
template <
|
||||||
|
typename Direction,
|
||||||
|
typename Row>
|
||||||
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||||
mean_field::stellar::Derivative<
|
mean_field::stellar::Derivative<
|
||||||
mean_field::stellar::equation::PoissonEquation,
|
mean_field::stellar::equation::PoissonEquation,
|
||||||
@@ -222,7 +222,9 @@ namespace unsupported_physical_preconditioner_test {
|
|||||||
return mean_field::stellar::zeroDerivative;
|
return mean_field::stellar::zeroDerivative;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Direction, typename Row>
|
template <
|
||||||
|
typename Direction,
|
||||||
|
typename Row>
|
||||||
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||||
mean_field::stellar::Derivative<
|
mean_field::stellar::Derivative<
|
||||||
mean_field::stellar::equation::PoissonEquation,
|
mean_field::stellar::equation::PoissonEquation,
|
||||||
@@ -277,42 +279,40 @@ template <> struct mean_field::preconditioning::StellarEquilibriumProblemTraits<
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
namespace blocks = mean_field::utils::blocks;
|
namespace blocks = mean_field::utils::blocks;
|
||||||
namespace preconditioning = mean_field::preconditioning;
|
namespace preconditioning = mean_field::preconditioning;
|
||||||
|
|
||||||
using ModelWithoutPhase = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using ModelWithoutPhase = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::models::FixedTotalMass>>;
|
mean_field::models::FixedTotalMass>>;
|
||||||
using CentralDensityModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using CentralDensityModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::models::FixedTotalMass,
|
mean_field::models::FixedTotalMass,
|
||||||
mean_field::models::FixedCentralDensity>>;
|
mean_field::models::FixedCentralDensity>>;
|
||||||
using AngularMomentumModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using AngularMomentumModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::models::FixedTotalMass,
|
mean_field::models::FixedTotalMass,
|
||||||
mean_field::models::FixedAngularMomentum>>;
|
mean_field::models::FixedAngularMomentum>>;
|
||||||
using ProvenZeroPhysicalEdgeModel = mean_field::model::StellarModel<
|
using ProvenZeroPhysicalEdgeModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::models::SpecificationSet<
|
mean_field::eos::Polytrope,
|
||||||
mean_field::eos::Polytrope,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::models::FixedTotalMass,
|
||||||
mean_field::models::FixedTotalMass,
|
unsupported_physical_preconditioner_test::Constraint>>;
|
||||||
unsupported_physical_preconditioner_test::Constraint>>;
|
|
||||||
|
|
||||||
using ProblemWithoutPhase = mean_field::equilibrium::StellarEquilibriumProblem<ModelWithoutPhase>;
|
using ProblemWithoutPhase = mean_field::equilibrium::StellarEquilibriumProblem<ModelWithoutPhase>;
|
||||||
using CentralDensityProblem = mean_field::equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
|
using CentralDensityProblem = mean_field::equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
|
||||||
using AngularMomentumProblem = mean_field::equilibrium::StellarEquilibriumProblem<AngularMomentumModel>;
|
using AngularMomentumProblem = mean_field::equilibrium::StellarEquilibriumProblem<AngularMomentumModel>;
|
||||||
using ProvenZeroPhysicalEdgeProblem =
|
using ProvenZeroPhysicalEdgeProblem =
|
||||||
mean_field::equilibrium::StellarEquilibriumProblem<ProvenZeroPhysicalEdgeModel>;
|
mean_field::equilibrium::StellarEquilibriumProblem<ProvenZeroPhysicalEdgeModel>;
|
||||||
using PlanWithoutPhase = preconditioning::IdentityPreconditionerPlanFor<ProblemWithoutPhase>;
|
using PlanWithoutPhase = preconditioning::IdentityPreconditionerPlanFor<ProblemWithoutPhase>;
|
||||||
using CentralDensityPlan = preconditioning::IdentityPreconditionerPlanFor<CentralDensityProblem>;
|
using CentralDensityPlan = preconditioning::IdentityPreconditionerPlanFor<CentralDensityProblem>;
|
||||||
|
|
||||||
template <typename Problem>
|
template <typename Problem>
|
||||||
concept CanMakeDefaultStellarStructureBlock = requires(const Problem &problem) {
|
concept CanMakeDefaultStellarStructureBlock =
|
||||||
preconditioning::stellarStructureBlock(problem);
|
requires(const Problem &problem) { preconditioning::stellarStructureBlock(problem); };
|
||||||
};
|
|
||||||
|
|
||||||
using RefreshingDensityIdentity = preconditioning::ComponentDeclaration<
|
using RefreshingDensityIdentity = preconditioning::ComponentDeclaration<
|
||||||
blocks::type_list<blocks::density::mass::value>,
|
blocks::type_list<blocks::density::mass::value>,
|
||||||
@@ -367,67 +367,51 @@ TEST_CASE(
|
|||||||
"Default Stellar Structure Availability Distinguishes Proven Zeros From Unhandled Physical Edges",
|
"Default Stellar Structure Availability Distinguishes Proven Zeros From Unhandled Physical Edges",
|
||||||
"[preconditioning][stellar_structure][type_contract][compiler]"
|
"[preconditioning][stellar_structure][type_contract][compiler]"
|
||||||
) {
|
) {
|
||||||
using BaseCompilation =
|
using BaseCompilation = mean_field::operators::CompiledStellarEquilibriumSystem<ModelWithoutPhase>;
|
||||||
mean_field::operators::CompiledStellarEquilibriumSystem<ModelWithoutPhase>;
|
using BaseSupport = preconditioning::DefaultStellarStructurePhysicalTopologySupport<ModelWithoutPhase>;
|
||||||
using BaseSupport =
|
|
||||||
preconditioning::DefaultStellarStructurePhysicalTopologySupport<
|
|
||||||
ModelWithoutPhase>;
|
|
||||||
using ProvenZeroSupport =
|
using ProvenZeroSupport =
|
||||||
preconditioning::DefaultStellarStructurePhysicalTopologySupport<
|
preconditioning::DefaultStellarStructurePhysicalTopologySupport<ProvenZeroPhysicalEdgeModel>;
|
||||||
ProvenZeroPhysicalEdgeModel>;
|
using TrustedFixedMassEdge = mean_field::operators::StellarEquilibriumJacobianCoupling<
|
||||||
using TrustedFixedMassEdge =
|
blocks::enthalpy::specific::residual, blocks::density::mass::value>;
|
||||||
mean_field::operators::StellarEquilibriumJacobianCoupling<
|
using ProvenZeroPoissonEnthalpyEdge = mean_field::operators::StellarEquilibriumJacobianCoupling<
|
||||||
blocks::enthalpy::specific::residual,
|
blocks::gravity::poisson::residual, blocks::enthalpy::specific::value>;
|
||||||
blocks::density::mass::value>;
|
|
||||||
using ProvenZeroPoissonEnthalpyEdge =
|
|
||||||
mean_field::operators::StellarEquilibriumJacobianCoupling<
|
|
||||||
blocks::gravity::poisson::residual,
|
|
||||||
blocks::enthalpy::specific::value>;
|
|
||||||
|
|
||||||
// FixedTotalMass contributes h <- rho outside the generic five-field base
|
// FixedTotalMass contributes h <- rho outside the generic five-field base
|
||||||
// graph. It remains supported because that specification is explicitly
|
// graph. It remains supported because that specification is explicitly
|
||||||
// embedded in the trusted numerical core, not because of a model-pack
|
// embedded in the trusted numerical core, not because of a model-pack
|
||||||
// special case.
|
// special case.
|
||||||
STATIC_CHECK_FALSE(mean_field::utils::blocks::contains_type_v<
|
STATIC_CHECK_FALSE(
|
||||||
TrustedFixedMassEdge,
|
mean_field::utils::blocks::contains_type_v<
|
||||||
typename BaseCompilation::BaseJacobianCouplings>);
|
TrustedFixedMassEdge, typename BaseCompilation::BaseJacobianCouplings>
|
||||||
STATIC_CHECK(mean_field::utils::blocks::contains_type_v<
|
);
|
||||||
TrustedFixedMassEdge,
|
STATIC_CHECK(
|
||||||
typename BaseCompilation::ContributionJacobianCouplings>);
|
mean_field::utils::blocks::contains_type_v<
|
||||||
|
TrustedFixedMassEdge, typename BaseCompilation::ContributionJacobianCouplings>
|
||||||
|
);
|
||||||
STATIC_CHECK(BaseSupport::UnsupportedCouplings::size == 0);
|
STATIC_CHECK(BaseSupport::UnsupportedCouplings::size == 0);
|
||||||
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
|
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<ModelWithoutPhase>);
|
||||||
ModelWithoutPhase>);
|
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<CentralDensityModel>);
|
||||||
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
|
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<AngularMomentumModel>);
|
||||||
CentralDensityModel>);
|
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<ProblemWithoutPhase>);
|
||||||
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
|
|
||||||
AngularMomentumModel>);
|
|
||||||
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<
|
|
||||||
ProblemWithoutPhase>);
|
|
||||||
STATIC_CHECK(CanMakeDefaultStellarStructureBlock<ProblemWithoutPhase>);
|
STATIC_CHECK(CanMakeDefaultStellarStructureBlock<ProblemWithoutPhase>);
|
||||||
STATIC_CHECK(preconditioning::DefaultStellarPreconditionerAvailableFor<
|
STATIC_CHECK(preconditioning::DefaultStellarPreconditionerAvailableFor<ProblemWithoutPhase>);
|
||||||
ProblemWithoutPhase>);
|
|
||||||
|
|
||||||
// The mock's novel Poisson <- enthalpy edge is absent from the structure
|
// The mock's novel Poisson <- enthalpy edge is absent from the structure
|
||||||
// backend, but its exact nested provider returns StructuralZero. That is
|
// backend, but its exact nested provider returns StructuralZero. That is
|
||||||
// a compile-time proof that no preconditioner term is missing; generated-
|
// a compile-time proof that no preconditioner term is missing; generated-
|
||||||
// coordinate edges are handled independently by the inferred border.
|
// coordinate edges are handled independently by the inferred border.
|
||||||
STATIC_CHECK(mean_field::equilibrium::StellarEquilibriumModel<
|
STATIC_CHECK(mean_field::equilibrium::StellarEquilibriumModel<ProvenZeroPhysicalEdgeModel>);
|
||||||
ProvenZeroPhysicalEdgeModel>);
|
STATIC_CHECK(mean_field::equilibrium::DiscretizedStellarEquilibriumProblem<ProvenZeroPhysicalEdgeProblem>);
|
||||||
STATIC_CHECK(mean_field::equilibrium::DiscretizedStellarEquilibriumProblem<
|
|
||||||
ProvenZeroPhysicalEdgeProblem>);
|
|
||||||
STATIC_CHECK(ProvenZeroSupport::UnsupportedCouplings::size == 0);
|
STATIC_CHECK(ProvenZeroSupport::UnsupportedCouplings::size == 0);
|
||||||
STATIC_CHECK(mean_field::utils::blocks::contains_type_v<
|
STATIC_CHECK(
|
||||||
ProvenZeroPoissonEnthalpyEdge,
|
mean_field::utils::blocks::contains_type_v<
|
||||||
typename mean_field::operators::CompiledStellarEquilibriumSystem<
|
ProvenZeroPoissonEnthalpyEdge, typename mean_field::operators::CompiledStellarEquilibriumSystem<
|
||||||
ProvenZeroPhysicalEdgeModel>::ContributionJacobianCouplings>);
|
ProvenZeroPhysicalEdgeModel>::ContributionJacobianCouplings>
|
||||||
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
|
);
|
||||||
ProvenZeroPhysicalEdgeModel>);
|
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<ProvenZeroPhysicalEdgeModel>);
|
||||||
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<
|
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<ProvenZeroPhysicalEdgeProblem>);
|
||||||
ProvenZeroPhysicalEdgeProblem>);
|
STATIC_CHECK(CanMakeDefaultStellarStructureBlock<ProvenZeroPhysicalEdgeProblem>);
|
||||||
STATIC_CHECK(CanMakeDefaultStellarStructureBlock<
|
STATIC_CHECK(preconditioning::DefaultStellarPreconditionerAvailableFor<ProvenZeroPhysicalEdgeProblem>);
|
||||||
ProvenZeroPhysicalEdgeProblem>);
|
|
||||||
STATIC_CHECK(preconditioning::DefaultStellarPreconditionerAvailableFor<
|
|
||||||
ProvenZeroPhysicalEdgeProblem>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
|
|||||||
@@ -108,12 +108,12 @@ namespace {
|
|||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
using PolytropicModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using PolytropicModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::integral::FixedTotalMass,
|
mean_field::integral::FixedTotalMass,
|
||||||
mean_field::constraint::FixedCentralDensity>>;
|
mean_field::constraint::FixedCentralDensity>>;
|
||||||
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
|
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
|
||||||
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
|
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
|
||||||
using MaterialComponent =
|
using MaterialComponent =
|
||||||
decltype(preconditioning::materialSurfaceBlock(std::declval<const PolytropicProblem &>()));
|
decltype(preconditioning::materialSurfaceBlock(std::declval<const PolytropicProblem &>()));
|
||||||
@@ -181,20 +181,21 @@ TEST_CASE(
|
|||||||
preconditioning::Coupling<blocks::enthalpy::specific::residual, blocks::gravity::poisson::value>>;
|
preconditioning::Coupling<blocks::enthalpy::specific::residual, blocks::gravity::poisson::value>>;
|
||||||
|
|
||||||
STATIC_CHECK(preconditioning::PreconditionerComponent<PolytropicStructure>);
|
STATIC_CHECK(preconditioning::PreconditionerComponent<PolytropicStructure>);
|
||||||
STATIC_CHECK(preconditioning::ExecutableStellarStructureRuntimeFor<
|
STATIC_CHECK(
|
||||||
mean_field::operators::PreparedStellarEquilibriumOperator>);
|
preconditioning::ExecutableStellarStructureRuntimeFor<mean_field::operators::PreparedStellarEquilibriumOperator>
|
||||||
|
);
|
||||||
STATIC_CHECK_FALSE(preconditioning::ExecutableStellarStructureRuntimeFor<DistinctPhysicalCore>);
|
STATIC_CHECK_FALSE(preconditioning::ExecutableStellarStructureRuntimeFor<DistinctPhysicalCore>);
|
||||||
STATIC_CHECK(preconditioning::StellarStructureRuntimeFor<
|
STATIC_CHECK(
|
||||||
PolytropicMaterialSurfaceDescriptor,
|
preconditioning::StellarStructureRuntimeFor<
|
||||||
mean_field::operators::PreparedStellarEquilibriumOperator>);
|
PolytropicMaterialSurfaceDescriptor, mean_field::operators::PreparedStellarEquilibriumOperator>
|
||||||
STATIC_CHECK_FALSE(preconditioning::StellarStructureRuntimeFor<
|
);
|
||||||
PolytropicMaterialSurfaceDescriptor,
|
STATIC_CHECK_FALSE(
|
||||||
DistinctPhysicalCore>);
|
preconditioning::StellarStructureRuntimeFor<PolytropicMaterialSurfaceDescriptor, DistinctPhysicalCore>
|
||||||
|
);
|
||||||
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<PolytropicProblem>);
|
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<PolytropicProblem>);
|
||||||
STATIC_CHECK(preconditioning::StellarStructurePreparableFor<
|
STATIC_CHECK(
|
||||||
PolytropicProblem,
|
preconditioning::StellarStructurePreparableFor<PolytropicProblem, MaterialComponent, GravityComponent>
|
||||||
MaterialComponent,
|
);
|
||||||
GravityComponent>);
|
|
||||||
STATIC_CHECK(CanPrepareDefaultStellarStructure<PolytropicProblem>);
|
STATIC_CHECK(CanPrepareDefaultStellarStructure<PolytropicProblem>);
|
||||||
STATIC_CHECK(std::same_as<typename PolytropicStructure::MaterialToGravityCouplings, ExpectedMaterialToGravity>);
|
STATIC_CHECK(std::same_as<typename PolytropicStructure::MaterialToGravityCouplings, ExpectedMaterialToGravity>);
|
||||||
STATIC_CHECK(std::same_as<typename PolytropicStructure::GravityToMaterialCouplings, ExpectedGravityToMaterial>);
|
STATIC_CHECK(std::same_as<typename PolytropicStructure::GravityToMaterialCouplings, ExpectedGravityToMaterial>);
|
||||||
@@ -268,7 +269,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||||
);
|
);
|
||||||
auto problem = equilibrium::discretize(stellarModel, finiteElements);
|
auto problem = equilibrium::discretize(stellarModel, std::move(finiteElements));
|
||||||
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
|
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
|
||||||
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
||||||
const auto &physical = problem.GetPreparedOperator().GetPhysicalOperator();
|
const auto &physical = problem.GetPreparedOperator().GetPhysicalOperator();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <numbers>
|
#include <numbers>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <catch2/catch_approx.hpp>
|
#include <catch2/catch_approx.hpp>
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
@@ -14,8 +15,8 @@ import test_helpers;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
template <typename... Specifications>
|
template <typename... Specifications>
|
||||||
using ProjectionModelWith = mean_field::model::StellarModel<
|
using ProjectionModelWith =
|
||||||
mean_field::models::SpecificationSet<Specifications...>>;
|
mean_field::model::StellarModel<mean_field::models::SpecificationSet<Specifications...>>;
|
||||||
|
|
||||||
class UnregisteredProjectionConstraint final {
|
class UnregisteredProjectionConstraint final {
|
||||||
public:
|
public:
|
||||||
@@ -45,11 +46,10 @@ namespace {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct IncompleteProjectionPhysics final {
|
struct IncompleteProjectionPhysics final {
|
||||||
static constexpr bool registered = true;
|
static constexpr bool registered = true;
|
||||||
static constexpr bool providesRadialMass = false;
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
template <typename Model>
|
template <typename Model> static constexpr bool supports = true;
|
||||||
static constexpr bool supports = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class IncompleteProjectionConstraint final {
|
class IncompleteProjectionConstraint final {
|
||||||
@@ -69,9 +69,8 @@ namespace {
|
|||||||
class UnregisteredProjectionEquationOfState final {
|
class UnregisteredProjectionEquationOfState final {
|
||||||
public:
|
public:
|
||||||
struct Parameters final { };
|
struct Parameters final { };
|
||||||
using ModelDefinition = mean_field::eos::ConstitutiveLaw<
|
using ModelDefinition = mean_field::eos::
|
||||||
UnregisteredProjectionEquationOfState,
|
ConstitutiveLaw<UnregisteredProjectionEquationOfState, "UnregisteredProjectionEquationOfState">;
|
||||||
"UnregisteredProjectionEquationOfState">;
|
|
||||||
|
|
||||||
explicit constexpr UnregisteredProjectionEquationOfState(Parameters) noexcept {
|
explicit constexpr UnregisteredProjectionEquationOfState(Parameters) noexcept {
|
||||||
}
|
}
|
||||||
@@ -80,27 +79,27 @@ namespace {
|
|||||||
class UnregisteredProjectionSurface final {
|
class UnregisteredProjectionSurface final {
|
||||||
public:
|
public:
|
||||||
struct Parameters final { };
|
struct Parameters final { };
|
||||||
using ModelDefinition = mean_field::surface::BoundaryCondition<
|
using ModelDefinition =
|
||||||
UnregisteredProjectionSurface,
|
mean_field::surface::BoundaryCondition<UnregisteredProjectionSurface, "UnregisteredProjectionSurface">;
|
||||||
"UnregisteredProjectionSurface">;
|
|
||||||
|
|
||||||
explicit constexpr UnregisteredProjectionSurface(Parameters) noexcept {
|
explicit constexpr UnregisteredProjectionSurface(Parameters) noexcept {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SecondRadialMassProjectionPhysics final {
|
struct SecondRadialMassProjectionPhysics final {
|
||||||
static constexpr bool registered = true;
|
static constexpr bool registered = true;
|
||||||
static constexpr bool providesRadialMass = true;
|
static constexpr bool providesRadialMass = true;
|
||||||
|
|
||||||
template <typename Model>
|
template <typename Model> static constexpr bool supports = true;
|
||||||
static constexpr bool supports = true;
|
|
||||||
|
|
||||||
template <typename Specification>
|
template <typename Specification>
|
||||||
[[nodiscard]] static mean_field::dimensions::MassValue targetMass(const Specification &specification) {
|
[[nodiscard]] static mean_field::dimensions::MassValue targetMass(const Specification &specification) {
|
||||||
return specification.targetMass();
|
return specification.targetMass();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Specification, typename Model>
|
template <
|
||||||
|
typename Specification,
|
||||||
|
typename Model>
|
||||||
static void validate(
|
static void validate(
|
||||||
const Specification &,
|
const Specification &,
|
||||||
const Model &,
|
const Model &,
|
||||||
@@ -109,7 +108,9 @@ namespace {
|
|||||||
) noexcept {
|
) noexcept {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Specification, typename Model>
|
template <
|
||||||
|
typename Specification,
|
||||||
|
typename Model>
|
||||||
static void initialize(
|
static void initialize(
|
||||||
const Specification &,
|
const Specification &,
|
||||||
const Model &,
|
const Model &,
|
||||||
@@ -128,9 +129,8 @@ namespace {
|
|||||||
struct Parameters final {
|
struct Parameters final {
|
||||||
mean_field::dimensions::MassValue mass;
|
mean_field::dimensions::MassValue mass;
|
||||||
};
|
};
|
||||||
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
|
using ModelDefinition =
|
||||||
SecondRadialMassConstraint,
|
mean_field::integral::FixedWithMultiplier<SecondRadialMassConstraint, "SecondRadialMassConstraint">;
|
||||||
"SecondRadialMassConstraint">;
|
|
||||||
using RadialProjection = mean_field::seed::projection::Use<SecondRadialMassProjectionPhysics>;
|
using RadialProjection = mean_field::seed::projection::Use<SecondRadialMassProjectionPhysics>;
|
||||||
|
|
||||||
explicit constexpr SecondRadialMassConstraint(Parameters parameters) noexcept : m_mass(parameters.mass) {
|
explicit constexpr SecondRadialMassConstraint(Parameters parameters) noexcept : m_mass(parameters.mass) {
|
||||||
@@ -178,46 +178,24 @@ TEST_CASE(
|
|||||||
tags::stellar_seed_projection_type_contract
|
tags::stellar_seed_projection_type_contract
|
||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
using BaseModel = ProjectionModelWith<eos::Polytrope, surface::Isobaric, integral::FixedTotalMass>;
|
using BaseModel = ProjectionModelWith<eos::Polytrope, surface::Isobaric, integral::FixedTotalMass>;
|
||||||
using CentralModel = ProjectionModelWith<
|
using CentralModel = ProjectionModelWith<
|
||||||
eos::Polytrope,
|
eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, constraint::FixedCentralDensity>;
|
||||||
surface::Isobaric,
|
|
||||||
integral::FixedTotalMass,
|
|
||||||
constraint::FixedCentralDensity>;
|
|
||||||
using AngularModel = ProjectionModelWith<
|
using AngularModel = ProjectionModelWith<
|
||||||
eos::Polytrope,
|
eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, integral::FixedAngularMomentum>;
|
||||||
surface::Isobaric,
|
|
||||||
integral::FixedTotalMass,
|
|
||||||
integral::FixedAngularMomentum>;
|
|
||||||
using ExplicitExtensionModel = ProjectionModelWith<
|
using ExplicitExtensionModel = ProjectionModelWith<
|
||||||
eos::Polytrope,
|
eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, ExplicitNoChangeProjectionConstraint>;
|
||||||
surface::Isobaric,
|
|
||||||
integral::FixedTotalMass,
|
|
||||||
ExplicitNoChangeProjectionConstraint>;
|
|
||||||
using MissingConstraintRuleModel = ProjectionModelWith<
|
using MissingConstraintRuleModel = ProjectionModelWith<
|
||||||
eos::Polytrope,
|
eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, UnregisteredProjectionConstraint>;
|
||||||
surface::Isobaric,
|
|
||||||
integral::FixedTotalMass,
|
|
||||||
UnregisteredProjectionConstraint>;
|
|
||||||
using IncompleteConstraintRuleModel = ProjectionModelWith<
|
using IncompleteConstraintRuleModel = ProjectionModelWith<
|
||||||
eos::Polytrope,
|
eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, IncompleteProjectionConstraint>;
|
||||||
surface::Isobaric,
|
using MissingEquationOfStateRuleModel =
|
||||||
integral::FixedTotalMass,
|
ProjectionModelWith<UnregisteredProjectionEquationOfState, surface::Isobaric, integral::FixedTotalMass>;
|
||||||
IncompleteProjectionConstraint>;
|
using MissingSurfaceRuleModel =
|
||||||
using MissingEquationOfStateRuleModel = ProjectionModelWith<
|
ProjectionModelWith<eos::Polytrope, UnregisteredProjectionSurface, integral::FixedTotalMass>;
|
||||||
UnregisteredProjectionEquationOfState,
|
|
||||||
surface::Isobaric,
|
|
||||||
integral::FixedTotalMass>;
|
|
||||||
using MissingSurfaceRuleModel = ProjectionModelWith<
|
|
||||||
eos::Polytrope,
|
|
||||||
UnregisteredProjectionSurface,
|
|
||||||
integral::FixedTotalMass>;
|
|
||||||
using MissingMassProviderModel = ProjectionModelWith<eos::Polytrope, surface::Isobaric>;
|
using MissingMassProviderModel = ProjectionModelWith<eos::Polytrope, surface::Isobaric>;
|
||||||
using AmbiguousMassProviderModel = ProjectionModelWith<
|
using AmbiguousMassProviderModel =
|
||||||
eos::Polytrope,
|
ProjectionModelWith<eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, SecondRadialMassConstraint>;
|
||||||
surface::Isobaric,
|
|
||||||
integral::FixedTotalMass,
|
|
||||||
SecondRadialMassConstraint>;
|
|
||||||
|
|
||||||
STATIC_CHECK(seed::RadialProfileProjectableModel<BaseModel>);
|
STATIC_CHECK(seed::RadialProfileProjectableModel<BaseModel>);
|
||||||
STATIC_CHECK(seed::RadialProfileProjectableModel<CentralModel>);
|
STATIC_CHECK(seed::RadialProfileProjectableModel<CentralModel>);
|
||||||
@@ -272,7 +250,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||||
);
|
);
|
||||||
auto problem = equilibrium::discretize(stellarModel, finiteElementModel);
|
auto problem = equilibrium::discretize(stellarModel, std::move(finiteElementModel));
|
||||||
|
|
||||||
STATIC_CHECK(seed::RadialSeedStrategyFor<seed::LaneEmden, decltype(stellarModel)>);
|
STATIC_CHECK(seed::RadialSeedStrategyFor<seed::LaneEmden, decltype(stellarModel)>);
|
||||||
STATIC_CHECK(
|
STATIC_CHECK(
|
||||||
@@ -346,7 +324,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
||||||
);
|
);
|
||||||
auto problem = equilibrium::discretize(stellarModel, finiteElementModel);
|
auto problem = equilibrium::discretize(stellarModel, std::move(finiteElementModel));
|
||||||
|
|
||||||
const seed::RadialProfile mismatchedProfile =
|
const seed::RadialProfile mismatchedProfile =
|
||||||
seed::generateRadialProfile(problem.GetStellarModel(), seed::LaneEmden({.radialSampleCount = 64}));
|
seed::generateRadialProfile(problem.GetStellarModel(), seed::LaneEmden({.radialSampleCount = 64}));
|
||||||
@@ -371,7 +349,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||||
);
|
);
|
||||||
auto problem = equilibrium::discretize(stellarModel, finiteElementModel);
|
auto problem = equilibrium::discretize(stellarModel, std::move(finiteElementModel));
|
||||||
const seed::RadialProfile profile =
|
const seed::RadialProfile profile =
|
||||||
seed::generateRadialProfile(problem.GetStellarModel(), seed::LaneEmden({.radialSampleCount = 64}));
|
seed::generateRadialProfile(problem.GetStellarModel(), seed::LaneEmden({.radialSampleCount = 64}));
|
||||||
|
|
||||||
|
|||||||
1049
tests/solver/stellar_equilibrium_architecture.cpp
Normal file
1049
tests/solver/stellar_equilibrium_architecture.cpp
Normal file
File diff suppressed because it is too large
Load Diff
319
tests/solver/stellar_equilibrium_architecture_internal.cpp
Normal file
319
tests/solver/stellar_equilibrium_architecture_internal.cpp
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
#include <numbers>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <mfem.hpp>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
|
module mean_field;
|
||||||
|
|
||||||
|
import :solver.stellar_equilibrium;
|
||||||
|
|
||||||
|
namespace solver_internal_architecture_test {
|
||||||
|
struct LifetimeProbe final {
|
||||||
|
const void *problemIdentity{nullptr};
|
||||||
|
bool backendDestroyed{false};
|
||||||
|
bool dependenciesAliveAtBackendDestruction{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InspectingBackend final : mean_field::solver::LinearBackendConfigurationTag {
|
||||||
|
static constexpr mean_field::preconditioning::ApplicationContract supportedPreconditionerContract =
|
||||||
|
mean_field::preconditioning::ApplicationContract::flexible;
|
||||||
|
|
||||||
|
std::shared_ptr<LifetimeProbe> probe;
|
||||||
|
|
||||||
|
explicit InspectingBackend(std::shared_ptr<LifetimeProbe> lifetimeProbe = nullptr)
|
||||||
|
: probe(std::move(lifetimeProbe)) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ThrowingBackend final : mean_field::solver::LinearBackendConfigurationTag {
|
||||||
|
static constexpr mean_field::preconditioning::ApplicationContract supportedPreconditionerContract =
|
||||||
|
mean_field::preconditioning::ApplicationContract::flexible;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ZeroMetric final { };
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::solver::nonlinear::MetricEvaluation getMetric(
|
||||||
|
const ZeroMetric &,
|
||||||
|
const mfem::Vector &,
|
||||||
|
MPI_Comm
|
||||||
|
) {
|
||||||
|
return {.residualNorm = 0.0, .merit = 0.0};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Operator, typename Preconditioner> class PreparedBackend final {
|
||||||
|
public:
|
||||||
|
PreparedBackend(
|
||||||
|
const Operator &operation,
|
||||||
|
Preconditioner &preconditioner,
|
||||||
|
const MPI_Comm communicator,
|
||||||
|
std::shared_ptr<LifetimeProbe> probe
|
||||||
|
)
|
||||||
|
: m_operation(&operation),
|
||||||
|
m_preconditioner(&preconditioner),
|
||||||
|
m_communicator(communicator),
|
||||||
|
m_rightHandSide(operation.Height()),
|
||||||
|
m_correction(operation.Width()),
|
||||||
|
m_probe(std::move(probe)) {
|
||||||
|
m_rightHandSide = 0.0;
|
||||||
|
m_correction = 0.0;
|
||||||
|
if (m_probe != nullptr) {
|
||||||
|
m_probe->problemIdentity = std::addressof(operation.GetProblem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PreparedBackend(const PreparedBackend &) = delete;
|
||||||
|
PreparedBackend &operator=(const PreparedBackend &) = delete;
|
||||||
|
PreparedBackend(PreparedBackend &&) = delete;
|
||||||
|
PreparedBackend &operator=(PreparedBackend &&) = delete;
|
||||||
|
|
||||||
|
~PreparedBackend() {
|
||||||
|
if (m_probe != nullptr) {
|
||||||
|
m_probe->backendDestroyed = true;
|
||||||
|
m_probe->dependenciesAliveAtBackendDestruction =
|
||||||
|
m_operation != nullptr && m_preconditioner != nullptr && m_preconditioner->IsCurrent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const Operator &GetOperator() const noexcept {
|
||||||
|
return *m_operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const Preconditioner &GetPreconditioner() const noexcept {
|
||||||
|
return *m_preconditioner;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MPI_Comm GetCommunicator() const noexcept {
|
||||||
|
return m_communicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsReady() const noexcept {
|
||||||
|
return m_operation != nullptr && m_preconditioner != nullptr && m_communicator != MPI_COMM_NULL &&
|
||||||
|
m_rightHandSide.Size() == m_operation->Height() && m_correction.Size() == m_operation->Width();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int RightHandSideSize() const noexcept {
|
||||||
|
return m_rightHandSide.Size();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int CorrectionSize() const noexcept {
|
||||||
|
return m_correction.Size();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::solver::LinearSolveReport Solve(
|
||||||
|
const mfem::Vector &rightHandSide,
|
||||||
|
mfem::Vector &correction,
|
||||||
|
const mean_field::solver::LinearSolveControl &control
|
||||||
|
) {
|
||||||
|
control.Validate();
|
||||||
|
if (rightHandSide.Size() != RightHandSideSize() || correction.Size() != CorrectionSize()) {
|
||||||
|
throw std::invalid_argument("The internal test backend requires preallocated compatible vectors.");
|
||||||
|
}
|
||||||
|
m_rightHandSide = rightHandSide;
|
||||||
|
m_correction = 0.0;
|
||||||
|
correction = m_correction;
|
||||||
|
return {
|
||||||
|
.status = mean_field::solver::LinearSolveStatus::converged,
|
||||||
|
.control = control,
|
||||||
|
.initialResidualNorm = rightHandSide.Norml2(),
|
||||||
|
.reportedResidualNorm = 0.0,
|
||||||
|
.trueResidualNorm = 0.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Operator *m_operation;
|
||||||
|
Preconditioner *m_preconditioner;
|
||||||
|
MPI_Comm m_communicator;
|
||||||
|
mfem::Vector m_rightHandSide;
|
||||||
|
mfem::Vector m_correction;
|
||||||
|
std::shared_ptr<LifetimeProbe> m_probe;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Operator,
|
||||||
|
typename Preconditioner>
|
||||||
|
[[nodiscard]] auto prepareLinearBackend(
|
||||||
|
InspectingBackend configuration,
|
||||||
|
const Operator &operation,
|
||||||
|
Preconditioner &preconditioner,
|
||||||
|
const MPI_Comm communicator
|
||||||
|
) {
|
||||||
|
return PreparedBackend<Operator, Preconditioner>{
|
||||||
|
operation, preconditioner, communicator, std::move(configuration.probe)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Operator,
|
||||||
|
typename Preconditioner>
|
||||||
|
[[nodiscard]] auto prepareLinearBackend(
|
||||||
|
ThrowingBackend,
|
||||||
|
const Operator &,
|
||||||
|
Preconditioner &,
|
||||||
|
MPI_Comm
|
||||||
|
)
|
||||||
|
-> PreparedBackend<
|
||||||
|
Operator,
|
||||||
|
Preconditioner> {
|
||||||
|
throw std::runtime_error("The internal test backend rejected restart preparation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::fem::FEM makeFiniteElements() {
|
||||||
|
mean_field::utils::Args arguments;
|
||||||
|
arguments.mesh_file = "sandbox.smesh";
|
||||||
|
arguments.p.rtol = 1.0e-12;
|
||||||
|
arguments.p.atol = 1.0e-12;
|
||||||
|
return mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double matchingCentralDensity() {
|
||||||
|
constexpr double stellarRadius = mean_field::utils::RADIUS;
|
||||||
|
constexpr double targetMass = mean_field::utils::MASS;
|
||||||
|
return std::numbers::pi_v<double> * targetMass / (4.0 * stellarRadius * stellarRadius * stellarRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto makeModel() {
|
||||||
|
using namespace mean_field;
|
||||||
|
constexpr double stellarRadius = utils::RADIUS;
|
||||||
|
constexpr double targetMass = utils::MASS;
|
||||||
|
const double polytropicConstant = 2.0 * utils::G * stellarRadius * stellarRadius / std::numbers::pi_v<double>;
|
||||||
|
return model::StellarModel(
|
||||||
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
||||||
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{matchingCentralDensity()}})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto makeGeneratedRotationModel() {
|
||||||
|
using namespace mean_field;
|
||||||
|
constexpr double stellarRadius = utils::RADIUS;
|
||||||
|
constexpr double targetMass = utils::MASS;
|
||||||
|
const double polytropicConstant = 2.0 * utils::G * stellarRadius * stellarRadius / std::numbers::pi_v<double>;
|
||||||
|
return model::StellarModel(
|
||||||
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
||||||
|
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.05}}),
|
||||||
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{matchingCentralDensity()}})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkZeroRotation(const mean_field::physics::RigidRotation &rotation) {
|
||||||
|
REQUIRE(rotation.angular_velocity().Size() == 3);
|
||||||
|
REQUIRE(rotation.center().Size() == 3);
|
||||||
|
for (int component = 0; component < 3; ++component) {
|
||||||
|
CHECK(rotation.angular_velocity()(component) == 0.0);
|
||||||
|
CHECK(rotation.center()(component) == 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace solver_internal_architecture_test
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Report Views Retain Context-Owned Accepted State After Solver Destruction",
|
||||||
|
"[solver][architecture][ownership][report][internal]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
using namespace solver_internal_architecture_test;
|
||||||
|
|
||||||
|
auto finiteElements = makeFiniteElements();
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
const MPI_Comm expectedCommunicator = finiteElements.mesh->GetComm();
|
||||||
|
auto probe = std::make_shared<LifetimeProbe>();
|
||||||
|
{
|
||||||
|
auto context = solver::makeContext(
|
||||||
|
makeModel(), equilibrium::StellarDiscretization{std::move(finiteElements)},
|
||||||
|
preconditioning::makePreconditioner(), InspectingBackend{probe}
|
||||||
|
);
|
||||||
|
REQUIRE(context.isReady());
|
||||||
|
REQUIRE(probe->problemIdentity != nullptr);
|
||||||
|
|
||||||
|
auto report = [&] {
|
||||||
|
auto borrowingSolver =
|
||||||
|
solver::make(context, solver::nonlinear::Newton(solver::nonlinear::NewtonOptions{}, ZeroMetric{}));
|
||||||
|
return borrowingSolver.evaluate();
|
||||||
|
}();
|
||||||
|
CHECK_FALSE(context.hasActiveSolver());
|
||||||
|
CHECK_FALSE(probe->backendDestroyed);
|
||||||
|
REQUIRE(report.converged());
|
||||||
|
|
||||||
|
auto structure = report.structureView();
|
||||||
|
REQUIRE(structure.valid());
|
||||||
|
CHECK(
|
||||||
|
structure.model().template specification<constraint::FixedCentralDensity>().targetDensity() ==
|
||||||
|
dimensions::DensityValue{matchingCentralDensity()}
|
||||||
|
);
|
||||||
|
const auto state = structure.state();
|
||||||
|
REQUIRE_FALSE(state.empty());
|
||||||
|
CHECK(std::ranges::all_of(state, [](const mfem::real_t value) { return std::isfinite(value); }));
|
||||||
|
REQUIRE_FALSE(structure.stateDescriptors().empty());
|
||||||
|
REQUIRE_FALSE(structure.stateBlock(utils::blocks::density_field.mass_term).empty());
|
||||||
|
|
||||||
|
int communicatorComparison = MPI_UNEQUAL;
|
||||||
|
REQUIRE(
|
||||||
|
MPI_Comm_compare(structure.communicator(), expectedCommunicator, &communicatorComparison) == MPI_SUCCESS
|
||||||
|
);
|
||||||
|
CHECK((communicatorComparison == MPI_IDENT || communicatorComparison == MPI_CONGRUENT));
|
||||||
|
REQUIRE(structure.prescribedRotation().has_value());
|
||||||
|
checkZeroRotation(*structure.prescribedRotation());
|
||||||
|
checkZeroRotation(structure.rotation());
|
||||||
|
CHECK_THROWS_AS(structure.capture(), std::logic_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(probe->backendDestroyed);
|
||||||
|
CHECK(probe->dependenciesAliveAtBackendDestruction);
|
||||||
|
|
||||||
|
auto rejectedFiniteElements = makeFiniteElements();
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
solver::makeContext(
|
||||||
|
makeModel(), equilibrium::StellarDiscretization{std::move(rejectedFiniteElements)},
|
||||||
|
preconditioning::makePreconditioner(), ThrowingBackend{}
|
||||||
|
),
|
||||||
|
std::runtime_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Generated Rotation Is Reported Without A Prescribed Rotation Payload",
|
||||||
|
"[solver][architecture][ownership][rotation][report][internal]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
using namespace solver_internal_architecture_test;
|
||||||
|
|
||||||
|
auto finiteElements = makeFiniteElements();
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
auto probe = std::make_shared<LifetimeProbe>();
|
||||||
|
auto context = solver::makeContext(
|
||||||
|
makeGeneratedRotationModel(), equilibrium::StellarDiscretization{std::move(finiteElements)},
|
||||||
|
preconditioning::makePreconditioner(), InspectingBackend{probe}
|
||||||
|
);
|
||||||
|
|
||||||
|
REQUIRE(context.isReady());
|
||||||
|
auto borrowingSolver =
|
||||||
|
solver::make(context, solver::nonlinear::Newton(solver::nonlinear::NewtonOptions{}, ZeroMetric{}));
|
||||||
|
auto report = borrowingSolver.evaluate();
|
||||||
|
auto structure = report.structureView();
|
||||||
|
|
||||||
|
CHECK_FALSE(probe->backendDestroyed);
|
||||||
|
REQUIRE(structure.valid());
|
||||||
|
CHECK_FALSE(structure.prescribedRotation().has_value());
|
||||||
|
const auto rotation = structure.rotation();
|
||||||
|
REQUIRE(rotation.angular_velocity().Size() == 3);
|
||||||
|
REQUIRE(rotation.center().Size() == 3);
|
||||||
|
CHECK(rotation.angular_velocity()(0) == 0.0);
|
||||||
|
CHECK(rotation.angular_velocity()(1) == 0.0);
|
||||||
|
CHECK(rotation.angular_velocity()(2) > 0.0);
|
||||||
|
for (int component = 0; component < 3; ++component) {
|
||||||
|
CHECK(std::isfinite(rotation.angular_velocity()(component)));
|
||||||
|
CHECK(rotation.center()(component) == 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
1455
tests/solver/stellar_equilibrium_runtime.cpp
Normal file
1455
tests/solver/stellar_equilibrium_runtime.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
|||||||
#include <concepts>
|
#include <concepts>
|
||||||
|
#include <numbers>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
@@ -8,6 +10,16 @@ import mean_field;
|
|||||||
import test_helpers;
|
import test_helpers;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
struct UserApiZeroMetric final { };
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::solver::nonlinear::MetricEvaluation getMetric(
|
||||||
|
const UserApiZeroMetric &,
|
||||||
|
const mfem::Vector &,
|
||||||
|
MPI_Comm
|
||||||
|
) {
|
||||||
|
return {.residualNorm = 0.0, .merit = 0.0};
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] mean_field::fem::FEM makeFiniteElements() {
|
[[nodiscard]] mean_field::fem::FEM makeFiniteElements() {
|
||||||
const mean_field::utils::Args arguments = test_utils::setup_args();
|
const mean_field::utils::Args arguments = test_utils::setup_args();
|
||||||
return mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
|
return mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
@@ -18,9 +30,7 @@ namespace {
|
|||||||
Problem &problem,
|
Problem &problem,
|
||||||
const mfem::Vector &state,
|
const mfem::Vector &state,
|
||||||
const mean_field::operators::StellarEquilibriumDependencies &dependencies
|
const mean_field::operators::StellarEquilibriumDependencies &dependencies
|
||||||
) {
|
) { problem.Prepare(state, dependencies); };
|
||||||
problem.Prepare(state, dependencies);
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Problem>
|
template <typename Problem>
|
||||||
concept PreparesWithPrescribedRotation = requires(
|
concept PreparesWithPrescribedRotation = requires(
|
||||||
@@ -28,9 +38,7 @@ namespace {
|
|||||||
const mfem::Vector &state,
|
const mfem::Vector &state,
|
||||||
const mean_field::operators::StellarEquilibriumDependencies &dependencies,
|
const mean_field::operators::StellarEquilibriumDependencies &dependencies,
|
||||||
const mean_field::physics::RigidRotation &rotation
|
const mean_field::physics::RigidRotation &rotation
|
||||||
) {
|
) { problem.Prepare(state, dependencies, rotation); };
|
||||||
problem.Prepare(state, dependencies, rotation);
|
|
||||||
};
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
@@ -47,7 +55,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}})
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}})
|
||||||
);
|
);
|
||||||
|
|
||||||
auto problem = equilibrium::discretize(model, finiteElements);
|
auto problem = equilibrium::discretize(model, std::move(finiteElements));
|
||||||
auto preconditioner = preconditioning::makePreconditioner(problem);
|
auto preconditioner = preconditioning::makePreconditioner(problem);
|
||||||
const auto &gravity = preconditioner.structureComponent().gravityComponent();
|
const auto &gravity = preconditioner.structureComponent().gravityComponent();
|
||||||
|
|
||||||
@@ -62,6 +70,52 @@ TEST_CASE(
|
|||||||
CHECK(gravity.potentialSchurBackend().application.cycles == 3);
|
CHECK(gravity.potentialSchurBackend().application.cycles == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Complete User API Builds A Context And Evaluates A Borrowing Newton Solver",
|
||||||
|
"[user-api][solver][context][report]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
auto finiteElements = makeFiniteElements();
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
|
constexpr double radius = utils::RADIUS;
|
||||||
|
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
||||||
|
const double centralDensity = std::numbers::pi_v<double> * utils::MASS / (4.0 * radius * radius * radius);
|
||||||
|
auto stellarModel = model::StellarModel(
|
||||||
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{utils::MASS}}),
|
||||||
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||||
|
);
|
||||||
|
auto discretization = equilibrium::makeStellarDiscretization(
|
||||||
|
std::move(finiteElements), normalization::PhysicalRieszDiagonal{dimensions::LengthValue{radius}, utils::G}
|
||||||
|
);
|
||||||
|
auto context = solver::makeContext(
|
||||||
|
std::move(stellarModel), std::move(discretization), preconditioning::makePreconditioner(),
|
||||||
|
solver::linear::FGMRES({.restartLength = 20, .printLevel = -1})
|
||||||
|
);
|
||||||
|
|
||||||
|
int beforeCalls = 0;
|
||||||
|
int afterCalls = 0;
|
||||||
|
auto observer = solver::nonlinear::makeObserver(
|
||||||
|
[&beforeCalls](const solver::nonlinear::BeforeIteration &) { ++beforeCalls; },
|
||||||
|
[&afterCalls](const solver::nonlinear::AfterIteration &) { ++afterCalls; }
|
||||||
|
);
|
||||||
|
auto newton = solver::nonlinear::Newton(solver::nonlinear::NewtonOptions{}, UserApiZeroMetric{});
|
||||||
|
auto equilibriumSolver = solver::make(context, newton, observer);
|
||||||
|
auto report = equilibriumSolver.evaluate();
|
||||||
|
|
||||||
|
REQUIRE(report.converged());
|
||||||
|
CHECK(report.completedNonlinearIterations() == 0);
|
||||||
|
CHECK(beforeCalls == 0);
|
||||||
|
CHECK(afterCalls == 0);
|
||||||
|
auto structure = report.structureView();
|
||||||
|
REQUIRE(structure.valid());
|
||||||
|
CHECK_FALSE(structure.state().empty());
|
||||||
|
CHECK(context.hasActiveSolver());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Fixed Angular Momentum User API Generates Rotation And Its Composable Solver Border",
|
"Fixed Angular Momentum User API Generates Rotation And Its Composable Solver Border",
|
||||||
"[user-api][fixed-angular-momentum][type]"
|
"[user-api][fixed-angular-momentum][type]"
|
||||||
@@ -72,18 +126,14 @@ TEST_CASE(
|
|||||||
REQUIRE(finiteElements.okay());
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
auto model = model::StellarModel(
|
auto model = model::StellarModel(
|
||||||
eos::Polytrope({.n = 1.0, .K = 0.25}),
|
eos::Polytrope({.n = 1.0, .K = 0.25}), surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
|
||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
||||||
integral::FixedAngularMomentum({
|
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.2}, .axis = {0.0, 0.0, 2.0}})
|
||||||
.Jtotal = dimensions::AngularMomentumValue{0.2},
|
|
||||||
.axis = {0.0, 0.0, 2.0}
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
auto problem = equilibrium::discretize(model, finiteElements);
|
auto problem = equilibrium::discretize(model, std::move(finiteElements));
|
||||||
auto preconditioner = preconditioning::makePreconditioner(problem);
|
auto preconditioner = preconditioning::makePreconditioner(problem);
|
||||||
using Problem = std::remove_cvref_t<decltype(problem)>;
|
using Problem = std::remove_cvref_t<decltype(problem)>;
|
||||||
using Preconditioner = std::remove_cvref_t<decltype(preconditioner)>;
|
using Preconditioner = std::remove_cvref_t<decltype(preconditioner)>;
|
||||||
|
|
||||||
STATIC_CHECK(Problem::hasFixedAngularMomentum);
|
STATIC_CHECK(Problem::hasFixedAngularMomentum);
|
||||||
@@ -99,8 +149,9 @@ TEST_CASE(
|
|||||||
CHECK(problem.StateSize() == problem.EquationSize());
|
CHECK(problem.StateSize() == problem.EquationSize());
|
||||||
CHECK(problem.StateSize() == problem.GetPhysicalOperator().Width() + 1);
|
CHECK(problem.StateSize() == problem.GetPhysicalOperator().Width() + 1);
|
||||||
REQUIRE(problem.GetManifest().constraints().size() == 3);
|
REQUIRE(problem.GetManifest().constraints().size() == 3);
|
||||||
CHECK(problem.GetManifest().template specification<models::FixedAngularMomentum>().stableId ==
|
CHECK(
|
||||||
"FixedAngularMomentum");
|
problem.GetManifest().template specification<models::FixedAngularMomentum>().stableId == "FixedAngularMomentum"
|
||||||
|
);
|
||||||
CHECK(problem.GetManifest().valueBlocks().back().symbol == "Omega");
|
CHECK(problem.GetManifest().valueBlocks().back().symbol == "Omega");
|
||||||
CHECK(problem.GetManifest().residualBlocks().back().symbol == "R_J");
|
CHECK(problem.GetManifest().residualBlocks().back().symbol == "R_J");
|
||||||
}
|
}
|
||||||
@@ -119,7 +170,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
||||||
);
|
);
|
||||||
auto problem = equilibrium::discretize(model, finiteElements);
|
auto problem = equilibrium::discretize(model, std::move(finiteElements));
|
||||||
|
|
||||||
auto material = preconditioning::materialSurfaceBlock(problem);
|
auto material = preconditioning::materialSurfaceBlock(problem);
|
||||||
auto gravity = preconditioning::GravityFieldBlock(
|
auto gravity = preconditioning::GravityFieldBlock(
|
||||||
@@ -151,7 +202,7 @@ TEST_CASE(
|
|||||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
||||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
||||||
);
|
);
|
||||||
auto problem = equilibrium::discretize(model, finiteElements);
|
auto problem = equilibrium::discretize(model, std::move(finiteElements));
|
||||||
|
|
||||||
auto material = preconditioning::materialSurfaceBlock(
|
auto material = preconditioning::materialSurfaceBlock(
|
||||||
problem, preconditioning::backend::Diagonal{}, preconditioning::backend::Diagonal{},
|
problem, preconditioning::backend::Diagonal{}, preconditioning::backend::Diagonal{},
|
||||||
|
|||||||
Reference in New Issue
Block a user