feat(surface): major work on implementing surface constraints in a presciption agnostic manner

This commit is contained in:
2026-08-30 16:41:14 -04:00
parent 36adfa1174
commit 0a7f18c5c7
95 changed files with 30144 additions and 25766 deletions

View File

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.28) cmake_minimum_required(VERSION 3.28)
project(MeanField CXX) project(MeanField C CXX)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -138,15 +138,24 @@ target_sources(mean_field
libmeanfield/interface/operators/kernels/rotation_displacement_force_kernels.cppm libmeanfield/interface/operators/kernels/rotation_displacement_force_kernels.cppm
libmeanfield/interface/operators/prepared_rotation_displacement_force.cppm libmeanfield/interface/operators/prepared_rotation_displacement_force.cppm
libmeanfield/interface/operators/prepared_displacement_operator.cppm libmeanfield/interface/operators/prepared_displacement_operator.cppm
libmeanfield/interface/eos/eos_base.cppm libmeanfield/interface/eos/quantities.cppm
libmeanfield/interface/eos/relations.cppm
libmeanfield/interface/eos/concepts.cppm
libmeanfield/interface/eos/evaluation.cppm
libmeanfield/interface/eos/pressure_surface.cppm
libmeanfield/interface/eos/runtime.cppm
libmeanfield/interface/eos/polytropic.cppm libmeanfield/interface/eos/polytropic.cppm
libmeanfield/interface/models/structure/structure_base.cppm libmeanfield/interface/models/structure/structure_base.cppm
libmeanfield/interface/models/structure/polytropic.cppm libmeanfield/interface/models/structure/polytropic.cppm
libmeanfield/interface/models/structure_profile.cppm libmeanfield/interface/models/structure_profile.cppm
libmeanfield/interface/surface/surface_base.cppm libmeanfield/interface/surface/constant.cppm
libmeanfield/interface/surface/isobaric.cppm libmeanfield/interface/surface/dependencies.cppm
libmeanfield/interface/surface/compiled.cppm
libmeanfield/interface/surface/compiler.cppm
libmeanfield/interface/models/stellar_model.cppm libmeanfield/interface/models/stellar_model.cppm
libmeanfield/interface/operators/prepared_mass_normalization.cppm libmeanfield/interface/operators/prepared_mass_normalization.cppm
libmeanfield/interface/operators/prepared_centering_constraint.cppm
libmeanfield/interface/operators/prepared_surface_constraint.cppm
libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm
) )
@@ -194,6 +203,12 @@ add_executable(tests
tests/operators/contexts/gravity_field_context.cpp tests/operators/contexts/gravity_field_context.cpp
tests/physics/gravity_monopole_accuracy.cpp tests/physics/gravity_monopole_accuracy.cpp
tests/physics/barotrope.cpp tests/physics/barotrope.cpp
tests/physics/polytropic_eos_characterization.cpp
tests/physics/equation_of_state_type_system.cpp
tests/physics/equation_of_state_consumer_contracts.cpp
tests/physics/polytropic_eos_relations.cpp
tests/physics/equation_of_state_runtime_view.cpp
tests/surface/constant_surface_compilation.cpp
tests/operators/kernels/barotropic_closure_kernels.cpp tests/operators/kernels/barotropic_closure_kernels.cpp
tests/operators/prepared_barotropic_closure.cpp tests/operators/prepared_barotropic_closure.cpp
tests/operators/contexts/barotropic_closure_linearization_context.cpp tests/operators/contexts/barotropic_closure_linearization_context.cpp
@@ -215,7 +230,6 @@ add_executable(tests
tests/operators/prepared_rotation_displacement_force_analytic.cpp tests/operators/prepared_rotation_displacement_force_analytic.cpp
tests/operators/prepared_rotation_displacement_force_affine_deformation.cpp tests/operators/prepared_rotation_displacement_force_affine_deformation.cpp
tests/operators/prepared_displacement_operator.cpp tests/operators/prepared_displacement_operator.cpp
tests/surface/isobaric.cpp
tests/models/stellar_model.cpp tests/models/stellar_model.cpp
tests/operators/prepared_mass_normalization.cpp tests/operators/prepared_mass_normalization.cpp
tests/operators/prepared_stellar_equilibrium.cpp tests/operators/prepared_stellar_equilibrium.cpp
@@ -233,6 +247,7 @@ target_sources(experiment_mod
PUBLIC PUBLIC
FILE_SET CXX_MODULES FILES FILE_SET CXX_MODULES FILES
experiments/experiment_results.cppm experiments/experiment_results.cppm
experiments/stellar_null_space.cppm
) )
target_link_libraries(experiment_mod target_link_libraries(experiment_mod
PUBLIC PUBLIC
@@ -247,6 +262,21 @@ add_executable(experiments
target_link_libraries(experiments PRIVATE mean_field test_mod experiment_mod Catch2::Catch2 Boost::boost) target_link_libraries(experiments PRIVATE mean_field test_mod experiment_mod Catch2::Catch2 Boost::boost)
add_executable(stellar_null_space_experiments
experiments/experiment_main.cpp
experiments/rigid_motion_null_space.cpp
experiments/gravity_completed_rigid_motion.cpp
)
target_link_libraries(stellar_null_space_experiments
PRIVATE
mean_field
test_mod
experiment_mod
Catch2::Catch2
Boost::boost
)
include (CTest) include (CTest)
include (Catch) include (Catch)
catch_discover_tests( catch_discover_tests(

View File

@@ -24,6 +24,49 @@ Run only the budget and choose its output path with:
./mean_field_experiments --experiment-output gravity_budget.csv --catch2 "[accuracy]" ./mean_field_experiments --experiment-output gravity_budget.csv --catch2 "[accuracy]"
``` ```
## Stellar-equilibrium null-space experiments
`stellar_null_space_experiments` is a dedicated diagnostic executable rather
than an ordinary verification or validation test. It constructs the analytic
`n = 3` Lane-Emden seed, probes the three computational translations and three
computational rotations, and compares the Jacobian before and after the strong
centering-row replacement. It records total and residual-block response norms,
the isolated centering contribution, and centered finite-difference errors.
The experiment prints rank-zero progress messages while it builds the seed,
solves its gravity field, and completes each rigid-mode case. Run it with:
```text
mpirun -np 1 ./cmake-build-debug-homebrew/stellar_null_space_experiments \
--experiment-output stellar_null_space.csv \
--catch2 "[null_space][rigid_motion]"
```
The rotation sweep includes zero rotation and a spherical-state diagnostic at
half the Keplerian angular speed. The rotating result is an operator-symmetry
probe, not a definitive rotating-equilibrium null-space measurement.
The gravity-completed probe solves the linearized mixed gravity subsystem for
the gravity-gradient and gravity-potential variations accompanying each rigid
displacement. It then measures the complete equilibrium response with and
without the centering rows:
```text
mpirun -np 1 ./cmake-build-debug-homebrew/stellar_null_space_experiments \
--experiment-output gravity_completed_null_space.csv \
--catch2 "[null_space][gravity_completed]"
```
The gravity solver prints its convergence summary, while the experiment prints
the current mode and completed-case count. This probe prepares each rotation
state only once and does not repeat the expensive nonlinear finite-difference
calculations from the original rigid-motion diagnostic.
A whole-Jacobian dense singular-value experiment is intentionally deferred.
The checked-in `sandbox.smesh` is too large for a useful dense SVD, and the
current matrix-free root operator does not provide a transpose action needed by
a scalable smallest-singular-value method.
The executable needs the same dependencies, generated module mapping, and The executable needs the same dependencies, generated module mapping, and
configuration registration as the existing Catch2 test executable. Add configuration registration as the existing Catch2 test executable. Add
`experiment_main.cpp` and `gravity_accuracy_budget.cpp` as a second executable `experiment_main.cpp` and `gravity_accuracy_budget.cpp` as a second executable

View File

@@ -0,0 +1,273 @@
#include <catch2/catch_test_macros.hpp>
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <map>
#include <string>
#include <mfem.hpp>
#include <mpi.h>
import experiment;
import experiment.stellar_null_space;
import mean_field;
import test_helpers;
namespace {
class GravityUnknownJacobian final : public mfem::Operator {
public:
explicit GravityUnknownJacobian(
const mean_field::operators::PreparedStellarEquilibriumOperator &stellarOperator
)
: mfem::Operator(
stellarOperator.GetLayout().size(experiment::null_space::gravityGradientValue) +
stellarOperator.GetLayout().size(experiment::null_space::gravityPotentialValue)
),
m_stellarOperator(stellarOperator),
m_gravityGradientSize(stellarOperator.GetLayout().size(experiment::null_space::gravityGradientValue)) {
MFEM_VERIFY(Width() == Height(), "The reduced gravity Jacobian must be square.");
}
void Mult(
const mfem::Vector &gravityDirection,
mfem::Vector &gravityAction
) const override {
MFEM_VERIFY(gravityDirection.Size() == Width(), "The reduced gravity direction has the wrong size.");
const mfem::Vector gravityGradientDirection(
const_cast<mfem::real_t *>(gravityDirection.GetData()), m_gravityGradientSize
);
const mfem::Vector gravityPotentialDirection(
const_cast<mfem::real_t *>(gravityDirection.GetData()) + m_gravityGradientSize,
Width() - m_gravityGradientSize
);
m_stellarOperator.GetGravityOperator().ApplyGravityUnknowns(
gravityGradientDirection,
gravityPotentialDirection,
m_stellarOperator.GetGravityContext().GetGeometryContext(),
gravityAction
);
}
[[nodiscard]] int gravity_gradient_size() const noexcept {
return m_gravityGradientSize;
}
private:
const mean_field::operators::PreparedStellarEquilibriumOperator &m_stellarOperator;
int m_gravityGradientSize;
};
void add_block_metrics(
std::map<
std::string,
double> &metrics,
const std::string &prefix,
const std::array<
double,
6> &norms
) {
for (std::size_t block = 0; block < norms.size(); ++block) {
metrics.emplace(prefix + experiment::null_space::residualBlockNames[block] + "_norm", norms[block]);
}
}
[[nodiscard]] mfem::Vector gravity_residual_blocks(
const mfem::Vector &completeAction,
const mean_field::operators::StellarEquilibriumLayout &layout
) {
const mfem::Vector gradient = experiment::null_space::const_residual_view(
completeAction, layout, experiment::null_space::gravityGradientResidual
);
const mfem::Vector potential = experiment::null_space::const_residual_view(
completeAction, layout, experiment::null_space::gravityPotentialResidual
);
mfem::Vector result(gradient.Size() + potential.Size());
mfem::Vector(result.GetData(), gradient.Size()) = gradient;
mfem::Vector(result.GetData() + gradient.Size(), potential.Size()) = potential;
return result;
}
void assign_gravity_completion(
mfem::Vector &completeDirection,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mfem::Vector &gravityCompletion,
const int gravityGradientSize
) {
const mfem::Vector gravityGradient(
const_cast<mfem::real_t *>(gravityCompletion.GetData()), gravityGradientSize
);
const mfem::Vector gravityPotential(
const_cast<mfem::real_t *>(gravityCompletion.GetData()) + gravityGradientSize,
gravityCompletion.Size() - gravityGradientSize
);
experiment::null_space::assign_value_block(
completeDirection, layout, experiment::null_space::gravityGradientValue, gravityGradient
);
experiment::null_space::assign_value_block(
completeDirection, layout, experiment::null_space::gravityPotentialValue, gravityPotential
);
}
void apply_centering_rows(
const mean_field::operators::PreparedStellarEquilibriumOperator &stellarOperator,
const mfem::Vector &direction,
mfem::Vector &action
) {
const auto &layout = stellarOperator.GetLayout();
const mfem::Vector displacementDirection =
experiment::null_space::const_value_view(direction, layout, experiment::null_space::displacementValue);
mfem::Vector displacementAction =
experiment::null_space::residual_view(action, layout, experiment::null_space::displacementResidual);
stellarOperator.GetCenteringConstraintOperator().ApplyJacobianRows(displacementDirection, displacementAction);
}
} // namespace
TEST_CASE(
"Gravity-Completed Rigid Motion Responses Of The Stellar Equilibrium Jacobian",
"[null_space][gravity_completed]"
) {
mean_field::utils::Args args = test_utils::setup_args();
args.p.rtol = 1.0e-11;
args.p.atol = std::min(args.p.atol, 1.0e-13);
args.p.max_iters = std::max(args.p.max_iters, 2000);
experiment::null_space::N3Equilibrium fixture(std::move(args));
const MPI_Comm communicator = fixture.fem().mesh->GetComm();
int rank = 0;
MPI_Comm_rank(communicator, &rank);
const auto modes = experiment::null_space::make_rigid_modes(fixture);
constexpr std::array<double, 2> rotationFractions{0.0, 0.5};
const int totalCases = static_cast<int>(rotationFractions.size() * modes.size());
int completedCases = 0;
for (const double rotationFraction : rotationFractions) {
const mean_field::physics::RigidRotation rotation = fixture.rotation(rotationFraction);
fixture.prepare(fixture.state(), rotation);
GravityUnknownJacobian gravityUnknownJacobian(fixture.stellar_operator());
mean_field::operators::ReducedGravityFieldPreconditioner gravityPreconditioner(
fixture.fem(), fixture.stellar_operator().GetGravityContext().GetGeometryContext()
);
mfem::MINRESSolver gravitySolver(communicator);
gravitySolver.SetOperator(gravityUnknownJacobian);
gravitySolver.SetPreconditioner(gravityPreconditioner);
gravitySolver.SetRelTol(1.0e-11);
gravitySolver.SetAbsTol(1.0e-13);
gravitySolver.SetMaxIter(2000);
gravitySolver.SetPrintLevel(1);
for (const experiment::null_space::RigidMode &mode : modes) {
experiment::null_space::report_progress(
communicator, "solving the gravity completion for " + mode.name + " at rotation fraction " +
std::to_string(rotationFraction) + " (" + std::to_string(completedCases + 1) + "/" +
std::to_string(totalCases) + ")"
);
const mfem::Vector displacementOnlyAction = fixture.unpinned_jacobian_action(mode.direction);
mfem::Vector gravityRightHandSide =
gravity_residual_blocks(displacementOnlyAction, fixture.stellar_operator().GetLayout());
gravityRightHandSide *= -1.0;
mfem::Vector gravityCompletion(gravityUnknownJacobian.Width());
gravityCompletion = 0.0;
gravitySolver.Mult(gravityRightHandSide, gravityCompletion);
REQUIRE(gravitySolver.GetConverged());
mfem::Vector gravitySolveAction;
gravityUnknownJacobian.Mult(gravityCompletion, gravitySolveAction);
mfem::Vector gravitySolveResidual(gravitySolveAction);
gravitySolveResidual -= gravityRightHandSide;
const double gravityRightHandSideNorm =
experiment::null_space::global_norm(gravityRightHandSide, communicator);
const double gravitySolveResidualNorm =
experiment::null_space::global_norm(gravitySolveResidual, communicator);
const double gravitySolveRelativeResidual =
gravitySolveResidualNorm / std::max(gravityRightHandSideNorm, std::numeric_limits<double>::epsilon());
REQUIRE(std::isfinite(gravitySolveRelativeResidual));
mfem::Vector completedDirection(mode.direction);
assign_gravity_completion(
completedDirection, fixture.stellar_operator().GetLayout(), gravityCompletion,
gravityUnknownJacobian.gravity_gradient_size()
);
const mfem::Vector completedUnpinnedAction = fixture.unpinned_jacobian_action(completedDirection);
mfem::Vector completedConstrainedAction(completedUnpinnedAction);
apply_centering_rows(fixture.stellar_operator(), completedDirection, completedConstrainedAction);
mfem::Vector centeringContribution(completedConstrainedAction);
centeringContribution -= completedUnpinnedAction;
std::map<std::string, double> metrics{
{"displacement_only_input_norm", experiment::null_space::global_norm(mode.direction, communicator)},
{"gravity_completion_norm", experiment::null_space::global_norm(gravityCompletion, communicator)},
{"completed_input_norm", experiment::null_space::global_norm(completedDirection, communicator)},
{"displacement_only_action_norm",
experiment::null_space::global_norm(displacementOnlyAction, communicator)},
{"gravity_completed_unpinned_action_norm",
experiment::null_space::global_norm(completedUnpinnedAction, communicator)},
{"gravity_completed_constrained_action_norm",
experiment::null_space::global_norm(completedConstrainedAction, communicator)},
{"centering_contribution_norm",
experiment::null_space::global_norm(centeringContribution, communicator)},
{"gravity_solve_rhs_norm", gravityRightHandSideNorm},
{"gravity_solve_residual_norm", gravitySolveResidualNorm},
{"gravity_solve_relative_residual", gravitySolveRelativeResidual},
{"gravity_solve_iterations", static_cast<double>(gravitySolver.GetNumIterations())},
{"gravity_solve_final_norm", gravitySolver.GetFinalNorm()}
};
add_block_metrics(
metrics, "displacement_only_",
experiment::null_space::residual_block_norms(
displacementOnlyAction, fixture.stellar_operator().GetLayout(), communicator
)
);
add_block_metrics(
metrics, "gravity_completed_unpinned_",
experiment::null_space::residual_block_norms(
completedUnpinnedAction, fixture.stellar_operator().GetLayout(), communicator
)
);
add_block_metrics(
metrics, "gravity_completed_constrained_",
experiment::null_space::residual_block_norms(
completedConstrainedAction, fixture.stellar_operator().GetLayout(), communicator
)
);
if (rank == 0) {
experiment::record_experiment_result(
"gravity_completed_stellar_rigid_motion_null_space", mode.name,
{{"mode_kind",
mode.kind == experiment::null_space::RigidModeKind::translation ? "translation" : "rotation"},
{"axis", std::to_string(mode.axis)},
{"rotation_fraction_of_keplerian", std::to_string(rotationFraction)},
{"mesh_file", test_utils::setup_args().mesh_file},
{"local_state_dofs", std::to_string(fixture.stellar_operator().Width())}},
std::move(metrics)
);
}
++completedCases;
experiment::null_space::report_progress(
communicator, "completed " + std::to_string(completedCases) + "/" + std::to_string(totalCases) +
" gravity-completed rigid-mode cases"
);
}
}
experiment::null_space::report_progress(
communicator, "gravity-completed rigid-motion probe complete; writing CSV output"
);
}

View File

@@ -0,0 +1,174 @@
#include <catch2/catch_test_macros.hpp>
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <map>
#include <string>
#include <mfem.hpp>
#include <mpi.h>
import experiment;
import experiment.stellar_null_space;
import mean_field;
import test_helpers;
namespace {
[[nodiscard]] double relative_difference(
const mfem::Vector &computed,
const mfem::Vector &reference,
const MPI_Comm communicator
) {
mfem::Vector difference(computed);
difference -= reference;
const double scale = std::max(
{experiment::null_space::global_norm(computed, communicator),
experiment::null_space::global_norm(reference, communicator), std::numeric_limits<double>::epsilon()}
);
return experiment::null_space::global_norm(difference, communicator) / scale;
}
void add_block_metrics(
std::map<
std::string,
double> &metrics,
const std::string &prefix,
const std::array<
double,
6> &norms
) {
for (std::size_t block = 0; block < norms.size(); ++block) {
metrics.emplace(prefix + experiment::null_space::residualBlockNames[block] + "_norm", norms[block]);
}
}
} // namespace
TEST_CASE(
"Rigid Motion Responses Of The Stellar Equilibrium Jacobian",
"[null_space][rigid_motion]"
) {
mean_field::utils::Args args = test_utils::setup_args();
args.p.rtol = 1.0e-12;
args.p.atol = std::min(args.p.atol, 1.0e-14);
args.p.max_iters = std::max(args.p.max_iters, 2000);
experiment::null_space::N3Equilibrium fixture(std::move(args));
const MPI_Comm communicator = fixture.fem().mesh->GetComm();
int rank = 0;
MPI_Comm_rank(communicator, &rank);
const auto modes = experiment::null_space::make_rigid_modes(fixture);
constexpr std::array<double, 2> rotationFractions{0.0, 0.5};
constexpr std::array<double, 2> finiteDifferenceSteps{1.0e-4, 1.0e-6};
const int totalCases = static_cast<int>(rotationFractions.size() * modes.size());
int completedCases = 0;
for (const double rotationFraction : rotationFractions) {
const mean_field::physics::RigidRotation rotation = fixture.rotation(rotationFraction);
fixture.prepare(fixture.state(), rotation);
mfem::Vector constrainedResidual;
fixture.stellar_operator().BuildResidual(constrainedResidual);
const mfem::Vector unpinnedResidual = fixture.unpinned_residual();
REQUIRE(constrainedResidual.Size() == unpinnedResidual.Size());
REQUIRE(std::isfinite(experiment::null_space::global_norm(constrainedResidual, communicator)));
REQUIRE(std::isfinite(experiment::null_space::global_norm(unpinnedResidual, communicator)));
for (const experiment::null_space::RigidMode &mode : modes) {
experiment::null_space::report_progress(
communicator, "probing " + mode.name + " at rotation fraction " + std::to_string(rotationFraction) +
" (" + std::to_string(completedCases + 1) + "/" + std::to_string(totalCases) + ")"
);
fixture.prepare(fixture.state(), rotation);
const mfem::Vector unpinnedAction = fixture.unpinned_jacobian_action(mode.direction);
mfem::Vector constrainedAction;
fixture.stellar_operator().Mult(mode.direction, constrainedAction);
mfem::Vector centeringContribution(constrainedAction);
centeringContribution -= unpinnedAction;
const double inputNorm = experiment::null_space::global_norm(mode.direction, communicator);
const double unpinnedNorm = experiment::null_space::global_norm(unpinnedAction, communicator);
const double constrainedNorm = experiment::null_space::global_norm(constrainedAction, communicator);
REQUIRE(inputNorm > 0.0);
REQUIRE(std::isfinite(unpinnedNorm));
REQUIRE(std::isfinite(constrainedNorm));
std::map<std::string, double> metrics{
{"input_algebraic_norm", inputNorm},
{"unpinned_action_norm", unpinnedNorm},
{"unpinned_action_per_input_norm", unpinnedNorm / inputNorm},
{"constrained_action_norm", constrainedNorm},
{"constrained_action_per_input_norm", constrainedNorm / inputNorm},
{"centering_contribution_norm",
experiment::null_space::global_norm(centeringContribution, communicator)},
{"unpinned_base_residual_norm", experiment::null_space::global_norm(unpinnedResidual, communicator)},
{"constrained_base_residual_norm",
experiment::null_space::global_norm(constrainedResidual, communicator)}
};
add_block_metrics(
metrics, "unpinned_",
experiment::null_space::residual_block_norms(
unpinnedAction, fixture.stellar_operator().GetLayout(), communicator
)
);
add_block_metrics(
metrics, "constrained_",
experiment::null_space::residual_block_norms(
constrainedAction, fixture.stellar_operator().GetLayout(), communicator
)
);
for (const double step : finiteDifferenceSteps) {
mfem::Vector plusState(fixture.state());
plusState.Add(step, mode.direction);
fixture.prepare(plusState, rotation);
const mfem::Vector plusResidual = fixture.unpinned_residual();
mfem::Vector minusState(fixture.state());
minusState.Add(-step, mode.direction);
fixture.prepare(minusState, rotation);
const mfem::Vector minusResidual = fixture.unpinned_residual();
mfem::Vector finiteDifference(plusResidual);
finiteDifference -= minusResidual;
finiteDifference /= 2.0 * step;
const std::string stepName = step == finiteDifferenceSteps.front() ? "1e-4" : "1e-6";
metrics.emplace(
"finite_difference_relative_error_" + stepName,
relative_difference(unpinnedAction, finiteDifference, communicator)
);
}
fixture.prepare(fixture.state(), rotation);
if (rank == 0) {
experiment::record_experiment_result(
"stellar_rigid_motion_null_space", mode.name,
{{"mode_kind",
mode.kind == experiment::null_space::RigidModeKind::translation ? "translation" : "rotation"},
{"axis", std::to_string(mode.axis)},
{"rotation_fraction_of_keplerian", std::to_string(rotationFraction)},
{"mesh_file", test_utils::setup_args().mesh_file},
{"local_state_dofs", std::to_string(fixture.stellar_operator().Width())}},
std::move(metrics)
);
}
++completedCases;
experiment::null_space::report_progress(
communicator,
"completed " + std::to_string(completedCases) + "/" + std::to_string(totalCases) + " rigid-mode cases"
);
}
}
experiment::null_space::report_progress(communicator, "rigid-motion probe complete; writing CSV output");
}

View File

@@ -0,0 +1,519 @@
module;
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <string>
#include <utility>
#include <mfem.hpp>
#include <mpi.h>
export module experiment.stellar_null_space;
import mean_field;
import test_helpers;
export namespace experiment::null_space {
using Form = mean_field::utils::blocks::barotropic_equilibrium_form;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using Model = mean_field::models::StellarModel<mean_field::models::structure::PolytropicStructure>;
constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::displacement_field.geometry_term);
constexpr auto gravityGradientValue =
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue =
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::enthalpy_field.specific_term);
constexpr auto bernoulliValue = mean_field::utils::blocks::get_value_block<Form>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
constexpr auto gravityGradientResidual =
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialResidual =
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto densityResidual =
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block<Form>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto enthalpyResidual =
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::enthalpy_field.specific_term);
constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<Form>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
inline constexpr std::array<const char *, 6> residualBlockNames{"gravity_gradient", "gravity_potential", "closure",
"displacement", "hydrostatic", "mass"};
template <int index>
[[nodiscard]] mfem::Vector value_view(
mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::value_block<index> block
) {
return mfem::Vector(vector.GetData() + layout.offset(block), layout.size(block));
}
template <int index>
[[nodiscard]] mfem::Vector const_value_view(
const mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::value_block<index> block
) {
return mfem::Vector(const_cast<mfem::real_t *>(vector.GetData()) + layout.offset(block), layout.size(block));
}
template <int index>
[[nodiscard]] mfem::Vector residual_view(
mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::residual_block<index> block
) {
return mfem::Vector(vector.GetData() + layout.offset(block), layout.size(block));
}
template <int index>
[[nodiscard]] mfem::Vector const_residual_view(
const mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::residual_block<index> block
) {
return mfem::Vector(const_cast<mfem::real_t *>(vector.GetData()) + layout.offset(block), layout.size(block));
}
template <int index>
void assign_value_block(
mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::value_block<index> block,
const mfem::Vector &source
) {
MFEM_VERIFY(source.Size() == layout.size(block), "Null-space experiment received a block with the wrong size.");
value_view(vector, layout, block) = source;
}
[[nodiscard]] inline double global_norm(
const mfem::Vector &vector,
const MPI_Comm communicator
) {
const double localNormSquared = vector * vector;
double globalNormSquared = 0.0;
MPI_Allreduce(&localNormSquared, &globalNormSquared, 1, MPI_DOUBLE, MPI_SUM, communicator);
return std::sqrt(globalNormSquared);
}
inline void report_progress(
const MPI_Comm communicator,
const std::string &message
) {
int rank = 0;
MPI_Comm_rank(communicator, &rank);
if (rank == 0) {
std::cout << "[null-space experiment] " << message << std::endl;
}
}
[[nodiscard]] inline mean_field::operators::StellarEquilibriumDependencies make_dependencies() {
return {
.discretization = {.identity = 2003, .revision = 1},
.density = {.identity = 2011, .revision = 1},
.displacement = {.identity = 2017, .revision = 1},
.gravityGradient = {.identity = 2027, .revision = 1},
.gravityPotential = {.identity = 2029, .revision = 1},
.enthalpy = {.identity = 2039, .revision = 1},
.bernoulliConstant = {.identity = 2053, .revision = 1},
.rotation = {.identity = 2063, .revision = 1},
.targetMass = {.identity = 2069, .revision = 1}
};
}
inline void increment_state_revisions(mean_field::operators::StellarEquilibriumDependencies &dependencies) {
++dependencies.density.revision;
++dependencies.displacement.revision;
++dependencies.gravityGradient.revision;
++dependencies.gravityPotential.revision;
++dependencies.enthalpy.revision;
++dependencies.bernoulliConstant.revision;
}
[[nodiscard]] inline mfem::Vector pack_gravity_state(
const mfem::Vector &density,
const mfem::Vector &displacement,
const mfem::Vector &gravityGradient,
const mfem::Vector &gravityPotential
) {
const std::array<int, 5> offsets{
0, density.Size(), density.Size() + displacement.Size(),
density.Size() + displacement.Size() + gravityGradient.Size(),
density.Size() + displacement.Size() + gravityGradient.Size() + gravityPotential.Size()
};
mfem::Vector packed(offsets.back());
mfem::Vector(packed.GetData() + offsets[0], density.Size()) = density;
mfem::Vector(packed.GetData() + offsets[1], displacement.Size()) = displacement;
mfem::Vector(packed.GetData() + offsets[2], gravityGradient.Size()) = gravityGradient;
mfem::Vector(packed.GetData() + offsets[3], gravityPotential.Size()) = gravityPotential;
return packed;
}
[[nodiscard]] inline Model make_model() {
const double pi = std::acos(-1.0);
const double targetMass = mean_field::utils::MASS;
constexpr double dimensionlessMass = 2.0182359509662283534;
const double polytropicConstant =
pi * mean_field::utils::G * std::pow(targetMass / (4.0 * pi * dimensionlessMass), 2.0 / 3.0);
return Model{
mean_field::models::structure::PolytropicStructure{
mean_field::eos::Polytrope{3.0, polytropicConstant}, targetMass
},
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
};
}
class N3Equilibrium final {
public:
explicit N3Equilibrium(mean_field::utils::Args args)
: m_args(std::move(args)),
m_fem(
mean_field::fem::setup_fem(
m_args.mesh_file,
m_args,
0
)
),
m_model(make_model()),
m_operator(
m_fem,
*m_fem.domainMapperStateless,
m_model
),
m_state(m_operator.GetLayout().value_offsets().Last()),
m_dependencies(make_dependencies()) {
MFEM_VERIFY(m_fem.okay(), "The null-space experiment could not construct the finite-element problem.");
m_state = 0.0;
initialize_state();
}
[[nodiscard]] mean_field::fem::FEM &fem() noexcept {
return m_fem;
}
[[nodiscard]] const mean_field::fem::FEM &fem() const noexcept {
return m_fem;
}
[[nodiscard]] mean_field::operators::PreparedStellarEquilibriumOperator &stellar_operator() noexcept {
return m_operator;
}
[[nodiscard]] const mean_field::operators::PreparedStellarEquilibriumOperator &
stellar_operator() const noexcept {
return m_operator;
}
[[nodiscard]] const mfem::Vector &state() const noexcept {
return m_state;
}
[[nodiscard]] mean_field::physics::RigidRotation rotation(const double fractionOfKeplerian) const {
const double radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS;
const double keplerianSpeed = std::sqrt(mean_field::utils::G * mass / (radius * radius * radius));
mfem::Vector angularVelocity(3);
angularVelocity = 0.0;
angularVelocity(2) = fractionOfKeplerian * keplerianSpeed;
mfem::Vector center(3);
center = 0.0;
return mean_field::physics::RigidRotation(angularVelocity, center);
}
void prepare(
const mfem::Vector &state,
const mean_field::physics::RigidRotation &rotation
) {
m_currentState = state;
increment_state_revisions(m_dependencies);
++m_dependencies.rotation.revision;
m_operator.Prepare(state, m_dependencies, rotation);
}
[[nodiscard]] mfem::Vector unpinned_residual() const {
const auto &layout = m_operator.GetLayout();
const mfem::Vector reducedDensity = const_value_view(m_currentState, layout, densityValue);
const mfem::Vector displacement = const_value_view(m_currentState, layout, displacementValue);
const mfem::Vector gravityGradient = const_value_view(m_currentState, layout, gravityGradientValue);
const mfem::Vector gravityPotential = const_value_view(m_currentState, layout, gravityPotentialValue);
const mfem::Vector gravityState =
pack_gravity_state(reducedDensity, displacement, gravityGradient, gravityPotential);
mfem::Vector gravity;
mfem::Vector closure;
mfem::Vector displacementRows;
mfem::Vector hydrostatic;
mfem::Vector mass;
m_operator.GetGravityOperator().Mult(gravityState, gravity);
m_operator.GetBarotropicClosureOperator().BuildResidual(closure);
m_operator.GetDisplacementOperator().BuildResidual(displacementRows);
m_operator.GetHydrostaticOperator().BuildResidual(hydrostatic);
m_operator.GetSurfaceConstraintOperator().ApplyResidualRows(hydrostatic);
m_operator.GetMassNormalizationOperator().BuildResidual(mass);
return pack_residual(gravity, closure, displacementRows, hydrostatic, mass);
}
[[nodiscard]] mfem::Vector unpinned_jacobian_action(const mfem::Vector &direction) const {
const auto &layout = m_operator.GetLayout();
const mfem::Vector reducedDensityDirection = const_value_view(direction, layout, densityValue);
const mfem::Vector displacementDirection = const_value_view(direction, layout, displacementValue);
const mfem::Vector gravityGradientDirection = const_value_view(direction, layout, gravityGradientValue);
const mfem::Vector gravityPotentialDirection = const_value_view(direction, layout, gravityPotentialValue);
const mfem::Vector reducedEnthalpyDirection = const_value_view(direction, layout, enthalpyValue);
const mfem::Vector bernoulliDirection = const_value_view(direction, layout, bernoulliValue);
const mfem::Vector gravityDirection = pack_gravity_state(
reducedDensityDirection, displacementDirection, gravityGradientDirection, gravityPotentialDirection
);
mfem::Vector gravity;
mfem::Vector closure;
mfem::Vector displacementRows;
mfem::Vector hydrostatic;
mfem::Vector mass;
m_operator.GetGravityJacobianOperator().Mult(gravityDirection, gravity);
m_operator.GetBarotropicClosureOperator().Mult(
reducedDensityDirection, reducedEnthalpyDirection, displacementDirection, closure
);
m_operator.GetDisplacementOperator().ApplyCompleteJacobianAction(
reducedDensityDirection, displacementDirection, gravityGradientDirection, reducedEnthalpyDirection,
displacementRows
);
m_operator.GetHydrostaticOperator().ApplyCompleteJacobianAction(
reducedEnthalpyDirection, gravityPotentialDirection, bernoulliDirection(0), displacementDirection,
hydrostatic
);
m_operator.GetSurfaceConstraintOperator().ApplyJacobianRows(reducedEnthalpyDirection, hydrostatic);
m_operator.GetMassNormalizationOperator().ApplyCompleteJacobianAction(
reducedDensityDirection, displacementDirection, mass
);
return pack_residual(gravity, closure, displacementRows, hydrostatic, mass);
}
private:
void initialize_state() {
report_progress(m_fem.mesh->GetComm(), "constructing the analytic n=3 Lane-Emden state");
constexpr double surfaceCoordinate = 6.8968486193769603755;
constexpr int radialSampleCount = 8192;
const double pi = std::acos(-1.0);
const double radius = mean_field::utils::RADIUS;
const double targetMass = mean_field::utils::MASS;
constexpr double dimensionlessMass = 2.0182359509662283534;
const double polytropicConstant =
pi * mean_field::utils::G * std::pow(targetMass / (4.0 * pi * dimensionlessMass), 2.0 / 3.0);
const double centralDensity =
std::pow(surfaceCoordinate * std::sqrt(polytropicConstant / (pi * mean_field::utils::G)) / radius, 3.0);
const mean_field::models::structure::StructureSeed seed =
m_model.makeInitialSeed({.centralDensity = centralDensity, .radialSampleCount = radialSampleCount});
const auto interpolate = [](const mfem::Vector &radii, const mfem::Vector &values, const double r) {
if (r <= radii(0)) {
return values(0);
}
const int finalIndex = radii.Size() - 1;
if (r >= radii(finalIndex)) {
return values(finalIndex);
}
int lower = 0;
int upper = finalIndex;
while (upper - lower > 1) {
const int middle = lower + (upper - lower) / 2;
if (radii(middle) <= r) {
lower = middle;
} else {
upper = middle;
}
}
const double fraction = (r - radii(lower)) / (radii(upper) - radii(lower));
return (1.0 - fraction) * values(lower) + fraction * values(upper);
};
mfem::FunctionCoefficient densityCoefficient([&seed, &interpolate](const mfem::Vector &position) {
const double r = position.Norml2();
return r >= seed.stellarRadius ? 0.0 : interpolate(seed.radius, seed.density, r);
});
mfem::FunctionCoefficient enthalpyCoefficient([&seed, &interpolate](const mfem::Vector &position) {
const double r = position.Norml2();
return r >= seed.stellarRadius ? 0.0 : interpolate(seed.radius, seed.enthalpy, r);
});
mfem::ParGridFunction densityField(m_fem.densityFes.get());
mfem::ParGridFunction enthalpyField(m_fem.enthalpyFes.get());
mfem::ParGridFunction displacementField(m_fem.displacementFes.get());
densityField = 0.0;
enthalpyField = 0.0;
displacementField = 0.0;
densityField.ProjectCoefficient(densityCoefficient);
enthalpyField.ProjectCoefficient(enthalpyCoefficient);
*m_fem.displacement = displacementField;
report_progress(m_fem.mesh->GetComm(), "solving the gravity field for the seed state");
const mean_field::physics::GravitySolution gravity =
mean_field::physics::solve_gravity_field(m_fem, m_args, densityField, displacementField);
mfem::Vector densityTrue;
mfem::Vector enthalpyTrue;
mfem::Vector displacementTrue;
mfem::Vector gravityGradientTrue;
mfem::Vector gravityPotentialTrue;
densityField.GetTrueDofs(densityTrue);
enthalpyField.GetTrueDofs(enthalpyTrue);
displacementField.GetTrueDofs(displacementTrue);
gravity.gradPhi.GetTrueDofs(gravityGradientTrue);
gravity.phi.GetTrueDofs(gravityPotentialTrue);
const auto &layout = m_operator.GetLayout();
const mean_field::field::FieldDofMap densityMap =
mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*m_fem.densityFes);
const mean_field::field::FieldDofMap enthalpyMap =
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy, DomainSchema>(*m_fem.enthalpyFes);
assign_value_block(m_state, layout, densityValue, densityMap.gather(densityTrue));
assign_value_block(m_state, layout, displacementValue, displacementTrue);
assign_value_block(m_state, layout, gravityGradientValue, gravityGradientTrue);
assign_value_block(m_state, layout, gravityPotentialValue, gravityPotentialTrue);
assign_value_block(m_state, layout, enthalpyValue, enthalpyMap.gather(enthalpyTrue));
value_view(m_state, layout, bernoulliValue)(0) = -mean_field::utils::G * targetMass / radius;
m_currentState = m_state;
prepare(m_state, rotation(0.0));
report_progress(m_fem.mesh->GetComm(), "analytic state is prepared");
}
[[nodiscard]] mfem::Vector pack_residual(
const mfem::Vector &gravity,
const mfem::Vector &closure,
const mfem::Vector &displacementRows,
const mfem::Vector &hydrostatic,
const mfem::Vector &mass
) const {
const auto &layout = m_operator.GetLayout();
mfem::Vector result(layout.residual_offsets().Last());
result = 0.0;
const mfem::Vector gravityGradient(gravity.GetData(), layout.size(gravityGradientResidual));
const mfem::Vector gravityPotential(
gravity.GetData() + layout.size(gravityGradientResidual), layout.size(gravityPotentialResidual)
);
residual_view(result, layout, gravityGradientResidual) = gravityGradient;
residual_view(result, layout, gravityPotentialResidual) = gravityPotential;
residual_view(result, layout, densityResidual) = closure;
residual_view(result, layout, displacementResidual) = displacementRows;
residual_view(result, layout, enthalpyResidual) = hydrostatic;
residual_view(result, layout, massResidual) = mass;
return result;
}
mean_field::utils::Args m_args;
mean_field::fem::FEM m_fem;
Model m_model;
mean_field::operators::PreparedStellarEquilibriumOperator m_operator;
mfem::Vector m_state;
mfem::Vector m_currentState;
mean_field::operators::StellarEquilibriumDependencies m_dependencies;
};
enum class RigidModeKind : std::uint8_t { translation, rotation };
struct RigidMode final {
std::string name;
RigidModeKind kind;
int axis;
mfem::Vector direction;
};
[[nodiscard]] inline std::array<
RigidMode,
6>
make_rigid_modes(const N3Equilibrium &fixture) {
const auto &fem = fixture.fem();
const auto &layout = fixture.stellar_operator().GetLayout();
std::array<RigidMode, 6> modes;
for (int axis = 0; axis < 3; ++axis) {
mfem::ParGridFunction translation(fem.displacementFes.get());
mfem::Vector translationValue(3);
translationValue = 0.0;
translationValue(axis) = 1.0;
mfem::VectorConstantCoefficient coefficient(translationValue);
translation.ProjectCoefficient(coefficient);
mfem::Vector translationTrue;
translation.GetTrueDofs(translationTrue);
mfem::Vector direction(layout.value_offsets().Last());
direction = 0.0;
assign_value_block(direction, layout, displacementValue, translationTrue);
modes[axis] = RigidMode{
.name = std::string("translation_") + static_cast<char>('x' + axis),
.kind = RigidModeKind::translation,
.axis = axis,
.direction = std::move(direction)
};
}
for (int axis = 0; axis < 3; ++axis) {
mfem::ParGridFunction rotation(fem.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(3, [axis](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(3);
value = 0.0;
const int first = (axis + 1) % 3;
const int second = (axis + 2) % 3;
value(first) = -position(second);
value(second) = position(first);
});
rotation.ProjectCoefficient(coefficient);
mfem::Vector rotationTrue;
rotation.GetTrueDofs(rotationTrue);
mfem::Vector direction(layout.value_offsets().Last());
direction = 0.0;
assign_value_block(direction, layout, displacementValue, rotationTrue);
modes[3 + axis] = RigidMode{
.name = std::string("rotation_") + static_cast<char>('x' + axis),
.kind = RigidModeKind::rotation,
.axis = axis,
.direction = std::move(direction)
};
}
return modes;
}
[[nodiscard]] inline std::array<
double,
6>
residual_block_norms(
const mfem::Vector &action,
const mean_field::operators::StellarEquilibriumLayout &layout,
const MPI_Comm communicator
) {
return {
global_norm(const_residual_view(action, layout, gravityGradientResidual), communicator),
global_norm(const_residual_view(action, layout, gravityPotentialResidual), communicator),
global_norm(const_residual_view(action, layout, densityResidual), communicator),
global_norm(const_residual_view(action, layout, displacementResidual), communicator),
global_norm(const_residual_view(action, layout, enthalpyResidual), communicator),
global_norm(const_residual_view(action, layout, massResidual), communicator)
};
}
} // namespace experiment::null_space

View File

@@ -14,20 +14,23 @@ namespace {
) { ) {
switch (domain) { switch (domain) {
case mean_field::utils::DOMAINS::CORE: case mean_field::utils::DOMAINS::CORE:
return mean_field::utils::domain::make_attribute_marker< return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Core, DomainSchema>(
mean_field::utils::domain::Core, DomainSchema>(mesh); mesh
);
case mean_field::utils::DOMAINS::ENVELOPE: case mean_field::utils::DOMAINS::ENVELOPE:
return mean_field::utils::domain::make_attribute_marker< return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Envelope, DomainSchema>(
mean_field::utils::domain::Envelope, DomainSchema>(mesh); mesh
);
case mean_field::utils::DOMAINS::ALL: case mean_field::utils::DOMAINS::ALL:
return mean_field::utils::domain::make_attribute_marker< return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::All, DomainSchema>(mesh);
mean_field::utils::domain::All, DomainSchema>(mesh);
case mean_field::utils::DOMAINS::STELLAR: case mean_field::utils::DOMAINS::STELLAR:
return mean_field::utils::domain::make_attribute_marker< return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Stellar, DomainSchema>(
mean_field::utils::domain::Stellar, DomainSchema>(mesh); mesh
);
case mean_field::utils::DOMAINS::VACUUM: case mean_field::utils::DOMAINS::VACUUM:
return mean_field::utils::domain::make_attribute_marker< return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Vacuum, DomainSchema>(
mean_field::utils::domain::Vacuum, DomainSchema>(mesh); mesh
);
} }
MFEM_ABORT("Unsupported integration domain."); MFEM_ABORT("Unsupported integration domain.");
} }
@@ -62,15 +65,14 @@ namespace mean_field::analysis {
mfem::LinearForm lf(fem.densityFes.get()); mfem::LinearForm lf(fem.densityFes.get());
mfem::GridFunctionCoefficient gf_c(&gf); mfem::GridFunctionCoefficient gf_c(&gf);
double local_integral; double local_integral;
mfem::Array<int> elem_markers = make_domain_marker(*fem.mesh, domain); mfem::Array<int> elem_markers = make_domain_marker(*fem.mesh, domain);
const mfem::ElementTransformation &representative_transformation = *fem.mesh->GetElementTransformation(0); const mfem::ElementTransformation &representative_transformation = *fem.mesh->GetElementTransformation(0);
const mfem::IntegrationRule &integration_rule = const mfem::IntegrationRule &integration_rule =
get_density_rule<field::Density::Form::MassConservation>(fem, representative_transformation, {}, domain); get_density_rule<field::Density::Form::MassConservation>(fem, representative_transformation, {}, domain);
if (fem.has_mapping() && coord_space == mapping::COORDINATE_SPACE::PHYSICAL) { if (fem.has_mapping() && coord_space == mapping::COORDINATE_SPACE::PHYSICAL) {
mapping::MappedScalarCoefficient mapped_gf_c( mapping::MappedScalarCoefficient mapped_gf_c(
*fem.domainMapperStateless, *fem.displacement, *fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, gf_c
*fem.compactificationCoordinate, gf_c
); );
// ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM // ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM
@@ -107,16 +109,14 @@ namespace mean_field::analysis {
) { ) {
const int dim = fem.mesh->Dimension(); const int dim = fem.mesh->Dimension();
mapping::GridFunctionMappingEvaluator mapping_evaluator( mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
*fem.compactificationCoordinate
); );
mfem::Vector local_com(dim); mfem::Vector local_com(dim);
local_com = 0.0; local_com = 0.0;
double local_mass = 0.0; double local_mass = 0.0;
for (int i = 0; i < fem.mesh->GetNE(); ++i) { for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>( if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(fem.mesh->GetAttribute(i)))
fem.mesh->GetAttribute(i)))
continue; continue;
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i); mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir = get_density_rule<field::Density::Form::CenterOfMass>( const mfem::IntegrationRule &ir = get_density_rule<field::Density::Form::CenterOfMass>(
@@ -129,16 +129,15 @@ namespace mean_field::analysis {
mapping::VolumeMappingContext mapping_context; mapping::VolumeMappingContext mapping_context;
MFEM_VERIFY( MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping::MappingStatus::valid,
mapping::MappingStatus::valid,
"Center-of-mass integration encountered an invalid mapping." "Center-of-mass integration encountered an invalid mapping."
); );
const double weight = mapping_context.quadrature.weight; const double weight = mapping_context.quadrature.weight;
double rho_val = rho.GetValue(i, ip); double rho_val = rho.GetValue(i, ip);
const mfem::Vector &phys_point = mapping_context.mapping.physical_position; const mfem::Vector &phys_point = mapping_context.mapping.physical_position;
const double mass_term = rho_val * weight; const double mass_term = rho_val * weight;
local_mass += mass_term; local_mass += mass_term;
for (int d = 0; d < dim; ++d) { for (int d = 0; d < dim; ++d) {
@@ -183,8 +182,7 @@ namespace mean_field::analysis {
std::unique_ptr<mfem::Coefficient> s2_coeff; std::unique_ptr<mfem::Coefficient> s2_coeff;
if (fem.has_mapping()) { if (fem.has_mapping()) {
s2_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>( s2_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(
*fem.domainMapperStateless, *fem.displacement, *fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, s2_func
*fem.compactificationCoordinate, s2_func
); );
} else { } else {
s2_coeff = std::make_unique<mfem::FunctionCoefficient>(s2_func); s2_coeff = std::make_unique<mfem::FunctionCoefficient>(s2_func);
@@ -204,8 +202,7 @@ namespace mean_field::analysis {
double local_I = 0.0; double local_I = 0.0;
if (fem.has_mapping()) { if (fem.has_mapping()) {
mapping::MappedScalarCoefficient mapped_integrand( mapping::MappedScalarCoefficient mapped_integrand(
*fem.domainMapperStateless, *fem.displacement, *fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, I_integrand
*fem.compactificationCoordinate, I_integrand
); );
auto *integrator = new mfem::DomainLFIntegrator(mapped_integrand); auto *integrator = new mfem::DomainLFIntegrator(mapped_integrand);
integrator->SetIntRule(&integration_rule); integrator->SetIntRule(&integration_rule);
@@ -239,18 +236,16 @@ namespace mean_field::analysis {
double local_volume = 0.0; double local_volume = 0.0;
mapping::GridFunctionMappingEvaluator mapping_evaluator( mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
*fem.compactificationCoordinate
); );
for (int e = 0; e < mesh.GetNE(); ++e) { for (int e = 0; e < mesh.GetNE(); ++e) {
const int attr = mesh.GetAttribute(e); const int attr = mesh.GetAttribute(e);
const bool selected = const bool selected = domain == utils::DOMAINS::ALL ||
domain == utils::DOMAINS::ALL || (domain == utils::DOMAINS::STELLAR &&
(domain == utils::DOMAINS::STELLAR && DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(attr)) ||
DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(attr)) || (domain == utils::DOMAINS::VACUUM &&
(domain == utils::DOMAINS::VACUUM && DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr));
DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr));
if (!selected) if (!selected)
continue; continue;
mfem::ElementTransformation *T = mesh.GetElementTransformation(e); mfem::ElementTransformation *T = mesh.GetElementTransformation(e);
@@ -266,8 +261,7 @@ namespace mean_field::analysis {
if (physical) { if (physical) {
mapping::VolumeMappingContext context; mapping::VolumeMappingContext context;
MFEM_VERIFY( MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*T, ip, context) == mapping_evaluator.EvaluateVolume(*T, ip, context) == mapping::MappingStatus::valid,
mapping::MappingStatus::valid,
"Mesh-volume integration encountered an invalid mapping." "Mesh-volume integration encountered an invalid mapping."
); );
dV = context.quadrature.weight; dV = context.quadrature.weight;

View File

@@ -21,361 +21,322 @@ import :utils.misc;
import :utils.user; import :utils.user;
namespace mean_field::fem { namespace mean_field::fem {
FEM setup_fem(const std::string &filename, const utils::Args &args, FEM setup_fem(
const int extraRefine) { const std::string &filename,
FEM fem; const utils::Args &args,
const int extraRefine
) {
FEM fem;
using GravityPotential = field::Gravity::Potential; using GravityPotential = field::Gravity::Potential;
using GravityFlux = field::Gravity::Flux; using GravityFlux = field::Gravity::Flux;
using DisplacementVector = field::Displacement::Vector; using DisplacementVector = field::Displacement::Vector;
using DensityScalar = field::Density::Scalar; using DensityScalar = field::Density::Scalar;
using EnthalpyScalar = field::Enthalpy::Scalar; using EnthalpyScalar = field::Enthalpy::Scalar;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
// ===================================================================== // =====================================================================
// Section 1: Mesh construction // Section 1: Mesh construction
// ===================================================================== // =====================================================================
fem.smesh = stroid::IO::LoadStroidMesh(filename).value(); fem.smesh = stroid::IO::LoadStroidMesh(filename).value();
if (extraRefine > 0) { if (extraRefine > 0) {
stroid::refinement::UniformRefinement(fem.smesh, extraRefine); stroid::refinement::UniformRefinement(fem.smesh, extraRefine);
} }
int mpiSize = 1; int mpiSize = 1;
MPI_Comm_size(MPI_COMM_WORLD, &mpiSize); MPI_Comm_size(MPI_COMM_WORLD, &mpiSize);
const std::unique_ptr<int[]> meshPartitioning( const std::unique_ptr<int[]> meshPartitioning(fem.smesh.mesh->GeneratePartitioning(mpiSize, 1));
fem.smesh.mesh->GeneratePartitioning(mpiSize, 1));
fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh, fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh, meshPartitioning.get(), 1);
meshPartitioning.get(), 1);
fem.mesh->EnsureNodes(); fem.mesh->EnsureNodes();
// ===================================================================== // =====================================================================
// Section 2: Exterior compactification coordinate // Section 2: Exterior compactification coordinate
// ===================================================================== // =====================================================================
if (fem.smesh.exterior_coordinate == nullptr) { if (fem.smesh.exterior_coordinate == nullptr) {
throw std::runtime_error("Exterior coordinate not set."); throw std::runtime_error("Exterior coordinate not set.");
} }
if (fem.smesh.exterior_coordinate->space == nullptr) { if (fem.smesh.exterior_coordinate->space == nullptr) {
throw std::runtime_error("Space for exterior coordinate not set."); throw std::runtime_error("Space for exterior coordinate not set.");
} }
if (fem.smesh.exterior_coordinate->values == nullptr) { if (fem.smesh.exterior_coordinate->values == nullptr) {
throw std::runtime_error("Values for exterior coordinate not set."); throw std::runtime_error("Values for exterior coordinate not set.");
} }
const mfem::FiniteElementSpace &serialCoordinateSpace = const mfem::FiniteElementSpace &serialCoordinateSpace = *fem.smesh.exterior_coordinate->space;
*fem.smesh.exterior_coordinate->space;
const mfem::GridFunction &serialCoordinate = const mfem::GridFunction &serialCoordinate = *fem.smesh.exterior_coordinate->values;
*fem.smesh.exterior_coordinate->values;
if (serialCoordinate.FESpace() != &serialCoordinateSpace) { if (serialCoordinate.FESpace() != &serialCoordinateSpace) {
throw std::runtime_error( throw std::runtime_error(
"Exterior coordinate values are not associated with the " "Exterior coordinate values are not associated with the "
"supplied finite-element space."); "supplied finite-element space."
} );
}
if (serialCoordinateSpace.GetMesh() != fem.smesh.mesh.get()) { if (serialCoordinateSpace.GetMesh() != fem.smesh.mesh.get()) {
throw std::runtime_error( throw std::runtime_error(
"Exterior coordinate space is not associated with the " "Exterior coordinate space is not associated with the "
"loaded STROID mesh."); "loaded STROID mesh."
} );
}
if (serialCoordinateSpace.GetVDim() != 1) { if (serialCoordinateSpace.GetVDim() != 1) {
throw std::runtime_error("Exterior coordinate must be a scalar field."); throw std::runtime_error("Exterior coordinate must be a scalar field.");
} }
if (serialCoordinate.Size() != serialCoordinateSpace.GetVSize()) { if (serialCoordinate.Size() != serialCoordinateSpace.GetVSize()) {
throw std::runtime_error( throw std::runtime_error(
"Exterior coordinate value count does not match its " "Exterior coordinate value count does not match its "
"finite-element space."); "finite-element space."
} );
}
const int compactificationOrder = serialCoordinateSpace.GetMaxElementOrder(); const int compactificationOrder = serialCoordinateSpace.GetMaxElementOrder();
const int dimension = fem.mesh->Dimension(); const int dimension = fem.mesh->Dimension();
fem.compactificationFec = fem.compactificationFec = std::make_unique<mfem::H1_FECollection>(compactificationOrder, dimension);
std::make_unique<mfem::H1_FECollection>(compactificationOrder, dimension);
fem.compactificationFes = std::make_unique<mfem::ParFiniteElementSpace>( fem.compactificationFes =
fem.mesh.get(), fem.compactificationFec.get()); std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.compactificationFec.get());
mfem::ParGridFunction distributedCoordinate(fem.mesh.get(), &serialCoordinate, mfem::ParGridFunction distributedCoordinate(fem.mesh.get(), &serialCoordinate, meshPartitioning.get());
meshPartitioning.get());
if (distributedCoordinate.Size() != fem.compactificationFes->GetVSize()) { if (distributedCoordinate.Size() != fem.compactificationFes->GetVSize()) {
throw std::runtime_error( throw std::runtime_error(
"Distributed exterior coordinate does not match the " "Distributed exterior coordinate does not match the "
"constructed parallel finite-element space."); "constructed parallel finite-element space."
} );
}
fem.compactificationCoordinate = fem.compactificationCoordinate = std::make_unique<mfem::ParGridFunction>(fem.compactificationFes.get());
std::make_unique<mfem::ParGridFunction>(fem.compactificationFes.get());
*fem.compactificationCoordinate = distributedCoordinate; *fem.compactificationCoordinate = distributedCoordinate;
double localMinimum = std::numeric_limits<double>::infinity(); double localMinimum = std::numeric_limits<double>::infinity();
double localMaximum = -std::numeric_limits<double>::infinity(); double localMaximum = -std::numeric_limits<double>::infinity();
for (int index = 0; index < fem.compactificationCoordinate->Size(); ++index) { for (int index = 0; index < fem.compactificationCoordinate->Size(); ++index) {
const double value = (*fem.compactificationCoordinate)(index); const double value = (*fem.compactificationCoordinate)(index);
if (!std::isfinite(value)) { if (!std::isfinite(value)) {
throw std::runtime_error( throw std::runtime_error("Exterior coordinate contains a non-finite value.");
"Exterior coordinate contains a non-finite value."); }
localMinimum = std::min(localMinimum, value);
localMaximum = std::max(localMaximum, value);
}
double globalMinimum = 0.0;
double globalMaximum = 0.0;
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD);
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
constexpr double coordinateTolerance = 1.0e-12;
if (globalMinimum < -coordinateTolerance || globalMaximum > 1.0 + coordinateTolerance) {
throw std::runtime_error(
"Exterior coordinate lies outside the expected "
"interval [0, 1]."
);
}
// =====================================================================
// Section 3: Compile-time field realization
// =====================================================================
// ---------------------------------------------------------------------
// Gravity potential: scalar L2
// ---------------------------------------------------------------------
fem.gravityPotentialFec = GravityField::make_fec<GravityPotential>(dimension);
fem.gravityPotentialFes = GravityField::make_fespace<GravityPotential>(*fem.mesh, *fem.gravityPotentialFec);
// ---------------------------------------------------------------------
// Gravity flux: H(div)/RT. Basis choices are encoded by field.mfem.
// ---------------------------------------------------------------------
fem.gravityFluxFec = GravityField::make_fec<GravityFlux>(dimension);
fem.gravityFluxFes = GravityField::make_fespace<GravityFlux>(*fem.mesh, *fem.gravityFluxFec);
// ---------------------------------------------------------------------
// Displacement: vector H1. Ordering is encoded by field.mfem.
// ---------------------------------------------------------------------
fem.displacementFec = DisplacementField::make_fec<DisplacementVector>(dimension);
fem.displacementFes = DisplacementField::make_fespace<DisplacementVector>(*fem.mesh, *fem.displacementFec);
fem.displacement = std::make_unique<mfem::ParGridFunction>(fem.displacementFes.get());
*fem.displacement = 0.0;
// ---------------------------------------------------------------------
// Density: scalar discontinuous L2
// ---------------------------------------------------------------------
fem.densityFec = DensityField::make_fec<DensityScalar>(dimension);
fem.densityFes = DensityField::make_fespace<DensityScalar>(*fem.mesh, *fem.densityFec);
// ---------------------------------------------------------------------
// Specific enthalpy: scalar continuous H1
// ---------------------------------------------------------------------
fem.enthalpyFec = EnthalpyField::make_fec<EnthalpyScalar>(dimension);
fem.enthalpyFes = EnthalpyField::make_fespace<EnthalpyScalar>(*fem.mesh, *fem.enthalpyFec);
// =====================================================================
// Section 4: Multipole data
// =====================================================================
fem.com.SetSize(dimension);
fem.com = 0.0;
fem.Q.SetSize(dimension, dimension);
fem.Q = 0.0;
// =====================================================================
// Section 5: Boundary markers
// =====================================================================
const int boundaryAttributeCount = fem.mesh->bdr_attributes.Max();
fem.boundaryContext.inf_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.stellar_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.inf_bounds = 0;
fem.boundaryContext.stellar_bounds = 0;
fem.boundaryContext.inf_bounds[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
fem.boundaryContext.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1;
// =====================================================================
// Section 7: Quadrature policy
// =====================================================================
const quadrature::QuadratureOptions &quadratureOptions = args.quadrature;
if (quadratureOptions.validation.reject_negative_boosts && quadratureOptions.global_boost < 0) {
throw std::invalid_argument("Global quadrature boost cannot be negative.");
}
quadrature::RuleSet quadratureRuleSet =
quadrature::make_rule_set(quadratureOptions.mode, quadratureOptions.global_boost);
if (quadratureOptions.fallback_fixed_order.has_value()) {
if (*quadratureOptions.fallback_fixed_order < 0) {
throw std::invalid_argument("Fallback quadrature order cannot be negative.");
}
quadratureRuleSet.fallback.fixed_order = quadratureOptions.fallback_fixed_order;
}
auto apply_quadrature_options = [&quadratureOptions](
quadrature::RuleControl &ruleControl,
const quadrature::QuadratureTermOptions &termOptions
) {
if (termOptions.fixed_order.has_value() && *termOptions.fixed_order < 0) {
throw std::invalid_argument("Fixed quadrature order cannot be negative.");
}
if (quadratureOptions.validation.reject_negative_boosts && termOptions.additional_boost < 0) {
throw std::invalid_argument("Term quadrature boost cannot be negative.");
}
ruleControl.boost += termOptions.additional_boost;
if (termOptions.fixed_order.has_value()) {
ruleControl.fixed_order = termOptions.fixed_order;
}
};
apply_quadrature_options(quadratureRuleSet.gravity_hdiv_mass, quadratureOptions.gravity_hdiv_mass);
apply_quadrature_options(quadratureRuleSet.gravity_divergence, quadratureOptions.gravity_divergence);
apply_quadrature_options(quadratureRuleSet.gravity_source, quadratureOptions.gravity_source);
apply_quadrature_options(quadratureRuleSet.gravity_force, quadratureOptions.gravity_force);
apply_quadrature_options(quadratureRuleSet.gravity_boundary, quadratureOptions.gravity_boundary);
apply_quadrature_options(quadratureRuleSet.centrifugal, quadratureOptions.centrifugal);
apply_quadrature_options(quadratureRuleSet.density_projection, quadratureOptions.density_projection);
apply_quadrature_options(quadratureRuleSet.eos_closure, quadratureOptions.eos_closure);
apply_quadrature_options(quadratureRuleSet.hydrostatic_equilibrium, quadratureOptions.hydrostatic_equilibrium);
apply_quadrature_options(quadratureRuleSet.isobaric_surface, quadratureOptions.isobaric_surface);
apply_quadrature_options(quadratureRuleSet.mesh_extension, quadratureOptions.mesh_extension);
apply_quadrature_options(quadratureRuleSet.mass_conservation, quadratureOptions.mass_conservation);
apply_quadrature_options(quadratureRuleSet.mass_normalization, quadratureOptions.mass_normalization);
apply_quadrature_options(quadratureRuleSet.center_of_mass, quadratureOptions.center_of_mass);
apply_quadrature_options(quadratureRuleSet.quadrupole, quadratureOptions.quadrupole);
apply_quadrature_options(quadratureRuleSet.gravitational_energy, quadratureOptions.gravitational_energy);
apply_quadrature_options(quadratureRuleSet.pressure_integral, quadratureOptions.pressure_integral);
apply_quadrature_options(quadratureRuleSet.pressure_force, quadratureOptions.pressure_force);
apply_quadrature_options(quadratureRuleSet.virial, quadratureOptions.virial);
apply_quadrature_options(quadratureRuleSet.error_norm, quadratureOptions.error_norm);
apply_quadrature_options(quadratureRuleSet.roles.discretization, quadratureOptions.roles.discretization);
apply_quadrature_options(quadratureRuleSet.roles.preconditioner, quadratureOptions.roles.preconditioner);
apply_quadrature_options(quadratureRuleSet.roles.diagnostic, quadratureOptions.roles.diagnostic);
apply_quadrature_options(quadratureRuleSet.roles.projection, quadratureOptions.roles.projection);
fem.quadratureFactory =
std::make_unique<quadrature::RuleFactory>(quadrature::Policy(std::move(quadratureRuleSet)));
// =====================================================================
// Section 11: Stateless domain mapper
// =====================================================================
auto exteriorDomain =
std::make_unique<const mapping::compactification::KelvinCompactification>(args.kelvin_options);
MFEM_VERIFY(
args.domain_mapper_options.vacuum_element_attribute ==
DomainSchema::template material_attribute<utils::domain::Vacuum>(),
"The domain-mapper compactification attribute must match the vacuum "
"material registered by the "
"production domain schema."
);
fem.domainMapperStateless =
std::make_unique<mapping::DomainMapper>(args.domain_mapper_options, std::move(exteriorDomain));
return fem;
} }
localMinimum = std::min(localMinimum, value);
localMaximum = std::max(localMaximum, value);
}
double globalMinimum = 0.0;
double globalMaximum = 0.0;
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN,
MPI_COMM_WORLD);
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX,
MPI_COMM_WORLD);
constexpr double coordinateTolerance = 1.0e-12;
if (globalMinimum < -coordinateTolerance ||
globalMaximum > 1.0 + coordinateTolerance) {
throw std::runtime_error("Exterior coordinate lies outside the expected "
"interval [0, 1].");
}
// =====================================================================
// Section 3: Compile-time field realization
// =====================================================================
// ---------------------------------------------------------------------
// Gravity potential: scalar L2
// ---------------------------------------------------------------------
fem.gravityPotentialFec = GravityField::make_fec<GravityPotential>(dimension);
fem.gravityPotentialFes = GravityField::make_fespace<GravityPotential>(
*fem.mesh, *fem.gravityPotentialFec);
// ---------------------------------------------------------------------
// Gravity flux: H(div)/RT. Basis choices are encoded by field.mfem.
// ---------------------------------------------------------------------
fem.gravityFluxFec = GravityField::make_fec<GravityFlux>(dimension);
fem.gravityFluxFes =
GravityField::make_fespace<GravityFlux>(*fem.mesh, *fem.gravityFluxFec);
// ---------------------------------------------------------------------
// Displacement: vector H1. Ordering is encoded by field.mfem.
// ---------------------------------------------------------------------
fem.displacementFec =
DisplacementField::make_fec<DisplacementVector>(dimension);
fem.displacementFes = DisplacementField::make_fespace<DisplacementVector>(
*fem.mesh, *fem.displacementFec);
fem.displacement =
std::make_unique<mfem::ParGridFunction>(fem.displacementFes.get());
*fem.displacement = 0.0;
// ---------------------------------------------------------------------
// Density: scalar discontinuous L2
// ---------------------------------------------------------------------
fem.densityFec = DensityField::make_fec<DensityScalar>(dimension);
fem.densityFes =
DensityField::make_fespace<DensityScalar>(*fem.mesh, *fem.densityFec);
// ---------------------------------------------------------------------
// Specific enthalpy: scalar continuous H1
// ---------------------------------------------------------------------
fem.enthalpyFec = EnthalpyField::make_fec<EnthalpyScalar>(dimension);
fem.enthalpyFes =
EnthalpyField::make_fespace<EnthalpyScalar>(*fem.mesh, *fem.enthalpyFec);
// =====================================================================
// Section 4: Multipole data
// =====================================================================
fem.com.SetSize(dimension);
fem.com = 0.0;
fem.Q.SetSize(dimension, dimension);
fem.Q = 0.0;
// =====================================================================
// Section 5: Boundary markers
// =====================================================================
const int boundaryAttributeCount = fem.mesh->bdr_attributes.Max();
fem.boundaryContext.inf_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.stellar_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.inf_bounds = 0;
fem.boundaryContext.stellar_bounds = 0;
fem.boundaryContext
.inf_bounds[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
fem.boundaryContext
.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) -
1] = 1;
// =====================================================================
// Section 7: Quadrature policy
// =====================================================================
const quadrature::QuadratureOptions &quadratureOptions = args.quadrature;
if (quadratureOptions.validation.reject_negative_boosts &&
quadratureOptions.global_boost < 0) {
throw std::invalid_argument("Global quadrature boost cannot be negative.");
}
quadrature::RuleSet quadratureRuleSet = quadrature::make_rule_set(
quadratureOptions.mode, quadratureOptions.global_boost);
if (quadratureOptions.fallback_fixed_order.has_value()) {
if (*quadratureOptions.fallback_fixed_order < 0) {
throw std::invalid_argument(
"Fallback quadrature order cannot be negative.");
}
quadratureRuleSet.fallback.fixed_order =
quadratureOptions.fallback_fixed_order;
}
auto apply_quadrature_options = [&quadratureOptions](
quadrature::RuleControl &ruleControl,
const quadrature::QuadratureTermOptions
&termOptions) {
if (termOptions.fixed_order.has_value() && *termOptions.fixed_order < 0) {
throw std::invalid_argument("Fixed quadrature order cannot be negative.");
}
if (quadratureOptions.validation.reject_negative_boosts &&
termOptions.additional_boost < 0) {
throw std::invalid_argument("Term quadrature boost cannot be negative.");
}
ruleControl.boost += termOptions.additional_boost;
if (termOptions.fixed_order.has_value()) {
ruleControl.fixed_order = termOptions.fixed_order;
}
};
apply_quadrature_options(quadratureRuleSet.gravity_hdiv_mass,
quadratureOptions.gravity_hdiv_mass);
apply_quadrature_options(quadratureRuleSet.gravity_divergence,
quadratureOptions.gravity_divergence);
apply_quadrature_options(quadratureRuleSet.gravity_source,
quadratureOptions.gravity_source);
apply_quadrature_options(quadratureRuleSet.gravity_force,
quadratureOptions.gravity_force);
apply_quadrature_options(quadratureRuleSet.gravity_boundary,
quadratureOptions.gravity_boundary);
apply_quadrature_options(quadratureRuleSet.centrifugal,
quadratureOptions.centrifugal);
apply_quadrature_options(quadratureRuleSet.density_projection,
quadratureOptions.density_projection);
apply_quadrature_options(quadratureRuleSet.eos_closure,
quadratureOptions.eos_closure);
apply_quadrature_options(quadratureRuleSet.hydrostatic_equilibrium,
quadratureOptions.hydrostatic_equilibrium);
apply_quadrature_options(quadratureRuleSet.isobaric_surface,
quadratureOptions.isobaric_surface);
apply_quadrature_options(quadratureRuleSet.mesh_extension,
quadratureOptions.mesh_extension);
apply_quadrature_options(quadratureRuleSet.mass_conservation,
quadratureOptions.mass_conservation);
apply_quadrature_options(quadratureRuleSet.mass_normalization,
quadratureOptions.mass_normalization);
apply_quadrature_options(quadratureRuleSet.center_of_mass,
quadratureOptions.center_of_mass);
apply_quadrature_options(quadratureRuleSet.quadrupole,
quadratureOptions.quadrupole);
apply_quadrature_options(quadratureRuleSet.gravitational_energy,
quadratureOptions.gravitational_energy);
apply_quadrature_options(quadratureRuleSet.pressure_integral,
quadratureOptions.pressure_integral);
apply_quadrature_options(quadratureRuleSet.pressure_force,
quadratureOptions.pressure_force);
apply_quadrature_options(quadratureRuleSet.virial, quadratureOptions.virial);
apply_quadrature_options(quadratureRuleSet.error_norm,
quadratureOptions.error_norm);
apply_quadrature_options(quadratureRuleSet.roles.discretization,
quadratureOptions.roles.discretization);
apply_quadrature_options(quadratureRuleSet.roles.preconditioner,
quadratureOptions.roles.preconditioner);
apply_quadrature_options(quadratureRuleSet.roles.diagnostic,
quadratureOptions.roles.diagnostic);
apply_quadrature_options(quadratureRuleSet.roles.projection,
quadratureOptions.roles.projection);
fem.quadratureFactory = std::make_unique<quadrature::RuleFactory>(
quadrature::Policy(std::move(quadratureRuleSet)));
// =====================================================================
// Section 11: Stateless domain mapper
// =====================================================================
auto exteriorDomain =
std::make_unique<const mapping::compactification::KelvinCompactification>(
args.kelvin_options);
MFEM_VERIFY(
args.domain_mapper_options.vacuum_element_attribute ==
DomainSchema::template material_attribute<utils::domain::Vacuum>(),
"The domain-mapper compactification attribute must match the vacuum "
"material registered by the "
"production domain schema.");
fem.domainMapperStateless = std::make_unique<mapping::DomainMapper>(
args.domain_mapper_options, std::move(exteriorDomain));
return fem;
}
} // namespace mean_field::fem } // namespace mean_field::fem

View File

@@ -9,7 +9,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &displacement, const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate const mfem::GridFunction &compactification_coordinate
) )
: m_mapping(mapper, displacement, compactification_coordinate) { : m_mapping(
mapper,
displacement,
compactification_coordinate
) {
} }
void AdvectionIntegrator::AssembleElementVector( void AdvectionIntegrator::AssembleElementVector(

View File

@@ -9,7 +9,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &compactification_coordinate, const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega const mfem::Vector &omega
) )
: m_mapping(mapper, displacement, compactification_coordinate), : m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_omega(3) { m_omega(3) {
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D"); MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
m_omega = omega; m_omega = omega;

View File

@@ -10,7 +10,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &compactification_coordinate, const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega const mfem::Vector &omega
) )
: m_mapping(mapper, displacement, compactification_coordinate), : m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_omega(omega) { m_omega(omega) {
m_omega_mat.SetSize(3, 3); m_omega_mat.SetSize(3, 3);
m_omega_mat = 0.0; m_omega_mat = 0.0;

View File

@@ -19,7 +19,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &compactification_coordinate, const mfem::GridFunction &compactification_coordinate,
const GravityForceJacobianMode jacobian_mode const GravityForceJacobianMode jacobian_mode
) )
: m_mapping(mapper, displacement, compactification_coordinate), : m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_jacobian_mode(jacobian_mode) { m_jacobian_mode(jacobian_mode) {
} }

View File

@@ -9,7 +9,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &displacement, const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate const mfem::GridFunction &compactification_coordinate
) )
: m_mapping(mapper, displacement, compactification_coordinate) { }; : m_mapping(
mapper,
displacement,
compactification_coordinate
) { };
void ContinuityVolumeIntegrator::AssembleElementVector( void ContinuityVolumeIntegrator::AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el, const mfem::Array<const mfem::FiniteElement *> &el,
@@ -174,7 +178,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &displacement, const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate const mfem::GridFunction &compactification_coordinate
) )
: m_mapping(mapper, displacement, compactification_coordinate) { : m_mapping(
mapper,
displacement,
compactification_coordinate
) {
} }
void ContinuityFaceIntegrator::AssembleFaceVector( void ContinuityFaceIntegrator::AssembleFaceVector(
@@ -206,11 +214,11 @@ namespace mean_field::integrators {
} }
mfem::Vector &r_rho = *elvect[1]; mfem::Vector &r_rho = *elvect[1];
r_rho.SetSize(dof_rho_minus + dof_rho_plus); r_rho.SetSize(dof_rho_minus + dof_rho_plus);
r_rho = 0.0; r_rho = 0.0;
const int attr_minus = Tr.Elem1->Attribute; const int attr_minus = Tr.Elem1->Attribute;
const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1; const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
if (DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_minus) || if (DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_minus) ||
DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_plus)) { DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_plus)) {
return; // No flux contribution for vacuum faces return; // No flux contribution for vacuum faces
@@ -416,9 +424,9 @@ namespace mean_field::integrators {
} }
bool ContinuityFaceIntegrator::skip_face(const mfem::FaceElementTransformations &Tr) { bool ContinuityFaceIntegrator::skip_face(const mfem::FaceElementTransformations &Tr) {
const int attr_minus = Tr.Elem1->Attribute; const int attr_minus = Tr.Elem1->Attribute;
const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1; const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
if (DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_minus) || if (DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_minus) ||
DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_plus)) { DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_plus)) {
return true; // No flux contribution for vacuum faces return true; // No flux contribution for vacuum faces

View File

@@ -10,7 +10,11 @@ namespace mean_field::integrators {
const double mu, const double mu,
const int quad_boost const int quad_boost
) )
: m_mapping(mapper, displacement, compactification_coordinate), : m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_mu(mu), m_mu(mu),
m_quad_boost(quad_boost) { m_quad_boost(quad_boost) {
} }

View File

@@ -15,7 +15,11 @@ namespace mean_field::mapping {
Coefficient &coeff, Coefficient &coeff,
const COORDINATE_SPACE coord_space const COORDINATE_SPACE coord_space
) )
: m_mapping(mapper, displacement, compactification_coordinate), : m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_coeff(coeff), m_coeff(coeff),
m_coord_space(coord_space) { }; m_coord_space(coord_space) { };
@@ -28,7 +32,7 @@ namespace mean_field::mapping {
switch (m_coord_space) { switch (m_coord_space) {
case COORDINATE_SPACE::PHYSICAL: { case COORDINATE_SPACE::PHYSICAL: {
f_val = eval_at_point(m_coeff, T, ip); f_val = eval_at_point(m_coeff, T, ip);
VolumeMappingContext context; VolumeMappingContext context;
MFEM_VERIFY( MFEM_VERIFY(
m_mapping.EvaluateVolume(T, ip, context) == MappingStatus::valid, m_mapping.EvaluateVolume(T, ip, context) == MappingStatus::valid,
@@ -63,7 +67,11 @@ namespace mean_field::mapping {
const int dim const int dim
) )
: MatrixCoefficient(dim), : MatrixCoefficient(dim),
m_mapping(mapper, displacement, compactification_coordinate), m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_scalar(&sigma), m_scalar(&sigma),
m_tensor(nullptr) { }; m_tensor(nullptr) { };
@@ -74,7 +82,11 @@ namespace mean_field::mapping {
MatrixCoefficient &sigma MatrixCoefficient &sigma
) )
: MatrixCoefficient(sigma.GetHeight()), : MatrixCoefficient(sigma.GetHeight()),
m_mapping(mapper, displacement, compactification_coordinate), m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_scalar(nullptr), m_scalar(nullptr),
m_tensor(&sigma) { }; m_tensor(&sigma) { };
@@ -92,7 +104,7 @@ namespace mean_field::mapping {
"Mapped diffusion coefficient encountered an invalid mapping." "Mapped diffusion coefficient encountered an invalid mapping."
); );
const mfem::DenseMatrix &JInv = context.mapping.inverse_mapping_jacobian; const mfem::DenseMatrix &JInv = context.mapping.inverse_mapping_jacobian;
const double detJ = context.mapping.mapping_determinant; const double detJ = context.mapping.mapping_determinant;
if (m_scalar) { if (m_scalar) {
const double sig_val = m_scalar->Eval(T, ip); const double sig_val = m_scalar->Eval(T, ip);
@@ -120,7 +132,11 @@ namespace mean_field::mapping {
VectorCoefficient &coeff VectorCoefficient &coeff
) )
: VectorCoefficient(coeff.GetVDim()), : VectorCoefficient(coeff.GetVDim()),
m_mapping(mapper, displacement, compactification_coordinate), m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_coeff(coeff) { }; m_coeff(coeff) { };
void MappedVectorCoefficient::Eval( void MappedVectorCoefficient::Eval(
@@ -137,7 +153,7 @@ namespace mean_field::mapping {
"Mapped vector coefficient encountered an invalid mapping." "Mapped vector coefficient encountered an invalid mapping."
); );
const mfem::DenseMatrix &JInv = context.mapping.inverse_mapping_jacobian; const mfem::DenseMatrix &JInv = context.mapping.inverse_mapping_jacobian;
const double detJ = context.mapping.mapping_determinant; const double detJ = context.mapping.mapping_determinant;
mfem::Vector C_phys(dim); mfem::Vector C_phys(dim);
m_coeff.Eval(C_phys, T, ip); m_coeff.Eval(C_phys, T, ip);
@@ -157,7 +173,11 @@ namespace mean_field::mapping {
Func f // std::function<double(const mfem::Vector&)> Func f // std::function<double(const mfem::Vector&)>
) )
: m_f(std::move(f)), : m_f(std::move(f)),
m_mapping(mapper, displacement, compactification_coordinate) { }; m_mapping(
mapper,
displacement,
compactification_coordinate
) { };
double PhysicalPositionFunctionCoefficient::Eval( double PhysicalPositionFunctionCoefficient::Eval(
mfem::ElementTransformation &T, mfem::ElementTransformation &T,
@@ -179,7 +199,11 @@ namespace mean_field::mapping {
const int dim const int dim
) )
: MatrixCoefficient(dim), : MatrixCoefficient(dim),
m_mapping(mapper, displacement, compactification_coordinate) { m_mapping(
mapper,
displacement,
compactification_coordinate
) {
} }
void MappedHDivMassCoefficient::Eval( void MappedHDivMassCoefficient::Eval(
@@ -195,7 +219,7 @@ namespace mean_field::mapping {
"Mapped H(div) coefficient encountered an invalid mapping." "Mapped H(div) coefficient encountered an invalid mapping."
); );
const mfem::DenseMatrix &map_jacobian = context.mapping.mapping_jacobian; const mfem::DenseMatrix &map_jacobian = context.mapping.mapping_jacobian;
const double map_determinant = context.mapping.mapping_determinant; const double map_determinant = context.mapping.mapping_determinant;
MFEM_VERIFY(map_determinant > 0.0, "Domain mapping has a non-positive Jacobian determinant."); MFEM_VERIFY(map_determinant > 0.0, "Domain mapping has a non-positive Jacobian determinant.");

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ namespace mean_field::models::structure {
validate(); validate();
} }
const eos::EquationOfState &PolytropicStructure::equationOfState() const noexcept { const eos::Polytrope &PolytropicStructure::equationOfState() const noexcept {
return m_equationOfState; return m_equationOfState;
} }
@@ -29,7 +29,9 @@ namespace mean_field::models::structure {
const double polytropicIndex = m_equationOfState.polytropic_index(); const double polytropicIndex = m_equationOfState.polytropic_index();
const std::vector<LaneEmdenPoint> laneEmdenSolution = solveLaneEmden(polytropicIndex); const std::vector<LaneEmdenPoint> laneEmdenSolution = solveLaneEmden(polytropicIndex);
const double surfaceCoordinate = laneEmdenSolution.back().coordinate; const double surfaceCoordinate = laneEmdenSolution.back().coordinate;
const double centralEnthalpy = m_equationOfState.enthalpy_from_density(request.centralDensity); const double centralEnthalpy =
eos::evaluate<eos::quantity::SpecificEnthalpy>(m_equationOfState, eos::DensityValue{request.centralDensity})
.value();
const double radialScaleSquared = const double radialScaleSquared =
centralEnthalpy / (4.0 * std::numbers::pi_v<double> * mean_field::utils::G * request.centralDensity); centralEnthalpy / (4.0 * std::numbers::pi_v<double> * mean_field::utils::G * request.centralDensity);
@@ -61,11 +63,12 @@ namespace mean_field::models::structure {
const double dimensionlessRadius = sampleFraction * surfaceCoordinate; const double dimensionlessRadius = sampleFraction * surfaceCoordinate;
const double laneEmdenValue = const double laneEmdenValue =
interpolateLaneEmdenValue(laneEmdenSolution, dimensionlessRadius, interpolationIndex); interpolateLaneEmdenValue(laneEmdenSolution, dimensionlessRadius, interpolationIndex);
const double density = request.centralDensity * std::pow(laneEmdenValue, polytropicIndex); const double density = request.centralDensity * std::pow(laneEmdenValue, polytropicIndex);
seed.radius(sampleIndex) = radialScale * dimensionlessRadius; seed.radius(sampleIndex) = radialScale * dimensionlessRadius;
seed.density(sampleIndex) = density; seed.density(sampleIndex) = density;
seed.enthalpy(sampleIndex) = m_equationOfState.enthalpy_from_density(density); seed.enthalpy(sampleIndex) =
eos::evaluate<eos::quantity::SpecificEnthalpy>(m_equationOfState, eos::DensityValue{density}).value();
} }
seed.radius(0) = 0.0; seed.radius(0) = 0.0;

View File

@@ -80,13 +80,19 @@ namespace mean_field::operators::context::hydrostatic {
: m_f(f), : m_f(f),
m_domainMapper(domainMapper), m_domainMapper(domainMapper),
m_enthalpyMap( m_enthalpyMap(
field::make_field_dof_map<field::Enthalpy, DomainSchema>(*f.enthalpyFes) field::make_field_dof_map<
field::Enthalpy,
DomainSchema>(*f.enthalpyFes)
), ),
m_gravityPotentialMap( m_gravityPotentialMap(
field::make_field_dof_map<field::Gravity, DomainSchema>(*f.gravityPotentialFes) field::make_field_dof_map<
field::Gravity,
DomainSchema>(*f.gravityPotentialFes)
), ),
m_displacementMap( m_displacementMap(
field::make_field_dof_map<field::Displacement, DomainSchema>(*f.displacementFes) field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
) { ) {
MFEM_VERIFY(m_f.mesh != nullptr, "HydrostaticEquilibriumContext requires a mesh."); MFEM_VERIFY(m_f.mesh != nullptr, "HydrostaticEquilibriumContext requires a mesh.");

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,8 @@ import :field.registry;
import :utils.domain; import :utils.domain;
namespace { namespace {
namespace eos = mean_field::eos;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using ClosureDomain = mean_field::field::FieldDomainT<mean_field::field::Density>; using ClosureDomain = mean_field::field::FieldDomainT<mean_field::field::Density>;
@@ -336,11 +338,21 @@ namespace {
if (closureAction == ClosureAction::residual) { if (closureAction == ClosureAction::residual) {
const double density = elementDensityInput * densityShape; const double density = elementDensityInput * densityShape;
integrand = density - barotrope.density_from_enthalpy(baseEnthalpy); const double equationOfStateDensity =
eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{baseEnthalpy})
.value();
integrand = density - equationOfStateDensity;
} else { } else {
const double enthalpyVariation = elementEnthalpyVariation * enthalpyShape; const double enthalpyVariation = elementEnthalpyVariation * enthalpyShape;
integrand = -barotrope.density_derivative_from_enthalpy(baseEnthalpy) * enthalpyVariation; const double densityDerivative =
eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{baseEnthalpy}
)
.value();
integrand = -densityDerivative * enthalpyVariation;
} }
} }
@@ -628,11 +640,14 @@ namespace mean_field::operators::kernels {
enthalpyElement.CalcShape(integrationPoint, enthalpyShape); enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
const double densityValue = elementBaseDensity * densityShape; const double densityValue = elementBaseDensity * densityShape;
const double enthalpyValue = elementBaseEnthalpy * enthalpyShape; const double enthalpyValue = elementBaseEnthalpy * enthalpyShape;
const double closureValue = densityValue - barotrope.density_from_enthalpy(enthalpyValue); const double equationOfStateDensity =
eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{enthalpyValue}).value();
const double closureValue = densityValue - equationOfStateDensity;
const double geometryActionValue = closureValue * mappingVariation.weight_variation; const double geometryActionValue = closureValue * mappingVariation.weight_variation;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -398,11 +398,17 @@ namespace mean_field::operators {
data.enthalpyBasis(quadraturePoint, enthalpyDof) = enthalpyShape(enthalpyDof); data.enthalpyBasis(quadraturePoint, enthalpyDof) = enthalpyShape(enthalpyDof);
} }
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;
const double eosDensity = m_equationOfState.density_from_enthalpy(enthalpy); const eos::SpecificEnthalpyValue specificEnthalpy{enthalpy};
const double enthalpyDerivative = m_equationOfState.density_derivative_from_enthalpy(enthalpy); const double eosDensity =
eos::evaluate<eos::quantity::Density>(m_equationOfState, specificEnthalpy).value();
const double enthalpyDerivative =
eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
m_equationOfState, specificEnthalpy
)
.value();
MFEM_VERIFY( MFEM_VERIFY(
std::isfinite(quadratureWeight) && quadratureWeight > 0.0 && std::isfinite(eosDensity) && std::isfinite(quadratureWeight) && quadratureWeight > 0.0 && std::isfinite(eosDensity) &&

View File

@@ -9,531 +9,535 @@ module mean_field;
import :operators.prepared_gravity_source; import :operators.prepared_gravity_source;
namespace { namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
int get_operator_height(const mean_field::fem::FEM &f) { int get_operator_height(const mean_field::fem::FEM &f) {
MFEM_VERIFY(f.gravityPotentialFes != nullptr, MFEM_VERIFY(
"PreparedMappedGravitySourceOperator requires the " f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the "
"gravity-potential " "gravity-potential "
"finite-element space."); "finite-element space."
return mean_field::field::make_field_dof_map<mean_field::field::Gravity, );
DomainSchema>( return mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes)
*f.gravityPotentialFes) .reduced_size();
.reduced_size(); }
}
int get_operator_width(const mean_field::fem::FEM &f) { int get_operator_width(const mean_field::fem::FEM &f) {
MFEM_VERIFY(f.densityFes != nullptr, MFEM_VERIFY(
"PreparedMappedGravitySourceOperator requires the density " f.densityFes != nullptr, "PreparedMappedGravitySourceOperator requires the density "
"finite-element space."); "finite-element space."
return mean_field::field::make_field_dof_map<mean_field::field::Density, );
DomainSchema>(*f.densityFes) return mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*f.densityFes)
.reduced_size(); .reduced_size();
} }
void true_to_local(const mfem::ParFiniteElementSpace &finite_element_space, void true_to_local(
const mfem::Vector &true_vector, const mfem::ParFiniteElementSpace &finite_element_space,
mfem::Vector &local_vector) { const mfem::Vector &true_vector,
local_vector.SetSize(finite_element_space.GetVSize()); mfem::Vector &local_vector
) {
local_vector.SetSize(finite_element_space.GetVSize());
const mfem::Operator *prolongation = const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) { if (prolongation != nullptr) {
prolongation->Mult(true_vector, local_vector); prolongation->Mult(true_vector, local_vector);
} else { } else {
local_vector = true_vector; local_vector = true_vector;
} }
} }
void local_to_true(const mfem::ParFiniteElementSpace &finite_element_space, void local_to_true(
const mfem::Vector &local_vector, const mfem::ParFiniteElementSpace &finite_element_space,
mfem::Vector &true_vector) { const mfem::Vector &local_vector,
MFEM_VERIFY(local_vector.Size() == finite_element_space.GetVSize(), mfem::Vector &true_vector
"Local vector has the wrong size."); ) {
MFEM_VERIFY(local_vector.Size() == finite_element_space.GetVSize(), "Local vector has the wrong size.");
true_vector.SetSize(finite_element_space.GetTrueVSize()); true_vector.SetSize(finite_element_space.GetTrueVSize());
true_vector = 0.0; true_vector = 0.0;
const mfem::Operator *prolongation = const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) { if (prolongation != nullptr) {
prolongation->MultTranspose(local_vector, true_vector); prolongation->MultTranspose(local_vector, true_vector);
} else { } else {
true_vector = local_vector; true_vector = local_vector;
} }
} }
const mfem::IntegrationRule & const mfem::IntegrationRule &get_source_rule(
get_source_rule(const mean_field::fem::FEM &f, const mean_field::fem::FEM &f,
const mfem::FiniteElement &density_element, const mfem::FiniteElement &density_element,
const mfem::FiniteElement &potential_element, const mfem::FiniteElement &potential_element,
const mfem::ElementTransformation &transformation) { const mfem::ElementTransformation &transformation
using GravityField = mean_field::field::Field<mean_field::field::Gravity>; ) {
MFEM_VERIFY(density_element.GetOrder() == using GravityField = mean_field::field::Field<mean_field::field::Gravity>;
mean_field::field::Density::Scalar::familyOrder, MFEM_VERIFY(
"The prepared source trial element does not match the registered " density_element.GetOrder() == mean_field::field::Density::Scalar::familyOrder,
"density field."); "The prepared source trial element does not match the registered "
MFEM_VERIFY(potential_element.GetOrder() == "density field."
mean_field::field::Gravity::Potential::familyOrder, );
"The prepared source test element does not match the registered " MFEM_VERIFY(
"gravity potential."); potential_element.GetOrder() == mean_field::field::Gravity::Potential::familyOrder,
const mean_field::quadrature::Query query = GravityField::make_query< "The prepared source test element does not match the registered "
mean_field::field::Gravity::Form::SourceProjection>( "gravity potential."
mean_field::quadrature::QuadratureRole::discretization, );
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR, const mean_field::quadrature::Query query =
mean_field::quadrature::MappingKind::general); GravityField::make_query<mean_field::field::Gravity::Form::SourceProjection>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
return *f.quadratureFactory->get(query, transformation.GetGeometryType()) return *f.quadratureFactory->get(query, transformation.GetGeometryType()).integration_rule;
.integration_rule; }
}
class FrozenMappedGravitySourceCoefficient final : public mfem::Coefficient { class FrozenMappedGravitySourceCoefficient final : public mfem::Coefficient {
public: public:
FrozenMappedGravitySourceCoefficient( FrozenMappedGravitySourceCoefficient(
const mean_field::fem::FEM &f, const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domain_mapper, const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::Vector &displacement_true) const mfem::Vector &displacement_true
: m_fem(f), m_domain_mapper(domain_mapper), )
m_workspace(domain_mapper.GetDimension()) { : m_fem(f),
true_to_local(*m_fem.displacementFes, displacement_true, m_domain_mapper(domain_mapper),
m_displacement_local); m_workspace(domain_mapper.GetDimension()) {
} true_to_local(*m_fem.displacementFes, displacement_true, m_displacement_local);
}
double Eval(mfem::ElementTransformation &transformation, double Eval(
const mfem::IntegrationPoint &integration_point) override { mfem::ElementTransformation &transformation,
transformation.SetIntPoint(&integration_point); const mfem::IntegrationPoint &integration_point
) override {
transformation.SetIntPoint(&integration_point);
const int element_id = transformation.ElementNo; const int element_id = transformation.ElementNo;
MFEM_VERIFY(element_id >= 0 && element_id < m_fem.mesh->GetNE(), MFEM_VERIFY(
element_id >= 0 && element_id < m_fem.mesh->GetNE(),
"Mapped gravity source coefficient received an invalid element " "Mapped gravity source coefficient received an invalid element "
"ID."); "ID."
if (DomainSchema::template attribute_belongs_to< );
mean_field::utils::domain::Vacuum>(transformation.Attribute)) { if (DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(
return 0.0; transformation.Attribute
} )) {
return 0.0;
}
LoadElement(element_id); LoadElement(element_id);
const mean_field::mapping::ElementMappingData mapping_data{ const mean_field::mapping::ElementMappingData mapping_data{
.displacement = *m_displacement_data, .displacement = *m_displacement_data, .compactification = *m_compactification_data
.compactification = *m_compactification_data}; };
mean_field::mapping::VolumeMappingContext mapping_context; mean_field::mapping::VolumeMappingContext mapping_context;
const mean_field::mapping::MappingStatus status = const mean_field::mapping::MappingStatus status = m_domain_mapper.EvaluateVolume(
m_domain_mapper.EvaluateVolume(mapping_data, transformation, mapping_data, transformation, integration_point, m_workspace, mapping_context
integration_point, m_workspace, );
mapping_context);
if (status != mean_field::mapping::MappingStatus::valid) { if (status != mean_field::mapping::MappingStatus::valid) {
const mfem::FiniteElement &displacement_element = const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id);
*m_fem.displacementFes->GetFE(element_id); const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*m_fem.compactificationFes->GetFE(element_id);
mfem::Vector displacement_shape(displacement_element.GetDof()); mfem::Vector displacement_shape(displacement_element.GetDof());
mfem::Vector compactification_shape(compactification_element.GetDof()); mfem::Vector compactification_shape(compactification_element.GetDof());
mfem::Vector reference_position(m_domain_mapper.GetDimension()); mfem::Vector reference_position(m_domain_mapper.GetDimension());
mfem::Vector displacement_value(m_domain_mapper.GetDimension()); mfem::Vector displacement_value(m_domain_mapper.GetDimension());
displacement_element.CalcShape(integration_point, displacement_shape); displacement_element.CalcShape(integration_point, displacement_shape);
compactification_element.CalcShape(integration_point, compactification_element.CalcShape(integration_point, compactification_shape);
compactification_shape); transformation.Transform(integration_point, reference_position);
transformation.Transform(integration_point, reference_position); m_displacement_data->GetDofMatrix().MultTranspose(displacement_shape, displacement_value);
m_displacement_data->GetDofMatrix().MultTranspose(displacement_shape,
displacement_value);
const double compactification_coordinate = const double compactification_coordinate = m_compactification_data->GetDofs() * compactification_shape;
m_compactification_data->GetDofs() * compactification_shape;
MFEM_ABORT( MFEM_ABORT(
"Stateless domain mapping failed while preparing the " "Stateless domain mapping failed while preparing the "
"gravity " "gravity "
"source operator." "source operator."
<< "\nMapping status = " << static_cast<int>(status) << "\nMapping status = " << static_cast<int>(status) << "\nElement ID = " << element_id
<< "\nElement ID = " << element_id << "\nElement attribute = " << transformation.Attribute
<< "\nElement attribute = " << transformation.Attribute << "\nIntegration-point index = " << integration_point.index << "\nIntegration point = <"
<< "\nIntegration-point index = " << integration_point.index << integration_point.x << ", " << integration_point.y << ", " << integration_point.z << ">"
<< "\nIntegration point = <" << integration_point.x << ", " << "\nReference position = <" << reference_position(0) << ", " << reference_position(1) << ", "
<< integration_point.y << ", " << integration_point.z << ">" << reference_position(2) << ">"
<< "\nReference position = <" << reference_position(0) << ", " << "\nReference radius = " << reference_position.Norml2() << "\nDisplacement value = <"
<< reference_position(1) << ", " << reference_position(2) << ">" << displacement_value(0) << ", " << displacement_value(1) << ", " << displacement_value(2) << ">"
<< "\nReference radius = " << reference_position.Norml2() << "\nDisplacement magnitude = " << displacement_value.Norml2()
<< "\nDisplacement value = <" << displacement_value(0) << ", " << "\nCompactification coordinate = " << compactification_coordinate
<< displacement_value(1) << ", " << displacement_value(2) << ">" << "\nDisplacement ordering = " << static_cast<int>(m_fem.displacementFes->GetOrdering())
<< "\nDisplacement magnitude = " << displacement_value.Norml2() );
<< "\nCompactification coordinate = " << compactification_coordinate }
<< "\nDisplacement ordering = " const double mapping_determinant = mapping_context.mapping.mapping_determinant;
<< static_cast<int>(m_fem.displacementFes->GetOrdering())); MFEM_VERIFY(
} std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
const double mapping_determinant =
mapping_context.mapping.mapping_determinant;
MFEM_VERIFY(std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
"Prepared gravity source operator encountered a non-positive " "Prepared gravity source operator encountered a non-positive "
"or " "or "
"non-finite mapping determinant."); "non-finite mapping determinant."
);
return 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant; return 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant;
} }
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) {
return; return;
} }
const mfem::FiniteElement &displacement_element = const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id);
*m_fem.displacementFes->GetFE(element_id); const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*m_fem.compactificationFes->GetFE(element_id);
mfem::DofTransformation *displacement_dof_transformation = mfem::DofTransformation *displacement_dof_transformation =
m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs); m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs);
mfem::DofTransformation *compactification_dof_transformation = mfem::DofTransformation *compactification_dof_transformation =
m_fem.compactificationFes->GetElementDofs(element_id, m_fem.compactificationFes->GetElementDofs(element_id, m_compactification_dofs);
m_compactification_dofs);
m_displacement_local.GetSubVector(m_displacement_dofs, m_displacement_local.GetSubVector(m_displacement_dofs, m_element_displacement);
m_element_displacement); m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs, m_element_compactification);
m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs,
m_element_compactification);
if (displacement_dof_transformation != nullptr) { if (displacement_dof_transformation != nullptr) {
displacement_dof_transformation->InvTransformPrimal( displacement_dof_transformation->InvTransformPrimal(m_element_displacement);
m_element_displacement); }
}
if (compactification_dof_transformation != nullptr) { if (compactification_dof_transformation != nullptr) {
compactification_dof_transformation->InvTransformPrimal( compactification_dof_transformation->InvTransformPrimal(m_element_compactification);
m_element_compactification); }
}
m_displacement_data = m_displacement_data = std::make_unique<mean_field::mapping::ElementDisplacementData>(
std::make_unique<mean_field::mapping::ElementDisplacementData>( mean_field::mapping::ElementDisplacementDataFromElementVDofs(
mean_field::mapping::ElementDisplacementDataFromElementVDofs( displacement_element, m_element_displacement
displacement_element, m_element_displacement)); )
);
m_compactification_data = m_compactification_data = std::make_unique<mean_field::mapping::ElementCompactificationData>(
std::make_unique<mean_field::mapping::ElementCompactificationData>( compactification_element, m_element_compactification
compactification_element, m_element_compactification); );
m_cached_element_id = element_id; m_cached_element_id = element_id;
} }
const mean_field::fem::FEM &m_fem; const mean_field::fem::FEM &m_fem;
const mean_field::mapping::DomainMapper &m_domain_mapper; const mean_field::mapping::DomainMapper &m_domain_mapper;
mfem::Vector m_displacement_local; mfem::Vector m_displacement_local;
mfem::Array<int> m_displacement_dofs; mfem::Array<int> m_displacement_dofs;
mfem::Array<int> m_compactification_dofs; mfem::Array<int> m_compactification_dofs;
mfem::Vector m_element_displacement; mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification; mfem::Vector m_element_compactification;
std::unique_ptr<mean_field::mapping::ElementDisplacementData> std::unique_ptr<mean_field::mapping::ElementDisplacementData> m_displacement_data;
m_displacement_data; std::unique_ptr<mean_field::mapping::ElementCompactificationData> m_compactification_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
m_compactification_data;
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};
}; };
} // namespace } // namespace
namespace mean_field::operators { namespace mean_field::operators {
PreparedMappedGravitySourceOperator::PreparedMappedGravitySourceOperator( PreparedMappedGravitySourceOperator::PreparedMappedGravitySourceOperator(
const fem::FEM &f, const mapping::DomainMapper &domain_mapper) const fem::FEM &f,
: Operator(get_operator_height(f), get_operator_width(f)), m_fem(f), const mapping::DomainMapper &domain_mapper
m_domain_mapper(domain_mapper), )
m_density_map(field::make_field_dof_map<field::Density, DomainSchema>( : Operator(
*f.densityFes)), get_operator_height(f),
m_potential_map(field::make_field_dof_map<field::Gravity, DomainSchema>( get_operator_width(f)
*f.gravityPotentialFes)), ),
m_displacement_map( m_fem(f),
field::make_field_dof_map<field::Displacement, DomainSchema>( m_domain_mapper(domain_mapper),
*f.displacementFes)) { m_density_map(
MFEM_VERIFY(f.mesh != nullptr, field::make_field_dof_map<
"PreparedMappedGravitySourceOperator requires a mesh."); field::Density,
MFEM_VERIFY(f.densityFes != nullptr, DomainSchema>(*f.densityFes)
"PreparedMappedGravitySourceOperator requires the density " ),
"finite-element space."); m_potential_map(
MFEM_VERIFY(f.gravityPotentialFes != nullptr, field::make_field_dof_map<
"PreparedMappedGravitySourceOperator requires the " field::Gravity,
"gravity-potential " DomainSchema>(*f.gravityPotentialFes)
"finite-element space."); ),
MFEM_VERIFY(f.displacementFes != nullptr, m_displacement_map(
"PreparedMappedGravitySourceOperator requires " field::make_field_dof_map<
"the displacement finite-element space."); field::Displacement,
MFEM_VERIFY( DomainSchema>(*f.displacementFes)
f.compactificationFes != nullptr, ) {
"PreparedMappedGravitySourceOperator requires the compactification " MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedGravitySourceOperator requires a mesh.");
"finite-element space."); MFEM_VERIFY(
MFEM_VERIFY( f.densityFes != nullptr, "PreparedMappedGravitySourceOperator requires the density "
f.compactificationCoordinate != nullptr, "finite-element space."
"PreparedMappedGravitySourceOperator requires the compactification " );
"coordinate."); MFEM_VERIFY(
MFEM_VERIFY(f.quadratureFactory != nullptr, f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the "
"PreparedMappedGravitySourceOperator " "gravity-potential "
"requires the quadrature-rule factory."); "finite-element space."
MFEM_VERIFY(domain_mapper.GetDimension() == f.mesh->Dimension(), );
"The stateless domain-mapper dimension does not match the mesh " MFEM_VERIFY(
"dimension."); f.displacementFes != nullptr, "PreparedMappedGravitySourceOperator requires "
"the displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr, "PreparedMappedGravitySourceOperator requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"PreparedMappedGravitySourceOperator requires the compactification "
"coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr, "PreparedMappedGravitySourceOperator "
"requires the quadrature-rule factory."
);
MFEM_VERIFY(
domain_mapper.GetDimension() == f.mesh->Dimension(),
"The stateless domain-mapper dimension does not match the mesh "
"dimension."
);
m_stellar_marker = m_stellar_marker = utils::domain::make_attribute_marker<utils::domain::Stellar, DomainSchema>(*f.mesh);
utils::domain::make_attribute_marker<utils::domain::Stellar,
DomainSchema>(*f.mesh);
}
void PreparedMappedGravitySourceOperator::Prepare(
const mfem::Vector &displacement) {
MFEM_VERIFY(displacement.Size() == m_displacement_map.reduced_size(),
"PreparedMappedGravitySourceOperator received a displacement "
"vector "
"with the wrong size.");
for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY(std::isfinite(displacement(i)),
"PreparedMappedGravitySourceOperator received a non-finite "
"displacement value.");
}
m_is_prepared = false;
m_displacement_true.SetSize(m_displacement_map.full_size());
m_displacement_map.scatter(displacement, m_displacement_true);
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
FrozenMappedGravitySourceCoefficient source_coefficient(
m_fem, m_domain_mapper, m_displacement_true);
for (int element_id = 0; element_id < m_fem.mesh->GetNE(); ++element_id) {
const int attribute = m_fem.mesh->GetAttribute(element_id);
if (attribute <= 0 || attribute > m_stellar_marker.Size() ||
m_stellar_marker[attribute - 1] == 0) {
continue;
} }
m_elements.emplace_back(); void PreparedMappedGravitySourceOperator::Prepare(const mfem::Vector &displacement) {
ElementPAData &data = m_elements.back(); MFEM_VERIFY(
displacement.Size() == m_displacement_map.reduced_size(),
"PreparedMappedGravitySourceOperator received a displacement "
"vector "
"with the wrong size."
);
data.element_id = element_id; for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(displacement(i)), "PreparedMappedGravitySourceOperator received a non-finite "
"displacement value."
);
}
data.density_dof_transformation = m_is_prepared = false;
m_fem.densityFes->GetElementDofs(element_id, data.density_dofs); m_displacement_true.SetSize(m_displacement_map.full_size());
m_displacement_map.scatter(displacement, m_displacement_true);
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
data.potential_dof_transformation = FrozenMappedGravitySourceCoefficient source_coefficient(m_fem, m_domain_mapper, m_displacement_true);
m_fem.gravityPotentialFes->GetElementDofs(element_id,
data.potential_dofs);
const mfem::FiniteElement &density_element = for (int element_id = 0; element_id < m_fem.mesh->GetNE(); ++element_id) {
*m_fem.densityFes->GetFE(element_id); const int attribute = m_fem.mesh->GetAttribute(element_id);
const mfem::FiniteElement &potential_element = if (attribute <= 0 || attribute > m_stellar_marker.Size() || m_stellar_marker[attribute - 1] == 0) {
*m_fem.gravityPotentialFes->GetFE(element_id); continue;
}
mfem::ElementTransformation &transformation = m_elements.emplace_back();
*m_fem.mesh->GetElementTransformation(element_id); ElementPAData &data = m_elements.back();
const mfem::IntegrationRule &integration_rule = get_source_rule( data.element_id = element_id;
m_fem, density_element, potential_element, transformation);
const int quadrature_point_count = integration_rule.GetNPoints(); data.density_dof_transformation = m_fem.densityFes->GetElementDofs(element_id, data.density_dofs);
const int density_dof_count = density_element.GetDof(); data.potential_dof_transformation =
m_fem.gravityPotentialFes->GetElementDofs(element_id, data.potential_dofs);
const int potential_dof_count = potential_element.GetDof(); const mfem::FiniteElement &density_element = *m_fem.densityFes->GetFE(element_id);
data.density_basis.SetSize(quadrature_point_count, density_dof_count); const mfem::FiniteElement &potential_element = *m_fem.gravityPotentialFes->GetFE(element_id);
data.potential_basis.SetSize(quadrature_point_count, potential_dof_count); mfem::ElementTransformation &transformation = *m_fem.mesh->GetElementTransformation(element_id);
data.quadrature_data.SetSize(quadrature_point_count); const mfem::IntegrationRule &integration_rule =
get_source_rule(m_fem, density_element, potential_element, transformation);
mfem::Vector density_shape(density_dof_count); const int quadrature_point_count = integration_rule.GetNPoints();
mfem::Vector potential_shape(potential_dof_count);
for (int quadrature_point = 0; quadrature_point < quadrature_point_count; const int density_dof_count = density_element.GetDof();
++quadrature_point) {
const mfem::IntegrationPoint &integration_point =
integration_rule.IntPoint(quadrature_point);
transformation.SetIntPoint(&integration_point); const int potential_dof_count = potential_element.GetDof();
// CalcPhysShape matches the scalar mixed-mass discretization, data.density_basis.SetSize(quadrature_point_count, density_dof_count);
// including the finite-element map type.
density_element.CalcPhysShape(transformation, density_shape);
potential_element.CalcPhysShape(transformation, potential_shape); data.potential_basis.SetSize(quadrature_point_count, potential_dof_count);
for (int i = 0; i < density_dof_count; ++i) { data.quadrature_data.SetSize(quadrature_point_count);
data.density_basis(quadrature_point, i) = density_shape(i);
}
for (int i = 0; i < potential_dof_count; ++i) { mfem::Vector density_shape(density_dof_count);
data.potential_basis(quadrature_point, i) = potential_shape(i); mfem::Vector potential_shape(potential_dof_count);
}
const double coefficient_value = for (int quadrature_point = 0; quadrature_point < quadrature_point_count; ++quadrature_point) {
source_coefficient.Eval(transformation, integration_point); const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(quadrature_point);
transformation.SetIntPoint(&integration_point); transformation.SetIntPoint(&integration_point);
const double quadrature_value = integration_point.weight * // CalcPhysShape matches the scalar mixed-mass discretization,
transformation.Weight() * // including the finite-element map type.
coefficient_value; density_element.CalcPhysShape(transformation, density_shape);
MFEM_VERIFY(std::isfinite(quadrature_value) && quadrature_value > 0.0, potential_element.CalcPhysShape(transformation, potential_shape);
"Prepared gravity source operator encountered invalid "
"quadrature data on element "
<< element_id << ", quadrature point " << quadrature_point
<< ".");
data.quadrature_data(quadrature_point) = quadrature_value; for (int i = 0; i < density_dof_count; ++i) {
data.density_basis(quadrature_point, i) = density_shape(i);
}
for (int i = 0; i < potential_dof_count; ++i) {
data.potential_basis(quadrature_point, i) = potential_shape(i);
}
const double coefficient_value = source_coefficient.Eval(transformation, integration_point);
transformation.SetIntPoint(&integration_point);
const double quadrature_value = integration_point.weight * transformation.Weight() * coefficient_value;
MFEM_VERIFY(
std::isfinite(quadrature_value) && quadrature_value > 0.0,
"Prepared gravity source operator encountered invalid "
"quadrature data on element "
<< element_id << ", quadrature point " << quadrature_point << "."
);
data.quadrature_data(quadrature_point) = quadrature_value;
}
}
MFEM_VERIFY(!m_elements.empty(), "PreparedMappedGravitySourceOperator found no stellar elements.");
m_is_prepared = true;
++m_preparation_count;
} }
} void PreparedMappedGravitySourceOperator::Mult(
const mfem::Vector &density,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedGravitySourceOperator must be prepared before "
"Mult is called."
);
MFEM_VERIFY(!m_elements.empty(), MFEM_VERIFY(
"PreparedMappedGravitySourceOperator found no stellar elements."); density.Size() == Width(), "PreparedMappedGravitySourceOperator received a density vector "
"with the wrong size."
);
m_is_prepared = true; m_density_true.SetSize(m_density_map.full_size());
++m_preparation_count; m_density_map.scatter(density, m_density_true);
}
void PreparedMappedGravitySourceOperator::Mult(const mfem::Vector &density,
mfem::Vector &action) const {
MFEM_VERIFY(m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before "
"Mult is called.");
MFEM_VERIFY(density.Size() == Width(), mfem::Vector density_local;
"PreparedMappedGravitySourceOperator received a density vector "
"with the wrong size.");
m_density_true.SetSize(m_density_map.full_size()); true_to_local(*m_fem.densityFes, m_density_true, density_local);
m_density_map.scatter(density, m_density_true);
mfem::Vector density_local; mfem::Vector local_action(m_fem.gravityPotentialFes->GetVSize());
local_action = 0.0;
true_to_local(*m_fem.densityFes, m_density_true, density_local); mfem::Vector element_density;
mfem::Vector quadrature_density;
mfem::Vector element_action;
mfem::Vector local_action(m_fem.gravityPotentialFes->GetVSize()); for (const ElementPAData &data : m_elements) {
local_action = 0.0; density_local.GetSubVector(data.density_dofs, element_density);
mfem::Vector element_density; if (data.density_dof_transformation != nullptr) {
mfem::Vector quadrature_density; data.density_dof_transformation->InvTransformPrimal(element_density);
mfem::Vector element_action; }
for (const ElementPAData &data : m_elements) { quadrature_density.SetSize(data.quadrature_data.Size());
density_local.GetSubVector(data.density_dofs, element_density);
if (data.density_dof_transformation != nullptr) { // B_density * x_e
data.density_dof_transformation->InvTransformPrimal(element_density); data.density_basis.Mult(element_density, quadrature_density);
// D * B_density * x_e
for (int q = 0; q < quadrature_density.Size(); ++q) {
quadrature_density(q) *= data.quadrature_data(q);
}
element_action.SetSize(data.potential_dofs.Size());
// B_potential^T * D * B_density * x_e
data.potential_basis.MultTranspose(quadrature_density, element_action);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->TransformDual(element_action);
}
local_action.AddElementVector(data.potential_dofs, element_action);
}
local_to_true(*m_fem.gravityPotentialFes, local_action, m_action_true);
action.SetSize(Height());
m_potential_map.gather(m_action_true, action);
} }
quadrature_density.SetSize(data.quadrature_data.Size()); void PreparedMappedGravitySourceOperator::MultTranspose(
const mfem::Vector &potential,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedGravitySourceOperator must be prepared before "
"MultTranspose is called."
);
// B_density * x_e MFEM_VERIFY(
data.density_basis.Mult(element_density, quadrature_density); potential.Size() == Height(), "PreparedMappedGravitySourceOperator received a potential vector "
"with the wrong size."
);
// D * B_density * x_e m_potential_true.SetSize(m_potential_map.full_size());
for (int q = 0; q < quadrature_density.Size(); ++q) { m_potential_map.scatter(potential, m_potential_true);
quadrature_density(q) *= data.quadrature_data(q);
mfem::Vector potential_local;
true_to_local(*m_fem.gravityPotentialFes, m_potential_true, potential_local);
mfem::Vector local_action(m_fem.densityFes->GetVSize());
local_action = 0.0;
mfem::Vector element_potential;
mfem::Vector quadrature_potential;
mfem::Vector element_action;
for (const ElementPAData &data : m_elements) {
potential_local.GetSubVector(data.potential_dofs, element_potential);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->InvTransformPrimal(element_potential);
}
quadrature_potential.SetSize(data.quadrature_data.Size());
data.potential_basis.Mult(element_potential, quadrature_potential);
for (int q = 0; q < quadrature_potential.Size(); ++q) {
quadrature_potential(q) *= data.quadrature_data(q);
}
element_action.SetSize(data.density_dofs.Size());
data.density_basis.MultTranspose(quadrature_potential, element_action);
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->TransformDual(element_action);
}
local_action.AddElementVector(data.density_dofs, element_action);
}
local_to_true(*m_fem.densityFes, local_action, m_action_true);
action.SetSize(Width());
m_density_map.gather(m_action_true, action);
}
bool PreparedMappedGravitySourceOperator::IsPrepared() const noexcept {
return m_is_prepared;
} }
element_action.SetSize(data.potential_dofs.Size()); std::uint64_t PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
// B_potential^T * D * B_density * x_e
data.potential_basis.MultTranspose(quadrature_density, element_action);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->TransformDual(element_action);
} }
local_action.AddElementVector(data.potential_dofs, element_action); const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetDensityMap() const noexcept {
} return m_density_map;
local_to_true(*m_fem.gravityPotentialFes, local_action, m_action_true);
action.SetSize(Height());
m_potential_map.gather(m_action_true, action);
}
void PreparedMappedGravitySourceOperator::MultTranspose(
const mfem::Vector &potential, mfem::Vector &action) const {
MFEM_VERIFY(m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before "
"MultTranspose is called.");
MFEM_VERIFY(potential.Size() == Height(),
"PreparedMappedGravitySourceOperator received a potential vector "
"with the wrong size.");
m_potential_true.SetSize(m_potential_map.full_size());
m_potential_map.scatter(potential, m_potential_true);
mfem::Vector potential_local;
true_to_local(*m_fem.gravityPotentialFes, m_potential_true, potential_local);
mfem::Vector local_action(m_fem.densityFes->GetVSize());
local_action = 0.0;
mfem::Vector element_potential;
mfem::Vector quadrature_potential;
mfem::Vector element_action;
for (const ElementPAData &data : m_elements) {
potential_local.GetSubVector(data.potential_dofs, element_potential);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->InvTransformPrimal(element_potential);
} }
quadrature_potential.SetSize(data.quadrature_data.Size()); const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetPotentialMap() const noexcept {
return m_potential_map;
data.potential_basis.Mult(element_potential, quadrature_potential);
for (int q = 0; q < quadrature_potential.Size(); ++q) {
quadrature_potential(q) *= data.quadrature_data(q);
} }
element_action.SetSize(data.density_dofs.Size()); const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetDisplacementMap() const noexcept {
return m_displacement_map;
data.density_basis.MultTranspose(quadrature_potential, element_action);
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->TransformDual(element_action);
} }
local_action.AddElementVector(data.density_dofs, element_action);
}
local_to_true(*m_fem.densityFes, local_action, m_action_true);
action.SetSize(Width());
m_density_map.gather(m_action_true, action);
}
bool PreparedMappedGravitySourceOperator::IsPrepared() const noexcept {
return m_is_prepared;
}
std::uint64_t
PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetDensityMap() const noexcept {
return m_density_map;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetPotentialMap() const noexcept {
return m_potential_map;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetDisplacementMap() const noexcept {
return m_displacement_map;
}
} // namespace mean_field::operators } // namespace mean_field::operators

View File

@@ -8,405 +8,413 @@ module mean_field;
import :operators.prepared_hdiv_mass; import :operators.prepared_hdiv_mass;
namespace { namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
int get_operator_size(const mean_field::fem::FEM &f) { int get_operator_size(const mean_field::fem::FEM &f) {
MFEM_VERIFY(f.gravityFluxFes != nullptr, MFEM_VERIFY(
"PreparedMappedHDivMassOperator requires the " f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the "
"gravity-gradient finite-element space."); "gravity-gradient finite-element space."
return mean_field::field::make_field_dof_map<mean_field::field::Gravity, );
DomainSchema>(*f.gravityFluxFes) return mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityFluxFes)
.reduced_size(); .reduced_size();
}
void true_to_local(const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &true_vector,
mfem::Vector &local_vector) {
local_vector.SetSize(finite_element_space.GetVSize());
const mfem::Operator *prolongation =
finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(true_vector, local_vector);
} else {
local_vector = true_vector;
}
}
int find_representative_element(const mean_field::fem::FEM &f,
const mfem::Array<int> &marker) {
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
const int attribute = f.mesh->GetAttribute(element_id);
if (attribute > 0 && attribute <= marker.Size() &&
marker[attribute - 1] != 0) {
return element_id;
}
}
return -1;
}
void validate_uniform_domain_discretization(
const mean_field::fem::FEM &f, const mfem::Array<int> &marker,
const int representative_element_id) {
const mfem::FiniteElement &representative_element =
*f.gravityFluxFes->GetFE(representative_element_id);
const mfem::ElementTransformation &representative_transformation =
*f.mesh->GetElementTransformation(representative_element_id);
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
const int attribute = f.mesh->GetAttribute(element_id);
if (attribute <= 0 || attribute > marker.Size() ||
marker[attribute - 1] == 0) {
continue;
} }
const mfem::FiniteElement &element = *f.gravityFluxFes->GetFE(element_id); void true_to_local(
const mfem::ElementTransformation &transformation = const mfem::ParFiniteElementSpace &finite_element_space,
*f.mesh->GetElementTransformation(element_id); const mfem::Vector &true_vector,
mfem::Vector &local_vector
) {
local_vector.SetSize(finite_element_space.GetVSize());
MFEM_VERIFY(element.GetGeomType() == representative_element.GetGeomType(), const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(true_vector, local_vector);
} else {
local_vector = true_vector;
}
}
int find_representative_element(
const mean_field::fem::FEM &f,
const mfem::Array<int> &marker
) {
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
const int attribute = f.mesh->GetAttribute(element_id);
if (attribute > 0 && attribute <= marker.Size() && marker[attribute - 1] != 0) {
return element_id;
}
}
return -1;
}
void validate_uniform_domain_discretization(
const mean_field::fem::FEM &f,
const mfem::Array<int> &marker,
const int representative_element_id
) {
const mfem::FiniteElement &representative_element = *f.gravityFluxFes->GetFE(representative_element_id);
const mfem::ElementTransformation &representative_transformation =
*f.mesh->GetElementTransformation(representative_element_id);
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
const int attribute = f.mesh->GetAttribute(element_id);
if (attribute <= 0 || attribute > marker.Size() || marker[attribute - 1] == 0) {
continue;
}
const mfem::FiniteElement &element = *f.gravityFluxFes->GetFE(element_id);
const mfem::ElementTransformation &transformation = *f.mesh->GetElementTransformation(element_id);
MFEM_VERIFY(
element.GetGeomType() == representative_element.GetGeomType(),
"Prepared H(div) mass domains currently require a uniform " "Prepared H(div) mass domains currently require a uniform "
"element " "element "
"geometry."); "geometry."
MFEM_VERIFY(element.GetOrder() == representative_element.GetOrder(), );
MFEM_VERIFY(
element.GetOrder() == representative_element.GetOrder(),
"Prepared H(div) mass domains currently require a uniform " "Prepared H(div) mass domains currently require a uniform "
"finite-element order."); "finite-element order."
MFEM_VERIFY(transformation.OrderW() == );
representative_transformation.OrderW(), MFEM_VERIFY(
transformation.OrderW() == representative_transformation.OrderW(),
"Prepared H(div) mass domains currently require a uniform " "Prepared H(div) mass domains currently require a uniform "
"geometry-weight order."); "geometry-weight order."
} );
} }
class FrozenMappedHDivMassCoefficient final : public mfem::MatrixCoefficient {
public:
FrozenMappedHDivMassCoefficient(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::Vector &displacement_true, bool elevates_vacuum)
: MatrixCoefficient(domain_mapper.GetDimension()), m_fem(f),
m_domain_mapper(domain_mapper),
m_workspace(domain_mapper.GetDimension()),
m_elevates_vacuum(elevates_vacuum) {
true_to_local(*m_fem.displacementFes, displacement_true,
m_displacement_local);
}
void Eval(mfem::DenseMatrix &mass_tensor,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point) override {
transformation.SetIntPoint(&integration_point);
const int element_id = transformation.ElementNo;
MFEM_VERIFY(
element_id >= 0 && element_id < m_fem.mesh->GetNE(),
"Mapped H(div) mass coefficient received an invalid element ID.");
const bool element_is_vacuum = DomainSchema::template attribute_belongs_to<
mean_field::utils::domain::Vacuum>(transformation.Attribute);
if (element_is_vacuum != m_elevates_vacuum) {
mass_tensor.SetSize(m_domain_mapper.GetDimension());
mass_tensor = 0.0;
return;
} }
LoadElement(element_id); class FrozenMappedHDivMassCoefficient final : public mfem::MatrixCoefficient {
public:
FrozenMappedHDivMassCoefficient(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::Vector &displacement_true,
bool elevates_vacuum
)
: MatrixCoefficient(domain_mapper.GetDimension()),
m_fem(f),
m_domain_mapper(domain_mapper),
m_workspace(domain_mapper.GetDimension()),
m_elevates_vacuum(elevates_vacuum) {
true_to_local(*m_fem.displacementFes, displacement_true, m_displacement_local);
}
const mean_field::mapping::ElementMappingData mapping_data{ void Eval(
.displacement = *m_displacement_data, mfem::DenseMatrix &mass_tensor,
.compactification = *m_compactification_data}; mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point
) override {
transformation.SetIntPoint(&integration_point);
mean_field::mapping::VolumeMappingContext mapping_context; const int element_id = transformation.ElementNo;
MFEM_VERIFY(
element_id >= 0 && element_id < m_fem.mesh->GetNE(),
"Mapped H(div) mass coefficient received an invalid element ID."
);
const mean_field::mapping::MappingStatus status = const bool element_is_vacuum =
m_domain_mapper.EvaluateVolume(mapping_data, transformation, DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(
integration_point, m_workspace, transformation.Attribute
mapping_context); );
MFEM_VERIFY(status == mean_field::mapping::MappingStatus::valid, if (element_is_vacuum != m_elevates_vacuum) {
mass_tensor.SetSize(m_domain_mapper.GetDimension());
mass_tensor = 0.0;
return;
}
LoadElement(element_id);
const mean_field::mapping::ElementMappingData mapping_data{
.displacement = *m_displacement_data, .compactification = *m_compactification_data
};
mean_field::mapping::VolumeMappingContext mapping_context;
const mean_field::mapping::MappingStatus status = m_domain_mapper.EvaluateVolume(
mapping_data, transformation, integration_point, m_workspace, mapping_context
);
MFEM_VERIFY(
status == mean_field::mapping::MappingStatus::valid,
"Stateless domain mapping failed while preparing the H(div) " "Stateless domain mapping failed while preparing the H(div) "
"mass " "mass "
"operator. Mapping status = " "operator. Mapping status = "
<< static_cast<int>(status) << static_cast<int>(status) << ", element ID = " << element_id
<< ", element ID = " << element_id
<< ", element attribute = " << transformation.Attribute << ", element attribute = " << transformation.Attribute
<< ", coefficient domain = " << ", coefficient domain = " << (m_elevates_vacuum ? "vacuum" : "stellar")
<< (m_elevates_vacuum ? "vacuum" : "stellar")); );
const mfem::DenseMatrix &mapping_jacobian = const mfem::DenseMatrix &mapping_jacobian = mapping_context.mapping.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, MFEM_VERIFY(
std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
"Prepared H(div) mass operator encountered a non-positive or " "Prepared H(div) mass operator encountered a non-positive or "
"non-finite mapping determinant."); "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;
} }
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) {
return; return;
} }
const mfem::FiniteElement &displacement_element = const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id);
*m_fem.displacementFes->GetFE(element_id); const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*m_fem.compactificationFes->GetFE(element_id);
mfem::DofTransformation *displacement_dof_transformation = mfem::DofTransformation *displacement_dof_transformation =
m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs); m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs);
mfem::DofTransformation *compactification_dof_transformation = mfem::DofTransformation *compactification_dof_transformation =
m_fem.compactificationFes->GetElementDofs(element_id, m_fem.compactificationFes->GetElementDofs(element_id, m_compactification_dofs);
m_compactification_dofs);
m_displacement_local.GetSubVector(m_displacement_dofs, m_displacement_local.GetSubVector(m_displacement_dofs, m_element_displacement);
m_element_displacement); m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs, m_element_compactification);
m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs,
m_element_compactification);
if (displacement_dof_transformation != nullptr) { if (displacement_dof_transformation != nullptr) {
displacement_dof_transformation->InvTransformPrimal( displacement_dof_transformation->InvTransformPrimal(m_element_displacement);
m_element_displacement); }
}
if (compactification_dof_transformation != nullptr) { if (compactification_dof_transformation != nullptr) {
compactification_dof_transformation->InvTransformPrimal( compactification_dof_transformation->InvTransformPrimal(m_element_compactification);
m_element_compactification); }
}
m_displacement_data = m_displacement_data = std::make_unique<mean_field::mapping::ElementDisplacementData>(
std::make_unique<mean_field::mapping::ElementDisplacementData>( mean_field::mapping::ElementDisplacementDataFromElementVDofs(
mean_field::mapping::ElementDisplacementDataFromElementVDofs( displacement_element, m_element_displacement
displacement_element, m_element_displacement)); )
);
m_compactification_data = m_compactification_data = std::make_unique<mean_field::mapping::ElementCompactificationData>(
std::make_unique<mean_field::mapping::ElementCompactificationData>( compactification_element, m_element_compactification
compactification_element, m_element_compactification); );
m_cached_element_id = element_id; m_cached_element_id = element_id;
} }
const mean_field::fem::FEM &m_fem; const mean_field::fem::FEM &m_fem;
const mean_field::mapping::DomainMapper &m_domain_mapper; const mean_field::mapping::DomainMapper &m_domain_mapper;
mfem::Vector m_displacement_local; mfem::Vector m_displacement_local;
mfem::Array<int> m_displacement_dofs; mfem::Array<int> m_displacement_dofs;
mfem::Array<int> m_compactification_dofs; mfem::Array<int> m_compactification_dofs;
mfem::Vector m_element_displacement; mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification; mfem::Vector m_element_compactification;
std::unique_ptr<mean_field::mapping::ElementDisplacementData> std::unique_ptr<mean_field::mapping::ElementDisplacementData> m_displacement_data;
m_displacement_data; std::unique_ptr<mean_field::mapping::ElementCompactificationData> m_compactification_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
m_compactification_data;
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;
}; };
} // namespace } // namespace
namespace mean_field::operators { namespace mean_field::operators {
PreparedMappedHDivMassOperator::PreparedMappedHDivMassOperator( PreparedMappedHDivMassOperator::PreparedMappedHDivMassOperator(
const fem::FEM &f, const mapping::DomainMapper &domain_mapper) const fem::FEM &f,
: Operator(get_operator_size(f)), m_fem(f), m_domain_mapper(domain_mapper), const mapping::DomainMapper &domain_mapper
m_flux_map(field::make_field_dof_map<field::Gravity, DomainSchema>( )
*f.gravityFluxFes)), : Operator(get_operator_size(f)),
m_displacement_map( m_fem(f),
field::make_field_dof_map<field::Displacement, DomainSchema>( m_domain_mapper(domain_mapper),
*f.displacementFes)) { m_flux_map(
MFEM_VERIFY(f.mesh != nullptr, field::make_field_dof_map<
"PreparedMappedHDivMassOperator requires a mesh."); field::Gravity,
MFEM_VERIFY(f.gravityFluxFes != nullptr, DomainSchema>(*f.gravityFluxFes)
"PreparedMappedHDivMassOperator requires the " ),
"gravity-gradient finite-element space."); m_displacement_map(
MFEM_VERIFY(f.displacementFes != nullptr, field::make_field_dof_map<
"PreparedMappedHDivMassOperator requires the " field::Displacement,
"displacement finite-element space."); DomainSchema>(*f.displacementFes)
MFEM_VERIFY(f.compactificationFes != nullptr, ) {
"PreparedMappedHDivMassOperator requires the compactification " MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedHDivMassOperator requires a mesh.");
"finite-element space."); MFEM_VERIFY(
MFEM_VERIFY(f.compactificationCoordinate != nullptr, f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the "
"PreparedMappedHDivMassOperator requires the compactification " "gravity-gradient finite-element space."
"coordinate."); );
MFEM_VERIFY(f.quadratureFactory != nullptr, MFEM_VERIFY(
"PreparedMappedHDivMassOperator requires the quadrature-rule " f.displacementFes != nullptr, "PreparedMappedHDivMassOperator requires the "
"factory."); "displacement finite-element space."
MFEM_VERIFY(domain_mapper.GetDimension() == f.mesh->Dimension(), );
"The stateless domain-mapper dimension does not match the mesh " MFEM_VERIFY(
"dimension."); f.compactificationFes != nullptr, "PreparedMappedHDivMassOperator requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr, "PreparedMappedHDivMassOperator requires the compactification "
"coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr, "PreparedMappedHDivMassOperator requires the quadrature-rule "
"factory."
);
MFEM_VERIFY(
domain_mapper.GetDimension() == f.mesh->Dimension(),
"The stateless domain-mapper dimension does not match the mesh "
"dimension."
);
m_stellar_marker = m_stellar_marker = utils::domain::make_attribute_marker<utils::domain::Stellar, DomainSchema>(*f.mesh);
utils::domain::make_attribute_marker<utils::domain::Stellar, m_vacuum_marker = utils::domain::make_attribute_marker<utils::domain::Vacuum, DomainSchema>(*f.mesh);
DomainSchema>(*f.mesh);
m_vacuum_marker =
utils::domain::make_attribute_marker<utils::domain::Vacuum, DomainSchema>(
*f.mesh);
const int stellar_element_id = const int stellar_element_id = find_representative_element(f, m_stellar_marker);
find_representative_element(f, m_stellar_marker); const int vacuum_element_id = find_representative_element(f, m_vacuum_marker);
const int vacuum_element_id = find_representative_element(f, m_vacuum_marker);
MFEM_VERIFY(stellar_element_id >= 0, MFEM_VERIFY(
"PreparedMappedHDivMassOperator requires " stellar_element_id >= 0, "PreparedMappedHDivMassOperator requires "
"at least one stellar element."); "at least one stellar element."
MFEM_VERIFY(vacuum_element_id >= 0, );
"PreparedMappedHDivMassOperator requires at " MFEM_VERIFY(
"least one compactified vacuum element."); vacuum_element_id >= 0, "PreparedMappedHDivMassOperator requires at "
"least one compactified vacuum element."
);
validate_uniform_domain_discretization(f, m_stellar_marker, validate_uniform_domain_discretization(f, m_stellar_marker, stellar_element_id);
stellar_element_id); validate_uniform_domain_discretization(f, m_vacuum_marker, vacuum_element_id);
validate_uniform_domain_discretization(f, m_vacuum_marker, vacuum_element_id); }
}
void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) { void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) {
MFEM_VERIFY(displacement.Size() == m_displacement_map.reduced_size(), MFEM_VERIFY(
"PreparedMappedHDivMassOperator received a displacement vector " displacement.Size() == m_displacement_map.reduced_size(),
"with " "PreparedMappedHDivMassOperator received a displacement vector "
"the wrong size."); "with "
"the wrong size."
);
for (int i = 0; i < displacement.Size(); ++i) { for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY(std::isfinite(displacement(i)), MFEM_VERIFY(
"PreparedMappedHDivMassOperator received a non-finite " std::isfinite(displacement(i)), "PreparedMappedHDivMassOperator received a non-finite "
"displacement " "displacement "
"value."); "value."
} );
}
m_displacement_true.SetSize(m_displacement_map.full_size()); m_displacement_true.SetSize(m_displacement_map.full_size());
m_displacement_map.scatter(displacement, m_displacement_true); m_displacement_map.scatter(displacement, m_displacement_true);
const int stellar_element_id = const int stellar_element_id = find_representative_element(m_fem, m_stellar_marker);
find_representative_element(m_fem, m_stellar_marker); const int vacuum_element_id = find_representative_element(m_fem, m_vacuum_marker);
const int vacuum_element_id =
find_representative_element(m_fem, m_vacuum_marker);
const mfem::FiniteElement &stellar_element = const mfem::FiniteElement &stellar_element = *m_fem.gravityFluxFes->GetFE(stellar_element_id);
*m_fem.gravityFluxFes->GetFE(stellar_element_id); const mfem::FiniteElement &vacuum_element = *m_fem.gravityFluxFes->GetFE(vacuum_element_id);
const mfem::FiniteElement &vacuum_element =
*m_fem.gravityFluxFes->GetFE(vacuum_element_id);
mfem::ElementTransformation &stellar_transformation = mfem::ElementTransformation &stellar_transformation = *m_fem.mesh->GetElementTransformation(stellar_element_id);
*m_fem.mesh->GetElementTransformation(stellar_element_id); mfem::ElementTransformation &vacuum_transformation = *m_fem.mesh->GetElementTransformation(vacuum_element_id);
mfem::ElementTransformation &vacuum_transformation =
*m_fem.mesh->GetElementTransformation(vacuum_element_id);
m_stellar_mass_form.reset(); m_stellar_mass_form.reset();
m_vacuum_mass_form.reset(); m_vacuum_mass_form.reset();
m_stellar_mass_coefficient.reset(); m_stellar_mass_coefficient.reset();
m_vacuum_mass_coefficient.reset(); m_vacuum_mass_coefficient.reset();
m_stellar_mass_coefficient = m_stellar_mass_coefficient =
std::make_unique<FrozenMappedHDivMassCoefficient>( std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, false);
m_fem, m_domain_mapper, m_displacement_true, false); m_vacuum_mass_coefficient =
m_vacuum_mass_coefficient = std::make_unique<FrozenMappedHDivMassCoefficient>( std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, true);
m_fem, m_domain_mapper, m_displacement_true, true);
m_stellar_mass_form = m_stellar_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
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 = m_stellar_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get()); m_vacuum_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
m_stellar_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
m_vacuum_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
auto stellar_integrator = std::make_unique<mfem::VectorFEMassIntegrator>( auto stellar_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*m_stellar_mass_coefficient);
*m_stellar_mass_coefficient); auto vacuum_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*m_vacuum_mass_coefficient);
auto vacuum_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(
*m_vacuum_mass_coefficient);
m_fem.quadratureFactory->configure_gravity_hdiv_mass( m_fem.quadratureFactory->configure_gravity_hdiv_mass(
*stellar_integrator, quadrature::QuadratureRole::discretization, *stellar_integrator, quadrature::QuadratureRole::discretization, stellar_element, stellar_transformation,
stellar_element, stellar_transformation, utils::DOMAINS::STELLAR, utils::DOMAINS::STELLAR, quadrature::MappingKind::general
quadrature::MappingKind::general); );
m_fem.quadratureFactory->configure_gravity_hdiv_mass( m_fem.quadratureFactory->configure_gravity_hdiv_mass(
*vacuum_integrator, quadrature::QuadratureRole::discretization, *vacuum_integrator, quadrature::QuadratureRole::discretization, vacuum_element, vacuum_transformation,
vacuum_element, vacuum_transformation, utils::DOMAINS::VACUUM, utils::DOMAINS::VACUUM, quadrature::MappingKind::kelvin
quadrature::MappingKind::kelvin); );
m_stellar_mass_form->AddDomainIntegrator(stellar_integrator.release(), m_stellar_mass_form->AddDomainIntegrator(stellar_integrator.release(), m_stellar_marker);
m_stellar_marker); m_vacuum_mass_form->AddDomainIntegrator(vacuum_integrator.release(), m_vacuum_marker);
m_vacuum_mass_form->AddDomainIntegrator(vacuum_integrator.release(), m_stellar_mass_form->Assemble();
m_vacuum_marker); m_vacuum_mass_form->Assemble();
m_stellar_mass_form->Assemble();
m_vacuum_mass_form->Assemble();
m_is_prepared = true; m_is_prepared = true;
++m_preparation_count; ++m_preparation_count;
} }
void PreparedMappedHDivMassOperator::Mult(const mfem::Vector &gravity_gradient, void PreparedMappedHDivMassOperator::Mult(
mfem::Vector &action) const { const mfem::Vector &gravity_gradient,
MFEM_VERIFY(m_is_prepared, "PreparedMappedHDivMassOperator must be prepared " mfem::Vector &action
"before Mult is called."); ) const {
MFEM_VERIFY( MFEM_VERIFY(
m_stellar_mass_form != nullptr && m_vacuum_mass_form != nullptr, m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
"PreparedMappedHDivMassOperator has incomplete domain mass forms."); "before Mult is called."
MFEM_VERIFY( );
gravity_gradient.Size() == Width(), MFEM_VERIFY(
"PreparedMappedHDivMassOperator received a gravity-gradient vector " m_stellar_mass_form != nullptr && m_vacuum_mass_form != nullptr,
"with the wrong size."); "PreparedMappedHDivMassOperator has incomplete domain mass forms."
);
MFEM_VERIFY(
gravity_gradient.Size() == Width(), "PreparedMappedHDivMassOperator received a gravity-gradient vector "
"with the wrong size."
);
m_flux_true.SetSize(m_flux_map.full_size()); m_flux_true.SetSize(m_flux_map.full_size());
m_action_true.SetSize(m_flux_map.full_size()); m_action_true.SetSize(m_flux_map.full_size());
m_domain_action_true.SetSize(m_flux_map.full_size()); m_domain_action_true.SetSize(m_flux_map.full_size());
m_flux_map.scatter(gravity_gradient, m_flux_true); m_flux_map.scatter(gravity_gradient, m_flux_true);
m_stellar_mass_form->Mult(m_flux_true, m_action_true); m_stellar_mass_form->Mult(m_flux_true, m_action_true);
m_vacuum_mass_form->Mult(m_flux_true, m_domain_action_true); m_vacuum_mass_form->Mult(m_flux_true, m_domain_action_true);
m_action_true += m_domain_action_true; m_action_true += m_domain_action_true;
action.SetSize(Height()); action.SetSize(Height());
m_flux_map.gather(m_action_true, action); m_flux_map.gather(m_action_true, action);
} }
void PreparedMappedHDivMassOperator::AssembleDiagonal( void PreparedMappedHDivMassOperator::AssembleDiagonal(mfem::Vector &diagonal) const {
mfem::Vector &diagonal) const { mfem::Vector true_diagonal;
mfem::Vector true_diagonal; AssembleTrueDiagonal(true_diagonal);
AssembleTrueDiagonal(true_diagonal); diagonal.SetSize(Height());
diagonal.SetSize(Height()); m_flux_map.gather(true_diagonal, diagonal);
m_flux_map.gather(true_diagonal, diagonal); }
}
void PreparedMappedHDivMassOperator::AssembleTrueDiagonal( void PreparedMappedHDivMassOperator::AssembleTrueDiagonal(mfem::Vector &diagonal) const {
mfem::Vector &diagonal) const { MFEM_VERIFY(
MFEM_VERIFY(m_is_prepared, "PreparedMappedHDivMassOperator must be prepared " m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
"before assembling its diagonal."); "before assembling its diagonal."
MFEM_VERIFY( );
m_stellar_mass_form != nullptr && m_vacuum_mass_form != nullptr, MFEM_VERIFY(
"PreparedMappedHDivMassOperator has incomplete domain mass forms."); m_stellar_mass_form != nullptr && m_vacuum_mass_form != nullptr,
"PreparedMappedHDivMassOperator has incomplete domain mass forms."
);
diagonal.SetSize(m_flux_map.full_size()); diagonal.SetSize(m_flux_map.full_size());
mfem::Vector domain_diagonal(m_flux_map.full_size()); mfem::Vector domain_diagonal(m_flux_map.full_size());
m_stellar_mass_form->AssembleDiagonal(diagonal); m_stellar_mass_form->AssembleDiagonal(diagonal);
m_vacuum_mass_form->AssembleDiagonal(domain_diagonal); m_vacuum_mass_form->AssembleDiagonal(domain_diagonal);
diagonal += domain_diagonal; diagonal += domain_diagonal;
} }
bool PreparedMappedHDivMassOperator::IsPrepared() const noexcept { bool PreparedMappedHDivMassOperator::IsPrepared() const noexcept {
return m_is_prepared; return m_is_prepared;
} }
std::uint64_t std::uint64_t PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept {
PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept { return m_preparation_count;
return m_preparation_count; }
}
const field::FieldDofMap & const field::FieldDofMap &PreparedMappedHDivMassOperator::GetFluxMap() const noexcept {
PreparedMappedHDivMassOperator::GetFluxMap() const noexcept { return m_flux_map;
return m_flux_map; }
}
const field::FieldDofMap & const field::FieldDofMap &PreparedMappedHDivMassOperator::GetDisplacementMap() const noexcept {
PreparedMappedHDivMassOperator::GetDisplacementMap() const noexcept { return m_displacement_map;
return m_displacement_map; }
}
} // namespace mean_field::operators } // namespace mean_field::operators

File diff suppressed because it is too large Load Diff

View File

@@ -617,13 +617,19 @@ namespace mean_field::operators {
data.enthalpyJacobian = 0.0; data.enthalpyJacobian = 0.0;
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const double enthalpy = quadratureEnthalpy(quadraturePoint); const double enthalpy = quadratureEnthalpy(quadraturePoint);
const double pressure = m_equationOfState.pressure_from_enthalpy(enthalpy); const eos::SpecificEnthalpyValue specificEnthalpy{enthalpy};
const double pressure =
eos::evaluate<eos::quantity::Pressure>(m_equationOfState, specificEnthalpy).value();
const double pressureDerivative = m_equationOfState.pressure_derivative_from_enthalpy(enthalpy); const double pressureDerivative =
eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
m_equationOfState, specificEnthalpy
)
.value();
const double quadratureWeight = data.quadratureWeights(quadraturePoint); const double quadratureWeight = data.quadratureWeights(quadraturePoint);
MFEM_VERIFY( MFEM_VERIFY(
std::isfinite(pressure) && std::isfinite(pressureDerivative), std::isfinite(pressure) && std::isfinite(pressureDerivative),
@@ -1134,4 +1140,4 @@ namespace mean_field::operators {
const BarotropicEquilibriumLayout &PreparedPressureForceJacobianOperator::GetLayout() const noexcept { const BarotropicEquilibriumLayout &PreparedPressureForceJacobianOperator::GetLayout() const noexcept {
return m_layout; return m_layout;
} }
} // namespace mean_field::operators } // namespace mean_field::operators

View File

@@ -21,6 +21,12 @@ namespace {
); );
} }
[[nodiscard]] mfem::Vector make_computational_origin(const mfem::ParMesh &mesh) {
mfem::Vector origin(mesh.SpaceDimension());
origin = 0.0;
return origin;
}
[[nodiscard]] mean_field::operators::StellarEquilibriumLayout make_layout( [[nodiscard]] mean_field::operators::StellarEquilibriumLayout make_layout(
const mean_field::field::FieldDofMap &densityMap, const mean_field::field::FieldDofMap &densityMap,
const mean_field::field::FieldDofMap &displacementMap, const mean_field::field::FieldDofMap &displacementMap,
@@ -253,6 +259,8 @@ namespace mean_field::operators {
field::FieldDofMap gravityFluxMap; field::FieldDofMap gravityFluxMap;
field::FieldDofMap gravityPotentialMap; field::FieldDofMap gravityPotentialMap;
field::FieldDofMap enthalpyMap; field::FieldDofMap enthalpyMap;
field::FieldBoundaryDofMap pressureSurfaceRows;
field::FieldPointDofMap centerDisplacementRows;
StellarEquilibriumLayout layout; StellarEquilibriumLayout layout;
mfem::Array<int> gravityStateOffsets; mfem::Array<int> gravityStateOffsets;
@@ -284,6 +292,23 @@ namespace mean_field::operators {
field::Enthalpy, field::Enthalpy,
DomainSchema>(*f.enthalpyFes) DomainSchema>(*f.enthalpyFes)
), ),
pressureSurfaceRows(
field::make_field_boundary_dof_map<
field::Enthalpy,
utils::domain::StellarSurface,
DomainSchema>(
*f.enthalpyFes,
enthalpyMap
)
),
centerDisplacementRows(
field::make_field_point_dof_map<field::Displacement>(
*f.displacementFes,
displacementMap,
make_computational_origin(*f.mesh),
1.0e-12
)
),
layout(make_layout( layout(make_layout(
densityMap, densityMap,
displacementMap, displacementMap,
@@ -314,27 +339,15 @@ namespace mean_field::operators {
fem::FEM &f, fem::FEM &f,
const mapping::DomainMapper &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
const models::StellarModel &stellarModel const double targetMass,
) const PressureSurfaceConstraintView surfaceConstraint
: PreparedStellarEquilibriumOperator(
f,
domainMapper,
equationOfState,
stellarModel.targetMass()
) {
}
PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
const double targetMass
) )
: PreparedStellarEquilibriumOperator( : PreparedStellarEquilibriumOperator(
f, f,
domainMapper, domainMapper,
equationOfState, equationOfState,
targetMass, targetMass,
surfaceConstraint,
MakeConstructionData(f) MakeConstructionData(f)
) { ) {
} }
@@ -344,6 +357,7 @@ namespace mean_field::operators {
const mapping::DomainMapper &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
const double targetMass, const double targetMass,
const PressureSurfaceConstraintView surfaceConstraint,
ConstructionData constructionData ConstructionData constructionData
) )
: mfem::Operator( : mfem::Operator(
@@ -390,6 +404,11 @@ namespace mean_field::operators {
domainMapper, domainMapper,
m_gravityContext m_gravityContext
), ),
m_surfaceConstraintOperator(
constructionData.pressureSurfaceRows,
surfaceConstraint
),
m_centeringConstraintOperator(constructionData.centerDisplacementRows),
m_targetMass(targetMass) { m_targetMass(targetMass) {
MFEM_VERIFY( MFEM_VERIFY(
std::isfinite(m_targetMass) && m_targetMass > 0.0, std::isfinite(m_targetMass) && m_targetMass > 0.0,
@@ -506,6 +525,14 @@ namespace mean_field::operators {
report.massNormalization = report.massNormalization =
m_massNormalizationOperator.Prepare({.targetMass = m_targetMass}, make_mass_dependencies(dependencies)); m_massNormalizationOperator.Prepare({.targetMass = m_targetMass}, make_mass_dependencies(dependencies));
report.surfaceConstraint = m_surfaceConstraintOperator.Prepare(
reducedEnthalpy, !wasPrepared || dependencies.enthalpy != m_preparedDependencies.enthalpy
);
report.centeringConstraint = m_centeringConstraintOperator.Prepare(
displacement, !wasPrepared || dependencies.displacement != m_preparedDependencies.displacement
);
const bool dependenciesChanged = !wasPrepared || dependencies != m_preparedDependencies; const bool dependenciesChanged = !wasPrepared || dependencies != m_preparedDependencies;
if (dependenciesChanged || report.DidAnyChildWork()) { if (dependenciesChanged || report.DidAnyChildWork()) {
AssembleResidual(); AssembleResidual();
@@ -542,7 +569,9 @@ namespace mean_field::operators {
m_gravityOperator.Mult(m_gravityState, gravity); m_gravityOperator.Mult(m_gravityState, gravity);
m_barotropicClosureOperator.BuildResidual(closure); m_barotropicClosureOperator.BuildResidual(closure);
m_displacementOperator.BuildResidual(displacement); m_displacementOperator.BuildResidual(displacement);
m_centeringConstraintOperator.ApplyResidualRows(displacement);
m_hydrostaticOperator.BuildResidual(hydrostatic); m_hydrostaticOperator.BuildResidual(hydrostatic);
m_surfaceConstraintOperator.ApplyResidualRows(hydrostatic);
m_massNormalizationOperator.BuildResidual(mass); m_massNormalizationOperator.BuildResidual(mass);
m_cachedResidual.SetSize(Height()); m_cachedResidual.SetSize(Height());
@@ -659,11 +688,13 @@ namespace mean_field::operators {
reducedDensityDirection, displacementDirection, gravityGradientDirection, reducedEnthalpyDirection, reducedDensityDirection, displacementDirection, gravityGradientDirection, reducedEnthalpyDirection,
displacementAction displacementAction
); );
m_centeringConstraintOperator.ApplyJacobianRows(displacementDirection, displacementAction);
m_hydrostaticOperator.ApplyCompleteJacobianAction( m_hydrostaticOperator.ApplyCompleteJacobianAction(
reducedEnthalpyDirection, gravityPotentialDirection, bernoulliDirection(0), displacementDirection, reducedEnthalpyDirection, gravityPotentialDirection, bernoulliDirection(0), displacementDirection,
hydrostaticAction hydrostaticAction
); );
m_surfaceConstraintOperator.ApplyJacobianRows(reducedEnthalpyDirection, hydrostaticAction);
m_massNormalizationOperator.ApplyCompleteJacobianAction( m_massNormalizationOperator.ApplyCompleteJacobianAction(
reducedDensityDirection, displacementDirection, massAction reducedDensityDirection, displacementDirection, massAction
@@ -712,7 +743,8 @@ namespace mean_field::operators {
bool PreparedStellarEquilibriumOperator::IsPrepared() const noexcept { bool PreparedStellarEquilibriumOperator::IsPrepared() const noexcept {
return m_isPrepared && m_gravityContext.IsPrepared() && m_barotropicClosureOperator.IsPrepared() && return m_isPrepared && m_gravityContext.IsPrepared() && m_barotropicClosureOperator.IsPrepared() &&
m_hydrostaticOperator.IsPrepared() && m_displacementOperator.IsPrepared() && m_hydrostaticOperator.IsPrepared() && m_displacementOperator.IsPrepared() &&
m_massNormalizationOperator.IsPrepared(); m_massNormalizationOperator.IsPrepared() && m_surfaceConstraintOperator.IsPrepared() &&
m_centeringConstraintOperator.IsPrepared();
} }
double PreparedStellarEquilibriumOperator::GetTargetMass() const noexcept { double PreparedStellarEquilibriumOperator::GetTargetMass() const noexcept {
@@ -771,6 +803,16 @@ namespace mean_field::operators {
return m_massNormalizationOperator; return m_massNormalizationOperator;
} }
const PreparedPressureSurfaceConstraint &
PreparedStellarEquilibriumOperator::GetSurfaceConstraintOperator() const noexcept {
return m_surfaceConstraintOperator;
}
const PreparedCenteringConstraint &
PreparedStellarEquilibriumOperator::GetCenteringConstraintOperator() const noexcept {
return m_centeringConstraintOperator;
}
void PreparedStellarEquilibriumOperator::VerifyPrepared() const { void PreparedStellarEquilibriumOperator::VerifyPrepared() const {
MFEM_VERIFY( MFEM_VERIFY(
IsPrepared(), "PreparedStellarEquilibriumOperator must be prepared before residual or Jacobian application." IsPrepared(), "PreparedStellarEquilibriumOperator must be prepared before residual or Jacobian application."

View File

@@ -13,16 +13,14 @@ namespace mean_field::physics {
) { ) {
const int dim = fem.mesh->Dimension(); const int dim = fem.mesh->Dimension();
mfem::DenseMatrix local_Q(dim, dim); mfem::DenseMatrix local_Q(dim, dim);
local_Q = 0.0; local_Q = 0.0;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
mapping::GridFunctionMappingEvaluator mapping_evaluator( mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
*fem.compactificationCoordinate
); );
for (int i = 0; i < fem.mesh->GetNE(); ++i) { for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>( if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(fem.mesh->GetAttribute(i)))
fem.mesh->GetAttribute(i)))
continue; continue;
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i); mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
@@ -40,13 +38,12 @@ namespace mean_field::physics {
mapping::VolumeMappingContext mapping_context; mapping::VolumeMappingContext mapping_context;
MFEM_VERIFY( MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping::MappingStatus::valid,
mapping::MappingStatus::valid,
"Quadrupole integration encountered an invalid mapping." "Quadrupole integration encountered an invalid mapping."
); );
const double weight = mapping_context.quadrature.weight; const double weight = mapping_context.quadrature.weight;
const double rho_val = rho.GetValue(i, ip); const double rho_val = rho.GetValue(i, ip);
const mfem::Vector &phys_point = mapping_context.mapping.physical_position; const mfem::Vector &phys_point = mapping_context.mapping.physical_position;
@@ -145,7 +142,7 @@ namespace mean_field::physics {
constexpr auto gravity_poisson_residual_block = constexpr auto gravity_poisson_residual_block =
utils::blocks::get_residual_block<form>(utils::blocks::gravity_field.poisson_term); utils::blocks::get_residual_block<form>(utils::blocks::gravity_field.poisson_term);
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
const field::FieldDofGridFunctionAdapter density_adapter = const field::FieldDofGridFunctionAdapter density_adapter =
field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*f.densityFes); field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*f.densityFes);
const field::FieldDofGridFunctionAdapter displacement_adapter = const field::FieldDofGridFunctionAdapter displacement_adapter =
@@ -220,12 +217,8 @@ namespace mean_field::physics {
GravitySolution solution(f); GravitySolution solution(f);
gravity_flux_adapter.scatter( gravity_flux_adapter.scatter(gravity_state.GetBlock(gravity_gradient_residual_block), solution.gradPhi);
gravity_state.GetBlock(gravity_gradient_residual_block), solution.gradPhi gravity_potential_adapter.scatter(gravity_state.GetBlock(gravity_poisson_residual_block), solution.phi);
);
gravity_potential_adapter.scatter(
gravity_state.GetBlock(gravity_poisson_residual_block), solution.phi
);
return solution; return solution;
} }

View File

@@ -9,16 +9,14 @@ namespace mean_field::physics {
const fem::FEM &fem, const fem::FEM &fem,
const mfem::GridFunction &rho_ref const mfem::GridFunction &rho_ref
) { ) {
double local_I = 0.0; double local_I = 0.0;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
mapping::GridFunctionMappingEvaluator mapping_evaluator( mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
*fem.compactificationCoordinate
); );
for (int i = 0; i < fem.mesh->GetNE(); i++) { for (int i = 0; i < fem.mesh->GetNE(); i++) {
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>( if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(fem.mesh->GetAttribute(i)))
fem.mesh->GetAttribute(i)))
continue; continue;
mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(i); mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(i);
@@ -37,14 +35,13 @@ namespace mean_field::physics {
mapping::VolumeMappingContext mapping_context; mapping::VolumeMappingContext mapping_context;
MFEM_VERIFY( MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*T, ip, mapping_context) == mapping_evaluator.EvaluateVolume(*T, ip, mapping_context) == mapping::MappingStatus::valid,
mapping::MappingStatus::valid,
"Moment-of-inertia integration encountered an invalid mapping." "Moment-of-inertia integration encountered an invalid mapping."
); );
const mfem::Vector &x_phys = mapping_context.mapping.physical_position; const mfem::Vector &x_phys = mapping_context.mapping.physical_position;
const double r_cyl_sq = x_phys(0) * x_phys(0) + x_phys(1) * x_phys(1); const double r_cyl_sq = x_phys(0) * x_phys(0) + x_phys(1) * x_phys(1);
const double weight = mapping_context.quadrature.weight; const double weight = mapping_context.quadrature.weight;
local_I += rho_hat * r_cyl_sq * weight; local_I += rho_hat * r_cyl_sq * weight;
} }

View File

@@ -13,8 +13,7 @@ namespace mean_field::utils {
const int dim = fem.mesh->Dimension(); const int dim = fem.mesh->Dimension();
x_ref = x_phys_target; x_ref = x_phys_target;
mapping::GridFunctionMappingEvaluator mapping_evaluator( mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
*fem.compactificationCoordinate
); );
mfem::Array<int> init_elem; mfem::Array<int> init_elem;
@@ -39,8 +38,7 @@ namespace mean_field::utils {
mapping::MappingPointContext context; mapping::MappingPointContext context;
MFEM_VERIFY( MFEM_VERIFY(
mapping_evaluator.EvaluatePoint(*T0, origin_ip[0], context) == mapping_evaluator.EvaluatePoint(*T0, origin_ip[0], context) == mapping::MappingStatus::valid,
mapping::MappingStatus::valid,
"Reference-point initialization encountered an invalid mapping." "Reference-point initialization encountered an invalid mapping."
); );
@@ -104,8 +102,7 @@ namespace mean_field::utils {
T->SetIntPoint(&ip); T->SetIntPoint(&ip);
mapping::MappingPointContext context; mapping::MappingPointContext context;
if (mapping_evaluator.EvaluatePoint(*T, ip, context) != if (mapping_evaluator.EvaluatePoint(*T, ip, context) != mapping::MappingStatus::valid) {
mapping::MappingStatus::valid) {
return false; return false;
} }
const mfem::Vector &current_x_phys = context.physical_position; const mfem::Vector &current_x_phys = context.physical_position;

View File

@@ -4,21 +4,25 @@ module;
module mean_field; module mean_field;
namespace mean_field::utils { namespace mean_field::utils {
DOMAINS operator|(DOMAINS lhs, DOMAINS rhs) { DOMAINS operator|(
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) | DOMAINS lhs,
static_cast<uint8_t>(rhs)); DOMAINS rhs
} ) {
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
}
DOMAINS operator&(DOMAINS lhs, DOMAINS rhs) { DOMAINS operator&(
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) & DOMAINS lhs,
static_cast<uint8_t>(rhs)); DOMAINS rhs
} ) {
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
}
int get_mesh_order(const mfem::Mesh &mesh) { int get_mesh_order(const mfem::Mesh &mesh) {
if (mesh.GetNodes() != nullptr) { if (mesh.GetNodes() != nullptr) {
return mesh.GetNodes()->FESpace()->GetMaxElementOrder(); return mesh.GetNodes()->FESpace()->GetMaxElementOrder();
} }
return 1; return 1;
} }
} // namespace mean_field::utils } // namespace mean_field::utils

View File

@@ -0,0 +1,100 @@
module;
#include <concepts>
#include <type_traits>
export module mean_field:eos.concepts;
export import :eos.relations;
export namespace mean_field::eos {
namespace detail {
template <typename EquationOfState, typename RelationType> struct ImplementsRelation : std::false_type { };
template <
typename EquationOfState,
typename Output,
typename... Inputs>
struct ImplementsRelation<
EquationOfState,
Relation<
Output,
Inputs...>> : std::bool_constant <
requires(
const std::remove_cvref_t<EquationOfState> &equationOfState,
QuantityValue<Inputs>... inputValues
) {
{equationOfState.evaluate(Relation<Output, Inputs...>{}, inputValues...)}
->std::same_as<QuantityValue<Output>>;
}>{};
template <typename EquationOfState, typename Catalog> struct ImplementsRelationCatalog : std::false_type { };
template <typename EquationOfState, typename... Relations>
struct ImplementsRelationCatalog<EquationOfState, RelationCatalog<Relations...>>
: std::bool_constant<(ImplementsRelation<EquationOfState, Relations>::value && ...)> { };
template <typename Candidate, typename = void> struct IsEquationOfStateModel : std::false_type { };
template <typename Candidate>
struct IsEquationOfStateModel<Candidate, std::void_t<typename std::remove_cvref_t<Candidate>::Relations>>
: std::bool_constant<
ValidRelationCatalog<typename std::remove_cvref_t<Candidate>::Relations> &&
ImplementsRelationCatalog<
std::remove_cvref_t<Candidate>,
typename std::remove_cvref_t<Candidate>::Relations>::value> { };
template <typename EquationOfState, typename RelationType, typename InputQuantity>
struct ImplementsPartialDerivative : std::false_type { };
template <
typename EquationOfState,
typename Output,
typename... Inputs,
typename InputQuantity>
struct ImplementsPartialDerivative<
EquationOfState,
Relation<
Output,
Inputs...>,
InputQuantity> : std::bool_constant <
(std::same_as<
InputQuantity,
Inputs> ||
...) &&
requires(
const std::remove_cvref_t<EquationOfState> &equationOfState,
QuantityValue<Inputs>... inputValues
) {
{equationOfState
.partialDerivative(Relation<Output, Inputs...>{}, WithRespectTo<InputQuantity>{}, inputValues...)}
->std::same_as<PartialDerivative<Output, InputQuantity>>;
}>{};
} // namespace detail
template <typename Candidate>
concept EquationOfStateModel = detail::IsEquationOfStateModel<Candidate>::value;
template <typename EquationOfState, typename RelationType>
concept SupportsRelation =
EquationOfStateModel<EquationOfState> && ThermodynamicRelationType<RelationType> &&
relationCatalogContains<typename std::remove_cvref_t<EquationOfState>::Relations, RelationType>;
template <typename EquationOfState, typename RelationType, typename InputQuantity>
concept SupportsPartialDerivative =
SupportsRelation<EquationOfState, RelationType> && ThermodynamicQuantityType<InputQuantity> &&
detail::ImplementsPartialDerivative<EquationOfState, RelationType, InputQuantity>::value;
template <typename Candidate>
concept StructureSeedEquationOfState =
EquationOfStateModel<Candidate> && SupportsRelation<Candidate, SpecificEnthalpyFromDensity>;
template <typename Candidate>
concept BarotropicClosureEquationOfState =
EquationOfStateModel<Candidate> && SupportsRelation<Candidate, DensityFromSpecificEnthalpy> &&
SupportsPartialDerivative<Candidate, DensityFromSpecificEnthalpy, quantity::SpecificEnthalpy>;
template <typename Candidate>
concept PressureForceEquationOfState =
EquationOfStateModel<Candidate> && SupportsRelation<Candidate, PressureFromSpecificEnthalpy> &&
SupportsPartialDerivative<Candidate, PressureFromSpecificEnthalpy, quantity::SpecificEnthalpy>;
} // namespace mean_field::eos

View File

@@ -1,16 +0,0 @@
export module mean_field:eos.base;
export namespace mean_field::eos {
class EquationOfState {
public:
virtual ~EquationOfState() = default;
[[nodiscard]] virtual double pressure_from_density(double density) const = 0;
[[nodiscard]] virtual double pressure_from_enthalpy(double enthalpy) const = 0;
[[nodiscard]] virtual double enthalpy_from_density(double density) const = 0;
[[nodiscard]] virtual double enthalpy_from_pressure(double pressure) const = 0;
[[nodiscard]] virtual double density_from_enthalpy(double enthalpy) const = 0;
[[nodiscard]] virtual double density_derivative_from_enthalpy(double enthalpy) const = 0;
[[nodiscard]] virtual double pressure_derivative_from_enthalpy(double enthalpy) const = 0;
[[nodiscard]] virtual double pressure_derivative_from_density(double density) const = 0;
};
} // namespace mean_field::eos

View File

@@ -0,0 +1,90 @@
module;
#include <stdexcept>
#include <string>
#include <utility>
export module mean_field:eos.evaluation;
export import :eos.concepts;
export namespace mean_field::eos {
enum class EvaluationErrorCode {
unsupported_relation,
unsupported_derivative,
wrong_input_count,
wrong_input_quantity,
nonfinite_input,
outside_domain,
nonfinite_result
};
class EvaluationError final : public std::domain_error {
public:
explicit EvaluationError(
const EvaluationErrorCode code,
std::string message
)
: std::domain_error(std::move(message)),
m_code(code) {
}
[[nodiscard]] EvaluationErrorCode code() const noexcept {
return m_code;
}
private:
EvaluationErrorCode m_code;
};
template <
ThermodynamicQuantityType OutputQuantity,
EquationOfStateModel EquationOfState,
QuantityValueType... InputValues>
requires SupportsRelation<
EquationOfState,
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>>
[[nodiscard]] constexpr QuantityValue<OutputQuantity> evaluate(
const EquationOfState &equationOfState,
const InputValues... inputValues
) noexcept(noexcept(equationOfState
.evaluate(
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>{},
inputValues...
))) {
return equationOfState.evaluate(Relation<OutputQuantity, QuantityOfT<InputValues>...>{}, inputValues...);
}
template <
ThermodynamicQuantityType OutputQuantity,
ThermodynamicQuantityType InputQuantity,
EquationOfStateModel EquationOfState,
QuantityValueType... InputValues>
requires SupportsPartialDerivative<
EquationOfState,
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>,
InputQuantity>
[[nodiscard]] constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
partialDerivative(
const EquationOfState &equationOfState,
const InputValues... inputValues
) noexcept(noexcept(equationOfState
.partialDerivative(
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>{},
WithRespectTo<InputQuantity>{},
inputValues...
))) {
return equationOfState.partialDerivative(
Relation<OutputQuantity, QuantityOfT<InputValues>...>{}, WithRespectTo<InputQuantity>{}, inputValues...
);
}
} // namespace mean_field::eos

View File

@@ -3,11 +3,18 @@ module;
#include <format> #include <format>
#include <stdexcept> #include <stdexcept>
export module mean_field:eos.polytrope; export module mean_field:eos.polytrope;
export import :eos.base; export import :eos.evaluation;
export namespace mean_field::eos { export namespace mean_field::eos {
class Polytrope final : public EquationOfState { class Polytrope final {
public: public:
using Relations = RelationCatalog<
PressureFromDensity,
PressureFromSpecificEnthalpy,
SpecificEnthalpyFromDensity,
SpecificEnthalpyFromPressure,
DensityFromSpecificEnthalpy>;
Polytrope( Polytrope(
const double polytropic_index, const double polytropic_index,
const double polytropic_constant const double polytropic_constant
@@ -49,82 +56,128 @@ export namespace mean_field::eos {
return m_enthalpy_scale; return m_enthalpy_scale;
} }
[[nodiscard]] double pressure_from_density(const double density) const override { [[nodiscard]] PressureValue evaluate(
validate_nonnegativity(density, "density"); PressureFromDensity,
if (density == 0.0) { const DensityValue density
return 0.0; ) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return PressureValue{0.0};
} }
return m_polytropic_constant * std::pow(density, 1.0 + 1.0 / m_polytropic_index); return PressureValue{m_polytropic_constant * std::pow(density.value(), 1.0 + 1.0 / m_polytropic_index)};
} }
[[nodiscard]] double enthalpy_from_density(const double density) const override { [[nodiscard]] SpecificEnthalpyValue evaluate(
validate_nonnegativity(density, "density"); SpecificEnthalpyFromDensity,
if (density == 0.0) { const DensityValue density
return 0.0; ) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return SpecificEnthalpyValue{0.0};
} }
return m_enthalpy_scale * std::pow(density, 1.0 / m_polytropic_index); return SpecificEnthalpyValue{m_enthalpy_scale * std::pow(density.value(), 1.0 / m_polytropic_index)};
} }
[[nodiscard]] double density_from_enthalpy(const double enthalpy) const override { [[nodiscard]] DensityValue evaluate(
validate_finite(enthalpy, "enthalpy"); DensityFromSpecificEnthalpy,
const SpecificEnthalpyValue specificEnthalpy
) const {
validate_finite(specificEnthalpy.value(), "specific enthalpy");
if (enthalpy <= 0.0) { if (specificEnthalpy.value() <= 0.0) {
return 0.0; return DensityValue{0.0};
} }
return std::pow(enthalpy / m_enthalpy_scale, m_polytropic_index); return DensityValue{std::pow(specificEnthalpy.value() / m_enthalpy_scale, m_polytropic_index)};
} }
[[nodiscard]] double pressure_from_enthalpy(const double enthalpy) const override { [[nodiscard]] PressureValue evaluate(
validate_finite(enthalpy, "enthalpy"); PressureFromSpecificEnthalpy,
const SpecificEnthalpyValue specificEnthalpy
) const {
const DensityValue density = evaluate(DensityFromSpecificEnthalpy{}, specificEnthalpy);
if (enthalpy <= 0.0) { if (specificEnthalpy.value() <= 0.0) {
return 0.0; return PressureValue{0.0};
} }
return density_from_enthalpy(enthalpy) * enthalpy / (m_polytropic_index + 1.0); return PressureValue{density.value() * specificEnthalpy.value() / (m_polytropic_index + 1.0)};
} }
[[nodiscard]] double density_derivative_from_enthalpy(const double enthalpy) const override { [[nodiscard]] SpecificEnthalpyValue evaluate(
validate_finite(enthalpy, "enthalpy"); SpecificEnthalpyFromPressure,
if (enthalpy < 0.0) { const PressureValue pressure
return 0.0; ) const {
validate_nonnegativity(pressure.value(), "pressure");
if (pressure.value() == 0.0) {
return SpecificEnthalpyValue{0.0};
} }
if (enthalpy == 0.0) { const double indexPlusOne = m_polytropic_index + 1.0;
return m_polytropic_index == 1.0 ? 1.0 / m_enthalpy_scale : 0.0;
}
return m_polytropic_index / m_enthalpy_scale * return SpecificEnthalpyValue{
std::pow(enthalpy / m_enthalpy_scale, m_polytropic_index - 1.0); indexPlusOne * std::pow(m_polytropic_constant, m_polytropic_index / indexPlusOne) *
std::pow(pressure.value(), 1.0 / indexPlusOne)
};
} }
[[nodiscard]] double pressure_derivative_from_enthalpy(const double enthalpy) const override { [[nodiscard]] PartialDerivative<
validate_finite(enthalpy, "enthalpy"); quantity::Density,
quantity::SpecificEnthalpy>
if (enthalpy <= 0.0) { partialDerivative(
return 0.0; DensityFromSpecificEnthalpy,
WithRespectTo<quantity::SpecificEnthalpy>,
const SpecificEnthalpyValue specificEnthalpy
) const {
validate_finite(specificEnthalpy.value(), "specific enthalpy");
if (specificEnthalpy.value() < 0.0) {
return PartialDerivative<quantity::Density, quantity::SpecificEnthalpy>{0.0};
} }
return density_from_enthalpy(enthalpy); if (specificEnthalpy.value() == 0.0) {
} return PartialDerivative<quantity::Density, quantity::SpecificEnthalpy>{
m_polytropic_index == 1.0 ? 1.0 / m_enthalpy_scale : 0.0
[[nodiscard]] double pressure_derivative_from_density(const double density) const override { };
validate_nonnegativity(density, "density");
if (density == 0.0) {
return 0.0;
} }
return m_polytropic_constant * (1.0 + 1.0 / m_polytropic_index) * return PartialDerivative<quantity::Density, quantity::SpecificEnthalpy>{
std::pow(density, 1.0 / m_polytropic_index); m_polytropic_index / m_enthalpy_scale *
std::pow(specificEnthalpy.value() / m_enthalpy_scale, m_polytropic_index - 1.0)
};
} }
[[nodiscard]] double enthalpy_from_pressure(const double pressure) const override { [[nodiscard]] PartialDerivative<
validate_nonnegativity(pressure, "pressure"); quantity::Pressure,
const double np1 = m_polytropic_index + 1; quantity::SpecificEnthalpy>
return np1 * std::pow(m_polytropic_constant, m_polytropic_index / np1) * std::pow(pressure, 1.0 / np1); partialDerivative(
PressureFromSpecificEnthalpy,
WithRespectTo<quantity::SpecificEnthalpy>,
const SpecificEnthalpyValue specificEnthalpy
) const {
const DensityValue density = evaluate(DensityFromSpecificEnthalpy{}, specificEnthalpy);
return PartialDerivative<quantity::Pressure, quantity::SpecificEnthalpy>{density.value()};
}
[[nodiscard]] PartialDerivative<
quantity::Pressure,
quantity::Density>
partialDerivative(
PressureFromDensity,
WithRespectTo<quantity::Density>,
const DensityValue density
) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return PartialDerivative<quantity::Pressure, quantity::Density>{0.0};
}
return PartialDerivative<quantity::Pressure, quantity::Density>{
m_polytropic_constant * (1.0 + 1.0 / m_polytropic_index) *
std::pow(density.value(), 1.0 / m_polytropic_index)
};
} }
private: private:
@@ -133,12 +186,12 @@ export namespace mean_field::eos {
const char *quantity const char *quantity
) { ) {
if (!std::isfinite(value)) { if (!std::isfinite(value)) {
throw std::domain_error( throw EvaluationError(
std::format( EvaluationErrorCode::nonfinite_input, std::format(
"The {} must be finite. Instead a value of {} has been " "The {} must be finite. Instead a value of {} has been "
"provided", "provided",
quantity, value quantity, value
) )
); );
} }
} }
@@ -149,13 +202,13 @@ export namespace mean_field::eos {
) { ) {
validate_finite(value, quantity); validate_finite(value, quantity);
if (value < 0.0) { if (value < 0.0) {
throw std::domain_error( throw EvaluationError(
std::format( EvaluationErrorCode::outside_domain, std::format(
"The {} must be non-negative. Instead a value of {} " "The {} must be non-negative. Instead a value of {} "
"has been " "has been "
"provided", "provided",
quantity, value quantity, value
) )
); );
} }
} }

View File

@@ -0,0 +1,128 @@
module;
#include <memory>
#include <type_traits>
export module mean_field:eos.pressure_surface;
export import :eos.evaluation;
export namespace mean_field::eos {
namespace detail {
template <
ThermodynamicQuantityType InputQuantity,
typename SurfaceState>
[[nodiscard]] constexpr auto pressureSurfaceRelationInput(
const PressureValue targetPressure,
const SurfaceState &state
) {
if constexpr (std::same_as<InputQuantity, quantity::Pressure>) {
return targetPressure;
} else {
return state.value(InputQuantity{});
}
}
template <typename RelationType> struct PressureSurfaceRelationOperations;
template <typename CarrierQuantity, typename... InputQuantities>
struct PressureSurfaceRelationOperations<Relation<CarrierQuantity, InputQuantities...>> {
template <
typename EquationOfState,
typename SurfaceState>
[[nodiscard]] static QuantityValue<CarrierQuantity> requiredCarrierValue(
const EquationOfState &equationOfState,
const PressureValue targetPressure,
const SurfaceState &state
) {
return evaluate<CarrierQuantity>(
equationOfState, pressureSurfaceRelationInput<InputQuantities>(targetPressure, state)...
);
}
template <
typename InputQuantity,
typename EquationOfState,
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] static double inputJacobianContribution(
const EquationOfState &equationOfState,
const PressureValue targetPressure,
const SurfaceState &state,
const SurfaceVariation &variation
) {
if constexpr (std::same_as<InputQuantity, quantity::Pressure>) {
return 0.0;
} else {
const auto derivative = partialDerivative<CarrierQuantity, InputQuantity>(
equationOfState, pressureSurfaceRelationInput<InputQuantities>(targetPressure, state)...
);
return derivative.value() * variation.value(InputQuantity{}).value();
}
}
template <
typename EquationOfState,
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] static double carrierCorrectionJacobianAction(
const EquationOfState &equationOfState,
const PressureValue targetPressure,
const SurfaceState &state,
const SurfaceVariation &variation
) {
return (
0.0 + ... +
inputJacobianContribution<InputQuantities>(equationOfState, targetPressure, state, variation)
);
}
};
} // namespace detail
/*
* EOS-owned resolution of a constant-pressure condition into the carrier
* quantity used by an equation formulation. No field or solver concepts
* enter this type.
*/
template <EquationOfStateModel EquationOfState, ThermodynamicRelationType SelectedRelation>
class ResolvedPressureSurfaceRelation final {
public:
using RelationType = SelectedRelation;
using CarrierQuantity = RelationOutputT<RelationType>;
ResolvedPressureSurfaceRelation(
const EquationOfState &equationOfState,
const PressureValue targetPressure
) noexcept
: m_equationOfState(std::addressof(equationOfState)),
m_targetPressure(targetPressure) {
}
[[nodiscard]] PressureValue targetPressure() const noexcept {
return m_targetPressure;
}
template <typename SurfaceState>
[[nodiscard]] QuantityValue<CarrierQuantity> requiredCarrierValue(const SurfaceState &state) const {
return detail::PressureSurfaceRelationOperations<RelationType>::requiredCarrierValue(
*m_equationOfState, m_targetPressure, state
);
}
template <
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] double carrierCorrectionJacobianAction(
const SurfaceState &state,
const SurfaceVariation &variation
) const {
return detail::PressureSurfaceRelationOperations<RelationType>::carrierCorrectionJacobianAction(
*m_equationOfState, m_targetPressure, state, variation
);
}
private:
const EquationOfState *m_equationOfState;
PressureValue m_targetPressure;
};
} // namespace mean_field::eos

View File

@@ -0,0 +1,235 @@
module;
#include <compare>
#include <concepts>
#include <string_view>
#include <type_traits>
export module mean_field:eos.quantities;
export namespace mean_field::eos {
struct ThermodynamicQuantity { };
template <typename Candidate>
concept ThermodynamicQuantityType =
std::same_as<Candidate, std::remove_cv_t<Candidate>> && std::derived_from<Candidate, ThermodynamicQuantity>;
namespace quantity {
struct Density final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "density";
};
struct Pressure final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "pressure";
};
struct SpecificEnthalpy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "specific_enthalpy";
};
} // namespace quantity
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template <ThermodynamicQuantityType Quantity> class QuantityValue final {
public:
explicit constexpr QuantityValue(const double value) noexcept : m_value(value) {
}
[[nodiscard]] constexpr double value() const noexcept {
return m_value;
}
[[nodiscard]] friend constexpr bool operator==(
const QuantityValue &,
const QuantityValue &
) noexcept = default;
friend constexpr QuantityValue<Quantity> operator+(
const QuantityValue<Quantity> &lhs,
const QuantityValue<Quantity> &rhs
) noexcept {
return QuantityValue<Quantity>{lhs.m_value + rhs.m_value};
}
friend constexpr QuantityValue<Quantity> operator-(
const QuantityValue<Quantity> &lhs,
const QuantityValue<Quantity> &rhs
) noexcept {
return QuantityValue<Quantity>{lhs.m_value - rhs.m_value};
}
template <Numeric rhsT>
friend constexpr QuantityValue<Quantity> operator*(
const QuantityValue<Quantity> &lhs,
rhsT rhs
) noexcept {
return QuantityValue<Quantity>{lhs.m_value * static_cast<double>(rhs)};
}
template <Numeric lhsT>
friend constexpr QuantityValue<Quantity> operator*(
lhsT lhs,
const QuantityValue<Quantity> &rhs
) noexcept {
return QuantityValue<Quantity>{static_cast<double>(lhs) * rhs.m_value};
}
template <Numeric rhsT>
friend constexpr QuantityValue<Quantity> operator/(
const QuantityValue<Quantity> &lhs,
rhsT rhs
) noexcept {
return QuantityValue<Quantity>{lhs.m_value / static_cast<double>(rhs)};
}
template <Numeric compT>
friend constexpr std::partial_ordering operator<=>(
const QuantityValue<Quantity> &lhs,
compT rhs
) noexcept {
return lhs.m_value <=> static_cast<double>(rhs);
}
template <Numeric compT>
friend constexpr std::partial_ordering operator<=>(
compT lhs,
const QuantityValue<Quantity> &rhs
) noexcept {
return static_cast<double>(lhs) <=> rhs.m_value;
}
friend constexpr std::partial_ordering operator<=>(
const QuantityValue<Quantity> &lhs,
const QuantityValue<Quantity> &rhs
) noexcept {
return lhs.m_value <=> rhs.m_value;
}
private:
double m_value;
};
using DensityValue = QuantityValue<quantity::Density>;
using PressureValue = QuantityValue<quantity::Pressure>;
using SpecificEnthalpyValue = QuantityValue<quantity::SpecificEnthalpy>;
template <typename Candidate> struct IsQuantityValue : std::false_type { };
template <ThermodynamicQuantityType Quantity> struct IsQuantityValue<QuantityValue<Quantity>> : std::true_type { };
template <typename Candidate>
concept QuantityValueType = IsQuantityValue<std::remove_cvref_t<Candidate>>::value;
template <typename Candidate> struct QuantityOf;
template <ThermodynamicQuantityType Quantity> struct QuantityOf<QuantityValue<Quantity>> {
using Type = Quantity;
};
template <QuantityValueType Value> using QuantityOfT = typename QuantityOf<std::remove_cvref_t<Value>>::Type;
template <ThermodynamicQuantityType OutputQuantity, ThermodynamicQuantityType InputQuantity>
class PartialDerivative final {
public:
explicit constexpr PartialDerivative(const double value) noexcept : m_value(value) {
}
[[nodiscard]] constexpr double value() const noexcept {
return m_value;
}
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator+(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept;
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator-(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept;
template <Numeric rhsT>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator*(
const PartialDerivative<
OutputQuantity,
InputQuantity> &,
rhsT
) noexcept;
template <Numeric lhsT>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator*(
lhsT,
const PartialDerivative<
OutputQuantity,
InputQuantity> &
) noexcept;
template <Numeric rhsT>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator/(
const PartialDerivative<
OutputQuantity,
InputQuantity> &,
rhsT
) noexcept;
template <Numeric cmpT>
friend constexpr std::partial_ordering operator<=>(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
cmpT rhs
) noexcept {
return lhs.m_value <=> static_cast<double>(rhs);
}
template <Numeric cmpT>
friend constexpr std::partial_ordering operator<=>(
cmpT lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept {
return static_cast<double>(lhs) <=> rhs.m_value;
}
friend constexpr std::partial_ordering operator<=>(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept {
return lhs.m_value <=> rhs.m_value;
}
private:
double m_value;
};
template <ThermodynamicQuantityType Quantity> struct WithRespectTo final { };
} // namespace mean_field::eos

View File

@@ -0,0 +1,93 @@
module;
#include <concepts>
#include <cstddef>
#include <tuple>
#include <type_traits>
export module mean_field:eos.relations;
export import :eos.quantities;
export namespace mean_field::eos {
template <typename... Quantities> struct QuantityList final { };
template <typename Output, typename... Inputs> struct Relation final {
using OutputQuantity = Output;
using InputQuantities = QuantityList<Inputs...>;
static constexpr std::size_t inputCount = sizeof...(Inputs);
};
template <typename... Relations> struct RelationCatalog final {
static constexpr std::size_t size = sizeof...(Relations);
};
namespace detail {
template <typename... Types> struct TypesAreUnique;
template <typename Candidate> struct IsThermodynamicRelation : std::false_type { };
template <typename Output, typename... Inputs>
struct IsThermodynamicRelation<Relation<Output, Inputs...>>
: std::bool_constant<
ThermodynamicQuantityType<Output> && (ThermodynamicQuantityType<Inputs> && ...) &&
TypesAreUnique<Inputs...>::value> { };
template <typename... Types> struct TypesAreUnique : std::true_type { };
template <typename First, typename... Remaining>
struct TypesAreUnique<First, Remaining...>
: std::bool_constant<(!std::same_as<First, Remaining> && ...) && TypesAreUnique<Remaining...>::value> { };
template <typename Candidate> struct IsValidRelationCatalog : std::false_type { };
template <typename... Relations>
struct IsValidRelationCatalog<RelationCatalog<Relations...>>
: std::bool_constant<
(sizeof...(Relations) > 0) && (IsThermodynamicRelation<Relations>::value && ...) &&
TypesAreUnique<Relations...>::value> { };
template <typename Catalog, typename RelationType> struct CatalogContainsRelation : std::false_type { };
template <typename... Relations, typename RelationType>
struct CatalogContainsRelation<RelationCatalog<Relations...>, RelationType>
: std::bool_constant<(std::same_as<RelationType, Relations> || ...)> { };
template <typename RelationType, typename Quantity> struct RelationContainsInput : std::false_type { };
template <typename Output, typename... Inputs, typename Quantity>
struct RelationContainsInput<Relation<Output, Inputs...>, Quantity>
: std::bool_constant<(std::same_as<Quantity, Inputs> || ...)> { };
template <std::size_t Index, typename Quantities> struct QuantityAt;
template <std::size_t Index, typename... Quantities> struct QuantityAt<Index, QuantityList<Quantities...>> {
using Type = std::tuple_element_t<Index, std::tuple<Quantities...>>;
};
} // namespace detail
template <typename Candidate>
concept ThermodynamicRelationType = detail::IsThermodynamicRelation<std::remove_cv_t<Candidate>>::value;
template <typename Candidate>
concept ValidRelationCatalog = detail::IsValidRelationCatalog<std::remove_cv_t<Candidate>>::value;
template <typename Catalog, typename RelationType>
inline constexpr bool relationCatalogContains =
detail::CatalogContainsRelation<std::remove_cv_t<Catalog>, std::remove_cv_t<RelationType>>::value;
template <typename RelationType, typename Quantity>
inline constexpr bool relationContainsInput =
detail::RelationContainsInput<std::remove_cv_t<RelationType>, std::remove_cv_t<Quantity>>::value;
template <ThermodynamicRelationType RelationType> using RelationOutputT = typename RelationType::OutputQuantity;
template <std::size_t Index, ThermodynamicRelationType RelationType>
using RelationInputT = typename detail::QuantityAt<Index, typename RelationType::InputQuantities>::Type;
using PressureFromDensity = Relation<quantity::Pressure, quantity::Density>;
using PressureFromSpecificEnthalpy = Relation<quantity::Pressure, quantity::SpecificEnthalpy>;
using SpecificEnthalpyFromDensity = Relation<quantity::SpecificEnthalpy, quantity::Density>;
using SpecificEnthalpyFromPressure = Relation<quantity::SpecificEnthalpy, quantity::Pressure>;
using DensityFromSpecificEnthalpy = Relation<quantity::Density, quantity::SpecificEnthalpy>;
} // namespace mean_field::eos

View File

@@ -0,0 +1,645 @@
module;
#include <array>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
export module mean_field:eos.runtime;
export import :eos.evaluation;
export namespace mean_field::eos {
class ThermodynamicQuantityId final {
public:
explicit constexpr ThermodynamicQuantityId(const std::string_view name) noexcept : m_name(name) {
}
[[nodiscard]] constexpr std::string_view name() const noexcept {
return m_name;
}
[[nodiscard]] friend constexpr bool operator==(
const ThermodynamicQuantityId &,
const ThermodynamicQuantityId &
) noexcept = default;
private:
std::string_view m_name;
};
template <typename Quantity>
concept RuntimeIdentifiedThermodynamicQuantity = ThermodynamicQuantityType<Quantity> && requires {
{ Quantity::identifier } -> std::convertible_to<std::string_view>;
} && (std::string_view{Quantity::identifier}.size() > 0);
template <RuntimeIdentifiedThermodynamicQuantity Quantity>
inline constexpr ThermodynamicQuantityId thermodynamicQuantityId{std::string_view{Quantity::identifier}};
struct RuntimeQuantityValue final {
ThermodynamicQuantityId quantity;
double value;
};
struct RuntimeRelationDescriptor final {
ThermodynamicQuantityId outputQuantity;
std::span<const ThermodynamicQuantityId> inputQuantities;
std::uint64_t partialDerivativeMask;
[[nodiscard]] constexpr bool hasPartialDerivative(const std::size_t inputIndex) const noexcept {
return inputIndex < inputQuantities.size() &&
(partialDerivativeMask & (std::uint64_t{1} << inputIndex)) != 0;
}
};
namespace detail {
template <typename RelationType> struct HasRuntimeQuantityIdentifiers : std::false_type { };
template <typename Output, typename... Inputs>
struct HasRuntimeQuantityIdentifiers<Relation<Output, Inputs...>>
: std::bool_constant<
RuntimeIdentifiedThermodynamicQuantity<Output> &&
(RuntimeIdentifiedThermodynamicQuantity<Inputs> && ...)> { };
template <typename RelationType> struct RuntimeRelationQuantities;
template <typename Output, typename... Inputs> struct RuntimeRelationQuantities<Relation<Output, Inputs...>> {
using Type = std::tuple<Output, Inputs...>;
};
template <typename... Relations>
using RuntimeCatalogQuantityTuple =
decltype(std::tuple_cat(std::declval<typename RuntimeRelationQuantities<Relations>::Type>()...));
template <
typename FirstQuantity,
typename SecondQuantity>
[[nodiscard]] consteval bool runtimeQuantityIdentifiersAreCompatible() {
if constexpr (std::same_as<FirstQuantity, SecondQuantity>) {
return true;
} else {
return thermodynamicQuantityId<FirstQuantity> != thermodynamicQuantityId<SecondQuantity>;
}
}
template <
typename QuantityTuple,
std::size_t First,
std::size_t... Offsets>
[[nodiscard]] consteval bool runtimeQuantityIdentifierIsUnambiguous(std::index_sequence<Offsets...>) {
return (
runtimeQuantityIdentifiersAreCompatible<
std::tuple_element_t<First, QuantityTuple>,
std::tuple_element_t<First + 1 + Offsets, QuantityTuple>>() &&
...
);
}
template <
typename QuantityTuple,
std::size_t... Indices>
[[nodiscard]] consteval bool runtimeQuantityIdentifiersAreUnambiguous(std::index_sequence<Indices...>) {
return (
runtimeQuantityIdentifierIsUnambiguous<QuantityTuple, Indices>(
std::make_index_sequence<std::tuple_size_v<QuantityTuple> - Indices - 1>{}
) &&
...
);
}
template <bool QuantitiesAreIdentified, typename... Relations>
struct RuntimeRelationsAreSupported : std::false_type { };
template <typename... Relations>
struct RuntimeRelationsAreSupported<true, Relations...>
: std::bool_constant<runtimeQuantityIdentifiersAreUnambiguous<RuntimeCatalogQuantityTuple<Relations...>>(
std::make_index_sequence<std::tuple_size_v<RuntimeCatalogQuantityTuple<Relations...>>>{}
)> { };
template <typename Catalog> struct RuntimeCatalogIsSupported : std::false_type { };
template <typename... Relations>
struct RuntimeCatalogIsSupported<RelationCatalog<Relations...>>
: RuntimeRelationsAreSupported<(HasRuntimeQuantityIdentifiers<Relations>::value && ...), Relations...> { };
} // namespace detail
template <typename Candidate>
concept RuntimeEquationOfStateModel =
EquationOfStateModel<Candidate> &&
detail::RuntimeCatalogIsSupported<typename std::remove_cvref_t<Candidate>::Relations>::value;
namespace detail {
template <typename EquationOfState, typename RelationType> struct RuntimeRelationStorage;
template <typename EquationOfState, typename Output, typename... Inputs>
struct RuntimeRelationStorage<EquationOfState, Relation<Output, Inputs...>> {
using RelationType = Relation<Output, Inputs...>;
static_assert(
sizeof...(Inputs) <= 64,
"Runtime EOS relation descriptors support at most 64 inputs."
);
inline static constexpr std::array<ThermodynamicQuantityId, sizeof...(Inputs)> inputQuantityIds{
thermodynamicQuantityId<Inputs>...
};
template <std::size_t... Indices>
[[nodiscard]] static consteval std::uint64_t makePartialDerivativeMask(std::index_sequence<Indices...>) {
using InputTuple = std::tuple<Inputs...>;
return (
std::uint64_t{0} | ... |
(SupportsPartialDerivative<EquationOfState, RelationType, std::tuple_element_t<Indices, InputTuple>>
? (std::uint64_t{1} << Indices)
: std::uint64_t{0})
);
}
inline static constexpr std::uint64_t partialDerivativeMask =
makePartialDerivativeMask(std::index_sequence_for<Inputs...>{});
inline static constexpr RuntimeRelationDescriptor descriptor{
thermodynamicQuantityId<Output>, std::span<const ThermodynamicQuantityId>{inputQuantityIds},
partialDerivativeMask
};
};
template <typename EquationOfState, typename Catalog> struct RuntimeCatalogStorage;
template <typename EquationOfState, typename... Relations>
struct RuntimeCatalogStorage<EquationOfState, RelationCatalog<Relations...>> {
inline static constexpr std::array descriptors{
RuntimeRelationStorage<EquationOfState, Relations>::descriptor...
};
};
[[nodiscard]] inline std::expected<
double,
EvaluationError>
runtimeEvaluationFailure(
const EvaluationErrorCode code,
std::string message
) {
return std::unexpected<EvaluationError>{EvaluationError{code, std::move(message)}};
}
template <
typename EquationOfState,
typename Output,
typename... Inputs>
[[nodiscard]] std::expected<
double,
EvaluationError>
evaluateRuntimeRelation(
const EquationOfState &equationOfState,
Relation<
Output,
Inputs...>,
const std::span<const RuntimeQuantityValue> inputValues
) {
const auto invoke = [&]<std::size_t... Indices>(std::index_sequence<Indices...>) {
return eos::evaluate<Output>(equationOfState, QuantityValue<Inputs>{inputValues[Indices].value}...)
.value();
};
try {
return invoke(std::index_sequence_for<Inputs...>{});
} catch (const EvaluationError &error) {
return std::unexpected<EvaluationError>{error};
}
}
template <
typename InputQuantity,
typename EquationOfState,
typename Output,
typename... Inputs>
[[nodiscard]] bool tryRuntimePartialDerivative(
const EquationOfState &equationOfState,
Relation<
Output,
Inputs...> relation,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues,
std::expected<
double,
EvaluationError> &result
) {
if (withRespectTo != thermodynamicQuantityId<InputQuantity>) {
return false;
}
if constexpr (SupportsPartialDerivative<EquationOfState, Relation<Output, Inputs...>, InputQuantity>) {
const auto invoke = [&]<std::size_t... Indices>(std::index_sequence<Indices...>) {
return eos::partialDerivative<Output, InputQuantity>(
equationOfState, QuantityValue<Inputs>{inputValues[Indices].value}...
)
.value();
};
try {
result = invoke(std::index_sequence_for<Inputs...>{});
} catch (const EvaluationError &error) {
result = std::unexpected<EvaluationError>{error};
}
} else {
result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_derivative,
"The requested EOS partial derivative is not available."
);
}
return true;
}
template <
typename EquationOfState,
typename Output,
typename... Inputs>
[[nodiscard]] std::expected<
double,
EvaluationError>
evaluateRuntimePartialDerivative(
const EquationOfState &equationOfState,
Relation<
Output,
Inputs...> relation,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues
) {
std::expected<double, EvaluationError> result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_derivative,
"The requested quantity is not an input to the EOS relation."
);
const bool matched =
(tryRuntimePartialDerivative<Inputs>(equationOfState, relation, withRespectTo, inputValues, result) ||
...);
static_cast<void>(matched);
return result;
}
template <
typename EquationOfState,
typename RelationType>
[[nodiscard]] bool runtimeRelationMatches(
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) {
const RuntimeRelationDescriptor &descriptor =
RuntimeRelationStorage<EquationOfState, RelationType>::descriptor;
if (descriptor.outputQuantity != outputQuantity ||
descriptor.inputQuantities.size() != inputValues.size()) {
return false;
}
for (std::size_t index = 0; index < inputValues.size(); ++index) {
if (descriptor.inputQuantities[index] != inputValues[index].quantity) {
return false;
}
}
return true;
}
template <typename EquationOfState, typename Catalog> struct RuntimeCatalogDispatch;
template <typename EquationOfState, typename... Relations>
struct RuntimeCatalogDispatch<EquationOfState, RelationCatalog<Relations...>> {
[[nodiscard]] static std::expected<
double,
EvaluationError>
evaluate(
const void *object,
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) {
const auto &equationOfState = *static_cast<const EquationOfState *>(object);
std::expected<double, EvaluationError> result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available."
);
const bool matched =
((runtimeRelationMatches<EquationOfState, Relations>(outputQuantity, inputValues)
? (result = evaluateRuntimeRelation(equationOfState, Relations{}, inputValues), true)
: false) ||
...);
static_cast<void>(matched);
return result;
}
[[nodiscard]] static std::expected<
double,
EvaluationError>
partialDerivative(
const void *object,
const ThermodynamicQuantityId outputQuantity,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues
) {
const auto &equationOfState = *static_cast<const EquationOfState *>(object);
std::expected<double, EvaluationError> result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available."
);
const bool matched =
((runtimeRelationMatches<EquationOfState, Relations>(outputQuantity, inputValues)
? (result = evaluateRuntimePartialDerivative(
equationOfState, Relations{}, withRespectTo, inputValues
),
true)
: false) ||
...);
static_cast<void>(matched);
return result;
}
};
template <RuntimeEquationOfStateModel EquationOfState>
using RuntimeAdapter = RuntimeCatalogDispatch<EquationOfState, typename EquationOfState::Relations>;
template <RuntimeEquationOfStateModel EquationOfState>
[[nodiscard]] constexpr std::span<const RuntimeRelationDescriptor> runtimeRelationDescriptors() noexcept {
return RuntimeCatalogStorage<EquationOfState, typename EquationOfState::Relations>::descriptors;
}
} // namespace detail
class EquationOfStateView final {
public:
template <RuntimeEquationOfStateModel EquationOfState>
explicit EquationOfStateView(EquationOfState &equationOfState) noexcept
: m_object(std::addressof(equationOfState)),
m_relations(detail::runtimeRelationDescriptors<std::remove_cv_t<EquationOfState>>()),
m_evaluate(&detail::RuntimeAdapter<std::remove_cv_t<EquationOfState>>::evaluate),
m_partialDerivative(&detail::RuntimeAdapter<std::remove_cv_t<EquationOfState>>::partialDerivative) {
}
[[nodiscard]] std::span<const RuntimeRelationDescriptor> relations() const noexcept {
return m_relations;
}
[[nodiscard]] bool supports(
const ThermodynamicQuantityId outputQuantity,
const std::span<const ThermodynamicQuantityId> inputQuantities
) const noexcept {
return findRelation(outputQuantity, inputQuantities) != nullptr;
}
template <
RuntimeIdentifiedThermodynamicQuantity OutputQuantity,
RuntimeIdentifiedThermodynamicQuantity... InputQuantities>
[[nodiscard]] bool supports() const noexcept {
constexpr std::array<ThermodynamicQuantityId, sizeof...(InputQuantities)> inputs{
thermodynamicQuantityId<InputQuantities>...
};
return supports(thermodynamicQuantityId<OutputQuantity>, std::span<const ThermodynamicQuantityId>{inputs});
}
[[nodiscard]] std::expected<
RuntimeQuantityValue,
EvaluationError>
tryEvaluate(
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) const {
const auto validation = validateRelationRequest(outputQuantity, inputValues);
if (!validation.has_value()) {
return std::unexpected<EvaluationError>{validation.error()};
}
auto result = m_evaluate(m_object, outputQuantity, inputValues);
if (!result.has_value()) {
return std::unexpected<EvaluationError>{result.error()};
}
return RuntimeQuantityValue{outputQuantity, *result};
}
template <
RuntimeIdentifiedThermodynamicQuantity OutputQuantity,
QuantityValueType... InputValues>
[[nodiscard]] std::expected<
QuantityValue<OutputQuantity>,
EvaluationError>
tryEvaluate(const InputValues... inputValues) const {
constexpr bool inputsHaveRuntimeIdentifiers =
(RuntimeIdentifiedThermodynamicQuantity<QuantityOfT<InputValues>> && ...);
static_assert(inputsHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier.");
const std::array<RuntimeQuantityValue, sizeof...(InputValues)> runtimeInputs{
RuntimeQuantityValue{thermodynamicQuantityId<QuantityOfT<InputValues>>, inputValues.value()}...
};
auto result = tryEvaluate(
thermodynamicQuantityId<OutputQuantity>, std::span<const RuntimeQuantityValue>{runtimeInputs}
);
if (!result.has_value()) {
return std::unexpected<EvaluationError>{result.error()};
}
return QuantityValue<OutputQuantity>{result->value};
}
[[nodiscard]] std::expected<
double,
EvaluationError>
tryPartialDerivative(
const ThermodynamicQuantityId outputQuantity,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues
) const {
const auto validation = validateRelationRequest(outputQuantity, inputValues);
if (!validation.has_value()) {
return std::unexpected<EvaluationError>{validation.error()};
}
const RuntimeRelationDescriptor &descriptor = **validation;
bool derivativeAvailable = false;
for (std::size_t index = 0; index < descriptor.inputQuantities.size(); ++index) {
if (descriptor.inputQuantities[index] == withRespectTo) {
derivativeAvailable = descriptor.hasPartialDerivative(index);
break;
}
}
if (!derivativeAvailable) {
return runtimeFailure<double>(
EvaluationErrorCode::unsupported_derivative,
"The requested EOS partial derivative is not available."
);
}
return m_partialDerivative(m_object, outputQuantity, withRespectTo, inputValues);
}
template <
RuntimeIdentifiedThermodynamicQuantity OutputQuantity,
RuntimeIdentifiedThermodynamicQuantity InputQuantity,
QuantityValueType... InputValues>
[[nodiscard]] std::expected<
PartialDerivative<
OutputQuantity,
InputQuantity>,
EvaluationError>
tryPartialDerivative(const InputValues... inputValues) const {
constexpr bool inputsHaveRuntimeIdentifiers =
(RuntimeIdentifiedThermodynamicQuantity<QuantityOfT<InputValues>> && ...);
static_assert(inputsHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier.");
const std::array<RuntimeQuantityValue, sizeof...(InputValues)> runtimeInputs{
RuntimeQuantityValue{thermodynamicQuantityId<QuantityOfT<InputValues>>, inputValues.value()}...
};
auto result = tryPartialDerivative(
thermodynamicQuantityId<OutputQuantity>, thermodynamicQuantityId<InputQuantity>,
std::span<const RuntimeQuantityValue>{runtimeInputs}
);
if (!result.has_value()) {
return std::unexpected<EvaluationError>{result.error()};
}
return PartialDerivative<OutputQuantity, InputQuantity>{*result};
}
private:
using RuntimeEvaluateFunction = std::expected<
double,
EvaluationError> (*)(
const void *,
ThermodynamicQuantityId,
std::span<const RuntimeQuantityValue>
);
using RuntimePartialDerivativeFunction = std::expected<
double,
EvaluationError> (*)(
const void *,
ThermodynamicQuantityId,
ThermodynamicQuantityId,
std::span<const RuntimeQuantityValue>
);
[[nodiscard]] const RuntimeRelationDescriptor *findRelation(
const ThermodynamicQuantityId outputQuantity,
const std::span<const ThermodynamicQuantityId> inputQuantities
) const noexcept {
for (const RuntimeRelationDescriptor &descriptor : m_relations) {
if (descriptor.outputQuantity != outputQuantity ||
descriptor.inputQuantities.size() != inputQuantities.size()) {
continue;
}
bool matches = true;
for (std::size_t index = 0; index < inputQuantities.size(); ++index) {
if (descriptor.inputQuantities[index] != inputQuantities[index]) {
matches = false;
break;
}
}
if (matches) {
return std::addressof(descriptor);
}
}
return nullptr;
}
[[nodiscard]] std::expected<
const RuntimeRelationDescriptor *,
EvaluationError>
validateRelationRequest(
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) const {
bool outputAvailable = false;
bool inputCountAvailable = false;
for (const RuntimeRelationDescriptor &descriptor : m_relations) {
if (descriptor.outputQuantity != outputQuantity) {
continue;
}
outputAvailable = true;
if (descriptor.inputQuantities.size() != inputValues.size()) {
continue;
}
inputCountAvailable = true;
bool matches = true;
for (std::size_t index = 0; index < inputValues.size(); ++index) {
if (descriptor.inputQuantities[index] != inputValues[index].quantity) {
matches = false;
break;
}
}
if (matches) {
return std::addressof(descriptor);
}
}
if (!outputAvailable) {
return runtimeFailure<const RuntimeRelationDescriptor *>(
EvaluationErrorCode::unsupported_relation,
"The EOS does not provide a relation for output quantity '" + std::string{outputQuantity.name()} +
"'."
);
}
if (!inputCountAvailable) {
return runtimeFailure<const RuntimeRelationDescriptor *>(
EvaluationErrorCode::wrong_input_count, "No EOS relation for output quantity '" +
std::string{outputQuantity.name()} +
"' accepts the supplied number of inputs."
);
}
return runtimeFailure<const RuntimeRelationDescriptor *>(
EvaluationErrorCode::wrong_input_quantity, "No EOS relation for output quantity '" +
std::string{outputQuantity.name()} +
"' accepts the supplied input quantities."
);
}
template <typename Value>
[[nodiscard]] static std::expected<
Value,
EvaluationError>
runtimeFailure(
const EvaluationErrorCode code,
std::string message
) {
return std::unexpected<EvaluationError>{EvaluationError{code, std::move(message)}};
}
const void *m_object;
std::span<const RuntimeRelationDescriptor> m_relations;
RuntimeEvaluateFunction m_evaluate;
RuntimePartialDerivativeFunction m_partialDerivative;
};
} // namespace mean_field::eos

View File

@@ -1,6 +1,7 @@
module; module;
#include <array> #include <array>
#include <cmath>
#include <concepts> #include <concepts>
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
@@ -888,6 +889,282 @@ export namespace mean_field::field {
mfem::Array<int> m_trueToReduced; mfem::Array<int> m_trueToReduced;
}; };
/*
* Boundary rows expressed in a field's reduced solver ordering.
*
* This object is deliberately independent of any particular physical
* surface condition. Its template constructor below combines a field,
* a semantic boundary, and a domain schema. Consequently the same
* topology machinery can be used by any compiled surface formulation;
* it is not tied to enthalpy or pressure.
*/
class FieldBoundaryDofMap final {
public:
FieldBoundaryDofMap() = default;
FieldBoundaryDofMap(
const int fieldReducedSize,
const mfem::Array<int> &boundaryReducedDofs
)
: m_fieldReducedSize(fieldReducedSize),
m_boundaryReducedDofs(boundaryReducedDofs) {
if (m_fieldReducedSize < 0) {
throw std::invalid_argument("FieldBoundaryDofMap requires a non-negative field size.");
}
m_boundaryReducedDofMarker.SetSize(m_fieldReducedSize);
m_boundaryReducedDofMarker = 0;
int previousReducedDof = -1;
for (const int reducedDof : m_boundaryReducedDofs) {
if (reducedDof < 0 || reducedDof >= m_fieldReducedSize) {
throw std::invalid_argument("FieldBoundaryDofMap contains a DOF outside the reduced field vector.");
}
if (reducedDof <= previousReducedDof) {
throw std::invalid_argument("FieldBoundaryDofMap indices must be strictly increasing and unique.");
}
m_boundaryReducedDofMarker[reducedDof] = 1;
previousReducedDof = reducedDof;
}
}
[[nodiscard]] int field_size() const noexcept {
return m_fieldReducedSize;
}
[[nodiscard]] int size() const noexcept {
return m_boundaryReducedDofs.Size();
}
[[nodiscard]] bool empty() const noexcept {
return size() == 0;
}
[[nodiscard]] const mfem::Array<int> &reduced_dofs() const noexcept {
return m_boundaryReducedDofs;
}
[[nodiscard]] const mfem::Array<int> &reduced_dof_marker() const noexcept {
return m_boundaryReducedDofMarker;
}
[[nodiscard]] bool contains(const int reducedDof) const {
if (reducedDof < 0 || reducedDof >= m_fieldReducedSize) {
throw std::out_of_range("Reduced DOF index is outside FieldBoundaryDofMap.");
}
return m_boundaryReducedDofMarker[reducedDof] != 0;
}
private:
int m_fieldReducedSize{0};
mfem::Array<int> m_boundaryReducedDofs;
mfem::Array<int> m_boundaryReducedDofMarker;
};
/* Point-supported rows in a field's reduced solver ordering. */
class FieldPointDofMap final {
public:
FieldPointDofMap() = default;
FieldPointDofMap(
const int fieldReducedSize,
const mfem::Array<int> &pointReducedDofs
)
: m_selectedDofs(
fieldReducedSize,
pointReducedDofs
) {
}
[[nodiscard]] int field_size() const noexcept {
return m_selectedDofs.field_size();
}
[[nodiscard]] int size() const noexcept {
return m_selectedDofs.size();
}
[[nodiscard]] bool empty() const noexcept {
return m_selectedDofs.empty();
}
[[nodiscard]] const mfem::Array<int> &reduced_dofs() const noexcept {
return m_selectedDofs.reduced_dofs();
}
[[nodiscard]] const mfem::Array<int> &reduced_dof_marker() const noexcept {
return m_selectedDofs.reduced_dof_marker();
}
[[nodiscard]] bool contains(const int reducedDof) const {
return m_selectedDofs.contains(reducedDof);
}
private:
FieldBoundaryDofMap m_selectedDofs;
};
template <
MfemDomainField FieldT,
utils::domain::IsBoundary BoundaryT,
utils::domain::IsSchema SchemaT>
[[nodiscard]] FieldBoundaryDofMap make_field_boundary_dof_map(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const FieldDofMap &fieldDofMap
) {
static_assert(
SchemaT::template contains_boundary<BoundaryT>(),
"The requested boundary is not registered in the supplied DomainSchema."
);
MFEM_VERIFY(
!finiteElementSpace.Nonconforming(),
"Field boundary true-DOF resolution currently requires a conforming mfem::ParFiniteElementSpace."
);
MFEM_VERIFY(
fieldDofMap.full_size() == finiteElementSpace.GetTrueVSize(),
"The field map and finite-element space have incompatible true-DOF sizes."
);
const mfem::Mesh *mesh = finiteElementSpace.GetMesh();
MFEM_VERIFY(mesh != nullptr, "Field boundary DOF resolution requires an MFEM mesh.");
mfem::Array<int> boundaryVDofMarker(finiteElementSpace.GetVSize());
boundaryVDofMarker = 0;
mfem::Array<int> boundaryElementVDofs;
for (int boundaryElement = 0; boundaryElement < mesh->GetNBE(); ++boundaryElement) {
if (!SchemaT::template boundary_attribute_matches<BoundaryT>(mesh->GetBdrAttribute(boundaryElement))) {
continue;
}
finiteElementSpace.GetBdrElementVDofs(boundaryElement, boundaryElementVDofs);
for (const int encodedVDof : boundaryElementVDofs) {
const int vdof = mfem::FiniteElementSpace::DecodeDof(encodedVDof);
MFEM_VERIFY(
vdof >= 0 && vdof < finiteElementSpace.GetVSize(), "MFEM returned an invalid boundary vector DOF."
);
boundaryVDofMarker[vdof] = 1;
}
}
finiteElementSpace.Synchronize(boundaryVDofMarker);
mfem::Array<int> boundaryReducedDofMarker(fieldDofMap.reduced_size());
boundaryReducedDofMarker = 0;
for (int vdof = 0; vdof < boundaryVDofMarker.Size(); ++vdof) {
if (boundaryVDofMarker[vdof] == 0) {
continue;
}
const int trueDof = finiteElementSpace.GetLocalTDofNumber(vdof);
if (trueDof < 0) {
continue;
}
const std::optional<int> reducedDof = fieldDofMap.reduced_dof(trueDof);
MFEM_VERIFY(
reducedDof.has_value(),
"A boundary DOF selected for the field is absent from that field's reduced solver map."
);
boundaryReducedDofMarker[*reducedDof] = 1;
}
mfem::Array<int> boundaryReducedDofs;
mfem::FiniteElementSpace::MarkerToList(boundaryReducedDofMarker, boundaryReducedDofs);
return FieldBoundaryDofMap(fieldDofMap.reduced_size(), boundaryReducedDofs);
}
template <MfemDomainField FieldT>
[[nodiscard]] FieldPointDofMap make_field_point_dof_map(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const FieldDofMap &fieldDofMap,
const mfem::Vector &point,
const double tolerance
) {
MFEM_VERIFY(
!finiteElementSpace.Nonconforming(),
"Field point true-DOF resolution currently requires a conforming mfem::ParFiniteElementSpace."
);
MFEM_VERIFY(
fieldDofMap.full_size() == finiteElementSpace.GetTrueVSize(),
"The field map and finite-element space have incompatible true-DOF sizes."
);
MFEM_VERIFY(
std::isfinite(tolerance) && tolerance >= 0.0, "The field point tolerance must be finite and non-negative."
);
const mfem::Mesh *mesh = finiteElementSpace.GetMesh();
MFEM_VERIFY(mesh != nullptr, "Field point DOF resolution requires an MFEM mesh.");
MFEM_VERIFY(
point.Size() == mesh->SpaceDimension(), "The requested field point has the wrong coordinate dimension."
);
mfem::Array<int> pointVDofMarker(finiteElementSpace.GetVSize());
pointVDofMarker = 0;
mfem::Array<int> vertexVDofs;
for (int vertex = 0; vertex < mesh->GetNV(); ++vertex) {
const mfem::real_t *coordinates = mesh->GetVertex(vertex);
double distanceSquared = 0.0;
for (int component = 0; component < point.Size(); ++component) {
const double difference = coordinates[component] - point(component);
distanceSquared += difference * difference;
}
if (std::sqrt(distanceSquared) > tolerance) {
continue;
}
finiteElementSpace.GetVertexVDofs(vertex, vertexVDofs);
for (const int encodedVDof : vertexVDofs) {
const int vdof = mfem::FiniteElementSpace::DecodeDof(encodedVDof);
MFEM_VERIFY(
vdof >= 0 && vdof < finiteElementSpace.GetVSize(), "MFEM returned an invalid point vector DOF."
);
pointVDofMarker[vdof] = 1;
}
}
finiteElementSpace.Synchronize(pointVDofMarker);
mfem::Array<int> pointReducedDofMarker(fieldDofMap.reduced_size());
pointReducedDofMarker = 0;
for (int vdof = 0; vdof < pointVDofMarker.Size(); ++vdof) {
if (pointVDofMarker[vdof] == 0) {
continue;
}
const int trueDof = finiteElementSpace.GetLocalTDofNumber(vdof);
if (trueDof < 0) {
continue;
}
const std::optional<int> reducedDof = fieldDofMap.reduced_dof(trueDof);
MFEM_VERIFY(
reducedDof.has_value(),
"A point DOF selected for the field is absent from that field's reduced solver map."
);
pointReducedDofMarker[*reducedDof] = 1;
}
mfem::Array<int> pointReducedDofs;
mfem::FiniteElementSpace::MarkerToList(pointReducedDofMarker, pointReducedDofs);
const long long localPointDofCount = pointReducedDofs.Size();
long long globalPointDofCount = 0;
MPI_Allreduce(
&localPointDofCount, &globalPointDofCount, 1, MPI_LONG_LONG, MPI_SUM, finiteElementSpace.GetComm()
);
MFEM_VERIFY(
globalPointDofCount == finiteElementSpace.GetVDim(),
"The requested geometric point must identify exactly one field vertex globally."
);
return FieldPointDofMap(fieldDofMap.reduced_size(), pointReducedDofs);
}
/* /*
* Canonical adapter between an MFEM GridFunction and a reduced field * Canonical adapter between an MFEM GridFunction and a reduced field
* vector. * vector.
@@ -1015,9 +1292,6 @@ export namespace mean_field::field {
[[nodiscard]] [[nodiscard]]
FieldDofGridFunctionAdapter FieldDofGridFunctionAdapter
make_field_dof_grid_function_adapter(const mfem::ParFiniteElementSpace &finiteElementSpace) { make_field_dof_grid_function_adapter(const mfem::ParFiniteElementSpace &finiteElementSpace) {
return FieldDofGridFunctionAdapter( return FieldDofGridFunctionAdapter(make_field_dof_map<FieldT, SchemaT>(finiteElementSpace), finiteElementSpace);
make_field_dof_map<FieldT, SchemaT>(finiteElementSpace),
finiteElementSpace
);
} }
} // namespace mean_field::field } // namespace mean_field::field

View File

@@ -41,7 +41,11 @@ export namespace mean_field::integrators {
const mfem::GridFunction &compactification_coordinate, const mfem::GridFunction &compactification_coordinate,
utils::EOS_P<EOS_T> eos utils::EOS_P<EOS_T> eos
) )
: m_mapping(mapper, displacement, compactification_coordinate), : m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_eos(std::move(eos)) { m_eos(std::move(eos)) {
} }

View File

@@ -8,259 +8,288 @@ import :mapping.compactification;
import :utils.user; import :utils.user;
export namespace mean_field::mapping { export namespace mean_field::mapping {
enum class FaceElementSide : uint8_t { element_1, element_2 }; enum class FaceElementSide : uint8_t { element_1, element_2 };
class ElementDisplacementData { class ElementDisplacementData {
public: public:
ElementDisplacementData( ElementDisplacementData(
const mfem::FiniteElement &element, const mfem::Vector &displacement_dofs, const mfem::FiniteElement &element,
mfem::Ordering::Type ordering = mfem::Ordering::byNODES); const mfem::Vector &displacement_dofs,
mfem::Ordering::Type ordering = mfem::Ordering::byNODES
);
[[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept; [[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept;
[[nodiscard]] const mfem::DenseMatrix &GetDofMatrix() const noexcept; [[nodiscard]] const mfem::DenseMatrix &GetDofMatrix() const noexcept;
[[nodiscard]] int GetDimension() const noexcept; [[nodiscard]] int GetDimension() const noexcept;
[[nodiscard]] int GetDofCount() const noexcept; [[nodiscard]] int GetDofCount() const noexcept;
[[nodiscard]] mfem::Ordering::Type GetOrdering() const noexcept; [[nodiscard]] mfem::Ordering::Type GetOrdering() const noexcept;
private: private:
const mfem::FiniteElement *m_element; const mfem::FiniteElement *m_element;
mfem::DenseMatrix m_dof_matrix; mfem::DenseMatrix m_dof_matrix;
int m_dimension; int m_dimension;
mfem::Ordering::Type m_ordering; mfem::Ordering::Type m_ordering;
}; };
struct CompactificationPointData { struct CompactificationPointData {
double coordinate{0.0}; double coordinate{0.0};
mfem::Vector coordinate_gradient; mfem::Vector coordinate_gradient;
}; };
[[nodiscard]] ElementDisplacementData [[nodiscard]] ElementDisplacementData ElementDisplacementDataFromElementVDofs(
ElementDisplacementDataFromElementVDofs(const mfem::FiniteElement &element, const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs); const mfem::Vector &displacement_dofs
);
class ElementCompactificationData { class ElementCompactificationData {
public: public:
ElementCompactificationData(const mfem::FiniteElement &element, ElementCompactificationData(
const mfem::Vector &dofs); const mfem::FiniteElement &element,
const mfem::Vector &dofs
);
[[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept; [[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept;
[[nodiscard]] const mfem::Vector &GetDofs() const noexcept; [[nodiscard]] const mfem::Vector &GetDofs() const noexcept;
[[nodiscard]] int GetDofCount() const noexcept; [[nodiscard]] int GetDofCount() const noexcept;
private: private:
const mfem::FiniteElement *m_element; const mfem::FiniteElement *m_element;
mfem::Vector m_dofs; mfem::Vector m_dofs;
}; };
struct ElementMappingData { struct ElementMappingData {
const ElementDisplacementData &displacement; const ElementDisplacementData &displacement;
const ElementCompactificationData &compactification; const ElementCompactificationData &compactification;
}; };
class DomainMapper { class DomainMapper {
public: public:
class Workspace { class Workspace {
public: public:
explicit Workspace(int dimension = 3); explicit Workspace(int dimension = 3);
void SetDimension(int dimension); void SetDimension(int dimension);
[[nodiscard]] int GetDimension() const noexcept; [[nodiscard]] int GetDimension() const noexcept;
private: private:
friend class DomainMapper; friend class DomainMapper;
int m_dimension; int m_dimension;
mfem::Vector m_shape; mfem::Vector m_shape;
mfem::DenseMatrix m_mesh_dshape; mfem::DenseMatrix m_mesh_dshape;
mfem::Vector m_field_value; mfem::Vector m_field_value;
mfem::DenseMatrix m_field_jacobian; mfem::DenseMatrix m_field_jacobian;
mfem::Vector m_compactification_shape; mfem::Vector m_compactification_shape;
mfem::DenseMatrix m_compactification_dshape; mfem::DenseMatrix m_compactification_dshape;
CompactificationPointData m_compactification_point; CompactificationPointData m_compactification_point;
mfem::Vector m_reference_normal; mfem::Vector m_reference_normal;
mfem::Vector m_mapped_normal; mfem::Vector m_mapped_normal;
mfem::DenseMatrix m_full_element_jacobian; mfem::DenseMatrix m_full_element_jacobian;
mfem::Vector m_vector_temp; mfem::Vector m_vector_temp;
mfem::DenseMatrix m_matrix_temp_1; mfem::DenseMatrix m_matrix_temp_1;
mfem::DenseMatrix m_matrix_temp_2; mfem::DenseMatrix m_matrix_temp_2;
compactification::ExteriorMapResult m_exterior_result; compactification::ExteriorMapResult m_exterior_result;
compactification::ExteriorMapVariation m_exterior_variation; compactification::ExteriorMapVariation m_exterior_variation;
}; };
public: public:
DomainMapper( DomainMapper(
utils::DomainMapperOptions options, utils::DomainMapperOptions options,
std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map); std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map
);
DomainMapper(const DomainMapper &) = delete; DomainMapper(const DomainMapper &) = delete;
DomainMapper &operator=(const DomainMapper &) = delete; DomainMapper &operator=(const DomainMapper &) = delete;
DomainMapper(DomainMapper &&) = default; DomainMapper(DomainMapper &&) = default;
DomainMapper &operator=(DomainMapper &&) = default; DomainMapper &operator=(DomainMapper &&) = default;
[[nodiscard]] MappingStatus [[nodiscard]] MappingStatus EvaluatePoint(
EvaluatePoint(const ElementMappingData &element_data, const ElementMappingData &element_data,
mfem::ElementTransformation &transformation, mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, const mfem::IntegrationPoint &integration_point,
Workspace &workspace, MappingPointContext &context) const; Workspace &workspace,
MappingPointContext &context
) const;
[[nodiscard]] MappingStatus [[nodiscard]] MappingStatus EvaluateVolume(
EvaluateVolume(const ElementMappingData &element_data, const ElementMappingData &element_data,
mfem::ElementTransformation &transformation, mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, const mfem::IntegrationPoint &integration_point,
Workspace &workspace, VolumeMappingContext &context) const; Workspace &workspace,
VolumeMappingContext &context
) const;
[[nodiscard]] MappingStatus [[nodiscard]] MappingStatus EvaluateFace(
EvaluateFace(const ElementMappingData &element_data, const ElementMappingData &element_data,
mfem::FaceElementTransformations &transformation, mfem::FaceElementTransformations &transformation,
FaceElementSide side, FaceElementSide side,
const mfem::IntegrationPoint &integration_point, const mfem::IntegrationPoint &integration_point,
Workspace &workspace, FaceMappingContext &context) const; Workspace &workspace,
FaceMappingContext &context
) const;
[[nodiscard]] MappingStatus [[nodiscard]] MappingStatus EvaluatePointVariation(
EvaluatePointVariation(const ElementMappingData &element_data, const ElementMappingData &element_data,
const ElementDisplacementData &direction, const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation, mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, const mfem::IntegrationPoint &integration_point,
const MappingPointContext &base_context, const MappingPointContext &base_context,
Workspace &workspace, Workspace &workspace,
MappingPointVariation &variation) const; MappingPointVariation &variation
) const;
[[nodiscard]] MappingStatus [[nodiscard]] MappingStatus EvaluateVolumeVariation(
EvaluateVolumeVariation(const ElementMappingData &element_data, const ElementMappingData &element_data,
const ElementDisplacementData &direction, const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation, mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, const mfem::IntegrationPoint &integration_point,
const VolumeMappingContext &base_context, const VolumeMappingContext &base_context,
Workspace &workspace, Workspace &workspace,
VolumeMappingVariation &variation) const; VolumeMappingVariation &variation
) const;
[[nodiscard]] MappingStatus EvaluateFaceVariation( [[nodiscard]] MappingStatus EvaluateFaceVariation(
const ElementMappingData &element_data, const ElementMappingData &element_data,
const ElementDisplacementData &direction, const ElementDisplacementData &direction,
mfem::FaceElementTransformations &transformation, FaceElementSide side, mfem::FaceElementTransformations &transformation,
const mfem::IntegrationPoint &integration_point, FaceElementSide side,
const FaceMappingContext &base_context, Workspace &workspace, const mfem::IntegrationPoint &integration_point,
FaceMappingVariation &variation) const; const FaceMappingContext &base_context,
Workspace &workspace,
FaceMappingVariation &variation
) const;
[[nodiscard]] bool IsCompactifiedElement( [[nodiscard]] bool IsCompactifiedElement(const mfem::ElementTransformation &transformation) const noexcept;
const mfem::ElementTransformation &transformation) const noexcept; [[nodiscard]] int GetDimension() const noexcept;
[[nodiscard]] int GetDimension() const noexcept; [[nodiscard]] const compactification::ExteriorDomainMap &GetExteriorMap() const noexcept;
[[nodiscard]] const compactification::ExteriorDomainMap &
GetExteriorMap() const noexcept;
private: private:
void ValidateElementData(const ElementMappingData &element_data) const; void ValidateElementData(const ElementMappingData &element_data) const;
void EvaluateField(const ElementDisplacementData &field, void EvaluateField(
mfem::ElementTransformation &transformation, const ElementDisplacementData &field,
const mfem::IntegrationPoint &integration_point, mfem::ElementTransformation &transformation,
Workspace &workspace, mfem::Vector &value, const mfem::IntegrationPoint &integration_point,
mfem::DenseMatrix &jacobian) const; Workspace &workspace,
mfem::Vector &value,
mfem::DenseMatrix &jacobian
) const;
[[nodiscard]] MappingStatus EvaluateCompactificationCoordinate( [[nodiscard]] MappingStatus EvaluateCompactificationCoordinate(
const ElementCompactificationData &compactification, const ElementCompactificationData &compactification,
mfem::ElementTransformation &transformation, mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, Workspace &workspace, const mfem::IntegrationPoint &integration_point,
CompactificationPointData &point_data) const; Workspace &workspace,
CompactificationPointData &point_data
) const;
[[nodiscard]] static mfem::ElementTransformation & [[nodiscard]] static mfem::ElementTransformation &SelectFaceElementTransformation(
SelectFaceElementTransformation( mfem::FaceElementTransformations &transformation,
mfem::FaceElementTransformations &transformation, FaceElementSide side); FaceElementSide side
);
[[nodiscard]] static const mfem::IntegrationPoint & [[nodiscard]] static const mfem::IntegrationPoint &SelectFaceElementIntegrationPoint(
SelectFaceElementIntegrationPoint( mfem::FaceElementTransformations &transformation,
mfem::FaceElementTransformations &transformation, FaceElementSide side); FaceElementSide side
);
utils::DomainMapperOptions m_options; utils::DomainMapperOptions m_options;
std::unique_ptr<const compactification::ExteriorDomainMap> m_exterior_map; std::unique_ptr<const compactification::ExteriorDomainMap> m_exterior_map;
}; };
class GridFunctionMappingEvaluator { class GridFunctionMappingEvaluator {
public: public:
/* /*
* The evaluator references the supplied grid functions and caches copies of * The evaluator references the supplied grid functions and caches copies of
* their element-local DOFs. Call InvalidateCache() or Refresh() after either * their element-local DOFs. Call InvalidateCache() or Refresh() after either
* grid function's values are modified. Finite-element-space sequence changes * grid function's values are modified. Finite-element-space sequence changes
* are detected automatically. * are detected automatically.
* *
* This object owns mutable workspace and cache state and is not thread-safe. * This object owns mutable workspace and cache state and is not thread-safe.
*/ */
GridFunctionMappingEvaluator( GridFunctionMappingEvaluator(
const DomainMapper &mapper, const DomainMapper &mapper,
const mfem::GridFunction &displacement, const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate); const mfem::GridFunction &compactification_coordinate
);
/* /*
* Discard all element-local field data. The next evaluation reloads its * Discard all element-local field data. The next evaluation reloads its
* requested element lazily. This operation is idempotent. * requested element lazily. This operation is idempotent.
*/ */
void InvalidateCache() noexcept; void InvalidateCache() noexcept;
/* /*
* Reload the currently cached element immediately. If no element has been * Reload the currently cached element immediately. If no element has been
* evaluated yet, Refresh() is a validated no-op. If either finite-element * evaluated yet, Refresh() is a validated no-op. If either finite-element
* space changed sequence, the old element ID is discarded and the next * space changed sequence, the old element ID is discarded and the next
* evaluation reloads lazily against the updated spaces. * evaluation reloads lazily against the updated spaces.
*/ */
void Refresh(); void Refresh();
[[nodiscard]] MappingStatus [[nodiscard]] MappingStatus EvaluatePoint(
EvaluatePoint(mfem::ElementTransformation &transformation, mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, const mfem::IntegrationPoint &integration_point,
MappingPointContext &context); MappingPointContext &context
);
[[nodiscard]] MappingStatus [[nodiscard]] MappingStatus EvaluateVolume(
EvaluateVolume(mfem::ElementTransformation &transformation, mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, const mfem::IntegrationPoint &integration_point,
VolumeMappingContext &context); VolumeMappingContext &context
);
[[nodiscard]] MappingStatus [[nodiscard]] MappingStatus EvaluateFace(
EvaluateFace(mfem::FaceElementTransformations &transformation, mfem::FaceElementTransformations &transformation,
FaceElementSide side, FaceElementSide side,
const mfem::IntegrationPoint &integration_point, const mfem::IntegrationPoint &integration_point,
FaceMappingContext &context); FaceMappingContext &context
);
[[nodiscard]] VolumeQuadratureContext [[nodiscard]] VolumeQuadratureContext GetQuadratureContext(
GetQuadratureContext(mfem::ElementTransformation &transformation, mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point); const mfem::IntegrationPoint &integration_point
);
[[nodiscard]] FaceQuadratureContext [[nodiscard]] FaceQuadratureContext GetFaceQuadratureContext(
GetFaceQuadratureContext( mfem::FaceElementTransformations &transformation,
mfem::FaceElementTransformations &transformation, const mfem::IntegrationPoint &integration_point,
const mfem::IntegrationPoint &integration_point, FaceElementSide side = FaceElementSide::element_1
FaceElementSide side = FaceElementSide::element_1); );
void GetPhysicalPoint(mfem::ElementTransformation &transformation, void GetPhysicalPoint(
const mfem::IntegrationPoint &integration_point, mfem::ElementTransformation &transformation,
mfem::Vector &physical_position); const mfem::IntegrationPoint &integration_point,
mfem::Vector &physical_position
);
private: private:
void ValidateFieldBindings() const; void ValidateFieldBindings() const;
[[nodiscard]] bool InvalidateForChangedSpaces(); [[nodiscard]] bool InvalidateForChangedSpaces();
void LoadElement(int element_id); void LoadElement(int element_id);
const DomainMapper &m_mapper; const DomainMapper &m_mapper;
const mfem::GridFunction &m_displacement; const mfem::GridFunction &m_displacement;
const mfem::GridFunction &m_compactification_coordinate; const mfem::GridFunction &m_compactification_coordinate;
const mfem::FiniteElementSpace *m_displacement_space; const mfem::FiniteElementSpace *m_displacement_space;
const mfem::FiniteElementSpace *m_compactification_space; const mfem::FiniteElementSpace *m_compactification_space;
long m_displacement_space_sequence; long m_displacement_space_sequence;
long m_compactification_space_sequence; long m_compactification_space_sequence;
DomainMapper::Workspace m_workspace; DomainMapper::Workspace m_workspace;
mfem::Array<int> m_displacement_dofs; mfem::Array<int> m_displacement_dofs;
mfem::Array<int> m_compactification_dofs; mfem::Array<int> m_compactification_dofs;
mfem::Vector m_element_displacement; mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification; mfem::Vector m_element_compactification;
std::unique_ptr<ElementDisplacementData> m_displacement_data; std::unique_ptr<ElementDisplacementData> m_displacement_data;
std::unique_ptr<ElementCompactificationData> m_compactification_data; std::unique_ptr<ElementCompactificationData> m_compactification_data;
int m_cached_element_id{-1}; int m_cached_element_id{-1};
}; };
} // namespace mean_field::mapping } // namespace mean_field::mapping

View File

@@ -54,10 +54,19 @@ export import :operators.prepared_displacement_residual;
export import :model.structure_profile; export import :model.structure_profile;
export import :model.structure.base; export import :model.structure.base;
export import :model.structure.polytropic; export import :model.structure.polytropic;
export import :eos.base; export import :eos.quantities;
export import :eos.relations;
export import :eos.concepts;
export import :eos.evaluation;
export import :eos.pressure_surface;
export import :eos.runtime;
export import :eos.polytrope; export import :eos.polytrope;
export import :surface.base; export import :surface.constant;
export import :surface.isobaric; export import :surface.dependencies;
export import :surface.compiled;
export import :surface.compiler;
export import :model.stellar; export import :model.stellar;
export import :operators.prepared_mass_normalization; export import :operators.prepared_mass_normalization;
export import :operators.prepared_centering_constraint;
export import :operators.prepared_surface_constraint;
export import :operators.prepared_stellar_equilibrium; export import :operators.prepared_stellar_equilibrium;

View File

@@ -7,62 +7,84 @@ module;
export module mean_field:model.stellar; export module mean_field:model.stellar;
export import :eos.base; export import :eos.runtime;
export import :model.structure.base; export import :model.structure.base;
export import :surface.base; export import :surface.compiler;
export namespace mean_field::models { export namespace mean_field::models {
template <typename Candidate> namespace detail {
concept StructurePrescription = template <typename Candidate>
std::derived_from<std::remove_cvref_t<Candidate>, mean_field::models::structure::StructureBase>; concept ConstEquationOfStateReference =
std::is_lvalue_reference_v<Candidate> && std::is_const_v<std::remove_reference_t<Candidate>> &&
eos::EquationOfStateModel<std::remove_cvref_t<Candidate>>;
} // namespace detail
template <typename Candidate> template <typename Candidate>
concept SurfacePrescription = std::derived_from<std::remove_cvref_t<Candidate>, mean_field::surface::SurfaceBase>; concept StructurePrescription = requires(
const std::remove_cvref_t<Candidate> &structurePrescription,
const structure::StructureSeedRequest &seedRequest
) {
{ structurePrescription.equationOfState() } noexcept -> detail::ConstEquationOfStateReference;
{ structurePrescription.targetMass() } noexcept -> std::same_as<double>;
{ structurePrescription.makeInitialSeed(seedRequest) } -> std::same_as<structure::StructureSeed>;
{ structurePrescription.validate() } -> std::same_as<void>;
};
/* template <StructurePrescription Candidate>
* Public ownership facade for a physical structure prescription and its using StructureEquationOfStateT =
* stellar-surface prescription. std::remove_cvref_t<decltype(std::declval<const std::remove_cvref_t<Candidate> &>().equationOfState())>;
*
* The concrete prescriptions are allocated once at construction. Their template <typename Candidate, typename EquationOfState>
* stable addresses allow future prepared operators and contexts to borrow concept SurfacePrescription =
* references without making ownership part of the user-facing API. surface::ConstantPressureSurfaceType<Candidate> &&
*/ surface::PressureSurfaceCompilable<surface::BarotropicSurfaceFormulation, std::remove_cvref_t<EquationOfState>>;
template <StructurePrescription Structure>
requires SurfacePrescription<surface::ConstantPressureSurface, StructureEquationOfStateT<Structure>>
class StellarModel final { class StellarModel final {
public: public:
template < using StructurePrescriptionType = Structure;
StructurePrescription StructureType, using SurfacePrescriptionType = surface::ConstantPressureSurface;
SurfacePrescription SurfaceType> using EquationOfStateType = StructureEquationOfStateT<Structure>;
using SurfaceConstraintType =
surface::CompiledPressureSurfaceConstraintT<surface::BarotropicSurfaceFormulation, EquationOfStateType>;
template <typename StructureArgument>
requires std::same_as<
std::remove_cvref_t<StructureArgument>,
Structure>
explicit StellarModel( explicit StellarModel(
StructureType &&structurePrescription, StructureArgument &&structurePrescription,
SurfaceType &&surfacePrescription const surface::ConstantPressureSurface surfacePrescription
) )
: StellarModel( : m_structurePrescription(
std::make_unique<std::remove_cvref_t<StructureType>>( std::make_unique<Structure>(std::forward<StructureArgument>(structurePrescription))
std::forward<StructureType>(structurePrescription) ),
), m_surfacePrescription(std::make_unique<surface::ConstantPressureSurface>(surfacePrescription)),
std::make_unique<std::remove_cvref_t<SurfaceType>>(std::forward<SurfaceType>(surfacePrescription)) m_compiledSurfaceConstraint(
std::make_unique<SurfaceConstraintType>(validateAndCompileSurface(
*m_structurePrescription,
*m_surfacePrescription
))
) { ) {
} }
~StellarModel() = default; ~StellarModel() = default;
StellarModel(const StellarModel &) = delete; StellarModel(const StellarModel &) = delete;
StellarModel &operator=(const StellarModel &) = delete; StellarModel &operator=(const StellarModel &) = delete;
StellarModel(StellarModel &&) noexcept = default; StellarModel(StellarModel &&) noexcept = default;
StellarModel &operator=(StellarModel &&) noexcept = default; StellarModel &operator=(StellarModel &&) noexcept = default;
[[nodiscard]] const mean_field::models::structure::StructureBase &structurePrescription() const noexcept { [[nodiscard]] const Structure &structurePrescription() const noexcept {
return *m_structurePrescription; return *m_structurePrescription;
} }
[[nodiscard]] const mean_field::surface::SurfaceBase &surfacePrescription() const noexcept { [[nodiscard]] const surface::ConstantPressureSurface &surfacePrescription() const noexcept {
return *m_surfacePrescription; return *m_surfacePrescription;
} }
[[nodiscard]] const mean_field::eos::EquationOfState &equationOfState() const noexcept { [[nodiscard]] const EquationOfStateType &equationOfState() const noexcept {
return m_structurePrescription->equationOfState(); return m_structurePrescription->equationOfState();
} }
@@ -70,45 +92,99 @@ export namespace mean_field::models {
return m_structurePrescription->targetMass(); return m_structurePrescription->targetMass();
} }
[[nodiscard]] mean_field::models::structure::StructureSeed [[nodiscard]] structure::StructureSeed makeInitialSeed(const structure::StructureSeedRequest &request) const {
makeInitialSeed(const mean_field::models::structure::StructureSeedRequest &request) const {
return m_structurePrescription->makeInitialSeed(request); return m_structurePrescription->makeInitialSeed(request);
} }
[[nodiscard]] const mean_field::surface::ResolvedSurfaceCondition &resolvedSurfaceCondition() const noexcept { [[nodiscard]] const SurfaceConstraintType &compiledSurfaceConstraint() const noexcept {
return m_resolvedSurfaceCondition; return *m_compiledSurfaceConstraint;
} }
private: private:
explicit StellarModel( [[nodiscard]] static SurfaceConstraintType validateAndCompileSurface(
std::unique_ptr<mean_field::models::structure::StructureBase> structurePrescription, const Structure &structurePrescription,
std::unique_ptr<mean_field::surface::SurfaceBase> surfacePrescription const surface::ConstantPressureSurface &surfacePrescription
)
: m_structurePrescription(std::move(structurePrescription)),
m_surfacePrescription(std::move(surfacePrescription)),
m_resolvedSurfaceCondition(validateAndResolve(
*m_structurePrescription,
*m_surfacePrescription
)) {
}
[[nodiscard]] static mean_field::surface::ResolvedSurfaceCondition validateAndResolve(
const mean_field::models::structure::StructureBase &structurePrescription,
const mean_field::surface::SurfaceBase &surfacePrescription
) { ) {
structurePrescription.validate(); structurePrescription.validate();
const mean_field::eos::EquationOfState &equationOfState = structurePrescription.equationOfState(); return surface::compilePressureSurfaceConstraint<surface::BarotropicSurfaceFormulation>(
surfacePrescription, structurePrescription.equationOfState()
surfacePrescription.validate(equationOfState); );
return surfacePrescription.resolve(equationOfState);
} }
std::unique_ptr<mean_field::models::structure::StructureBase> m_structurePrescription; std::unique_ptr<Structure> m_structurePrescription;
std::unique_ptr<surface::ConstantPressureSurface> m_surfacePrescription;
std::unique_ptr<SurfaceConstraintType> m_compiledSurfaceConstraint;
};
std::unique_ptr<mean_field::surface::SurfaceBase> m_surfacePrescription; template <typename Structure>
StellarModel(
Structure &&,
surface::ConstantPressureSurface
) -> StellarModel<std::remove_cvref_t<Structure>>;
mean_field::surface::ResolvedSurfaceCondition m_resolvedSurfaceCondition; namespace detail {
template <typename Candidate> struct IsStellarModel : std::false_type { };
template <typename Structure> struct IsStellarModel<StellarModel<Structure>> : std::true_type { };
} // namespace detail
template <typename Candidate>
concept StellarModelType = detail::IsStellarModel<std::remove_cvref_t<Candidate>>::value;
class StellarModelView final {
public:
template <typename Model>
requires StellarModelType<Model> &&
eos::RuntimeEquationOfStateModel<typename std::remove_cvref_t<Model>::EquationOfStateType>
explicit StellarModelView(Model &model) noexcept
: m_equationOfState(model.equationOfState()),
m_structurePrescription(std::addressof(model.structurePrescription())),
m_makeInitialSeed(&makeInitialSeedFor<typename std::remove_cvref_t<Model>::StructurePrescriptionType>),
m_targetMass(model.targetMass()),
m_surfaceCondition(model.compiledSurfaceConstraint().descriptor()),
m_surfaceDependencies(model.compiledSurfaceConstraint().runtimeDependencies()) {
}
[[nodiscard]] eos::EquationOfStateView equationOfState() const noexcept {
return m_equationOfState;
}
[[nodiscard]] double targetMass() const noexcept {
return m_targetMass;
}
[[nodiscard]] structure::StructureSeed makeInitialSeed(const structure::StructureSeedRequest &request) const {
return m_makeInitialSeed(m_structurePrescription, request);
}
[[nodiscard]] surface::PressureSurfaceDescriptor surfaceCondition() const noexcept {
return m_surfaceCondition;
}
[[nodiscard]] surface::RuntimeSurfaceConstraintDependencies surfaceDependencies() const noexcept {
return m_surfaceDependencies;
}
private:
using MakeInitialSeedFunction = structure::StructureSeed (*)(
const void *,
const structure::StructureSeedRequest &
);
template <StructurePrescription Structure>
[[nodiscard]] static structure::StructureSeed makeInitialSeedFor(
const void *structurePrescription,
const structure::StructureSeedRequest &request
) {
return static_cast<const Structure *>(structurePrescription)->makeInitialSeed(request);
}
eos::EquationOfStateView m_equationOfState;
const void *m_structurePrescription;
MakeInitialSeedFunction m_makeInitialSeed;
double m_targetMass;
surface::PressureSurfaceDescriptor m_surfaceCondition;
surface::RuntimeSurfaceConstraintDependencies m_surfaceDependencies;
}; };
} // namespace mean_field::models } // namespace mean_field::models

View File

@@ -12,20 +12,20 @@ export import :model.structure.base;
import :utils.misc; import :utils.misc;
export namespace mean_field::models::structure { export namespace mean_field::models::structure {
class PolytropicStructure final : public StructureBase { class PolytropicStructure final {
public: public:
explicit PolytropicStructure( explicit PolytropicStructure(
eos::Polytrope equationOfState, eos::Polytrope equationOfState,
double targetMass double targetMass
); );
[[nodiscard]] const eos::EquationOfState &equationOfState() const noexcept override; [[nodiscard]] const eos::Polytrope &equationOfState() const noexcept;
[[nodiscard]] double targetMass() const noexcept override; [[nodiscard]] double targetMass() const noexcept;
[[nodiscard]] StructureSeed makeInitialSeed(const StructureSeedRequest &request) const override; [[nodiscard]] StructureSeed makeInitialSeed(const StructureSeedRequest &request) const;
void validate() const override; void validate() const;
private: private:
struct LaneEmdenPoint { struct LaneEmdenPoint {

View File

@@ -1,7 +1,7 @@
module; module;
#include <mfem.hpp> #include <mfem.hpp>
export module mean_field:model.structure.base; export module mean_field:model.structure.base;
export import :eos.base; export import :eos.runtime;
export namespace mean_field::models::structure { export namespace mean_field::models::structure {
struct StructureSeed { struct StructureSeed {
@@ -23,7 +23,7 @@ export namespace mean_field::models::structure {
public: public:
virtual ~StructureBase() = default; virtual ~StructureBase() = default;
[[nodiscard]] virtual const eos::EquationOfState &equationOfState() const noexcept = 0; [[nodiscard]] virtual eos::EquationOfStateView equationOfState() const noexcept = 0;
[[nodiscard]] virtual double targetMass() const noexcept = 0; [[nodiscard]] virtual double targetMass() const noexcept = 0;
@@ -34,4 +34,4 @@ export namespace mean_field::models::structure {
protected: protected:
StructureBase() = default; StructureBase() = default;
}; };
} // namespace mean_field::models::structure } // namespace mean_field::models::structure

View File

@@ -0,0 +1,103 @@
module;
#include <cmath>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.prepared_centering_constraint;
export import :field.mfem;
export namespace mean_field::operators {
struct PreparedCenteringConstraintReport final {
bool cachedCenterDisplacement{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return cachedCenterDisplacement;
}
};
/*
* Strong translational gauge: the material point at the computational
* origin has zero displacement. The three corresponding displacement
* residual rows replace redundant force-balance rows.
*/
class PreparedCenteringConstraint final {
public:
explicit PreparedCenteringConstraint(field::FieldPointDofMap centerRows)
: m_centerRows(std::move(centerRows)),
m_centerDisplacement(m_centerRows.size()) {
}
[[nodiscard]] PreparedCenteringConstraintReport Prepare(
const mfem::Vector &displacement,
const bool displacementChanged
) {
MFEM_VERIFY(
displacement.Size() == m_centerRows.field_size(),
"The centering constraint received a displacement vector with the wrong size."
);
PreparedCenteringConstraintReport report;
if (!m_isPrepared || displacementChanged) {
for (int centerIndex = 0; centerIndex < m_centerRows.size(); ++centerIndex) {
const double value = displacement(m_centerRows.reduced_dofs()[centerIndex]);
MFEM_VERIFY(
std::isfinite(value), "The centering constraint received a non-finite center displacement."
);
m_centerDisplacement(centerIndex) = value;
}
report.cachedCenterDisplacement = true;
}
m_isPrepared = true;
return report;
}
void ApplyResidualRows(mfem::Vector &displacementResidual) const {
VerifyPrepared();
MFEM_VERIFY(
displacementResidual.Size() == m_centerRows.field_size(),
"The centering constraint received a displacement residual with the wrong size."
);
for (int centerIndex = 0; centerIndex < m_centerRows.size(); ++centerIndex) {
displacementResidual(m_centerRows.reduced_dofs()[centerIndex]) = m_centerDisplacement(centerIndex);
}
}
void ApplyJacobianRows(
const mfem::Vector &displacementVariation,
mfem::Vector &displacementAction
) const {
VerifyPrepared();
MFEM_VERIFY(
displacementVariation.Size() == m_centerRows.field_size() &&
displacementAction.Size() == m_centerRows.field_size(),
"The centering constraint received a Jacobian vector with the wrong size."
);
for (const int centerRow : m_centerRows.reduced_dofs()) {
displacementAction(centerRow) = displacementVariation(centerRow);
}
}
[[nodiscard]] bool IsPrepared() const noexcept {
return m_isPrepared;
}
[[nodiscard]] const field::FieldPointDofMap &GetCenterRows() const noexcept {
return m_centerRows;
}
private:
void VerifyPrepared() const {
MFEM_VERIFY(m_isPrepared, "The centering constraint must be prepared before row application.");
}
field::FieldPointDofMap m_centerRows;
mfem::Vector m_centerDisplacement;
bool m_isPrepared{false};
};
} // namespace mean_field::operators

View File

@@ -1,7 +1,9 @@
module; module;
#include <compare> #include <compare>
#include <concepts>
#include <cstdint> #include <cstdint>
#include <type_traits>
#include <mfem.hpp> #include <mfem.hpp>
@@ -16,9 +18,11 @@ export import :operators.context.gravity_field;
export import :operators.gravity_field; export import :operators.gravity_field;
export import :operators.gravity_field_jacobian; export import :operators.gravity_field_jacobian;
export import :operators.prepared_barotropic_closure; export import :operators.prepared_barotropic_closure;
export import :operators.prepared_centering_constraint;
export import :operators.prepared_displacement_residual; export import :operators.prepared_displacement_residual;
export import :operators.prepared_hydrostatic_equilibrium; export import :operators.prepared_hydrostatic_equilibrium;
export import :operators.prepared_mass_normalization; export import :operators.prepared_mass_normalization;
export import :operators.prepared_surface_constraint;
export import :physics.rigid_rotation; export import :physics.rigid_rotation;
export import :utils.blocks; export import :utils.blocks;
@@ -50,11 +54,14 @@ export namespace mean_field::operators {
PreparedHydrostaticEquilibriumReport hydrostatic; PreparedHydrostaticEquilibriumReport hydrostatic;
PreparedDisplacementResidualReport displacement; PreparedDisplacementResidualReport displacement;
PreparedMassNormalizationReport massNormalization; PreparedMassNormalizationReport massNormalization;
PreparedSurfaceConstraintReport surfaceConstraint;
PreparedCenteringConstraintReport centeringConstraint;
bool assembledResidual{false}; bool assembledResidual{false};
[[nodiscard]] bool DidAnyChildWork() const noexcept { [[nodiscard]] bool DidAnyChildWork() const noexcept {
return gravity.DidAnyWork() || barotropicClosure.DidAnyWork() || hydrostatic.DidAnyWork() || return gravity.DidAnyWork() || barotropicClosure.DidAnyWork() || hydrostatic.DidAnyWork() ||
displacement.DidAnyWork() || massNormalization.DidAnyWork(); displacement.DidAnyWork() || massNormalization.DidAnyWork() || surfaceConstraint.DidAnyWork() ||
centeringConstraint.DidAnyWork();
} }
[[nodiscard]] bool DidAnyWork() const noexcept { [[nodiscard]] bool DidAnyWork() const noexcept {
@@ -74,19 +81,27 @@ export namespace mean_field::operators {
class PreparedStellarEquilibriumOperator final : public mfem::Operator { class PreparedStellarEquilibriumOperator final : public mfem::Operator {
public: public:
template <models::StellarModelType Model>
requires std::same_as<
typename std::remove_cvref_t<Model>::EquationOfStateType,
eos::Polytrope> &&
SingleFieldPressureSurfaceConstraintFor<
typename std::remove_cvref_t<Model>::SurfaceConstraintType,
field::Enthalpy> &&
std::is_lvalue_reference_v<Model &&>
PreparedStellarEquilibriumOperator( PreparedStellarEquilibriumOperator(
fem::FEM &f, fem::FEM &f,
const mapping::DomainMapper &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, Model &&stellarModel
double targetMass )
); : PreparedStellarEquilibriumOperator(
f,
PreparedStellarEquilibriumOperator( domainMapper,
fem::FEM &f, stellarModel.equationOfState(),
const mapping::DomainMapper &domainMapper, stellarModel.targetMass(),
const eos::Polytrope &equationOfState, PressureSurfaceConstraintView{stellarModel.compiledSurfaceConstraint()}
const models::StellarModel &stellarModel ) {
); }
PreparedStellarEquilibriumOperator(const PreparedStellarEquilibriumOperator &) = delete; PreparedStellarEquilibriumOperator(const PreparedStellarEquilibriumOperator &) = delete;
PreparedStellarEquilibriumOperator &operator=(const PreparedStellarEquilibriumOperator &) = delete; PreparedStellarEquilibriumOperator &operator=(const PreparedStellarEquilibriumOperator &) = delete;
@@ -122,6 +137,8 @@ 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]] const PreparedPressureSurfaceConstraint &GetSurfaceConstraintOperator() const noexcept;
[[nodiscard]] const PreparedCenteringConstraint &GetCenteringConstraintOperator() const noexcept;
private: private:
struct ConstructionData; struct ConstructionData;
@@ -133,6 +150,15 @@ export namespace mean_field::operators {
const mapping::DomainMapper &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
double targetMass, double targetMass,
PressureSurfaceConstraintView surfaceConstraint
);
PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
double targetMass,
PressureSurfaceConstraintView surfaceConstraint,
ConstructionData constructionData ConstructionData constructionData
); );
@@ -150,6 +176,8 @@ export namespace mean_field::operators {
PreparedHydrostaticEquilibriumOperator m_hydrostaticOperator; PreparedHydrostaticEquilibriumOperator m_hydrostaticOperator;
PreparedDisplacementResidualOperator m_displacementOperator; PreparedDisplacementResidualOperator m_displacementOperator;
PreparedMassNormalizationOperator m_massNormalizationOperator; PreparedMassNormalizationOperator m_massNormalizationOperator;
PreparedPressureSurfaceConstraint m_surfaceConstraintOperator;
PreparedCenteringConstraint m_centeringConstraintOperator;
StellarEquilibriumDependencies m_preparedDependencies; StellarEquilibriumDependencies m_preparedDependencies;
mfem::Vector m_cachedResidual; mfem::Vector m_cachedResidual;

View File

@@ -0,0 +1,235 @@
module;
#include <cmath>
#include <concepts>
#include <memory>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.prepared_surface_constraint;
export import :field.mfem;
export import :surface.compiled;
namespace mean_field::operators::detail {
template <eos::ThermodynamicQuantityType Quantity> struct SingleQuantitySurfaceState final {
eos::QuantityValue<Quantity> quantityValue;
[[nodiscard]] eos::QuantityValue<Quantity> value(Quantity) const noexcept {
return quantityValue;
}
};
} // namespace mean_field::operators::detail
export namespace mean_field::operators {
/*
* Runtime enforcement currently supports a pointwise pressure constraint
* whose row field is also its sole state field. The concept is expressed
* entirely in compiled-constraint metadata: no thermodynamic carrier or
* concrete field is selected by this prepared layer.
*/
template <typename Candidate>
concept SingleFieldPressureSurfaceConstraint =
requires {
typename std::remove_cvref_t<Candidate>::PhysicalQuantity;
typename std::remove_cvref_t<Candidate>::CarrierQuantity;
typename std::remove_cvref_t<Candidate>::CarrierField;
typename std::remove_cvref_t<Candidate>::SurfaceDependencies;
} && std::same_as<typename std::remove_cvref_t<Candidate>::PhysicalQuantity, eos::quantity::Pressure> &&
std::same_as<
typename std::remove_cvref_t<Candidate>::SurfaceDependencies::RowField,
typename std::remove_cvref_t<Candidate>::CarrierField> &&
std::same_as<
typename std::remove_cvref_t<Candidate>::SurfaceDependencies::StateFieldTypes,
field::TypeList<typename std::remove_cvref_t<Candidate>::CarrierField>>;
template <typename Candidate, typename Field>
concept SingleFieldPressureSurfaceConstraintFor =
SingleFieldPressureSurfaceConstraint<Candidate> &&
std::same_as<typename std::remove_cvref_t<Candidate>::SurfaceDependencies::RowField, Field>;
/*
* Non-owning runtime bridge for a statically compiled pressure constraint.
* There is one function-pointer dispatch per complete row application;
* the concrete loop remains templated so EOS operations can be inlined.
*/
class PressureSurfaceConstraintView final {
public:
template <SingleFieldPressureSurfaceConstraint Constraint>
explicit PressureSurfaceConstraintView(const Constraint &constraint) noexcept
: m_constraint(std::addressof(constraint)),
m_applyResidualRows(&applyResidualRows<Constraint>),
m_applyJacobianRows(&applyJacobianRows<Constraint>),
m_descriptor(constraint.descriptor()) {
}
void ApplyResidualRows(
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
mfem::Vector &rowResidual
) const {
m_applyResidualRows(m_constraint, surfaceState, surfaceRows, rowResidual);
}
void ApplyJacobianRows(
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
const mfem::Vector &stateVariation,
mfem::Vector &rowAction
) const {
m_applyJacobianRows(m_constraint, surfaceState, surfaceRows, stateVariation, rowAction);
}
[[nodiscard]] surface::PressureSurfaceDescriptor descriptor() const noexcept {
return m_descriptor;
}
private:
using ApplyResidualRowsFunction = void (*)(
const void *,
const mfem::Vector &,
const field::FieldBoundaryDofMap &,
mfem::Vector &
);
using ApplyJacobianRowsFunction = void (*)(
const void *,
const mfem::Vector &,
const field::FieldBoundaryDofMap &,
const mfem::Vector &,
mfem::Vector &
);
template <SingleFieldPressureSurfaceConstraint Constraint>
static void applyResidualRows(
const void *constraint,
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
mfem::Vector &rowResidual
) {
using CarrierQuantity = typename Constraint::CarrierQuantity;
for (int surfaceIndex = 0; surfaceIndex < surfaceRows.size(); ++surfaceIndex) {
const detail::SingleQuantitySurfaceState<CarrierQuantity> state{
eos::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
};
rowResidual(surfaceRows.reduced_dofs()[surfaceIndex]) =
static_cast<const Constraint *>(constraint)->residual(state);
}
}
template <SingleFieldPressureSurfaceConstraint Constraint>
static void applyJacobianRows(
const void *constraint,
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
const mfem::Vector &stateVariation,
mfem::Vector &rowAction
) {
using CarrierQuantity = typename Constraint::CarrierQuantity;
for (int surfaceIndex = 0; surfaceIndex < surfaceRows.size(); ++surfaceIndex) {
const int reducedDof = surfaceRows.reduced_dofs()[surfaceIndex];
const detail::SingleQuantitySurfaceState<CarrierQuantity> state{
eos::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
};
const detail::SingleQuantitySurfaceState<CarrierQuantity> variation{
eos::QuantityValue<CarrierQuantity>{stateVariation(reducedDof)}
};
rowAction(reducedDof) = static_cast<const Constraint *>(constraint)->jacobianAction(state, variation);
}
}
const void *m_constraint;
ApplyResidualRowsFunction m_applyResidualRows;
ApplyJacobianRowsFunction m_applyJacobianRows;
surface::PressureSurfaceDescriptor m_descriptor;
};
struct PreparedSurfaceConstraintReport final {
bool cachedSurfaceState{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return cachedSurfaceState;
}
};
class PreparedPressureSurfaceConstraint final {
public:
PreparedPressureSurfaceConstraint(
field::FieldBoundaryDofMap surfaceRows,
const PressureSurfaceConstraintView constraint
)
: m_surfaceRows(std::move(surfaceRows)),
m_constraint(constraint),
m_surfaceState(m_surfaceRows.size()) {
}
[[nodiscard]] PreparedSurfaceConstraintReport Prepare(
const mfem::Vector &reducedState,
const bool stateChanged
) {
MFEM_VERIFY(
reducedState.Size() == m_surfaceRows.field_size(),
"The pressure surface constraint received a state vector with the wrong size."
);
PreparedSurfaceConstraintReport report;
if (!m_isPrepared || stateChanged) {
for (int surfaceIndex = 0; surfaceIndex < m_surfaceRows.size(); ++surfaceIndex) {
const double value = reducedState(m_surfaceRows.reduced_dofs()[surfaceIndex]);
MFEM_VERIFY(std::isfinite(value), "The pressure surface constraint received non-finite state.");
m_surfaceState(surfaceIndex) = value;
}
report.cachedSurfaceState = true;
}
m_isPrepared = true;
return report;
}
void ApplyResidualRows(mfem::Vector &rowResidual) const {
VerifyPrepared();
MFEM_VERIFY(
rowResidual.Size() == m_surfaceRows.field_size(),
"The pressure surface constraint received a residual vector with the wrong size."
);
m_constraint.ApplyResidualRows(m_surfaceState, m_surfaceRows, rowResidual);
}
void ApplyJacobianRows(
const mfem::Vector &stateVariation,
mfem::Vector &rowAction
) const {
VerifyPrepared();
MFEM_VERIFY(
stateVariation.Size() == m_surfaceRows.field_size() && rowAction.Size() == m_surfaceRows.field_size(),
"The pressure surface constraint received a Jacobian vector with the wrong size."
);
m_constraint.ApplyJacobianRows(m_surfaceState, m_surfaceRows, stateVariation, rowAction);
}
[[nodiscard]] bool IsPrepared() const noexcept {
return m_isPrepared;
}
[[nodiscard]] const field::FieldBoundaryDofMap &GetSurfaceRows() const noexcept {
return m_surfaceRows;
}
[[nodiscard]] surface::PressureSurfaceDescriptor GetPhysicalCondition() const noexcept {
return m_constraint.descriptor();
}
private:
void VerifyPrepared() const {
MFEM_VERIFY(m_isPrepared, "The pressure surface constraint must be prepared before row application.");
}
field::FieldBoundaryDofMap m_surfaceRows;
PressureSurfaceConstraintView m_constraint;
mfem::Vector m_surfaceState;
bool m_isPrepared{false};
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,66 @@
module;
export module mean_field:surface.compiled;
export import :eos.pressure_surface;
export import :surface.constant;
export import :surface.dependencies;
export namespace mean_field::surface {
template <
eos::EquationOfStateModel EquationOfState,
SurfaceConstraintFormulationType Formulation,
eos::ThermodynamicRelationType SelectedRelation,
typename Dependencies>
class CompiledPressureSurfaceConstraint final {
public:
using PhysicalCondition = ConstantPressureSurface;
using PhysicalQuantity = eos::quantity::Pressure;
using CarrierQuantity = typename Formulation::CarrierQuantity;
using CarrierField = typename Formulation::CarrierField;
using Relation = SelectedRelation;
using SurfaceDependencies = Dependencies;
CompiledPressureSurfaceConstraint(
const ConstantPressureSurface condition,
const EquationOfState &equationOfState
) noexcept
: m_condition(condition),
m_resolvedRelation(
equationOfState,
condition.targetPressure()
) {
}
[[nodiscard]] eos::PressureValue targetPressure() const noexcept {
return m_condition.targetPressure();
}
[[nodiscard]] PressureSurfaceDescriptor descriptor() const noexcept {
return m_condition.descriptor();
}
[[nodiscard]] static constexpr RuntimeSurfaceConstraintDependencies runtimeDependencies() noexcept {
return SurfaceDependencies::runtimeDescription();
}
template <typename SurfaceState> [[nodiscard]] double residual(const SurfaceState &state) const {
return state.value(CarrierQuantity{}).value() - m_resolvedRelation.requiredCarrierValue(state).value();
}
template <
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] double jacobianAction(
const SurfaceState &state,
const SurfaceVariation &variation
) const {
return variation.value(CarrierQuantity{}).value() -
m_resolvedRelation.carrierCorrectionJacobianAction(state, variation);
}
private:
ConstantPressureSurface m_condition;
eos::ResolvedPressureSurfaceRelation<EquationOfState, Relation> m_resolvedRelation;
};
} // namespace mean_field::surface

View File

@@ -0,0 +1,143 @@
module;
#include <cstddef>
#include <tuple>
#include <type_traits>
export module mean_field:surface.compiler;
export import :surface.compiled;
export namespace mean_field::surface {
namespace detail {
template <typename RelationType, typename Formulation, typename EquationOfState>
struct PressureSurfaceRelationMatches : std::false_type { };
template <typename OutputQuantity, typename... InputQuantities, typename Formulation, typename EquationOfState>
struct PressureSurfaceRelationMatches<
eos::Relation<OutputQuantity, InputQuantities...>,
Formulation,
EquationOfState>
: std::bool_constant<
std::same_as<OutputQuantity, typename Formulation::CarrierQuantity> &&
(std::same_as<eos::quantity::Pressure, InputQuantities> || ...) &&
((std::same_as<eos::quantity::Pressure, InputQuantities> ||
(surfaceBindingCount<typename Formulation::StateBindings, InputQuantities> == 1 &&
eos::SupportsPartialDerivative<
EquationOfState,
eos::Relation<OutputQuantity, InputQuantities...>,
InputQuantities>)) &&
...)> { };
template <typename Catalog, typename Formulation, typename EquationOfState>
struct MatchingPressureSurfaceRelations;
template <typename... Relations, typename Formulation, typename EquationOfState>
struct MatchingPressureSurfaceRelations<eos::RelationCatalog<Relations...>, Formulation, EquationOfState> {
using Tuple = decltype(std::tuple_cat(
std::conditional_t<
PressureSurfaceRelationMatches<Relations, Formulation, EquationOfState>::value,
std::tuple<Relations>,
std::tuple<>>{}...
));
static constexpr std::size_t count = std::tuple_size_v<Tuple>;
};
template <std::size_t Count, typename Tuple> struct UniquePressureSurfaceRelation {
using Type = void;
};
template <typename Tuple> struct UniquePressureSurfaceRelation<1, Tuple> {
using Type = std::tuple_element_t<0, Tuple>;
};
template <typename Dependencies, typename Field> struct AppendSurfaceDependency;
template <typename RowField, typename... StateFields, typename Field>
struct AppendSurfaceDependency<SurfaceConstraintDependencies<RowField, StateFields...>, Field> {
using Type = SurfaceConstraintDependencies<RowField, StateFields..., Field>;
};
template <typename Dependencies, typename InputQuantity, typename Bindings>
struct AppendPressureSurfaceInputDependency {
using Type =
typename AppendSurfaceDependency<Dependencies, SurfaceFieldForQuantityT<Bindings, InputQuantity>>::Type;
};
template <typename Dependencies, typename Bindings>
struct AppendPressureSurfaceInputDependency<Dependencies, eos::quantity::Pressure, Bindings> {
using Type = Dependencies;
};
template <typename Dependencies, typename Bindings, typename... InputQuantities>
struct AppendPressureSurfaceInputDependencies;
template <typename Dependencies, typename Bindings>
struct AppendPressureSurfaceInputDependencies<Dependencies, Bindings> {
using Type = Dependencies;
};
template <typename Dependencies, typename Bindings, typename FirstInput, typename... RemainingInputs>
struct AppendPressureSurfaceInputDependencies<Dependencies, Bindings, FirstInput, RemainingInputs...> {
using WithFirst = typename AppendPressureSurfaceInputDependency<Dependencies, FirstInput, Bindings>::Type;
using Type = typename AppendPressureSurfaceInputDependencies<WithFirst, Bindings, RemainingInputs...>::Type;
};
template <typename RelationType, typename Formulation> struct PressureSurfaceDependenciesForRelation;
template <typename OutputQuantity, typename... InputQuantities, typename Formulation>
struct PressureSurfaceDependenciesForRelation<eos::Relation<OutputQuantity, InputQuantities...>, Formulation> {
using InitialDependencies =
SurfaceConstraintDependencies<typename Formulation::CarrierField, typename Formulation::CarrierField>;
using Type = typename AppendPressureSurfaceInputDependencies<
InitialDependencies,
typename Formulation::StateBindings,
InputQuantities...>::Type;
};
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
struct PressureSurfaceCompilation {
using Matches =
MatchingPressureSurfaceRelations<typename EquationOfState::Relations, Formulation, EquationOfState>;
using Relation = typename UniquePressureSurfaceRelation<Matches::count, typename Matches::Tuple>::Type;
};
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
requires(PressureSurfaceCompilation<Formulation, EquationOfState>::Matches::count == 1)
struct CompiledPressureSurfaceConstraintType {
using Compilation = PressureSurfaceCompilation<Formulation, EquationOfState>;
using Relation = typename Compilation::Relation;
using Dependencies = typename PressureSurfaceDependenciesForRelation<Relation, Formulation>::Type;
using Type = CompiledPressureSurfaceConstraint<EquationOfState, Formulation, Relation, Dependencies>;
};
} // namespace detail
template <typename Formulation, typename EquationOfState>
concept PressureSurfaceCompilable =
SurfaceConstraintFormulationType<Formulation> && eos::EquationOfStateModel<EquationOfState> &&
(detail::PressureSurfaceCompilation<std::remove_cvref_t<Formulation>, std::remove_cvref_t<EquationOfState>>::
Matches::count == 1);
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
requires PressureSurfaceCompilable<Formulation, EquationOfState>
using CompiledPressureSurfaceConstraintT = typename detail::CompiledPressureSurfaceConstraintType<
std::remove_cvref_t<Formulation>,
std::remove_cvref_t<EquationOfState>>::Type;
template <
SurfaceConstraintFormulationType Formulation,
eos::EquationOfStateModel EquationOfState>
requires PressureSurfaceCompilable<
Formulation,
EquationOfState>
[[nodiscard]] CompiledPressureSurfaceConstraintT<
Formulation,
EquationOfState>
compilePressureSurfaceConstraint(
const ConstantPressureSurface condition,
const EquationOfState &equationOfState
) noexcept {
return CompiledPressureSurfaceConstraintT<Formulation, EquationOfState>{condition, equationOfState};
}
} // namespace mean_field::surface

View File

@@ -0,0 +1,65 @@
module;
#include <cmath>
#include <format>
#include <stdexcept>
#include <type_traits>
export module mean_field:surface.constant;
export import :eos.quantities;
export namespace mean_field::surface {
struct PressureSurfaceDescriptor final {
double targetPressure;
};
/*
* The only physical surface prescription currently supported by
* MeanField. It says nothing about which thermodynamic variable appears
* in a nonlinear state vector; resolving pressure into that representation
* is an EOS responsibility.
*/
class ConstantPressureSurface final {
public:
using PhysicalQuantity = eos::quantity::Pressure;
using TargetValue = eos::PressureValue;
explicit ConstantPressureSurface(const TargetValue targetPressure) : m_targetPressure(targetPressure) {
if (!std::isfinite(targetPressure.value())) {
throw std::invalid_argument(
std::format(
"The target surface pressure must be finite. Instead P = {} was provided.",
targetPressure.value()
)
);
}
if (targetPressure.value() < 0.0) {
throw std::invalid_argument(
std::format(
"The target surface pressure must be non-negative. Instead P = {} was provided.",
targetPressure.value()
)
);
}
}
[[nodiscard]] TargetValue targetPressure() const noexcept {
return m_targetPressure;
}
[[nodiscard]] PressureSurfaceDescriptor descriptor() const noexcept {
return PressureSurfaceDescriptor{.targetPressure = m_targetPressure.value()};
}
private:
TargetValue m_targetPressure;
};
template <typename Candidate>
concept ConstantPressureSurfaceType = std::same_as<std::remove_cvref_t<Candidate>, ConstantPressureSurface>;
// Familiar physical terminology retained as a synonym, not as a second
// surface-condition type.
using Isobaric = ConstantPressureSurface;
} // namespace mean_field::surface

View File

@@ -0,0 +1,186 @@
module;
#include <array>
#include <concepts>
#include <cstddef>
#include <span>
#include <string_view>
#include <type_traits>
export module mean_field:surface.dependencies;
export import :eos.relations;
export import :field.registry;
export namespace mean_field::surface {
template <typename Candidate>
concept SurfaceFieldType = requires {
{ Candidate::name } -> std::convertible_to<std::string_view>;
} && (std::string_view{Candidate::name}.size() > 0);
class SurfaceFieldId final {
public:
explicit constexpr SurfaceFieldId(const std::string_view name) noexcept : m_name(name) {
}
[[nodiscard]] constexpr std::string_view name() const noexcept {
return m_name;
}
[[nodiscard]] friend constexpr bool operator==(
const SurfaceFieldId &,
const SurfaceFieldId &
) noexcept = default;
private:
std::string_view m_name;
};
template <SurfaceFieldType Field> inline constexpr SurfaceFieldId surfaceFieldId{std::string_view{Field::name}};
template <eos::ThermodynamicQuantityType ThermodynamicQuantity, SurfaceFieldType Field>
struct SurfaceStateBinding final {
using Quantity = ThermodynamicQuantity;
using FieldType = Field;
};
template <typename... Bindings> struct SurfaceStateBindings final { };
namespace detail {
template <typename... Types> struct SurfaceTypesAreUnique : std::true_type { };
template <typename First, typename... Remaining>
struct SurfaceTypesAreUnique<First, Remaining...>
: std::bool_constant<
(!std::same_as<First, Remaining> && ...) && SurfaceTypesAreUnique<Remaining...>::value> { };
template <typename Bindings> struct SurfaceBindingsAreValid : std::false_type { };
template <typename... Bindings>
struct SurfaceBindingsAreValid<SurfaceStateBindings<Bindings...>>
: std::bool_constant<
(sizeof...(Bindings) > 0) &&
(requires {
typename Bindings::Quantity;
typename Bindings::FieldType;
} && ...) &&
(eos::ThermodynamicQuantityType<
typename Bindings::Quantity> && ...) &&
(SurfaceFieldType<typename Bindings::FieldType> && ...) &&
SurfaceTypesAreUnique<
typename Bindings::Quantity...>::value> { };
template <typename Bindings, typename Quantity> struct SurfaceBindingCount;
template <typename Quantity, typename... Bindings>
struct SurfaceBindingCount<SurfaceStateBindings<Bindings...>, Quantity>
: std::integral_constant<
std::size_t,
(std::size_t{0} + ... +
(std::same_as<Quantity, typename Bindings::Quantity> ? std::size_t{1} : std::size_t{0}))> {
};
template <typename Bindings, typename Quantity> struct SurfaceFieldForQuantity;
template <typename Quantity, typename First, typename... Remaining>
struct SurfaceFieldForQuantity<SurfaceStateBindings<First, Remaining...>, Quantity>
: std::conditional_t<
std::same_as<Quantity, typename First::Quantity>,
std::type_identity<typename First::FieldType>,
SurfaceFieldForQuantity<SurfaceStateBindings<Remaining...>, Quantity>> { };
template <typename Candidate, std::size_t CarrierBindingCount>
struct CarrierFieldMatchesSurfaceBinding : std::false_type { };
template <typename Candidate>
struct CarrierFieldMatchesSurfaceBinding<Candidate, 1>
: std::bool_constant<std::same_as<
typename SurfaceFieldForQuantity<
typename Candidate::StateBindings,
typename Candidate::CarrierQuantity>::type,
typename Candidate::CarrierField>> { };
template <
typename Candidate,
bool BindingsAreValid = SurfaceBindingsAreValid<typename Candidate::StateBindings>::value>
struct FormulationBindingsMatchCarrier : std::false_type { };
template <typename Candidate>
struct FormulationBindingsMatchCarrier<Candidate, true>
: CarrierFieldMatchesSurfaceBinding<
Candidate,
SurfaceBindingCount<
typename Candidate::StateBindings,
typename Candidate::CarrierQuantity>::value> { };
template <typename Candidate, typename = void>
struct IsSurfaceConstraintFormulation : std::false_type { };
template <typename Candidate>
struct IsSurfaceConstraintFormulation<
Candidate,
std::void_t<
typename Candidate::CarrierQuantity,
typename Candidate::CarrierField,
typename Candidate::StateBindings>>
: std::bool_constant<
eos::ThermodynamicQuantityType<typename Candidate::CarrierQuantity> &&
SurfaceFieldType<typename Candidate::CarrierField> &&
FormulationBindingsMatchCarrier<Candidate>::value> { };
} // namespace detail
template <typename Candidate>
concept ValidSurfaceStateBindings = detail::SurfaceBindingsAreValid<std::remove_cv_t<Candidate>>::value;
template <ValidSurfaceStateBindings Bindings, typename Quantity>
inline constexpr std::size_t surfaceBindingCount = detail::SurfaceBindingCount<Bindings, Quantity>::value;
template <ValidSurfaceStateBindings Bindings, typename Quantity>
requires(surfaceBindingCount<Bindings, Quantity> == 1)
using SurfaceFieldForQuantityT = typename detail::SurfaceFieldForQuantity<Bindings, Quantity>::type;
template <
eos::ThermodynamicQuantityType CarrierThermodynamicQuantity,
SurfaceFieldType CarrierFieldType,
ValidSurfaceStateBindings Bindings>
requires(
surfaceBindingCount<Bindings, CarrierThermodynamicQuantity> == 1 &&
std::same_as<SurfaceFieldForQuantityT<Bindings, CarrierThermodynamicQuantity>, CarrierFieldType>
)
struct SurfaceConstraintFormulation final {
using CarrierQuantity = CarrierThermodynamicQuantity;
using CarrierField = CarrierFieldType;
using StateBindings = Bindings;
};
using BarotropicSurfaceFormulation = SurfaceConstraintFormulation<
eos::quantity::SpecificEnthalpy,
field::Enthalpy,
SurfaceStateBindings<SurfaceStateBinding<eos::quantity::SpecificEnthalpy, field::Enthalpy>>>;
template <typename Candidate>
concept SurfaceConstraintFormulationType =
detail::IsSurfaceConstraintFormulation<std::remove_cv_t<Candidate>>::value;
struct RuntimeSurfaceConstraintDependencies final {
SurfaceFieldId residualRowField;
std::span<const SurfaceFieldId> stateFields;
};
template <SurfaceFieldType ResidualField, SurfaceFieldType... StateFields>
struct SurfaceConstraintDependencies final {
using RowField = ResidualField;
using StateFieldTypes = field::TypeList<StateFields...>;
inline static constexpr std::array<SurfaceFieldId, sizeof...(StateFields)> runtimeStateFields{
surfaceFieldId<StateFields>...
};
[[nodiscard]] static constexpr RuntimeSurfaceConstraintDependencies runtimeDescription() noexcept {
return RuntimeSurfaceConstraintDependencies{
.residualRowField = surfaceFieldId<ResidualField>,
.stateFields = std::span<const SurfaceFieldId>{runtimeStateFields}
};
}
};
} // namespace mean_field::surface

View File

@@ -1,64 +0,0 @@
module;
#include <cmath>
#include <format>
#include <stdexcept>
export module mean_field:surface.isobaric;
export import :surface.base;
export namespace mean_field::surface {
class Isobaric final : public SurfaceBase {
public:
explicit Isobaric(const double targetPressure = 0.0) : m_targetPressure(targetPressure) {
validateTargetPressure();
}
[[nodiscard]] double targetPressure() const noexcept {
return m_targetPressure;
}
[[nodiscard]] ResolvedSurfaceCondition
resolve(const mean_field::eos::EquationOfState &equationOfState) const override {
return ResolvedSurfaceCondition{resolveTargetEnthalpy(equationOfState)};
}
void validate(const mean_field::eos::EquationOfState &equationOfState) const override {
static_cast<void>(resolveTargetEnthalpy(equationOfState));
}
private:
[[nodiscard]] double resolveTargetEnthalpy(const mean_field::eos::EquationOfState &equationOfState) const {
validateTargetPressure();
const double targetEnthalpy = equationOfState.enthalpy_from_pressure(m_targetPressure);
if (!std::isfinite(targetEnthalpy) || targetEnthalpy < 0.0) {
throw std::domain_error(
std::format(
"The equation of state resolved the isobaric "
"target P = {} to the invalid enthalpy h = {}.",
m_targetPressure, targetEnthalpy
)
);
}
return targetEnthalpy;
}
void validateTargetPressure() const {
if (!std::isfinite(m_targetPressure) || m_targetPressure < 0.0) {
throw std::invalid_argument(
std::format(
"The target surface pressure must be finite and "
"non-negative. Instead P = {} was provided.",
m_targetPressure
)
);
}
}
double m_targetPressure;
};
} // namespace mean_field::surface

View File

@@ -1,53 +0,0 @@
module;
#include <cmath>
#include <stdexcept>
export module mean_field:surface.base;
export import :eos.base;
export namespace mean_field::surface {
struct ResolvedSurfaceCondition final {
double targetEnthalpy{0.0};
explicit ResolvedSurfaceCondition(const double requestedTargetEnthalpy)
: targetEnthalpy(requestedTargetEnthalpy) {
if (!std::isfinite(targetEnthalpy) || targetEnthalpy < 0.0) {
throw std::invalid_argument(
"A resolved surface enthalpy must be finite and "
"non-negative."
);
}
}
[[nodiscard]] double residual(const double enthalpy) const {
if (!std::isfinite(enthalpy)) {
throw std::invalid_argument("A surface enthalpy value must be finite.");
}
return enthalpy - targetEnthalpy;
}
[[nodiscard]] static double jacobianAction(const double enthalpyVariation) {
if (!std::isfinite(enthalpyVariation)) {
throw std::invalid_argument("A surface enthalpy variation must be finite.");
}
return enthalpyVariation;
}
};
class SurfaceBase {
public:
virtual ~SurfaceBase() = default;
[[nodiscard]] virtual ResolvedSurfaceCondition
resolve(const mean_field::eos::EquationOfState &equationOfState) const = 0;
virtual void validate(const mean_field::eos::EquationOfState &equationOfState) const = 0;
protected:
SurfaceBase() = default;
};
} // namespace mean_field::surface

File diff suppressed because it is too large Load Diff

View File

@@ -11,82 +11,90 @@ export module mean_field:utils.misc;
import :utils.domain; import :utils.domain;
export namespace mean_field::utils { export namespace mean_field::utils {
constexpr double APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING = 1e-4; constexpr double APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING = 1e-4;
bool is_vacuum(const mfem::ElementTransformation &Tr, bool is_vacuum(
mfem::Array<mfem::Vector *> elvec) { const mfem::ElementTransformation &Tr,
using Schema = domain::CoreEnvelopeVacuumDomainSchema; mfem::Array<mfem::Vector *> elvec
) {
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
if (Schema::template attribute_belongs_to<domain::Vacuum>(Tr.Attribute)) { if (Schema::template attribute_belongs_to<domain::Vacuum>(Tr.Attribute)) {
const int size_elvec = elvec.Size(); const int size_elvec = elvec.Size();
for (int i = 0; i < size_elvec; i++) { for (int i = 0; i < size_elvec; i++) {
if (elvec[i]) { if (elvec[i]) {
*elvec[i] = 0.0; *elvec[i] = 0.0;
} }
} }
return true; return true;
}
return false;
}
bool is_vacuum(const mfem::ElementTransformation &Tr,
const mfem::Array2D<mfem::DenseMatrix *> &elmats) {
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
if (Schema::template attribute_belongs_to<domain::Vacuum>(Tr.Attribute)) {
const int cols = elmats.NumCols();
const int rows = elmats.NumRows();
for (int rowID = 0; rowID < rows; rowID++) {
for (int colID = 0; colID < cols; colID++) {
if (elmats(rowID, colID)) {
*elmats(rowID, colID) = 0.0;
} }
} return false;
} }
return true;
}
return false;
}
constexpr std::string_view ANSI_GREEN = "\033[32m"; bool is_vacuum(
constexpr std::string_view ANSI_RED = "\033[31m"; const mfem::ElementTransformation &Tr,
constexpr std::string_view ANSI_YELLOW = "\033[33m"; const mfem::Array2D<mfem::DenseMatrix *> &elmats
constexpr std::string_view ANSI_BLUE = "\033[34m"; ) {
constexpr std::string_view ANSI_MAGENTA = "\033[35m"; using Schema = domain::CoreEnvelopeVacuumDomainSchema;
constexpr std::string_view ANSI_CYAN = "\033[36m";
constexpr std::string_view ANSI_RESET = "\033[0m";
constexpr std::string_view ANSI_BCYAN = "\033[1;36m";
constexpr double G = 1.0; if (Schema::template attribute_belongs_to<domain::Vacuum>(Tr.Attribute)) {
constexpr double MASS = 1.0; const int cols = elmats.NumCols();
constexpr double RADIUS = 1.0; const int rows = elmats.NumRows();
for (int rowID = 0; rowID < rows; rowID++) {
for (int colID = 0; colID < cols; colID++) {
if (elmats(rowID, colID)) {
*elmats(rowID, colID) = 0.0;
}
}
}
return true;
}
return false;
}
[[maybe_unused]] constexpr char HOST[10] = "localhost"; constexpr std::string_view ANSI_GREEN = "\033[32m";
[[maybe_unused]] constexpr int PORT = 19916; constexpr std::string_view ANSI_RED = "\033[31m";
constexpr std::string_view ANSI_YELLOW = "\033[33m";
constexpr std::string_view ANSI_BLUE = "\033[34m";
constexpr std::string_view ANSI_MAGENTA = "\033[35m";
constexpr std::string_view ANSI_CYAN = "\033[36m";
constexpr std::string_view ANSI_RESET = "\033[0m";
constexpr std::string_view ANSI_BCYAN = "\033[1;36m";
template <typename T> constexpr double G = 1.0;
concept is_xad = std::is_same_v<T, xad::AReal<long double>> || constexpr double MASS = 1.0;
std::is_same_v<T, xad::AReal<double>> || constexpr double RADIUS = 1.0;
std::is_same_v<T, xad::AReal<float>>;
template <typename T> [[maybe_unused]] constexpr char HOST[10] = "localhost";
concept is_real = std::is_floating_point_v<T> || is_xad<T>; [[maybe_unused]] constexpr int PORT = 19916;
template <is_real T> template <typename T>
using EOS_P = std::function<T(const T &rho, const T &temp)>; concept is_xad = std::is_same_v<T, xad::AReal<long double>> || std::is_same_v<T, xad::AReal<double>> ||
std::is_same_v<T, xad::AReal<float>>;
enum class DOMAINS : uint8_t { template <typename T>
CORE = 1 << 0, concept is_real = std::is_floating_point_v<T> || is_xad<T>;
ENVELOPE = 1 << 1,
VACUUM = 1 << 2,
STELLAR = CORE | ENVELOPE,
ALL = CORE | ENVELOPE | VACUUM
};
DOMAINS operator|(DOMAINS lhs, DOMAINS rhs); template <is_real T> using EOS_P = std::function<T(const T &rho, const T &temp)>;
DOMAINS operator&(DOMAINS lhs, DOMAINS rhs); enum class DOMAINS : uint8_t {
CORE = 1 << 0,
ENVELOPE = 1 << 1,
VACUUM = 1 << 2,
STELLAR = CORE | ENVELOPE,
ALL = CORE | ENVELOPE | VACUUM
};
int get_mesh_order(const mfem::Mesh &mesh); DOMAINS operator|(
DOMAINS lhs,
DOMAINS rhs
);
DOMAINS operator&(
DOMAINS lhs,
DOMAINS rhs
);
int get_mesh_order(const mfem::Mesh &mesh);
} // namespace mean_field::utils } // namespace mean_field::utils

View File

@@ -94,6 +94,65 @@ namespace field_dof_map_test_utils {
domain::RelationList<>>; domain::RelationList<>>;
} // namespace field_dof_map_test_utils } // namespace field_dof_map_test_utils
TEST_CASE(
"Field Boundary DOF Map Selects The Stellar Surface In Reduced Field Ordering",
tags::surface_boundary_dof_topology
) {
namespace domain = mean_field::utils::domain;
namespace field = mean_field::field;
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 field::FieldDofMap enthalpyMap =
field::make_field_dof_map<field::Enthalpy, field_dof_map_test_utils::Schema>(*f.enthalpyFes);
const field::FieldBoundaryDofMap stellarSurface =
field::make_field_boundary_dof_map<field::Enthalpy, domain::StellarSurface, field_dof_map_test_utils::Schema>(
*f.enthalpyFes, enthalpyMap
);
CHECK(stellarSurface.field_size() == enthalpyMap.reduced_size());
CHECK(field_dof_map_test_utils::global_sum(stellarSurface.size()) > 0);
CHECK(
field_dof_map_test_utils::global_sum(stellarSurface.size()) <
field_dof_map_test_utils::global_sum(enthalpyMap.reduced_size())
);
for (const int reducedDof : stellarSurface.reduced_dofs()) {
CAPTURE(reducedDof);
CHECK(stellarSurface.contains(reducedDof));
CHECK(enthalpyMap.contains_true_dof(enthalpyMap.true_dof(reducedDof)));
}
}
TEST_CASE(
"Field Point DOF Map Selects One Vector Vertex At The Computational Origin",
tags::translational_centering_topology
) {
namespace field = mean_field::field;
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 field::FieldDofMap displacementMap =
field::make_field_dof_map<field::Displacement, field_dof_map_test_utils::Schema>(*f.displacementFes);
mfem::Vector origin(f.mesh->SpaceDimension());
origin = 0.0;
const field::FieldPointDofMap centerRows =
field::make_field_point_dof_map<field::Displacement>(*f.displacementFes, displacementMap, origin, 1.0e-12);
CHECK(centerRows.field_size() == displacementMap.reduced_size());
CHECK(field_dof_map_test_utils::global_sum(centerRows.size()) == f.mesh->SpaceDimension());
for (const int reducedDof : centerRows.reduced_dofs()) {
CAPTURE(reducedDof);
CHECK(centerRows.contains(reducedDof));
CHECK(displacementMap.contains_true_dof(displacementMap.true_dof(reducedDof)));
}
}
TEST_CASE( TEST_CASE(
"Field DOF Map Preserves Canonical Bidirectional Indexing", "Field DOF Map Preserves Canonical Bidirectional Indexing",
tags::field_dof_unit tags::field_dof_unit
@@ -523,9 +582,7 @@ TEST_CASE(
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Displacement>); STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Displacement>);
STATIC_REQUIRE_FALSE( STATIC_REQUIRE_FALSE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::BarotropicConstant>);
field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::BarotropicConstant>
);
CHECK(true); CHECK(true);
} }
@@ -750,9 +807,8 @@ TEST_CASE(
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh(); mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2); auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace = auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr); REQUIRE(finiteElementSpace != nullptr);
@@ -796,9 +852,8 @@ TEST_CASE(
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh(); mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Enthalpy>::make_fec<field::Enthalpy::Scalar>(2); auto fec = field::Field<field::Enthalpy>::make_fec<field::Enthalpy::Scalar>(2);
auto finiteElementSpace = auto finiteElementSpace = field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr); REQUIRE(finiteElementSpace != nullptr);
@@ -849,9 +904,8 @@ TEST_CASE(
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh(); mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2); auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace = auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr); REQUIRE(finiteElementSpace != nullptr);
@@ -900,9 +954,8 @@ TEST_CASE(
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh(); mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Displacement>::make_fec<field::Displacement::Vector>(2); auto fec = field::Field<field::Displacement>::make_fec<field::Displacement::Vector>(2);
auto finiteElementSpace = auto finiteElementSpace = field::Field<field::Displacement>::make_fespace<field::Displacement::Vector>(mesh, *fec);
field::Field<field::Displacement>::make_fespace<field::Displacement::Vector>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr); REQUIRE(finiteElementSpace != nullptr);
@@ -939,13 +992,11 @@ TEST_CASE(
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh(); mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2); auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace = auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
auto otherFec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2); auto otherFec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto otherFiniteElementSpace = auto otherFiniteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *otherFec);
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *otherFec);
REQUIRE(finiteElementSpace != nullptr); REQUIRE(finiteElementSpace != nullptr);
REQUIRE(otherFiniteElementSpace != nullptr); REQUIRE(otherFiniteElementSpace != nullptr);
@@ -959,8 +1010,7 @@ TEST_CASE(
const mfem::Array<int> empty; const mfem::Array<int> empty;
CHECK_THROWS_AS( CHECK_THROWS_AS(
(field::FieldDofGridFunctionAdapter( (field::FieldDofGridFunctionAdapter(
field::FieldDofMap(finiteElementSpace->GetTrueVSize() + 1, empty), field::FieldDofMap(finiteElementSpace->GetTrueVSize() + 1, empty), *finiteElementSpace
*finiteElementSpace
)), )),
std::invalid_argument std::invalid_argument
); );

View File

@@ -11,7 +11,10 @@ using namespace mean_field;
namespace { namespace {
struct SerialMappingData { struct SerialMappingData {
explicit SerialMappingData(mfem::Mesh &mesh) explicit SerialMappingData(mfem::Mesh &mesh)
: compactification_fes(&mesh, &compactification_fec), : compactification_fes(
&mesh,
&compactification_fec
),
compactification_coordinate(&compactification_fes), compactification_coordinate(&compactification_fes),
mapper(field_dof_test_utils::make_domain_mapper()) { mapper(field_dof_test_utils::make_domain_mapper()) {
compactification_coordinate = 0.0; compactification_coordinate = 0.0;
@@ -83,7 +86,7 @@ TEST_CASE(
quadrature::RuleFactory quadrature_factory(std::move(policy)); quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general; const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder(); const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal( quadrature_factory.configure_centrifugal(
integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation, integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation,
@@ -183,7 +186,7 @@ TEST_CASE(
quadrature::RuleFactory quadrature_factory(std::move(policy)); quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general; const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder(); const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal( quadrature_factory.configure_centrifugal(
integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation, integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation,
@@ -344,7 +347,7 @@ TEST_CASE(
quadrature::RuleFactory quadrature_factory(std::move(policy)); quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general; const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder(); const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal( quadrature_factory.configure_centrifugal(
integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation, integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation,
@@ -606,8 +609,8 @@ TEST_CASE(
mapping_evaluator.GetQuadratureContext(*transformation, integration_point); mapping_evaluator.GetQuadratureContext(*transformation, integration_point);
const double signed_map_determinant = context.detJ; const double signed_map_determinant = context.detJ;
local_minimum_map_determinant = std::min(local_minimum_map_determinant, signed_map_determinant); local_minimum_map_determinant = std::min(local_minimum_map_determinant, signed_map_determinant);
local_maximum_map_determinant = std::max(local_maximum_map_determinant, signed_map_determinant); local_maximum_map_determinant = std::max(local_maximum_map_determinant, signed_map_determinant);
mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical); mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
velocity_element->CalcShape(integration_point, velocity_shape); velocity_element->CalcShape(integration_point, velocity_shape);
@@ -808,7 +811,7 @@ TEST_CASE(
mapping_evaluator.GetQuadratureContext(*transformation, integration_point); mapping_evaluator.GetQuadratureContext(*transformation, integration_point);
const double signed_map_determinant = context.detJ; const double signed_map_determinant = context.detJ;
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant); local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical); mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
velocity_element->CalcShape(integration_point, velocity_shape); velocity_element->CalcShape(integration_point, velocity_shape);
@@ -999,7 +1002,7 @@ TEST_CASE(
mapping_evaluator.GetQuadratureContext(*transformation, integration_point); mapping_evaluator.GetQuadratureContext(*transformation, integration_point);
const double signed_map_determinant = context.detJ; const double signed_map_determinant = context.detJ;
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant); local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical); mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
velocity_element->CalcShape(integration_point, velocity_shape); velocity_element->CalcShape(integration_point, velocity_shape);

View File

@@ -43,8 +43,8 @@ TEST_CASE(
mfem::GridFunction displacement(&displacement_fes); mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0; displacement = 0.0;
mfem::GridFunction compactification_coordinate(&compactification_fes); mfem::GridFunction compactification_coordinate(&compactification_fes);
compactification_coordinate = 0.0; compactification_coordinate = 0.0;
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper(); mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0);
@@ -129,8 +129,7 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual; element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator( integrators::GravityMomentumIntegrator integrator(
domain_mapper, displacement, compactification_coordinate, domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
integrators::GravityForceJacobianMode::field_coupled
); );
const int maximum_order = std::max( const int maximum_order = std::max(
@@ -297,12 +296,10 @@ TEST_CASE(
mfem::GridFunction displacement(&displacement_fes); mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0; displacement = 0.0;
mfem::GridFunction compactification_coordinate(&compactification_fes); mfem::GridFunction compactification_coordinate(&compactification_fes);
compactification_coordinate = 0.0; compactification_coordinate = 0.0;
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper(); mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
mapping::GridFunctionMappingEvaluator mapping_evaluator( mapping::GridFunctionMappingEvaluator mapping_evaluator(domain_mapper, displacement, compactification_coordinate);
domain_mapper, displacement, compactification_coordinate
);
auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); }; auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); };
@@ -381,8 +378,7 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual; element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator( integrators::GravityMomentumIntegrator integrator(
domain_mapper, displacement, compactification_coordinate, domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
integrators::GravityForceJacobianMode::field_coupled
); );
const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8); const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8);
@@ -471,11 +467,9 @@ TEST_CASE(
mfem::GridFunction displacement(&displacement_fes); mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0; displacement = 0.0;
mfem::GridFunction compactification_coordinate(&compactification_fes); mfem::GridFunction compactification_coordinate(&compactification_fes);
compactification_coordinate = 0.0; compactification_coordinate = 0.0;
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper(); mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
mapping::GridFunctionMappingEvaluator mapping_evaluator( mapping::GridFunctionMappingEvaluator mapping_evaluator(domain_mapper, displacement, compactification_coordinate);
domain_mapper, displacement, compactification_coordinate
);
auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) { auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) {
gravity.SetSize(3); gravity.SetSize(3);
@@ -553,8 +547,7 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual; element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator( integrators::GravityMomentumIntegrator integrator(
domain_mapper, displacement, compactification_coordinate, domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
integrators::GravityForceJacobianMode::field_coupled
); );
const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8); const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8);

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
#include <array>
#include <cmath> #include <cmath>
#include <concepts>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
@@ -12,17 +14,11 @@ import test_helpers;
namespace { namespace {
struct StellarModelExtensionTracker final { struct StellarModelExtensionTracker final {
int structureValidationCount{0}; int structureValidationCount{0};
int surfaceValidationCount{0};
int surfaceResolutionCount{0};
const mean_field::eos::EquationOfState *structureEquationOfState{nullptr}; const mean_field::eos::Polytrope *structureEquationOfState{nullptr};
const mean_field::eos::EquationOfState *surfaceValidationEquationOfState{nullptr};
const mean_field::eos::EquationOfState *surfaceResolutionEquationOfState{nullptr};
}; };
class StellarModelTestStructure final : public mean_field::models::structure::StructureBase { class StellarModelTestStructure final {
public: public:
explicit StellarModelTestStructure(std::shared_ptr<StellarModelExtensionTracker> tracker) explicit StellarModelTestStructure(std::shared_ptr<StellarModelExtensionTracker> tracker)
: m_tracker(std::move(tracker)), : m_tracker(std::move(tracker)),
@@ -32,17 +28,17 @@ namespace {
) { ) {
} }
[[nodiscard]] const mean_field::eos::EquationOfState &equationOfState() const noexcept override { [[nodiscard]] const mean_field::eos::Polytrope &equationOfState() const noexcept {
m_tracker->structureEquationOfState = &m_equationOfState; m_tracker->structureEquationOfState = &m_equationOfState;
return m_equationOfState; return m_equationOfState;
} }
[[nodiscard]] double targetMass() const noexcept override { [[nodiscard]] double targetMass() const noexcept {
return 2.5; return 2.5;
} }
[[nodiscard]] mean_field::models::structure::StructureSeed [[nodiscard]] mean_field::models::structure::StructureSeed
makeInitialSeed(const mean_field::models::structure::StructureSeedRequest &request) const override { makeInitialSeed(const mean_field::models::structure::StructureSeedRequest &request) const {
mean_field::models::structure::StructureSeed seed; mean_field::models::structure::StructureSeed seed;
seed.radius.SetSize(2); seed.radius.SetSize(2);
@@ -65,7 +61,7 @@ namespace {
return seed; return seed;
} }
void validate() const override { void validate() const {
++m_tracker->structureValidationCount; ++m_tracker->structureValidationCount;
} }
@@ -74,59 +70,85 @@ namespace {
mean_field::eos::Polytrope m_equationOfState; mean_field::eos::Polytrope m_equationOfState;
}; };
class StellarModelTestSurface final : public mean_field::surface::SurfaceBase { class StructureWithoutSeed final {
public: public:
explicit StellarModelTestSurface(std::shared_ptr<StellarModelExtensionTracker> tracker) [[nodiscard]] const mean_field::eos::Polytrope &equationOfState() const noexcept;
: m_tracker(std::move(tracker)) {
}
[[nodiscard]] [[nodiscard]] double targetMass() const noexcept;
mean_field::surface::ResolvedSurfaceCondition
resolve(const mean_field::eos::EquationOfState &equationOfState) const override {
++m_tracker->surfaceResolutionCount;
m_tracker->surfaceResolutionEquationOfState = &equationOfState; void validate() const;
return mean_field::surface::ResolvedSurfaceCondition{0.375};
}
void validate(const mean_field::eos::EquationOfState &equationOfState) const override {
++m_tracker->surfaceValidationCount;
m_tracker->surfaceValidationEquationOfState = &equationOfState;
}
private:
std::shared_ptr<StellarModelExtensionTracker> m_tracker;
}; };
class SurfaceWithoutPhysicalQuantity final { };
struct ModelSurfaceState final {
double specificEnthalpy;
[[nodiscard]] mean_field::eos::SpecificEnthalpyValue
value(mean_field::eos::quantity::SpecificEnthalpy) const noexcept {
return mean_field::eos::SpecificEnthalpyValue{specificEnthalpy};
}
};
using PolytropicStellarModel = mean_field::models::StellarModel<mean_field::models::structure::PolytropicStructure>;
using ExtensionStellarModel = mean_field::models::StellarModel<StellarModelTestStructure>;
} // namespace } // namespace
TEST_CASE( TEST_CASE(
"Stellar Model Owns Structure And Surface Prescriptions", "Stellar Model Owns Structure And Surface Prescriptions",
tags::barotrope &tags::unit &tags::model tags::stellar_model_type_contract
) { ) {
STATIC_REQUIRE_FALSE(std::is_copy_constructible_v<mean_field::models::StellarModel>); STATIC_CHECK(mean_field::models::StructurePrescription<mean_field::models::structure::PolytropicStructure>);
STATIC_CHECK(mean_field::models::StructurePrescription<StellarModelTestStructure>);
STATIC_CHECK_FALSE(mean_field::models::StructurePrescription<StructureWithoutSeed>);
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<mean_field::models::StellarModel>); STATIC_CHECK(
mean_field::models::SurfacePrescription<
mean_field::surface::ConstantPressureSurface, mean_field::eos::Polytrope>
);
STATIC_CHECK_FALSE(
mean_field::models::SurfacePrescription<SurfaceWithoutPhysicalQuantity, mean_field::eos::Polytrope>
);
STATIC_REQUIRE(std::is_nothrow_move_constructible_v<mean_field::models::StellarModel>); STATIC_CHECK_FALSE(std::derived_from<StellarModelTestStructure, mean_field::models::structure::StructureBase>);
STATIC_CHECK_FALSE(
std::derived_from<
mean_field::models::structure::PolytropicStructure, mean_field::models::structure::StructureBase>
);
STATIC_CHECK(
std::same_as<
decltype(std::declval<const mean_field::models::structure::StructureBase &>().equationOfState()),
mean_field::eos::EquationOfStateView>
);
STATIC_REQUIRE(std::is_nothrow_move_assignable_v<mean_field::models::StellarModel>); STATIC_REQUIRE_FALSE(std::is_copy_constructible_v<PolytropicStellarModel>);
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<PolytropicStellarModel>);
STATIC_REQUIRE(std::is_nothrow_move_constructible_v<PolytropicStellarModel>);
STATIC_REQUIRE(std::is_nothrow_move_assignable_v<PolytropicStellarModel>);
mean_field::models::StellarModel model{ mean_field::models::StellarModel model{
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
mean_field::surface::Isobaric{0.0} mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
}; };
CHECK(model.targetMass() == 1.0); STATIC_CHECK(std::same_as<decltype(model), PolytropicStellarModel>);
CHECK(model.resolvedSurfaceCondition().targetEnthalpy == 0.0); STATIC_CHECK(
std::same_as<
CHECK( decltype(model.structurePrescription()), const mean_field::models::structure::PolytropicStructure &>
dynamic_cast<const mean_field::models::structure::PolytropicStructure *>(&model.structurePrescription()) !=
nullptr
); );
STATIC_CHECK(
std::same_as<decltype(model.surfacePrescription()), const mean_field::surface::ConstantPressureSurface &>
);
STATIC_CHECK(std::same_as<decltype(model.equationOfState()), const mean_field::eos::Polytrope &>);
CHECK(dynamic_cast<const mean_field::surface::Isobaric *>(&model.surfacePrescription()) != nullptr); CHECK(model.targetMass() == 1.0);
CHECK(model.compiledSurfaceConstraint().targetPressure() == mean_field::eos::PressureValue{0.0});
CHECK(&model.equationOfState() == &model.structurePrescription().equationOfState());
CHECK(model.surfacePrescription().targetPressure() == mean_field::eos::PressureValue{0.0});
} }
TEST_CASE( TEST_CASE(
@@ -135,7 +157,7 @@ TEST_CASE(
) { ) {
mean_field::models::StellarModel model{ mean_field::models::StellarModel model{
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
mean_field::surface::Isobaric{} mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
}; };
const mean_field::models::structure::StructureSeed seed = const mean_field::models::structure::StructureSeed seed =
@@ -153,45 +175,49 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Moving A Stellar Model Preserves Stable Prescription Addresses", "Moving A Stellar Model Preserves Stable Prescription Addresses",
tags::barotrope &tags::unit &tags::model tags::barotrope &tags::unit &tags::model &tags::surface_constraint_lifetime
) { ) {
mean_field::models::StellarModel originalModel{ mean_field::models::StellarModel originalModel{
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
mean_field::surface::Isobaric{} mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
}; };
const mean_field::models::structure::StructureBase *structureAddress = &originalModel.structurePrescription(); const mean_field::models::structure::PolytropicStructure *structureAddress = &originalModel.structurePrescription();
const mean_field::surface::SurfaceBase *surfaceAddress = &originalModel.surfacePrescription(); const mean_field::surface::ConstantPressureSurface *surfaceAddress = &originalModel.surfacePrescription();
const mean_field::eos::EquationOfState *equationOfStateAddress = &originalModel.equationOfState(); const mean_field::eos::Polytrope *equationOfStateAddress = &originalModel.equationOfState();
const auto *compiledSurfaceConstraintAddress = &originalModel.compiledSurfaceConstraint();
mean_field::models::StellarModel movedModel{std::move(originalModel)}; mean_field::models::StellarModel movedModel{std::move(originalModel)};
CHECK(&movedModel.structurePrescription() == structureAddress); CHECK(&movedModel.structurePrescription() == structureAddress);
CHECK(&movedModel.surfacePrescription() == surfaceAddress); CHECK(&movedModel.surfacePrescription() == surfaceAddress);
CHECK(&movedModel.equationOfState() == equationOfStateAddress); CHECK(&movedModel.equationOfState() == equationOfStateAddress);
CHECK(&movedModel.compiledSurfaceConstraint() == compiledSurfaceConstraintAddress);
CHECK(movedModel.targetMass() == 1.0); CHECK(movedModel.targetMass() == 1.0);
} }
TEST_CASE( TEST_CASE(
"Stellar Model Resolves A Positive Isobaric Surface", "Stellar Model Compiles A Positive Constant Pressure Surface",
tags::barotrope &tags::unit &tags::model tags::barotrope &tags::unit &tags::model
) { ) {
constexpr double targetPressure = 0.03125; constexpr double targetPressure = 0.03125;
mean_field::models::StellarModel model{ mean_field::models::StellarModel model{
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
mean_field::surface::Isobaric{targetPressure} mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{targetPressure}}
}; };
const double targetEnthalpy = model.resolvedSurfaceCondition().targetEnthalpy; const double requiredSpecificEnthalpy = mean_field::eos::evaluate<mean_field::eos::quantity::SpecificEnthalpy>(
model.equationOfState(), mean_field::eos::PressureValue{targetPressure}
)
.value();
CHECK(targetEnthalpy > 0.0); CHECK(requiredSpecificEnthalpy > 0.0);
CHECK( CHECK(model.compiledSurfaceConstraint().targetPressure() == mean_field::eos::PressureValue{targetPressure});
std::abs(model.equationOfState().pressure_from_enthalpy(targetEnthalpy) - targetPressure) < CHECK(model.compiledSurfaceConstraint().residual(ModelSurfaceState{requiredSpecificEnthalpy}) == 0.0);
64.0 * std::numeric_limits<double>::epsilon()
);
} }
TEST_CASE( TEST_CASE(
@@ -200,27 +226,22 @@ TEST_CASE(
) { ) {
const auto tracker = std::make_shared<StellarModelExtensionTracker>(); const auto tracker = std::make_shared<StellarModelExtensionTracker>();
mean_field::models::StellarModel model{StellarModelTestStructure{tracker}, StellarModelTestSurface{tracker}}; mean_field::models::StellarModel model{
StellarModelTestStructure{tracker},
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.375}}
};
STATIC_CHECK(std::same_as<decltype(model), ExtensionStellarModel>);
REQUIRE(tracker->structureValidationCount == 1); REQUIRE(tracker->structureValidationCount == 1);
REQUIRE(tracker->surfaceValidationCount == 1);
REQUIRE(tracker->surfaceResolutionCount == 1);
CHECK(dynamic_cast<const StellarModelTestStructure *>(&model.structurePrescription()) != nullptr); const mean_field::eos::Polytrope *ownedEquationOfState = &model.equationOfState();
CHECK(dynamic_cast<const StellarModelTestSurface *>(&model.surfacePrescription()) != nullptr);
const mean_field::eos::EquationOfState *ownedEquationOfState = &model.equationOfState();
CHECK(tracker->structureEquationOfState == ownedEquationOfState); CHECK(tracker->structureEquationOfState == ownedEquationOfState);
CHECK(tracker->surfaceValidationEquationOfState == ownedEquationOfState);
CHECK(tracker->surfaceResolutionEquationOfState == ownedEquationOfState);
CHECK(model.targetMass() == 2.5); CHECK(model.targetMass() == 2.5);
CHECK(model.resolvedSurfaceCondition().targetEnthalpy == 0.375); CHECK(model.compiledSurfaceConstraint().targetPressure() == mean_field::eos::PressureValue{0.375});
} }
TEST_CASE( TEST_CASE(
@@ -229,23 +250,25 @@ TEST_CASE(
) { ) {
mean_field::models::StellarModel sourceModel{ mean_field::models::StellarModel sourceModel{
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.25}, mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.25},
mean_field::surface::Isobaric{0.0} mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
}; };
mean_field::models::StellarModel destinationModel{ mean_field::models::StellarModel destinationModel{
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{2.0, 0.5}, 4.0}, mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{2.0, 0.5}, 4.0},
mean_field::surface::Isobaric{0.02} mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.02}}
}; };
const mean_field::models::structure::StructureBase *sourceStructureAddress = &sourceModel.structurePrescription(); const mean_field::models::structure::PolytropicStructure *sourceStructureAddress =
&sourceModel.structurePrescription();
const mean_field::surface::SurfaceBase *sourceSurfaceAddress = &sourceModel.surfacePrescription(); const mean_field::surface::ConstantPressureSurface *sourceSurfaceAddress = &sourceModel.surfacePrescription();
const mean_field::eos::EquationOfState *sourceEquationOfStateAddress = &sourceModel.equationOfState(); const mean_field::eos::Polytrope *sourceEquationOfStateAddress = &sourceModel.equationOfState();
const double sourceTargetEnthalpy = sourceModel.resolvedSurfaceCondition().targetEnthalpy; const mean_field::eos::PressureValue sourceTargetPressure =
sourceModel.compiledSurfaceConstraint().targetPressure();
destinationModel = std::move(sourceModel); destinationModel = std::move(sourceModel);
CHECK(&destinationModel.structurePrescription() == sourceStructureAddress); CHECK(&destinationModel.structurePrescription() == sourceStructureAddress);
@@ -255,5 +278,83 @@ TEST_CASE(
CHECK(destinationModel.targetMass() == 1.25); CHECK(destinationModel.targetMass() == 1.25);
CHECK(destinationModel.resolvedSurfaceCondition().targetEnthalpy == sourceTargetEnthalpy); CHECK(destinationModel.compiledSurfaceConstraint().targetPressure() == sourceTargetPressure);
} }
TEST_CASE(
"Stellar Model View Supports Heterogeneous Typed Models",
tags::stellar_model_runtime_view
) {
const auto tracker = std::make_shared<StellarModelExtensionTracker>();
const mean_field::models::StellarModel polytropicModel{
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
};
const mean_field::models::StellarModel extensionModel{
StellarModelTestStructure{tracker},
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.375}}
};
STATIC_CHECK(std::is_trivially_copyable_v<mean_field::models::StellarModelView>);
STATIC_CHECK_FALSE(std::constructible_from<mean_field::models::StellarModelView, PolytropicStellarModel &&>);
const std::array views{
mean_field::models::StellarModelView{polytropicModel}, mean_field::models::StellarModelView{extensionModel}
};
CHECK(views[0].targetMass() == 1.0);
CHECK(views[1].targetMass() == 2.5);
CHECK(views[1].surfaceCondition().targetPressure == 0.375);
REQUIRE(views[1].surfaceDependencies().stateFields.size() == 1);
CHECK(
views[1].surfaceDependencies().residualRowField ==
mean_field::surface::surfaceFieldId<mean_field::field::Enthalpy>
);
const auto pressure =
views[0].equationOfState().tryEvaluate<mean_field::eos::quantity::Pressure>(mean_field::eos::DensityValue{0.7});
REQUIRE(pressure.has_value());
CHECK(
pressure->value() == mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
polytropicModel.equationOfState(), mean_field::eos::DensityValue{0.7}
)
.value()
);
const mean_field::models::structure::StructureSeed seed =
views[1].makeInitialSeed({.centralDensity = 1.75, .radialSampleCount = 2});
CHECK(seed.centralDensity == 1.75);
CHECK(seed.radius.Size() == 2);
}
TEST_CASE(
"Stellar Model View Retains Stable Pointees When Its Owner Moves",
tags::stellar_model_runtime_view
) {
mean_field::models::StellarModel originalModel{
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
};
const mean_field::models::StellarModelView view{originalModel};
PolytropicStellarModel movedModel{std::move(originalModel)};
const auto pressure =
view.equationOfState().tryEvaluate<mean_field::eos::quantity::Pressure>(mean_field::eos::DensityValue{0.7});
const mean_field::models::structure::StructureSeed seed =
view.makeInitialSeed({.centralDensity = 1.0, .radialSampleCount = 8});
REQUIRE(pressure.has_value());
CHECK(view.targetMass() == movedModel.targetMass());
CHECK(
pressure->value() == mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
movedModel.equationOfState(), mean_field::eos::DensityValue{0.7}
)
.value()
);
CHECK(seed.radius.Size() == 8);
}

View File

@@ -164,9 +164,7 @@ TEST_CASE(
CHECK_FALSE(gravityPotentialReport.updatedDisplacement); CHECK_FALSE(gravityPotentialReport.updatedDisplacement);
CHECK_FALSE(gravityPotentialReport.updatedBernoulliConstant); CHECK_FALSE(gravityPotentialReport.updatedBernoulliConstant);
CHECK( CHECK(context.GetBaseGravityPotentialTrue()(context.GetGravityPotentialMap().true_dof(0)) == gravityPotential(0));
context.GetBaseGravityPotentialTrue()(context.GetGravityPotentialMap().true_dof(0)) == gravityPotential(0)
);
++dependencies.bernoulliConstant.revision; ++dependencies.bernoulliConstant.revision;

File diff suppressed because it is too large Load Diff

View File

@@ -11,386 +11,370 @@ import mean_field;
import test_helpers; import test_helpers;
namespace gravity_displacement_force_analytic_test_utils { namespace gravity_displacement_force_analytic_test_utils {
struct AffineCase { struct AffineCase {
const char *name; const char *name;
std::array<double, 3> scales; std::array<double, 3> scales;
}; };
[[nodiscard]] double analytic_sphere_volume(const double radius) { [[nodiscard]] double analytic_sphere_volume(const double radius) {
return (4.0 / 3.0) * std::numbers::pi * radius * radius * radius; return (4.0 / 3.0) * std::numbers::pi * radius * radius * radius;
} }
[[nodiscard]] double determinant(const std::array<double, 3> &scales) { [[nodiscard]] double determinant(
return scales[0] * scales[1] * scales[2]; const std::array<
} double,
3> &scales
) {
return scales[0] * scales[1] * scales[2];
}
[[nodiscard]] double relative_scalar_error(const double computed, [[nodiscard]] double relative_scalar_error(
const double expected) { const double computed,
return std::abs(computed - expected) / std::max(std::abs(expected), 1.0e-30); const double expected
} ) {
return std::abs(computed - expected) / std::max(std::abs(expected), 1.0e-30);
}
[[nodiscard]] mfem::Vector make_constant_density(const mean_field::fem::FEM &f, [[nodiscard]] mfem::Vector make_constant_density(
const double densityValue) { const mean_field::fem::FEM &f,
mfem::ParGridFunction densityField(f.densityFes.get()); const double densityValue
mfem::ConstantCoefficient densityCoefficient(densityValue); ) {
densityField.ProjectCoefficient(densityCoefficient); mfem::ParGridFunction densityField(f.densityFes.get());
mfem::ConstantCoefficient densityCoefficient(densityValue);
densityField.ProjectCoefficient(densityCoefficient);
mfem::Vector densityTrue; mfem::Vector densityTrue;
densityField.GetTrueDofs(densityTrue); densityField.GetTrueDofs(densityTrue);
return densityTrue; return densityTrue;
} }
[[nodiscard]] mfem::Vector [[nodiscard]] mfem::Vector make_reference_gravity(
make_reference_gravity(const mean_field::fem::FEM &f, const mean_field::fem::FEM &f,
const std::array<double, 3> &referenceGravity) { const std::array<
mfem::ParGridFunction gravityField(f.gravityFluxFes.get()); double,
3> &referenceGravity
) {
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
mfem::VectorFunctionCoefficient gravityCoefficient( mfem::VectorFunctionCoefficient gravityCoefficient(
f.mesh->Dimension(), f.mesh->Dimension(), [referenceGravity](const mfem::Vector &, mfem::Vector &value) {
[referenceGravity](const mfem::Vector &, mfem::Vector &value) { value.SetSize(3);
value.SetSize(3);
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
value(component) = value(component) = referenceGravity[static_cast<std::size_t>(component)];
referenceGravity[static_cast<std::size_t>(component)]; }
} }
}); );
gravityField.ProjectCoefficient(gravityCoefficient); gravityField.ProjectCoefficient(gravityCoefficient);
mfem::Vector gravityTrue; mfem::Vector gravityTrue;
gravityField.GetTrueDofs(gravityTrue); gravityField.GetTrueDofs(gravityTrue);
return gravityTrue; return gravityTrue;
} }
[[nodiscard]] mfem::Vector make_radial_gravity(const mean_field::fem::FEM &f, [[nodiscard]] mfem::Vector make_radial_gravity(
const double radialCoefficient) { const mean_field::fem::FEM &f,
mfem::ParGridFunction gravityField(f.gravityFluxFes.get()); const double radialCoefficient
) {
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
mfem::VectorFunctionCoefficient gravityCoefficient( mfem::VectorFunctionCoefficient gravityCoefficient(
f.mesh->Dimension(), f.mesh->Dimension(), [radialCoefficient](const mfem::Vector &position, mfem::Vector &value) {
[radialCoefficient](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) = radialCoefficient * position(component); value(component) = radialCoefficient * position(component);
} }
}); }
);
gravityField.ProjectCoefficient(gravityCoefficient); gravityField.ProjectCoefficient(gravityCoefficient);
mfem::Vector gravityTrue; mfem::Vector gravityTrue;
gravityField.GetTrueDofs(gravityTrue); gravityField.GetTrueDofs(gravityTrue);
return gravityTrue; return gravityTrue;
} }
[[nodiscard]] mfem::Vector [[nodiscard]] mfem::Vector make_affine_displacement(
make_affine_displacement(const mean_field::fem::FEM &f, const mean_field::fem::FEM &f,
const std::array<double, 3> &scales) { const std::array<
mfem::ParGridFunction displacementField(f.displacementFes.get()); double,
3> &scales
) {
mfem::ParGridFunction displacementField(f.displacementFes.get());
mfem::VectorFunctionCoefficient displacementCoefficient( mfem::VectorFunctionCoefficient displacementCoefficient(
f.mesh->Dimension(), f.mesh->Dimension(), [scales](const mfem::Vector &position, mfem::Vector &value) {
[scales](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) = value(component) = (scales[static_cast<std::size_t>(component)] - 1.0) * position(component);
(scales[static_cast<std::size_t>(component)] - 1.0) * }
position(component); }
} );
});
displacementField.ProjectCoefficient(displacementCoefficient); displacementField.ProjectCoefficient(displacementCoefficient);
mfem::Vector displacementTrue; mfem::Vector displacementTrue;
displacementField.GetTrueDofs(displacementTrue); displacementField.GetTrueDofs(displacementTrue);
return displacementTrue; return displacementTrue;
} }
[[nodiscard]] mfem::Vector [[nodiscard]] mfem::Vector make_constant_test_direction(
make_constant_test_direction(const mean_field::fem::FEM &f, const mean_field::fem::FEM &f,
const int selectedComponent) { const int selectedComponent
mfem::ParGridFunction testField(f.displacementFes.get()); ) {
mfem::ParGridFunction testField(f.displacementFes.get());
mfem::VectorFunctionCoefficient testCoefficient( mfem::VectorFunctionCoefficient testCoefficient(
f.mesh->Dimension(), f.mesh->Dimension(), [selectedComponent](const mfem::Vector &position, mfem::Vector &value) {
[selectedComponent](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(position.Size());
value.SetSize(position.Size()); value = 0.0;
value = 0.0; value(selectedComponent) = 1.0;
value(selectedComponent) = 1.0; }
}); );
testField.ProjectCoefficient(testCoefficient); testField.ProjectCoefficient(testCoefficient);
mfem::Vector testTrue; mfem::Vector testTrue;
testField.GetTrueDofs(testTrue); testField.GetTrueDofs(testTrue);
return testTrue; return testTrue;
} }
[[nodiscard]] mfem::Vector [[nodiscard]] mfem::Vector make_dilation_test_direction(const mean_field::fem::FEM &f) {
make_dilation_test_direction(const mean_field::fem::FEM &f) { mfem::ParGridFunction testField(f.displacementFes.get());
mfem::ParGridFunction testField(f.displacementFes.get());
mfem::VectorFunctionCoefficient testCoefficient( mfem::VectorFunctionCoefficient testCoefficient(
f.mesh->Dimension(), [](const mfem::Vector &position, f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) { value = position; }
mfem::Vector &value) { value = position; }); );
testField.ProjectCoefficient(testCoefficient); testField.ProjectCoefficient(testCoefficient);
mfem::Vector testTrue; mfem::Vector testTrue;
testField.GetTrueDofs(testTrue); testField.GetTrueDofs(testTrue);
return testTrue; return testTrue;
} }
void set_mass_normalized_density(mean_field::fem::FEM &f, void set_mass_normalized_density(
const double targetMass, mean_field::fem::FEM &f,
mfem::ParGridFunction &densityField) { const double targetMass,
const mfem::Vector stellarDensityTrue = mfem::ParGridFunction &densityField
gravity_prepared_test_utils::make_domain_supported_density(f, true); ) {
const mfem::Vector stellarDensityTrue = gravity_prepared_test_utils::make_domain_supported_density(f, true);
densityField.SetFromTrueDofs(stellarDensityTrue); densityField.SetFromTrueDofs(stellarDensityTrue);
const double unnormalizedMass = const double unnormalizedMass =
mean_field::analysis::domain_integrate_grid_function( mean_field::analysis::domain_integrate_grid_function(f, densityField, mean_field::utils::DOMAINS::STELLAR);
f, densityField, mean_field::utils::DOMAINS::STELLAR);
MFEM_VERIFY(unnormalizedMass > 0.0, MFEM_VERIFY(unnormalizedMass > 0.0, "The analytic gravity-force test obtained non-positive mass.");
"The analytic gravity-force test obtained non-positive mass.");
densityField *= targetMass / unnormalizedMass; densityField *= targetMass / unnormalizedMass;
} }
} // namespace gravity_displacement_force_analytic_test_utils } // namespace gravity_displacement_force_analytic_test_utils
TEST_CASE("Gravity Displacement Force Matches Analytic Affine Resultants", TEST_CASE(
tags::gravity &tags::accuracy &tags::analytic_comparison "Gravity Displacement Force Matches Analytic Affine Resultants",
&tags::integration) { tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration
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);
REQUIRE(f.okay()); REQUIRE(f.okay());
REQUIRE(f.domainMapperStateless != nullptr); REQUIRE(f.domainMapperStateless != nullptr);
REQUIRE(f.domainMapperStateless != nullptr); REQUIRE(f.domainMapperStateless != nullptr);
constexpr double densityValue = 1.37; constexpr double densityValue = 1.37;
constexpr std::array<double, 3> physicalGravity{0.31, -0.47, 0.22}; constexpr std::array<double, 3> physicalGravity{0.31, -0.47, 0.22};
constexpr std::array< constexpr std::array<gravity_displacement_force_analytic_test_utils::AffineCase, 3> affineCases{
gravity_displacement_force_analytic_test_utils::AffineCase, 3> {{.name = "identity geometry", .scales = {1.0, 1.0, 1.0}},
affineCases{{{.name = "identity geometry", .scales = {1.0, 1.0, 1.0}}, {.name = "volume-preserving affine geometry", .scales = {1.14, 0.93, 1.0 / (1.14 * 0.93)}},
{.name = "volume-preserving affine geometry", {.name = "volume-changing affine geometry", .scales = {1.11, 0.96, 1.07}}}
.scales = {1.14, 0.93, 1.0 / (1.14 * 0.93)}}, };
{.name = "volume-changing affine geometry",
.scales = {1.11, 0.96, 1.07}}}};
const mfem::Vector density = const mfem::Vector density = gravity_displacement_force_analytic_test_utils::make_constant_density(f, densityValue);
gravity_displacement_force_analytic_test_utils::make_constant_density(
f, densityValue);
const double referenceVolume = const double referenceVolume =
gravity_displacement_force_analytic_test_utils::analytic_sphere_volume( gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(mean_field::utils::RADIUS);
mean_field::utils::RADIUS);
constexpr double relativeTolerance = 5.0e-6; constexpr double relativeTolerance = 5.0e-6;
for (const gravity_displacement_force_analytic_test_utils::AffineCase for (const gravity_displacement_force_analytic_test_utils::AffineCase &affineCase : affineCases) {
&affineCase : affineCases) { DYNAMIC_SECTION(affineCase.name) {
DYNAMIC_SECTION(affineCase.name) { const double mapDeterminant =
const double mapDeterminant = gravity_displacement_force_analytic_test_utils::determinant(affineCase.scales);
gravity_displacement_force_analytic_test_utils::determinant(
affineCase.scales);
REQUIRE(mapDeterminant > 0.0); REQUIRE(mapDeterminant > 0.0);
std::array<double, 3> referenceGravity{}; std::array<double, 3> referenceGravity{};
/* /*
* For x = A X, the H(div) Piola relation is * For x = A X, the H(div) Piola relation is
* *
* g_phys = A g_ref / det(A). * g_phys = A g_ref / det(A).
* *
* Prescribe the RT pullback that represents the requested * Prescribe the RT pullback that represents the requested
* constant physical gravity field exactly. * constant physical gravity field exactly.
*/ */
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
referenceGravity[static_cast<std::size_t>(component)] = referenceGravity[static_cast<std::size_t>(component)] =
mapDeterminant * mapDeterminant * physicalGravity[static_cast<std::size_t>(component)] /
physicalGravity[static_cast<std::size_t>(component)] / affineCase.scales[static_cast<std::size_t>(component)];
affineCase.scales[static_cast<std::size_t>(component)]; }
}
const mfem::Vector gravityGradient = const mfem::Vector gravityGradient =
gravity_displacement_force_analytic_test_utils:: gravity_displacement_force_analytic_test_utils::make_reference_gravity(f, referenceGravity);
make_reference_gravity(f, referenceGravity);
const mfem::Vector displacement = const mfem::Vector displacement =
gravity_displacement_force_analytic_test_utils:: gravity_displacement_force_analytic_test_utils::make_affine_displacement(f, affineCase.scales);
make_affine_displacement(f, affineCase.scales);
mfem::Vector residual; mfem::Vector residual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual( mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement, f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
residual); );
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
const mfem::Vector testDirection = const mfem::Vector testDirection =
gravity_displacement_force_analytic_test_utils:: gravity_displacement_force_analytic_test_utils::make_constant_test_direction(f, component);
make_constant_test_direction(f, component);
const double computedResultant = const double computedResultant =
gravity_prepared_test_utils::global_dot(residual, testDirection, gravity_prepared_test_utils::global_dot(residual, testDirection, f.mesh->GetComm());
f.mesh->GetComm());
const double expectedResultant = const double expectedResultant = densityValue * physicalGravity[static_cast<std::size_t>(component)] *
densityValue * mapDeterminant * referenceVolume;
physicalGravity[static_cast<std::size_t>(component)] *
mapDeterminant * referenceVolume;
const double relativeError = const double relativeError = gravity_displacement_force_analytic_test_utils::relative_scalar_error(
gravity_displacement_force_analytic_test_utils:: computedResultant, expectedResultant
relative_scalar_error(computedResultant, expectedResultant); );
CAPTURE(component); CAPTURE(component);
INFO("Map determinant = " << mapDeterminant); INFO("Map determinant = " << mapDeterminant);
INFO("Computed resultant = " << computedResultant); INFO("Computed resultant = " << computedResultant);
INFO("Analytic resultant = " << expectedResultant); INFO("Analytic resultant = " << expectedResultant);
INFO("Relative resultant error = " << relativeError); INFO("Relative resultant error = " << relativeError);
CHECK(relativeError < relativeTolerance); CHECK(relativeError < relativeTolerance);
} }
}
} }
}
} }
TEST_CASE( TEST_CASE(
"Gravity Displacement Force Reproduces Analytic Homogeneous Sphere Work", "Gravity Displacement Force Reproduces Analytic Homogeneous Sphere Work",
tags::gravity &tags::accuracy &tags::analytic_comparison tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration
&tags::integration) { ) {
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);
REQUIRE(f.okay()); REQUIRE(f.okay());
REQUIRE(f.domainMapperStateless != nullptr); REQUIRE(f.domainMapperStateless != nullptr);
const double radius = mean_field::utils::RADIUS; const double radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS; const double mass = mean_field::utils::MASS;
const double volume = const double volume = gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(radius);
gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(
radius);
const double densityValue = mass / volume; const double densityValue = mass / volume;
const double radialGravityCoefficient = const double radialGravityCoefficient = mean_field::utils::G * mass / (radius * radius * radius);
mean_field::utils::G * mass / (radius * radius * radius);
const mfem::Vector density = const mfem::Vector density = gravity_displacement_force_analytic_test_utils::make_constant_density(f, densityValue);
gravity_displacement_force_analytic_test_utils::make_constant_density(
f, densityValue);
const mfem::Vector gravityGradient = const mfem::Vector gravityGradient =
gravity_displacement_force_analytic_test_utils::make_radial_gravity( gravity_displacement_force_analytic_test_utils::make_radial_gravity(f, radialGravityCoefficient);
f, radialGravityCoefficient);
mfem::Vector displacement(f.displacementFes->GetTrueVSize()); mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0; displacement = 0.0;
mfem::Vector residual; mfem::Vector residual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual( mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement, f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
residual); );
const mfem::Vector dilationDirection = const mfem::Vector dilationDirection =
gravity_displacement_force_analytic_test_utils:: gravity_displacement_force_analytic_test_utils::make_dilation_test_direction(f);
make_dilation_test_direction(f);
const double computedWork = gravity_prepared_test_utils::global_dot( const double computedWork = gravity_prepared_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm());
residual, dilationDirection, f.mesh->GetComm());
const double analyticWork = const double analyticWork = (3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
(3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
const double relativeError = const double relativeError =
gravity_displacement_force_analytic_test_utils::relative_scalar_error( gravity_displacement_force_analytic_test_utils::relative_scalar_error(computedWork, analyticWork);
computedWork, analyticWork);
INFO("Computed positive gravity work = " << computedWork); INFO("Computed positive gravity work = " << computedWork);
INFO("Analytic positive gravity work = " << analyticWork); INFO("Analytic positive gravity work = " << analyticWork);
INFO("Computed gravitational virial = " << -computedWork); INFO("Computed gravitational virial = " << -computedWork);
INFO("Analytic binding energy = " << -analyticWork); INFO("Analytic binding energy = " << -analyticWork);
INFO("Relative analytic work error = " << relativeError); INFO("Relative analytic work error = " << relativeError);
REQUIRE(computedWork > 0.0); REQUIRE(computedWork > 0.0);
CHECK(relativeError < 1.0e-5); CHECK(relativeError < 1.0e-5);
} }
TEST_CASE("Solved Homogeneous Sphere Gravity Force Matches Analytic Virial", TEST_CASE(
tags::gravity &tags::accuracy &tags::analytic_comparison "Solved Homogeneous Sphere Gravity Force Matches Analytic Virial",
&tags::integration &tags::initialization) { tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration &tags::initialization
mean_field::utils::Args args = test_utils::setup_args(); ) {
args.p.rtol = 1.0e-13; mean_field::utils::Args args = test_utils::setup_args();
args.p.max_iters = std::max(args.p.max_iters, 1000); args.p.rtol = 1.0e-13;
args.p.max_iters = std::max(args.p.max_iters, 1000);
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);
REQUIRE(f.okay()); REQUIRE(f.okay());
REQUIRE(f.domainMapperStateless != nullptr); REQUIRE(f.domainMapperStateless != nullptr);
mfem::ParGridFunction displacementField(f.displacementFes.get()); mfem::ParGridFunction displacementField(f.displacementFes.get());
displacementField = 0.0; displacementField = 0.0;
REQUIRE(f.domainMapperStateless != nullptr); REQUIRE(f.domainMapperStateless != nullptr);
*f.displacement = 0.0; *f.displacement = 0.0;
const double radius = mean_field::utils::RADIUS; const double radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS; const double mass = mean_field::utils::MASS;
mfem::ParGridFunction densityField(f.densityFes.get()); mfem::ParGridFunction densityField(f.densityFes.get());
gravity_displacement_force_analytic_test_utils::set_mass_normalized_density( gravity_displacement_force_analytic_test_utils::set_mass_normalized_density(f, mass, densityField);
f, mass, densityField);
const mean_field::physics::GravitySolution gravitySolution = const mean_field::physics::GravitySolution gravitySolution =
mean_field::physics::solve_gravity_field(f, args, densityField, mean_field::physics::solve_gravity_field(f, args, densityField, displacementField);
displacementField);
mfem::Vector densityTrue; mfem::Vector densityTrue;
mfem::Vector gravityGradientTrue; mfem::Vector gravityGradientTrue;
mfem::Vector displacementTrue; mfem::Vector displacementTrue;
densityField.GetTrueDofs(densityTrue); densityField.GetTrueDofs(densityTrue);
gravitySolution.gradPhi.GetTrueDofs(gravityGradientTrue); gravitySolution.gradPhi.GetTrueDofs(gravityGradientTrue);
displacementField.GetTrueDofs(displacementTrue); displacementField.GetTrueDofs(displacementTrue);
mfem::Vector residual; mfem::Vector residual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual( mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, densityTrue, gravityGradientTrue, f, *f.domainMapperStateless, densityTrue, gravityGradientTrue, displacementTrue, residual
displacementTrue, residual); );
const mfem::Vector dilationDirection = const mfem::Vector dilationDirection =
gravity_displacement_force_analytic_test_utils:: gravity_displacement_force_analytic_test_utils::make_dilation_test_direction(f);
make_dilation_test_direction(f);
const double computedWork = gravity_prepared_test_utils::global_dot( const double computedWork = gravity_prepared_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm());
residual, dilationDirection, f.mesh->GetComm());
const double analyticWork = const double analyticWork = (3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
(3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
const double relativeError = const double relativeError =
gravity_displacement_force_analytic_test_utils::relative_scalar_error( gravity_displacement_force_analytic_test_utils::relative_scalar_error(computedWork, analyticWork);
computedWork, analyticWork);
INFO("Solved-field positive gravity work = " << computedWork); INFO("Solved-field positive gravity work = " << computedWork);
INFO("Analytic positive gravity work = " << analyticWork); INFO("Analytic positive gravity work = " << analyticWork);
INFO("Solved-field gravitational virial = " << -computedWork); INFO("Solved-field gravitational virial = " << -computedWork);
INFO("Analytic homogeneous-sphere binding energy = " << -analyticWork); INFO("Analytic homogeneous-sphere binding energy = " << -analyticWork);
INFO("Relative solved-field virial error = " << relativeError); INFO("Relative solved-field virial error = " << relativeError);
REQUIRE(computedWork > 0.0); REQUIRE(computedWork > 0.0);
CHECK(relativeError < 1.0e-5); CHECK(relativeError < 1.0e-5);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -72,7 +72,10 @@ TEST_CASE(
constexpr double enthalpyValue = 0.8; constexpr double enthalpyValue = 0.8;
const double densityValue = barotrope.density_from_enthalpy(enthalpyValue); const double densityValue = mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
barotrope, mean_field::eos::SpecificEnthalpyValue{enthalpyValue}
)
.value();
const mfem::Vector enthalpy = project_constant(*f.enthalpyFes, enthalpyValue); const mfem::Vector enthalpy = project_constant(*f.enthalpyFes, enthalpyValue);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -242,8 +242,12 @@ namespace prepared_barotropic_closure_test_utils {
const ClosureCondition &condition const ClosureCondition &condition
) { ) {
mfem::FunctionCoefficient coefficient([&equationOfState, condition](const mfem::Vector &position) { mfem::FunctionCoefficient coefficient([&equationOfState, condition](const mfem::Vector &position) {
const double enthalpy = evaluate_enthalpy(position, condition); const double enthalpy = evaluate_enthalpy(position, condition);
return condition.densityFactor * equationOfState.density_from_enthalpy(enthalpy) + condition.densityOffset + const double equationOfStateDensity = mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
equationOfState, mean_field::eos::SpecificEnthalpyValue{enthalpy}
)
.value();
return condition.densityFactor * equationOfStateDensity + condition.densityOffset +
condition.densityGradient * (0.40 * position(0) + 0.25 * position(1) - 0.15 * position(2)); condition.densityGradient * (0.40 * position(0) + 0.25 * position(1) - 0.15 * position(2));
}); });
return project_scalar(*f.densityFes, coefficient); return project_scalar(*f.densityFes, coefficient);
@@ -784,8 +788,12 @@ namespace prepared_barotropic_closure_test_utils {
const Maps maps(f); const Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 1.5); const mean_field::eos::Polytrope equationOfState(3.0, 1.5);
constexpr double enthalpyValue = 1.20; constexpr double enthalpyValue = 1.20;
const double equilibriumDensityValue = equationOfState.density_from_enthalpy(enthalpyValue); const double equilibriumDensityValue =
mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
equationOfState, mean_field::eos::SpecificEnthalpyValue{enthalpyValue}
)
.value();
const mfem::Vector enthalpy = reduce(maps.enthalpy, make_constant_field(*f.enthalpyFes, enthalpyValue)); const mfem::Vector enthalpy = reduce(maps.enthalpy, make_constant_field(*f.enthalpyFes, enthalpyValue));
const mfem::Vector equilibriumDensity = const mfem::Vector equilibriumDensity =

View File

@@ -10,181 +10,157 @@ 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;
TEST_CASE("Prepared Mapped Hdiv Mass Matches Stateless Kernel", TEST_CASE(
tags::gravity_prepared) { "Prepared Mapped Hdiv Mass Matches Stateless Kernel",
auto args = test_utils::setup_args(); tags::gravity_prepared
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); ) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedHDivMassOperator prepared_operator( operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless);
f, *f.domainMapperStateless); REQUIRE(prepared_operator.Width() == prepared_operator.GetFluxMap().reduced_size());
REQUIRE(prepared_operator.Width() == REQUIRE(prepared_operator.Height() == prepared_operator.GetFluxMap().reduced_size());
prepared_operator.GetFluxMap().reduced_size());
REQUIRE(prepared_operator.Height() ==
prepared_operator.GetFluxMap().reduced_size());
const mfem::Vector gravity_gradient_true = const mfem::Vector gravity_gradient_true =
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.21);
0.21); const mfem::Vector gravity_gradient = prepared_operator.GetFluxMap().gather(gravity_gradient_true);
const mfem::Vector gravity_gradient = const MPI_Comm communicator = f.gravityFluxFes->GetComm();
prepared_operator.GetFluxMap().gather(gravity_gradient_true);
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
mfem::Vector identity_action; mfem::Vector identity_action;
mfem::Vector deformed_action; mfem::Vector deformed_action;
for (const double deformation_scale : {0.0, 1.0}) { for (const double deformation_scale : {0.0, 1.0}) {
const mfem::Vector displacement_true = const mfem::Vector displacement_true = prepared_test::make_displacement(f, deformation_scale);
prepared_test::make_displacement(f, deformation_scale); const mfem::Vector displacement = prepared_operator.GetDisplacementMap().gather(displacement_true);
prepared_operator.Prepare(displacement);
mfem::Vector prepared_action;
prepared_operator.Mult(gravity_gradient, prepared_action);
mfem::Vector reference_action_true;
operators::kernels::apply_mapped_hdiv_mass(
f, *f.domainMapperStateless, gravity_gradient_true, displacement_true, reference_action_true
);
const mfem::Vector reference_action = prepared_operator.GetFluxMap().gather(reference_action_true);
const double relative_error = prepared_test::relative_error(prepared_action, reference_action, communicator);
INFO("Deformation scale = " << deformation_scale);
INFO("Prepared action norm = " << prepared_test::global_norm(prepared_action, communicator));
INFO("Reference action norm = " << prepared_test::global_norm(reference_action, communicator));
INFO("Relative prepared-operator error = " << relative_error);
REQUIRE(prepared_operator.IsPrepared());
CHECK_THAT(relative_error, WithinAbs(0.0, 2.0e-11));
if (deformation_scale == 0.0) {
identity_action = prepared_action;
} else {
deformed_action = prepared_action;
}
}
const double geometry_change = prepared_test::relative_error(deformed_action, identity_action, communicator);
INFO("Relative action change under deformation = " << geometry_change);
CHECK(prepared_operator.GetPreparationCount() == 2);
CHECK(geometry_change > 1.0e-5);
}
TEST_CASE(
"Prepared Mapped Hdiv Mass Preserves Operator Identities",
tags::gravity_prepared
) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless);
REQUIRE(prepared_operator.Width() == prepared_operator.GetFluxMap().reduced_size());
REQUIRE(prepared_operator.Height() == prepared_operator.GetFluxMap().reduced_size());
const mfem::Vector displacement = const mfem::Vector displacement =
prepared_operator.GetDisplacementMap().gather(displacement_true); prepared_operator.GetDisplacementMap().gather(prepared_test::make_displacement(f, 1.0));
prepared_operator.Prepare(displacement); prepared_operator.Prepare(displacement);
mfem::Vector prepared_action; const mfem::Vector first = prepared_operator.GetFluxMap().gather(
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.17)
);
const mfem::Vector second = prepared_operator.GetFluxMap().gather(
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.83)
);
const mfem::Vector combination = prepared_test::linear_combination(first, 1.7, second, -0.4);
prepared_operator.Mult(gravity_gradient, prepared_action); mfem::Vector first_action;
mfem::Vector reference_action_true; mfem::Vector second_action;
operators::kernels::apply_mapped_hdiv_mass( mfem::Vector combination_action;
f, *f.domainMapperStateless, gravity_gradient_true, displacement_true, mfem::Vector zero_action;
reference_action_true);
const mfem::Vector reference_action =
prepared_operator.GetFluxMap().gather(reference_action_true);
const double relative_error = prepared_test::relative_error( prepared_operator.Mult(first, first_action);
prepared_action, reference_action, communicator); prepared_operator.Mult(second, second_action);
prepared_operator.Mult(combination, combination_action);
INFO("Deformation scale = " << deformation_scale); mfem::Vector expected_combination = prepared_test::linear_combination(first_action, 1.7, second_action, -0.4);
INFO("Prepared action norm = "
<< prepared_test::global_norm(prepared_action, communicator));
INFO("Reference action norm = "
<< prepared_test::global_norm(reference_action, communicator));
INFO("Relative prepared-operator error = " << relative_error);
REQUIRE(prepared_operator.IsPrepared()); mfem::Vector zero(first.Size());
CHECK_THAT(relative_error, WithinAbs(0.0, 2.0e-11)); zero = 0.0;
prepared_operator.Mult(zero, zero_action);
if (deformation_scale == 0.0) { const MPI_Comm communicator = f.gravityFluxFes->GetComm();
identity_action = prepared_action;
} else { const double first_second_product = prepared_test::global_dot(first, second_action, communicator);
deformed_action = prepared_action; const double second_first_product = prepared_test::global_dot(second, first_action, communicator);
const double symmetry_error = prepared_test::relative_scalar_error(first_second_product, second_first_product);
const double linearity_error =
prepared_test::relative_error(combination_action, expected_combination, communicator);
const double first_energy = prepared_test::global_dot(first, first_action, communicator);
const double second_energy = prepared_test::global_dot(second, second_action, communicator);
const std::uint64_t preparation_count = prepared_operator.GetPreparationCount();
mfem::Vector repeated_action;
prepared_operator.Mult(first, repeated_action);
INFO("u^T M v = " << first_second_product);
INFO("v^T M u = " << second_first_product);
INFO("Relative symmetry error = " << symmetry_error);
INFO("Relative linearity error = " << linearity_error);
INFO("u^T M u = " << first_energy);
INFO("v^T M v = " << second_energy);
CHECK_THAT(symmetry_error, WithinAbs(0.0, 2.0e-12));
CHECK_THAT(linearity_error, WithinAbs(0.0, 2.0e-12));
CHECK_THAT(prepared_test::global_norm(zero_action, communicator), WithinAbs(0.0, 1.0e-14));
CHECK(first_energy > 0.0);
CHECK(second_energy > 0.0);
CHECK(prepared_test::relative_error(repeated_action, first_action, communicator) < 2.0e-14);
CHECK(prepared_operator.GetPreparationCount() == preparation_count);
}
TEST_CASE(
"Prepared Mapped Hdiv Mass Diagonal Is Positive Across Both Domains",
tags::gravity_prepared
) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless);
const mfem::Vector displacement =
prepared_operator.GetDisplacementMap().gather(prepared_test::make_displacement(f, 1.0));
prepared_operator.Prepare(displacement);
mfem::Vector diagonal;
mfem::Vector true_diagonal;
prepared_operator.AssembleDiagonal(diagonal);
prepared_operator.AssembleTrueDiagonal(true_diagonal);
REQUIRE(diagonal.Size() == prepared_operator.Height());
REQUIRE(true_diagonal.Size() == prepared_operator.GetFluxMap().full_size());
const mfem::Vector gathered_true_diagonal = prepared_operator.GetFluxMap().gather(true_diagonal);
for (int i = 0; i < diagonal.Size(); ++i) {
REQUIRE(std::isfinite(diagonal(i)));
CHECK(diagonal(i) > 0.0);
CHECK_THAT(diagonal(i), WithinAbs(gathered_true_diagonal(i), 1.0e-14 * std::abs(diagonal(i))));
} }
}
const double geometry_change = prepared_test::relative_error(
deformed_action, identity_action, communicator);
INFO("Relative action change under deformation = " << geometry_change);
CHECK(prepared_operator.GetPreparationCount() == 2);
CHECK(geometry_change > 1.0e-5);
}
TEST_CASE("Prepared Mapped Hdiv Mass Preserves Operator Identities",
tags::gravity_prepared) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedHDivMassOperator prepared_operator(
f, *f.domainMapperStateless);
REQUIRE(prepared_operator.Width() ==
prepared_operator.GetFluxMap().reduced_size());
REQUIRE(prepared_operator.Height() ==
prepared_operator.GetFluxMap().reduced_size());
const mfem::Vector displacement =
prepared_operator.GetDisplacementMap().gather(
prepared_test::make_displacement(f, 1.0));
prepared_operator.Prepare(displacement);
const mfem::Vector first = prepared_operator.GetFluxMap().gather(
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(),
0.17));
const mfem::Vector second = prepared_operator.GetFluxMap().gather(
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(),
0.83));
const mfem::Vector combination =
prepared_test::linear_combination(first, 1.7, second, -0.4);
mfem::Vector first_action;
mfem::Vector second_action;
mfem::Vector combination_action;
mfem::Vector zero_action;
prepared_operator.Mult(first, first_action);
prepared_operator.Mult(second, second_action);
prepared_operator.Mult(combination, combination_action);
mfem::Vector expected_combination =
prepared_test::linear_combination(first_action, 1.7, second_action, -0.4);
mfem::Vector zero(first.Size());
zero = 0.0;
prepared_operator.Mult(zero, zero_action);
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
const double first_second_product =
prepared_test::global_dot(first, second_action, communicator);
const double second_first_product =
prepared_test::global_dot(second, first_action, communicator);
const double symmetry_error = prepared_test::relative_scalar_error(
first_second_product, second_first_product);
const double linearity_error = prepared_test::relative_error(
combination_action, expected_combination, communicator);
const double first_energy =
prepared_test::global_dot(first, first_action, communicator);
const double second_energy =
prepared_test::global_dot(second, second_action, communicator);
const std::uint64_t preparation_count =
prepared_operator.GetPreparationCount();
mfem::Vector repeated_action;
prepared_operator.Mult(first, repeated_action);
INFO("u^T M v = " << first_second_product);
INFO("v^T M u = " << second_first_product);
INFO("Relative symmetry error = " << symmetry_error);
INFO("Relative linearity error = " << linearity_error);
INFO("u^T M u = " << first_energy);
INFO("v^T M v = " << second_energy);
CHECK_THAT(symmetry_error, WithinAbs(0.0, 2.0e-12));
CHECK_THAT(linearity_error, WithinAbs(0.0, 2.0e-12));
CHECK_THAT(prepared_test::global_norm(zero_action, communicator),
WithinAbs(0.0, 1.0e-14));
CHECK(first_energy > 0.0);
CHECK(second_energy > 0.0);
CHECK(prepared_test::relative_error(repeated_action, first_action,
communicator) < 2.0e-14);
CHECK(prepared_operator.GetPreparationCount() == preparation_count);
}
TEST_CASE("Prepared Mapped Hdiv Mass Diagonal Is Positive Across Both Domains",
tags::gravity_prepared) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedHDivMassOperator prepared_operator(
f, *f.domainMapperStateless);
const mfem::Vector displacement =
prepared_operator.GetDisplacementMap().gather(
prepared_test::make_displacement(f, 1.0));
prepared_operator.Prepare(displacement);
mfem::Vector diagonal;
mfem::Vector true_diagonal;
prepared_operator.AssembleDiagonal(diagonal);
prepared_operator.AssembleTrueDiagonal(true_diagonal);
REQUIRE(diagonal.Size() == prepared_operator.Height());
REQUIRE(true_diagonal.Size() == prepared_operator.GetFluxMap().full_size());
const mfem::Vector gathered_true_diagonal =
prepared_operator.GetFluxMap().gather(true_diagonal);
for (int i = 0; i < diagonal.Size(); ++i) {
REQUIRE(std::isfinite(diagonal(i)));
CHECK(diagonal(i) > 0.0);
CHECK_THAT(diagonal(i), WithinAbs(gathered_true_diagonal(i),
1.0e-14 * std::abs(diagonal(i))));
}
} }

View File

@@ -9,430 +9,423 @@ import mean_field;
import test_helpers; import test_helpers;
namespace prepared_hydrostatic_analytic_solve_test_utils { namespace prepared_hydrostatic_analytic_solve_test_utils {
constexpr double bernoulliConstant = 0.83; constexpr double bernoulliConstant = 0.83;
constexpr double enthalpyAmplitude = 0.61; constexpr double enthalpyAmplitude = 0.61;
struct AnalyticCase { struct AnalyticCase {
const char *name; const char *name;
std::array<double, 3> deformationScale; std::array<double, 3> deformationScale;
std::array<double, 3> angularVelocity; std::array<double, 3> angularVelocity;
std::array<double, 3> rotationCenter; std::array<double, 3> rotationCenter;
}; };
class EnthalpyJacobianOperator final : public mfem::Operator { class EnthalpyJacobianOperator final : public mfem::Operator {
public: public:
EnthalpyJacobianOperator( EnthalpyJacobianOperator(
const int enthalpySize, const int enthalpySize,
const mean_field::operators::PreparedHydrostaticEquilibriumOperator const mean_field::operators::PreparedHydrostaticEquilibriumOperator &preparedOperator
&preparedOperator) )
: mfem::Operator(enthalpySize), m_preparedOperator(preparedOperator) {} : mfem::Operator(enthalpySize),
m_preparedOperator(preparedOperator) {
}
void Mult(const mfem::Vector &direction, void Mult(
mfem::Vector &action) const override { const mfem::Vector &direction,
m_preparedOperator.ApplyEnthalpyJacobianAction(direction, action); mfem::Vector &action
} ) const override {
m_preparedOperator.ApplyEnthalpyJacobianAction(direction, action);
}
private: private:
const mean_field::operators::PreparedHydrostaticEquilibriumOperator const mean_field::operators::PreparedHydrostaticEquilibriumOperator &m_preparedOperator;
&m_preparedOperator; };
};
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() {
make_dependencies() { return {
return {.discretization = {.identity = 701, .revision = 2}, .discretization = {.identity = 701, .revision = 2},
.enthalpy = {.identity = 709, .revision = 3}, .enthalpy = {.identity = 709, .revision = 3},
.gravityPotential = {.identity = 719, .revision = 5}, .gravityPotential = {.identity = 719, .revision = 5},
.displacement = {.identity = 727, .revision = 7}, .displacement = {.identity = 727, .revision = 7},
.rotation = {.identity = 733, .revision = 11}, .rotation = {.identity = 733, .revision = 11},
.bernoulliConstant = {.identity = 739, .revision = 13}}; .bernoulliConstant = {.identity = 739, .revision = 13}
} };
}
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state(
make_state(const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential, const mfem::Vector &enthalpy,
const mfem::Vector &displacement) { const mfem::Vector &gravityPotential,
return {.enthalpy = enthalpy, const mfem::Vector &displacement
.gravityPotential = gravityPotential, ) {
.displacement = displacement, return {
.bernoulliConstant = bernoulliConstant}; .enthalpy = enthalpy,
} .gravityPotential = gravityPotential,
.displacement = displacement,
.bernoulliConstant = bernoulliConstant
};
}
mfem::Vector make_vector(const std::array<double, 3> &values) { mfem::Vector make_vector(
mfem::Vector vector(3); const std::array<
double,
3> &values
) {
mfem::Vector vector(3);
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
vector(component) = values[static_cast<std::size_t>(component)]; vector(component) = values[static_cast<std::size_t>(component)];
} }
return vector; return vector;
} }
mean_field::physics::RigidRotation mean_field::physics::RigidRotation make_rotation(const AnalyticCase &analyticCase) {
make_rotation(const AnalyticCase &analyticCase) { return mean_field::physics::RigidRotation(
return mean_field::physics::RigidRotation( make_vector(analyticCase.angularVelocity), make_vector(analyticCase.rotationCenter)
make_vector(analyticCase.angularVelocity), );
make_vector(analyticCase.rotationCenter)); }
}
void map_to_physical(const mfem::Vector &referencePosition, void map_to_physical(
const AnalyticCase &analyticCase, const mfem::Vector &referencePosition,
mfem::Vector &physicalPosition) { const AnalyticCase &analyticCase,
physicalPosition.SetSize(3); mfem::Vector &physicalPosition
) {
physicalPosition.SetSize(3);
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
physicalPosition(component) = physicalPosition(component) =
analyticCase.deformationScale[static_cast<std::size_t>(component)] * analyticCase.deformationScale[static_cast<std::size_t>(component)] * referencePosition(component);
referencePosition(component); }
} }
}
double exact_enthalpy_value(const mfem::Vector &referencePosition) { double exact_enthalpy_value(const mfem::Vector &referencePosition) {
double normalizedRadiusSquared = 0.0; double normalizedRadiusSquared = 0.0;
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
const double normalizedCoordinate = const double normalizedCoordinate = referencePosition(component) / mean_field::utils::RADIUS;
referencePosition(component) / mean_field::utils::RADIUS;
normalizedRadiusSquared += normalizedCoordinate * normalizedCoordinate; normalizedRadiusSquared += normalizedCoordinate * normalizedCoordinate;
} }
return enthalpyAmplitude * std::max(0.0, 1.0 - normalizedRadiusSquared); return enthalpyAmplitude * std::max(0.0, 1.0 - normalizedRadiusSquared);
} }
double double exact_potential_value(
exact_potential_value(const mfem::Vector &referencePosition, const mfem::Vector &referencePosition,
const AnalyticCase &analyticCase, const AnalyticCase &analyticCase,
const mean_field::physics::RigidRotation &rotation) { const mean_field::physics::RigidRotation &rotation
mfem::Vector physicalPosition; ) {
mfem::Vector physicalPosition;
map_to_physical(referencePosition, analyticCase, physicalPosition); map_to_physical(referencePosition, analyticCase, physicalPosition);
/* /*
* Construct Phi so that * Construct Phi so that
* *
* h + Phi - Psi_rotation - C = 0 * h + Phi - Psi_rotation - C = 0
* *
* analytically. * analytically.
*/ */
return bernoulliConstant + rotation.potential(physicalPosition) - return bernoulliConstant + rotation.potential(physicalPosition) - exact_enthalpy_value(referencePosition);
exact_enthalpy_value(referencePosition); }
}
mfem::Array<int> make_stellar_element_marker(const mean_field::fem::FEM &f) { mfem::Array<int> make_stellar_element_marker(const mean_field::fem::FEM &f) {
mfem::Array<int> stellarElementMarker(f.mesh->GetNE()); mfem::Array<int> stellarElementMarker(f.mesh->GetNE());
const int vacuumAttribute = field_dof_test_utils::vacuum_material_attribute; const int vacuumAttribute = field_dof_test_utils::vacuum_material_attribute;
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
stellarElementMarker[elementId] = stellarElementMarker[elementId] = f.mesh->GetAttribute(elementId) != vacuumAttribute;
f.mesh->GetAttribute(elementId) != vacuumAttribute; }
}
return stellarElementMarker; return stellarElementMarker;
} }
} // namespace prepared_hydrostatic_analytic_solve_test_utils } // namespace prepared_hydrostatic_analytic_solve_test_utils
TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria", TEST_CASE(
tags::barotrope_hydrostatic_prepared_analytic &tags::convergence "Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
&tags::accuracy) { tags::barotrope_hydrostatic_prepared_analytic &tags::convergence &tags::accuracy
using prepared_hydrostatic_analytic_solve_test_utils::AnalyticCase; ) {
using prepared_hydrostatic_analytic_solve_test_utils::AnalyticCase;
constexpr double deformationX = 1.08;
constexpr double deformationY = 0.96;
/*
* The third scale makes the affine deformation
* volume-preserving:
*
* det(F) = sx * sy * sz = 1.
*/
constexpr double deformationZ = 1.0 / (deformationX * deformationY);
const std::array<AnalyticCase, 3> analyticCases{
{{.name = "spherical nonrotating equilibrium",
.deformationScale = {1.0, 1.0, 1.0},
.angularVelocity = {0.0, 0.0, 0.0},
.rotationCenter = {0.0, 0.0, 0.0}},
{.name = "spherical rotating equilibrium",
.deformationScale = {1.0, 1.0, 1.0},
.angularVelocity = {0.13, -0.09, 0.31},
.rotationCenter = {0.04, -0.03, 0.02}},
{.name = "volume-preserving deformed rotating equilibrium",
.deformationScale = {deformationX, deformationY, deformationZ},
.angularVelocity = {0.17, -0.12, 0.43},
.rotationCenter = {0.031, -0.024, 0.018}}}};
auto args = test_utils::setup_args(); constexpr double deformationX = 1.08;
constexpr double deformationY = 0.96;
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); /*
* The third scale makes the affine deformation
* volume-preserving:
*
* det(F) = sx * sy * sz = 1.
*/
constexpr double deformationZ = 1.0 / (deformationX * deformationY);
const MPI_Comm communicator = f.mesh->GetComm(); const std::array<AnalyticCase, 3> analyticCases{
{{.name = "spherical nonrotating equilibrium",
.deformationScale = {1.0, 1.0, 1.0},
.angularVelocity = {0.0, 0.0, 0.0},
.rotationCenter = {0.0, 0.0, 0.0}},
{.name = "spherical rotating equilibrium",
.deformationScale = {1.0, 1.0, 1.0},
.angularVelocity = {0.13, -0.09, 0.31},
.rotationCenter = {0.04, -0.03, 0.02}},
{.name = "volume-preserving deformed rotating equilibrium",
.deformationScale = {deformationX, deformationY, deformationZ},
.angularVelocity = {0.17, -0.12, 0.43},
.rotationCenter = {0.031, -0.024, 0.018}}}
};
const mean_field::field::FieldDofMap enthalpyMap = auto args = test_utils::setup_args();
field_dof_test_utils::make_map<mean_field::field::Enthalpy>(
*f.enthalpyFes);
const mean_field::field::FieldDofMap gravityPotentialMap = mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
field_dof_test_utils::make_map<mean_field::field::Gravity>(
*f.gravityPotentialFes);
const mean_field::field::FieldDofMap displacementMap = const MPI_Comm communicator = f.mesh->GetComm();
field_dof_test_utils::make_map<mean_field::field::Displacement>(
*f.displacementFes);
const mfem::Array<int> stellarElementMarker = const mean_field::field::FieldDofMap enthalpyMap =
prepared_hydrostatic_analytic_solve_test_utils:: field_dof_test_utils::make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
make_stellar_element_marker(f);
for (const AnalyticCase &analyticCase : analyticCases) { const mean_field::field::FieldDofMap gravityPotentialMap =
DYNAMIC_SECTION(analyticCase.name) { field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityPotentialFes);
const double deformationDeterminant = analyticCase.deformationScale[0] *
analyticCase.deformationScale[1] *
analyticCase.deformationScale[2];
REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14); const mean_field::field::FieldDofMap displacementMap =
field_dof_test_utils::make_map<mean_field::field::Displacement>(*f.displacementFes);
const mean_field::physics::RigidRotation rotation = const mfem::Array<int> stellarElementMarker =
prepared_hydrostatic_analytic_solve_test_utils::make_rotation( prepared_hydrostatic_analytic_solve_test_utils::make_stellar_element_marker(f);
analyticCase);
auto displacementFunction = for (const AnalyticCase &analyticCase : analyticCases) {
[&analyticCase](const mfem::Vector &referencePosition, DYNAMIC_SECTION(analyticCase.name) {
mfem::Vector &displacementValue) { const double deformationDeterminant =
mfem::Vector physicalPosition; analyticCase.deformationScale[0] * analyticCase.deformationScale[1] * analyticCase.deformationScale[2];
prepared_hydrostatic_analytic_solve_test_utils::map_to_physical( REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14);
referencePosition, analyticCase, physicalPosition);
displacementValue.SetSize(3); const mean_field::physics::RigidRotation rotation =
displacementValue = physicalPosition; prepared_hydrostatic_analytic_solve_test_utils::make_rotation(analyticCase);
displacementValue -= referencePosition;
};
auto potentialFunction = [&analyticCase, &rotation]( auto displacementFunction =
const mfem::Vector &referencePosition) { [&analyticCase](const mfem::Vector &referencePosition, mfem::Vector &displacementValue) {
return prepared_hydrostatic_analytic_solve_test_utils:: mfem::Vector physicalPosition;
exact_potential_value(referencePosition, analyticCase, rotation);
};
auto enthalpyFunction = [](const mfem::Vector &referencePosition) { prepared_hydrostatic_analytic_solve_test_utils::map_to_physical(
return prepared_hydrostatic_analytic_solve_test_utils:: referencePosition, analyticCase, physicalPosition
exact_enthalpy_value(referencePosition); );
};
mfem::VectorFunctionCoefficient displacementCoefficient( displacementValue.SetSize(3);
f.mesh->Dimension(), displacementFunction); displacementValue = physicalPosition;
displacementValue -= referencePosition;
};
mfem::FunctionCoefficient potentialCoefficient(potentialFunction); auto potentialFunction = [&analyticCase, &rotation](const mfem::Vector &referencePosition) {
return prepared_hydrostatic_analytic_solve_test_utils::exact_potential_value(
referencePosition, analyticCase, rotation
);
};
mfem::FunctionCoefficient exactEnthalpyCoefficient(enthalpyFunction); auto enthalpyFunction = [](const mfem::Vector &referencePosition) {
return prepared_hydrostatic_analytic_solve_test_utils::exact_enthalpy_value(referencePosition);
};
/* mfem::VectorFunctionCoefficient displacementCoefficient(f.mesh->Dimension(), displacementFunction);
* Project the prescribed geometry and potential.
*/
mfem::ParGridFunction displacementField(f.displacementFes.get());
mfem::ParGridFunction potentialField(f.gravityPotentialFes.get()); mfem::FunctionCoefficient potentialCoefficient(potentialFunction);
displacementField.ProjectCoefficient(displacementCoefficient); mfem::FunctionCoefficient exactEnthalpyCoefficient(enthalpyFunction);
potentialField.ProjectCoefficient(potentialCoefficient); /*
* Project the prescribed geometry and potential.
*/
mfem::ParGridFunction displacementField(f.displacementFes.get());
mfem::Vector displacementTrue; mfem::ParGridFunction potentialField(f.gravityPotentialFes.get());
mfem::Vector gravityPotentialTrue;
displacementField.GetTrueDofs(displacementTrue); displacementField.ProjectCoefficient(displacementCoefficient);
potentialField.GetTrueDofs(gravityPotentialTrue);
const mfem::Vector displacement = potentialField.ProjectCoefficient(potentialCoefficient);
displacementMap.gather(displacementTrue);
const mfem::Vector gravityPotential =
gravityPotentialMap.gather(gravityPotentialTrue);
/* mfem::Vector displacementTrue;
* This projection is not used as the solution. It gives mfem::Vector gravityPotentialTrue;
* the best directly available representation baseline
* against which the solved field can be compared.
*/
mfem::ParGridFunction projectedEnthalpyField(f.enthalpyFes.get());
projectedEnthalpyField.ProjectCoefficient(exactEnthalpyCoefficient); displacementField.GetTrueDofs(displacementTrue);
potentialField.GetTrueDofs(gravityPotentialTrue);
mfem::ParGridFunction zeroEnthalpyField(f.enthalpyFes.get()); const mfem::Vector displacement = displacementMap.gather(displacementTrue);
const mfem::Vector gravityPotential = gravityPotentialMap.gather(gravityPotentialTrue);
zeroEnthalpyField = 0.0; /*
* This projection is not used as the solution. It gives
* the best directly available representation baseline
* against which the solved field can be compared.
*/
mfem::ParGridFunction projectedEnthalpyField(f.enthalpyFes.get());
const double exactEnthalpyNorm = zeroEnthalpyField.ComputeL2Error( projectedEnthalpyField.ProjectCoefficient(exactEnthalpyCoefficient);
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
const double projectionError = projectedEnthalpyField.ComputeL2Error( mfem::ParGridFunction zeroEnthalpyField(f.enthalpyFes.get());
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
REQUIRE(exactEnthalpyNorm > 0.0); zeroEnthalpyField = 0.0;
const double relativeProjectionError = const double exactEnthalpyNorm =
projectionError / exactEnthalpyNorm; zeroEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
/* const double projectionError =
* Begin deliberately far from equilibrium. projectedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
*/
mfem::Vector enthalpy(enthalpyMap.reduced_size());
enthalpy = 0.0; REQUIRE(exactEnthalpyNorm > 0.0);
auto dependencies = const double relativeProjectionError = projectionError / exactEnthalpyNorm;
prepared_hydrostatic_analytic_solve_test_utils::make_dependencies();
mean_field::operators::PreparedHydrostaticEquilibriumOperator /*
preparedOperator(f, *f.domainMapperStateless); * Begin deliberately far from equilibrium.
*/
mfem::Vector enthalpy(enthalpyMap.reduced_size());
const auto initialReport = preparedOperator.Prepare( enthalpy = 0.0;
prepared_hydrostatic_analytic_solve_test_utils::make_state(
enthalpy, gravityPotential, displacement),
dependencies, rotation);
REQUIRE(initialReport.preparedResidual); auto dependencies = prepared_hydrostatic_analytic_solve_test_utils::make_dependencies();
REQUIRE(initialReport.preparedAlgebraicJacobianBlocks);
mfem::Vector initialResidual; mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
preparedOperator.BuildResidual(initialResidual); const auto initialReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
dependencies, rotation
);
const double initialResidualNorm = REQUIRE(initialReport.preparedResidual);
gravity_prepared_test_utils::global_norm(initialResidual, REQUIRE(initialReport.preparedAlgebraicJacobianBlocks);
communicator);
REQUIRE(initialResidualNorm > 1.0e-12); mfem::Vector initialResidual;
/* preparedOperator.BuildResidual(initialResidual);
* One discrete Newton step:
*
* M_h delta_h = -R_h.
*
* The full four-block Bernoulli Jacobian is rectangular
* and underdetermined in isolation. Freezing Phi, C,
* rotation, and displacement makes this a well-defined
* enthalpy solve.
*/
prepared_hydrostatic_analytic_solve_test_utils::EnthalpyJacobianOperator
enthalpyJacobian(enthalpyMap.reduced_size(), preparedOperator);
mfem::Vector rightHandSide(initialResidual); const double initialResidualNorm = gravity_prepared_test_utils::global_norm(initialResidual, communicator);
rightHandSide *= -1.0;
mfem::Vector enthalpyCorrection(enthalpyMap.reduced_size()); REQUIRE(initialResidualNorm > 1.0e-12);
enthalpyCorrection = 0.0; /*
* One discrete Newton step:
*
* M_h delta_h = -R_h.
*
* The full four-block Bernoulli Jacobian is rectangular
* and underdetermined in isolation. Freezing Phi, C,
* rotation, and displacement makes this a well-defined
* enthalpy solve.
*/
prepared_hydrostatic_analytic_solve_test_utils::EnthalpyJacobianOperator enthalpyJacobian(
enthalpyMap.reduced_size(), preparedOperator
);
/* mfem::Vector rightHandSide(initialResidual);
* The reduced operator contains only stellar-supported rightHandSide *= -1.0;
* enthalpy DOFs and is positive definite. MINRES remains
* appropriate for this symmetric system.
*/
mfem::MINRESSolver linearSolver(communicator);
linearSolver.SetOperator(enthalpyJacobian); mfem::Vector enthalpyCorrection(enthalpyMap.reduced_size());
linearSolver.SetRelTol(1.0e-13); enthalpyCorrection = 0.0;
linearSolver.SetAbsTol(1.0e-14);
linearSolver.SetMaxIter(2000);
linearSolver.SetPrintLevel(0);
linearSolver.Mult(rightHandSide, enthalpyCorrection); /*
* The reduced operator contains only stellar-supported
* enthalpy DOFs and is positive definite. MINRES remains
* appropriate for this symmetric system.
*/
mfem::MINRESSolver linearSolver(communicator);
INFO("Linear solver converged = " << linearSolver.GetConverged()); linearSolver.SetOperator(enthalpyJacobian);
INFO("Linear solver iterations = " << linearSolver.GetNumIterations()); linearSolver.SetRelTol(1.0e-13);
linearSolver.SetAbsTol(1.0e-14);
linearSolver.SetMaxIter(2000);
linearSolver.SetPrintLevel(0);
INFO("Linear solver final norm = " << linearSolver.GetFinalNorm()); linearSolver.Mult(rightHandSide, enthalpyCorrection);
REQUIRE(linearSolver.GetConverged()); INFO("Linear solver converged = " << linearSolver.GetConverged());
enthalpy += enthalpyCorrection; INFO("Linear solver iterations = " << linearSolver.GetNumIterations());
/* INFO("Linear solver final norm = " << linearSolver.GetFinalNorm());
* Only the enthalpy state changed. Geometry, rotation,
* and algebraic Jacobian data must remain reusable.
*/
++dependencies.enthalpy.revision;
const auto solvedReport = preparedOperator.Prepare( REQUIRE(linearSolver.GetConverged());
prepared_hydrostatic_analytic_solve_test_utils::make_state(
enthalpy, gravityPotential, displacement),
dependencies, rotation);
CHECK(solvedReport.contextReport.updatedEnthalpy); enthalpy += enthalpyCorrection;
CHECK(solvedReport.contextReport.preparedBaseState); /*
* Only the enthalpy state changed. Geometry, rotation,
* and algebraic Jacobian data must remain reusable.
*/
++dependencies.enthalpy.revision;
CHECK_FALSE(solvedReport.contextReport.preparedGeometryState); const auto solvedReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
dependencies, rotation
);
CHECK_FALSE(solvedReport.preparedAlgebraicJacobianBlocks); CHECK(solvedReport.contextReport.updatedEnthalpy);
mfem::Vector solvedResidual; CHECK(solvedReport.contextReport.preparedBaseState);
preparedOperator.BuildResidual(solvedResidual); CHECK_FALSE(solvedReport.contextReport.preparedGeometryState);
const double solvedResidualNorm = CHECK_FALSE(solvedReport.preparedAlgebraicJacobianBlocks);
gravity_prepared_test_utils::global_norm(solvedResidual,
communicator);
const double residualReduction = solvedResidualNorm / initialResidualNorm; mfem::Vector solvedResidual;
/* preparedOperator.BuildResidual(solvedResidual);
* Compare the solved field with the continuum analytic
* enthalpy over stellar elements only.
*
* All three mappings have determinant one, so this
* normalized L2 error is also unchanged by the physical
* volume transformation.
*/
mfem::ParGridFunction solvedEnthalpyField(f.enthalpyFes.get());
mfem::Vector enthalpyTrue(enthalpyMap.full_size()); const double solvedResidualNorm = gravity_prepared_test_utils::global_norm(solvedResidual, communicator);
enthalpyMap.scatter(enthalpy, enthalpyTrue);
solvedEnthalpyField.SetFromTrueDofs(enthalpyTrue);
const double solvedAnalyticError = solvedEnthalpyField.ComputeL2Error( const double residualReduction = solvedResidualNorm / initialResidualNorm;
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
const double relativeSolvedAnalyticError = /*
solvedAnalyticError / exactEnthalpyNorm; * Compare the solved field with the continuum analytic
* enthalpy over stellar elements only.
*
* All three mappings have determinant one, so this
* normalized L2 error is also unchanged by the physical
* volume transformation.
*/
mfem::ParGridFunction solvedEnthalpyField(f.enthalpyFes.get());
INFO("Deformation determinant = " << deformationDeterminant); mfem::Vector enthalpyTrue(enthalpyMap.full_size());
enthalpyMap.scatter(enthalpy, enthalpyTrue);
solvedEnthalpyField.SetFromTrueDofs(enthalpyTrue);
INFO("Initial weak residual norm = " << initialResidualNorm); const double solvedAnalyticError =
solvedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
INFO("Solved weak residual norm = " << solvedResidualNorm); const double relativeSolvedAnalyticError = solvedAnalyticError / exactEnthalpyNorm;
INFO("Weak residual reduction = " << residualReduction); INFO("Deformation determinant = " << deformationDeterminant);
INFO("Relative analytic projection floor = " << relativeProjectionError); INFO("Initial weak residual norm = " << initialResidualNorm);
INFO("Relative solved analytic L2 error = " INFO("Solved weak residual norm = " << solvedResidualNorm);
<< relativeSolvedAnalyticError);
/* INFO("Weak residual reduction = " << residualReduction);
* The discrete Bernoulli equation must be solved essentially
* to the linear-solver floor.
*/
CHECK(residualReduction < 1.0e-10);
/* INFO("Relative analytic projection floor = " << relativeProjectionError);
* The directly projected analytic enthalpy provides a lower
* representation bound, but it is not the expected solution
* of the cross-space discrete Bernoulli equation. The latter
* also contains potential-projection and mapped-space
* compatibility errors.
*/
CHECK(relativeSolvedAnalyticError <
std::max(5.0 * relativeProjectionError, 1.25e-4));
/* INFO("Relative solved analytic L2 error = " << relativeSolvedAnalyticError);
* Record that the analytic error remains within one order of
* magnitude of the direct enthalpy projection floor. /*
*/ * The discrete Bernoulli equation must be solved essentially
CHECK(relativeSolvedAnalyticError / relativeProjectionError < 5.0); * to the linear-solver floor.
*/
CHECK(residualReduction < 1.0e-10);
/*
* The directly projected analytic enthalpy provides a lower
* representation bound, but it is not the expected solution
* of the cross-space discrete Bernoulli equation. The latter
* also contains potential-projection and mapped-space
* compatibility errors.
*/
CHECK(relativeSolvedAnalyticError < std::max(5.0 * relativeProjectionError, 1.25e-4));
/*
* Record that the analytic error remains within one order of
* magnitude of the direct enthalpy projection floor.
*/
CHECK(relativeSolvedAnalyticError / relativeProjectionError < 5.0);
}
} }
}
} }

View File

@@ -138,9 +138,7 @@ TEST_CASE(
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
const mfem::Vector enthalpy = const mfem::Vector enthalpy =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>( field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 0.34);
*f.enthalpyFes, 0.34
);
const mfem::Vector gravityPotential = const mfem::Vector gravityPotential =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>( field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>(
@@ -161,9 +159,7 @@ TEST_CASE(
); );
const mfem::Vector enthalpyVariation = const mfem::Vector enthalpyVariation =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>( field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 1.07);
*f.enthalpyFes, 1.07
);
const mfem::Vector gravityPotentialVariation = const mfem::Vector gravityPotentialVariation =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>( field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>(
@@ -236,9 +232,7 @@ TEST_CASE(
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
const mfem::Vector enthalpy = const mfem::Vector enthalpy =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>( field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 0.41);
*f.enthalpyFes, 0.41
);
const mfem::Vector gravityPotential = const mfem::Vector gravityPotential =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>( field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>(
@@ -277,9 +271,7 @@ TEST_CASE(
CHECK(preparedOperator.GetEnthalpyMap().inactive_size() > 0); CHECK(preparedOperator.GetEnthalpyMap().inactive_size() > 0);
const mfem::Vector enthalpyVariation = const mfem::Vector enthalpyVariation =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>( field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 1.12);
*f.enthalpyFes, 1.12
);
const mfem::Vector gravityPotentialVariation = const mfem::Vector gravityPotentialVariation =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>( field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>(

View File

@@ -26,16 +26,13 @@ namespace mass_normalization_test_utils {
); );
[[nodiscard]] mean_field::operators::MassNormalizationLayout make_layout(const mean_field::fem::FEM &f) { [[nodiscard]] mean_field::operators::MassNormalizationLayout make_layout(const mean_field::fem::FEM &f) {
const auto densityMap = const auto densityMap = field_dof_test_utils::make_map<mean_field::field::Density>(*f.densityFes);
field_dof_test_utils::make_map<mean_field::field::Density>(*f.densityFes);
const auto displacementMap = const auto displacementMap =
field_dof_test_utils::make_map<mean_field::field::Displacement>(*f.displacementFes); field_dof_test_utils::make_map<mean_field::field::Displacement>(*f.displacementFes);
const auto gravityFluxMap = const auto gravityFluxMap = field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityFluxFes);
field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityFluxFes);
const auto gravityPotentialMap = const auto gravityPotentialMap =
field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityPotentialFes); field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityPotentialFes);
const auto enthalpyMap = const auto enthalpyMap = field_dof_test_utils::make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
field_dof_test_utils::make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
const std::array<int, CoupledForm::value_block_count> valueSizes{ const std::array<int, CoupledForm::value_block_count> valueSizes{
densityMap.reduced_size(), displacementMap.reduced_size(), gravityFluxMap.reduced_size(), densityMap.reduced_size(), displacementMap.reduced_size(), gravityFluxMap.reduced_size(),
@@ -43,7 +40,7 @@ namespace mass_normalization_test_utils {
}; };
const std::array<int, CoupledForm::residual_block_count> residualSizes{ const std::array<int, CoupledForm::residual_block_count> residualSizes{
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), densityMap.reduced_size(), gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), densityMap.reduced_size(),
displacementMap.reduced_size(), enthalpyMap.reduced_size(), 1 displacementMap.reduced_size(), enthalpyMap.reduced_size(), 1
}; };
@@ -345,8 +342,7 @@ TEST_CASE(
mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext); mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext);
massOperator.Prepare({.targetMass = 1.11}, dependencies); massOperator.Prepare({.targetMass = 1.11}, dependencies);
const mfem::Vector reducedDisplacementDirection = const mfem::Vector reducedDisplacementDirection = gravityContext.GetDisplacementMap().gather(displacementDirection);
gravityContext.GetDisplacementMap().gather(displacementDirection);
mfem::Vector analyticAction; mfem::Vector analyticAction;
massOperator.ApplyDisplacementJacobianAction(reducedDisplacementDirection, analyticAction); massOperator.ApplyDisplacementJacobianAction(reducedDisplacementDirection, analyticAction);
@@ -476,9 +472,8 @@ TEST_CASE(
mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext); mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext);
massOperator.Prepare({.targetMass = 1.19}, dependencies); massOperator.Prepare({.targetMass = 1.19}, 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 displacementAction; mfem::Vector displacementAction;
@@ -486,9 +481,7 @@ TEST_CASE(
massOperator.ApplyDensityJacobianAction(reducedDensityDirection, densityAction); massOperator.ApplyDensityJacobianAction(reducedDensityDirection, densityAction);
massOperator.ApplyDisplacementJacobianAction(reducedDisplacementDirection, displacementAction); massOperator.ApplyDisplacementJacobianAction(reducedDisplacementDirection, displacementAction);
massOperator.ApplyCompleteJacobianAction( massOperator.ApplyCompleteJacobianAction(reducedDensityDirection, reducedDisplacementDirection, completeAction);
reducedDensityDirection, reducedDisplacementDirection, completeAction
);
CHECK( CHECK(
mass_normalization_test_utils::relative_error(completeAction(0), densityAction(0) + displacementAction(0)) < mass_normalization_test_utils::relative_error(completeAction(0), densityAction(0) + displacementAction(0)) <
@@ -498,10 +491,7 @@ TEST_CASE(
const auto layout = mass_normalization_test_utils::make_layout(f); const auto layout = mass_normalization_test_utils::make_layout(f);
mean_field::operators::PreparedMassNormalizationJacobianOperator adapter(layout, massOperator); mean_field::operators::PreparedMassNormalizationJacobianOperator adapter(layout, massOperator);
CHECK( CHECK(layout.size(mass_normalization_test_utils::densityValue) == gravityContext.GetDensityMap().reduced_size());
layout.size(mass_normalization_test_utils::densityValue) ==
gravityContext.GetDensityMap().reduced_size()
);
CHECK( CHECK(
layout.size(mass_normalization_test_utils::displacementValue) == layout.size(mass_normalization_test_utils::displacementValue) ==
gravityContext.GetDisplacementMap().reduced_size() gravityContext.GetDisplacementMap().reduced_size()

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

View File

@@ -8,115 +8,202 @@
import mean_field; import mean_field;
import test_helpers; import test_helpers;
TEST_CASE("Polytropic EOS Satisfies Its Analytic Identities", TEST_CASE(
tags::barotrope_eos_unit) { "Polytropic EOS Satisfies Its Analytic Identities",
constexpr double polytropic_index = 3.0; tags::barotrope_eos_unit
constexpr double polytropic_constant = 1.5; ) {
using namespace mean_field::eos;
const mean_field::eos::Polytrope barotrope(polytropic_index, constexpr double polytropic_index = 3.0;
polytropic_constant); constexpr double polytropic_constant = 1.5;
const std::array<double, 5> densities{1.0e-6, 1.0e-3, 0.1, 0.7, 2.0}; const Polytrope barotrope(polytropic_index, polytropic_constant);
for (const double density : densities) { using densityV = DensityValue;
const double pressure = barotrope.pressure_from_density(density); using pressureV = PressureValue;
using enthalpyV = SpecificEnthalpyValue;
const double enthalpy = barotrope.enthalpy_from_density(density); constexpr std::array<densityV, 5> densities{
densityV{1.0e-6}, densityV{1.0e-3}, densityV{0.1}, densityV{0.7}, densityV{2.0}
};
const double reconstructed_density = for (const densityV density : densities) {
barotrope.density_from_enthalpy(enthalpy); const pressureV pressure = evaluate<quantity::Pressure>(barotrope, density);
const double reconstructed_pressure = const enthalpyV enthalpy = evaluate<quantity::SpecificEnthalpy>(barotrope, density);
barotrope.pressure_from_enthalpy(enthalpy); const densityV reconstructed_density = evaluate<quantity::Density>(barotrope, enthalpy);
const double reconstructed_enthalpy = const pressureV reconstructed_pressure = evaluate<quantity::Pressure>(barotrope, enthalpy);
barotrope.enthalpy_from_pressure(pressure);
CHECK_THAT(reconstructed_density, const enthalpyV reconstructed_enthalpy = evaluate<quantity::SpecificEnthalpy>(barotrope, pressure);
Catch::Matchers::WithinRel(density, 2.0e-14));
CHECK_THAT(reconstructed_pressure, CHECK_THAT(reconstructed_density.value(), Catch::Matchers::WithinRel(density.value(), 2.0e-14));
Catch::Matchers::WithinRel(pressure, 2.0e-14));
CHECK_THAT(reconstructed_enthalpy, CHECK_THAT(reconstructed_pressure.value(), Catch::Matchers::WithinRel(pressure.value(), 2.0e-14));
Catch::Matchers::WithinRel(enthalpy, 2.0e-14));
CHECK_THAT(pressure, CHECK_THAT(reconstructed_enthalpy.value(), Catch::Matchers::WithinRel(enthalpy.value(), 2.0e-14));
Catch::Matchers::WithinRel(
density * enthalpy / (polytropic_index + 1.0), 2.0e-14));
CHECK_THAT(barotrope.pressure_derivative_from_enthalpy(enthalpy), CHECK_THAT(
Catch::Matchers::WithinRel(density, 2.0e-14)); pressure.value(),
Catch::Matchers::WithinRel(density.value() * enthalpy.value() / (polytropic_index + 1.0), 2.0e-14)
);
CHECK_THAT( CHECK_THAT(
barotrope.pressure_derivative_from_density(density), (mean_field::eos::partialDerivative<
Catch::Matchers::WithinRel(enthalpy / polytropic_index, 2.0e-14)); mean_field::eos::quantity::Pressure, mean_field::eos::quantity::SpecificEnthalpy>(
} barotrope, mean_field::eos::SpecificEnthalpyValue{enthalpy}
)
.value()),
Catch::Matchers::WithinRel(density.value(), 2.0e-14)
);
CHECK_THAT(
(mean_field::eos::partialDerivative<
mean_field::eos::quantity::Pressure, mean_field::eos::quantity::Density>(
barotrope, mean_field::eos::DensityValue{density}
)
.value()),
Catch::Matchers::WithinRel(enthalpy.value() / polytropic_index, 2.0e-14)
);
}
} }
TEST_CASE("Polytropic EOS Derivatives Match Centered Differences", TEST_CASE(
tags::barotrope_eos_jacobian) { "Polytropic EOS Derivatives Match Centered Differences",
const mean_field::eos::Polytrope barotrope(3.0, 1.5); tags::barotrope_eos_jacobian
) {
using namespace mean_field::eos;
const std::array<double, 4> enthalpies{0.05, 0.2, 0.7, 1.4}; const Polytrope barotrope(3.0, 1.5);
for (const double enthalpy : enthalpies) { using densityV = DensityValue;
const double step = 1.0e-6 * std::max(1.0, enthalpy); using pressureV = PressureValue;
using enthalpyV = SpecificEnthalpyValue;
const double density_difference = constexpr std::array<enthalpyV, 4> enthalpies{enthalpyV{0.05}, enthalpyV{0.2}, enthalpyV{0.7}, enthalpyV{1.4}};
(barotrope.density_from_enthalpy(enthalpy + step) -
barotrope.density_from_enthalpy(enthalpy - step)) /
(2.0 * step);
const double pressure_difference = for (const enthalpyV enthalpy : enthalpies) {
(barotrope.pressure_from_enthalpy(enthalpy + step) - const enthalpyV step = enthalpyV{1.0e-6} * std::max(1.0, enthalpy.value());
barotrope.pressure_from_enthalpy(enthalpy - step)) /
(2.0 * step);
CHECK_THAT( const densityV density_difference = (evaluate<quantity::Density>(barotrope, enthalpy + step) -
density_difference, evaluate<quantity::Density>(barotrope, enthalpy - step)) /
Catch::Matchers::WithinRel( (2.0 * step.value());
barotrope.density_derivative_from_enthalpy(enthalpy), 5.0e-10));
CHECK_THAT( const pressureV pressure_difference = (evaluate<quantity::Pressure>(barotrope, enthalpy + step) -
pressure_difference, evaluate<quantity::Pressure>(barotrope, enthalpy - step)) /
Catch::Matchers::WithinRel( (2.0 * step.value());
barotrope.pressure_derivative_from_enthalpy(enthalpy), 5.0e-10));
} CHECK_THAT(
density_difference.value(),
Catch::Matchers::WithinRel(
mean_field::eos::partialDerivative<
mean_field::eos::quantity::Density, mean_field::eos::quantity::SpecificEnthalpy>(
barotrope, enthalpy
)
.value(),
5.0e-10
)
);
CHECK_THAT(
pressure_difference.value(),
Catch::Matchers::WithinRel(
mean_field::eos::partialDerivative<
mean_field::eos::quantity::Pressure, mean_field::eos::quantity::SpecificEnthalpy>(
barotrope, enthalpy
)
.value(),
5.0e-10
)
);
}
} }
TEST_CASE("Polytropic EOS Has An Exact Zero Density Surface", TEST_CASE(
tags::barotrope_eos_unit) { "Polytropic EOS Has An Exact Zero Density Surface",
const mean_field::eos::Polytrope barotrope(3.0, 1.5); tags::barotrope_eos_unit
) {
const mean_field::eos::Polytrope barotrope(3.0, 1.5);
CHECK(barotrope.density_from_enthalpy(-1.0) == 0.0); CHECK(
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0); mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
barotrope, mean_field::eos::SpecificEnthalpyValue{-1.0}
)
.value() == 0.0
);
CHECK(
mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
)
.value() == 0.0
);
CHECK(barotrope.pressure_from_enthalpy(-1.0) == 0.0); CHECK(
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0); mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
barotrope, mean_field::eos::SpecificEnthalpyValue{-1.0}
)
.value() == 0.0
);
CHECK(
mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
)
.value() == 0.0
);
CHECK(barotrope.density_derivative_from_enthalpy(-1.0) == 0.0); CHECK(
(mean_field::eos::partialDerivative<
mean_field::eos::quantity::Density, mean_field::eos::quantity::SpecificEnthalpy>(
barotrope, mean_field::eos::SpecificEnthalpyValue{-1.0}
)
.value() == 0.0)
);
CHECK(barotrope.density_derivative_from_enthalpy(0.0) == 0.0); CHECK(
(mean_field::eos::partialDerivative<
mean_field::eos::quantity::Density, mean_field::eos::quantity::SpecificEnthalpy>(
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
)
.value() == 0.0)
);
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0); CHECK(
(mean_field::eos::partialDerivative<
mean_field::eos::quantity::Pressure, mean_field::eos::quantity::SpecificEnthalpy>(
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
)
.value() == 0.0)
);
} }
TEST_CASE("Polytropic EOS Rejects Invalid Material Parameters", TEST_CASE(
tags::barotrope_eos_unit) { "Polytropic EOS Rejects Invalid Material Parameters",
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.5, 1.0), std::invalid_argument); tags::barotrope_eos_unit
) {
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.5, 1.0), std::invalid_argument);
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument); CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument);
CHECK_THROWS_AS( CHECK_THROWS_AS(mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0), std::invalid_argument);
mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0),
std::invalid_argument);
const mean_field::eos::Polytrope barotrope(3.0, 1.0); const mean_field::eos::Polytrope barotrope(3.0, 1.0);
CHECK_THROWS_AS(barotrope.pressure_from_density(-1.0), std::domain_error); CHECK_THROWS_AS(
mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(barotrope, mean_field::eos::DensityValue{-1.0}),
std::domain_error
);
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-1.0), std::domain_error); CHECK_THROWS_AS(
mean_field::eos::evaluate<mean_field::eos::quantity::SpecificEnthalpy>(
barotrope, mean_field::eos::DensityValue{-1.0}
),
std::domain_error
);
CHECK_THROWS_AS(barotrope.enthalpy_from_pressure(-1.0), std::domain_error); CHECK_THROWS_AS(
mean_field::eos::evaluate<mean_field::eos::quantity::SpecificEnthalpy>(
barotrope, mean_field::eos::PressureValue{-1.0}
),
std::domain_error
);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,125 @@
#include <catch2/catch_test_macros.hpp>
import mean_field;
import test_helpers;
namespace {
namespace eos = mean_field::eos;
class DensityClosureEquationOfState final {
public:
using Relations = eos::RelationCatalog<eos::DensityFromSpecificEnthalpy>;
[[nodiscard]] constexpr eos::DensityValue evaluate(
eos::DensityFromSpecificEnthalpy,
const eos::SpecificEnthalpyValue specificEnthalpy
) const noexcept {
return eos::DensityValue{specificEnthalpy.value()};
}
[[nodiscard]] constexpr eos::PartialDerivative<
eos::quantity::Density,
eos::quantity::SpecificEnthalpy>
partialDerivative(
eos::DensityFromSpecificEnthalpy,
eos::WithRespectTo<eos::quantity::SpecificEnthalpy>,
eos::SpecificEnthalpyValue
) const noexcept {
return eos::PartialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>{1.0};
}
};
class DensityClosureWithoutDerivative final {
public:
using Relations = eos::RelationCatalog<eos::DensityFromSpecificEnthalpy>;
[[nodiscard]] constexpr eos::DensityValue evaluate(
eos::DensityFromSpecificEnthalpy,
const eos::SpecificEnthalpyValue specificEnthalpy
) const noexcept {
return eos::DensityValue{specificEnthalpy.value()};
}
};
class EnthalpyPressureEquationOfState final {
public:
using Relations = eos::RelationCatalog<eos::PressureFromSpecificEnthalpy>;
[[nodiscard]] constexpr eos::PressureValue evaluate(
eos::PressureFromSpecificEnthalpy,
const eos::SpecificEnthalpyValue specificEnthalpy
) const noexcept {
return eos::PressureValue{2.0 * specificEnthalpy.value()};
}
[[nodiscard]] constexpr eos::PartialDerivative<
eos::quantity::Pressure,
eos::quantity::SpecificEnthalpy>
partialDerivative(
eos::PressureFromSpecificEnthalpy,
eos::WithRespectTo<eos::quantity::SpecificEnthalpy>,
eos::SpecificEnthalpyValue
) const noexcept {
return eos::PartialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>{2.0};
}
};
class DensitySeedEquationOfState final {
public:
using Relations = eos::RelationCatalog<eos::SpecificEnthalpyFromDensity>;
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
eos::SpecificEnthalpyFromDensity,
const eos::DensityValue density
) const noexcept {
return eos::SpecificEnthalpyValue{3.0 * density.value()};
}
};
class GeneralEquationOfStateWithoutCurrentConsumerRelations final {
public:
using Relations = eos::RelationCatalog<eos::SpecificEnthalpyFromPressure>;
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
eos::SpecificEnthalpyFromPressure,
const eos::PressureValue pressure
) const noexcept {
return eos::SpecificEnthalpyValue{pressure.value()};
}
};
} // namespace
TEST_CASE(
"Barotropic Closure EOS Requires Density And Its Enthalpy Derivative",
tags::barotropic_closure_equation_of_state_contract
) {
STATIC_CHECK(eos::BarotropicClosureEquationOfState<eos::Polytrope>);
STATIC_CHECK(eos::BarotropicClosureEquationOfState<DensityClosureEquationOfState>);
STATIC_CHECK(eos::EquationOfStateModel<DensityClosureWithoutDerivative>);
STATIC_CHECK_FALSE(eos::BarotropicClosureEquationOfState<DensityClosureWithoutDerivative>);
STATIC_CHECK_FALSE(eos::BarotropicClosureEquationOfState<EnthalpyPressureEquationOfState>);
}
TEST_CASE(
"Pressure Force EOS Requires Pressure And Its Enthalpy Derivative",
tags::pressure_force_equation_of_state_contract
) {
STATIC_CHECK(eos::PressureForceEquationOfState<eos::Polytrope>);
STATIC_CHECK(eos::PressureForceEquationOfState<EnthalpyPressureEquationOfState>);
STATIC_CHECK_FALSE(eos::PressureForceEquationOfState<DensityClosureEquationOfState>);
STATIC_CHECK_FALSE(eos::PressureForceEquationOfState<DensityClosureWithoutDerivative>);
}
TEST_CASE(
"Structure Seed EOS Requires Enthalpy From Density",
tags::structure_seed_equation_of_state_contract
) {
STATIC_CHECK(eos::StructureSeedEquationOfState<eos::Polytrope>);
STATIC_CHECK(eos::StructureSeedEquationOfState<DensitySeedEquationOfState>);
STATIC_CHECK_FALSE(eos::StructureSeedEquationOfState<DensityClosureEquationOfState>);
STATIC_CHECK(eos::EquationOfStateModel<GeneralEquationOfStateWithoutCurrentConsumerRelations>);
STATIC_CHECK_FALSE(eos::StructureSeedEquationOfState<GeneralEquationOfStateWithoutCurrentConsumerRelations>);
STATIC_CHECK_FALSE(eos::BarotropicClosureEquationOfState<GeneralEquationOfStateWithoutCurrentConsumerRelations>);
STATIC_CHECK_FALSE(eos::PressureForceEquationOfState<GeneralEquationOfStateWithoutCurrentConsumerRelations>);
}

View File

@@ -0,0 +1,322 @@
#include <array>
#include <concepts>
#include <expected>
#include <limits>
#include <memory>
#include <span>
#include <string_view>
#include <type_traits>
#include <catch2/catch_test_macros.hpp>
import mean_field;
import test_helpers;
namespace {
namespace eos = mean_field::eos;
class LinearPressureEquationOfState final {
public:
using Relations = eos::RelationCatalog<eos::PressureFromDensity>;
[[nodiscard]] constexpr eos::PressureValue evaluate(
eos::PressureFromDensity,
const eos::DensityValue density
) const noexcept {
return eos::PressureValue{2.0 * density.value() + 0.5};
}
};
struct DensityAlias final : eos::ThermodynamicQuantity {
static constexpr std::string_view identifier = "density";
};
class AmbiguouslyIdentifiedEquationOfState final {
public:
using Relations = eos::RelationCatalog<eos::Relation<DensityAlias, eos::quantity::Density>>;
[[nodiscard]] constexpr eos::QuantityValue<DensityAlias> evaluate(
eos::Relation<
DensityAlias,
eos::quantity::Density>,
const eos::DensityValue density
) const noexcept {
return eos::QuantityValue<DensityAlias>{density.value()};
}
};
[[nodiscard]] std::expected<
eos::PressureValue,
eos::EvaluationError>
pressureAtDensity(
const eos::EquationOfStateView equationOfState,
const eos::DensityValue density
) {
return equationOfState.tryEvaluate<eos::quantity::Pressure>(density);
}
[[nodiscard]] const eos::RuntimeRelationDescriptor *findRelation(
const eos::EquationOfStateView equationOfState,
const eos::ThermodynamicQuantityId output,
const eos::ThermodynamicQuantityId input
) {
for (const eos::RuntimeRelationDescriptor &relation : equationOfState.relations()) {
if (relation.outputQuantity == output && relation.inputQuantities.size() == 1 &&
relation.inputQuantities[0] == input) {
return std::addressof(relation);
}
}
return nullptr;
}
} // namespace
TEST_CASE(
"Runtime EOS View Generates The Polytropic Relation Catalog",
tags::equation_of_state_runtime_contract
) {
STATIC_CHECK(eos::RuntimeEquationOfStateModel<eos::Polytrope>);
STATIC_CHECK(eos::RuntimeEquationOfStateModel<LinearPressureEquationOfState>);
STATIC_CHECK(eos::EquationOfStateModel<AmbiguouslyIdentifiedEquationOfState>);
STATIC_CHECK_FALSE(eos::RuntimeEquationOfStateModel<AmbiguouslyIdentifiedEquationOfState>);
STATIC_CHECK(std::is_trivially_copyable_v<eos::EquationOfStateView>);
STATIC_CHECK_FALSE(std::constructible_from<eos::EquationOfStateView, eos::Polytrope &&>);
const eos::Polytrope equationOfState(3.0, 0.25);
const eos::Polytrope secondEquationOfState(1.5, 0.73);
const eos::EquationOfStateView view{equationOfState};
const eos::EquationOfStateView secondView{secondEquationOfState};
REQUIRE(view.relations().size() == eos::Polytrope::Relations::size);
CHECK(view.relations().data() == secondView.relations().data());
CHECK(eos::thermodynamicQuantityId<eos::quantity::Density>.name() == "density");
CHECK(eos::thermodynamicQuantityId<eos::quantity::Pressure>.name() == "pressure");
CHECK(eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>.name() == "specific_enthalpy");
const eos::RuntimeRelationDescriptor *pressureFromDensity = findRelation(
view, eos::thermodynamicQuantityId<eos::quantity::Pressure>,
eos::thermodynamicQuantityId<eos::quantity::Density>
);
REQUIRE(pressureFromDensity != nullptr);
CHECK(pressureFromDensity->hasPartialDerivative(0));
const eos::RuntimeRelationDescriptor *specificEnthalpyFromPressure = findRelation(
view, eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>,
eos::thermodynamicQuantityId<eos::quantity::Pressure>
);
REQUIRE(specificEnthalpyFromPressure != nullptr);
CHECK_FALSE(specificEnthalpyFromPressure->hasPartialDerivative(0));
const eos::RuntimeRelationDescriptor *pressureFromSpecificEnthalpy = findRelation(
view, eos::thermodynamicQuantityId<eos::quantity::Pressure>,
eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>
);
REQUIRE(pressureFromSpecificEnthalpy != nullptr);
CHECK(pressureFromSpecificEnthalpy->hasPartialDerivative(0));
const eos::RuntimeRelationDescriptor *specificEnthalpyFromDensity = findRelation(
view, eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>,
eos::thermodynamicQuantityId<eos::quantity::Density>
);
REQUIRE(specificEnthalpyFromDensity != nullptr);
CHECK_FALSE(specificEnthalpyFromDensity->hasPartialDerivative(0));
const eos::RuntimeRelationDescriptor *densityFromSpecificEnthalpy = findRelation(
view, eos::thermodynamicQuantityId<eos::quantity::Density>,
eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>
);
REQUIRE(densityFromSpecificEnthalpy != nullptr);
CHECK(densityFromSpecificEnthalpy->hasPartialDerivative(0));
}
TEST_CASE(
"Runtime EOS View Matches Typed Polytropic Evaluation",
tags::equation_of_state_runtime_compatibility
) {
const eos::Polytrope equationOfState(3.0, 0.25);
const eos::EquationOfStateView view{equationOfState};
const eos::DensityValue density{0.7};
const eos::SpecificEnthalpyValue specificEnthalpy{0.9};
const eos::PressureValue pressure{0.04};
const auto runtimePressureFromDensity = view.tryEvaluate<eos::quantity::Pressure>(density);
const auto runtimePressureFromSpecificEnthalpy = view.tryEvaluate<eos::quantity::Pressure>(specificEnthalpy);
const auto runtimeSpecificEnthalpyFromDensity = view.tryEvaluate<eos::quantity::SpecificEnthalpy>(density);
const auto runtimeSpecificEnthalpyFromPressure = view.tryEvaluate<eos::quantity::SpecificEnthalpy>(pressure);
const auto runtimeDensityFromSpecificEnthalpy = view.tryEvaluate<eos::quantity::Density>(specificEnthalpy);
REQUIRE(runtimePressureFromDensity.has_value());
REQUIRE(runtimePressureFromSpecificEnthalpy.has_value());
REQUIRE(runtimeSpecificEnthalpyFromDensity.has_value());
REQUIRE(runtimeSpecificEnthalpyFromPressure.has_value());
REQUIRE(runtimeDensityFromSpecificEnthalpy.has_value());
CHECK(
runtimePressureFromDensity->value() == eos::evaluate<eos::quantity::Pressure>(equationOfState, density).value()
);
CHECK(
runtimePressureFromSpecificEnthalpy->value() ==
eos::evaluate<eos::quantity::Pressure>(equationOfState, specificEnthalpy).value()
);
CHECK(
runtimeSpecificEnthalpyFromDensity->value() ==
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, density).value()
);
CHECK(
runtimeSpecificEnthalpyFromPressure->value() ==
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, pressure).value()
);
CHECK(
runtimeDensityFromSpecificEnthalpy->value() ==
eos::evaluate<eos::quantity::Density>(equationOfState, specificEnthalpy).value()
);
const std::array runtimeDensityInput{
eos::RuntimeQuantityValue{eos::thermodynamicQuantityId<eos::quantity::Density>, density.value()}
};
const auto erasedPressureFromDensity = view.tryEvaluate(
eos::thermodynamicQuantityId<eos::quantity::Pressure>,
std::span<const eos::RuntimeQuantityValue>{runtimeDensityInput}
);
REQUIRE(erasedPressureFromDensity.has_value());
CHECK(erasedPressureFromDensity->quantity == eos::thermodynamicQuantityId<eos::quantity::Pressure>);
CHECK(erasedPressureFromDensity->value == runtimePressureFromDensity->value());
const auto runtimePressureDerivative =
view.tryPartialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(specificEnthalpy);
const auto runtimeDensityDerivative =
view.tryPartialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(specificEnthalpy);
const auto runtimePressureDensityDerivative =
view.tryPartialDerivative<eos::quantity::Pressure, eos::quantity::Density>(density);
REQUIRE(runtimePressureDerivative.has_value());
REQUIRE(runtimeDensityDerivative.has_value());
REQUIRE(runtimePressureDensityDerivative.has_value());
CHECK(
runtimePressureDerivative->value() ==
eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
equationOfState, specificEnthalpy
)
.value()
);
CHECK(
runtimeDensityDerivative->value() ==
eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
equationOfState, specificEnthalpy
)
.value()
);
CHECK(
runtimePressureDensityDerivative->value() ==
eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(equationOfState, density).value()
);
const auto erasedPressureDensityDerivative = view.tryPartialDerivative(
eos::thermodynamicQuantityId<eos::quantity::Pressure>, eos::thermodynamicQuantityId<eos::quantity::Density>,
std::span<const eos::RuntimeQuantityValue>{runtimeDensityInput}
);
REQUIRE(erasedPressureDensityDerivative.has_value());
CHECK(*erasedPressureDensityDerivative == runtimePressureDensityDerivative->value());
}
TEST_CASE(
"Runtime EOS View Reports Unsupported And Invalid Requests",
tags::equation_of_state_runtime_contract
) {
const eos::Polytrope equationOfState(3.0, 0.25);
const eos::EquationOfStateView view{equationOfState};
constexpr eos::ThermodynamicQuantityId temperature{"temperature"};
const std::array densityInput{eos::RuntimeQuantityValue{eos::thermodynamicQuantityId<eos::quantity::Density>, 0.7}};
const std::array pressureInput{
eos::RuntimeQuantityValue{eos::thermodynamicQuantityId<eos::quantity::Pressure>, 0.04}
};
const std::array<eos::RuntimeQuantityValue, 0> noInputs{};
const auto unsupportedOutput =
view.tryEvaluate(temperature, std::span<const eos::RuntimeQuantityValue>{densityInput});
REQUIRE_FALSE(unsupportedOutput.has_value());
CHECK(unsupportedOutput.error().code() == eos::EvaluationErrorCode::unsupported_relation);
const auto wrongInputCount = view.tryEvaluate(
eos::thermodynamicQuantityId<eos::quantity::Pressure>, std::span<const eos::RuntimeQuantityValue>{noInputs}
);
REQUIRE_FALSE(wrongInputCount.has_value());
CHECK(wrongInputCount.error().code() == eos::EvaluationErrorCode::wrong_input_count);
const auto wrongInputQuantity = view.tryEvaluate(
eos::thermodynamicQuantityId<eos::quantity::Density>, std::span<const eos::RuntimeQuantityValue>{pressureInput}
);
REQUIRE_FALSE(wrongInputQuantity.has_value());
CHECK(wrongInputQuantity.error().code() == eos::EvaluationErrorCode::wrong_input_quantity);
const auto unsupportedDerivative = view.tryPartialDerivative(
eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>,
eos::thermodynamicQuantityId<eos::quantity::Pressure>, std::span<const eos::RuntimeQuantityValue>{pressureInput}
);
REQUIRE_FALSE(unsupportedDerivative.has_value());
CHECK(unsupportedDerivative.error().code() == eos::EvaluationErrorCode::unsupported_derivative);
const auto invalidDensity = view.tryEvaluate<eos::quantity::Pressure>(eos::DensityValue{-0.1});
REQUIRE_FALSE(invalidDensity.has_value());
CHECK(invalidDensity.error().code() == eos::EvaluationErrorCode::outside_domain);
const auto nonfiniteDensity =
view.tryEvaluate<eos::quantity::Pressure>(eos::DensityValue{std::numeric_limits<double>::quiet_NaN()});
REQUIRE_FALSE(nonfiniteDensity.has_value());
CHECK(nonfiniteDensity.error().code() == eos::EvaluationErrorCode::nonfinite_input);
}
TEST_CASE(
"One Runtime EOS Function Accepts Heterogeneous Concrete Models",
tags::equation_of_state_runtime_compatibility
) {
const eos::Polytrope polytrope(3.0, 0.25);
const LinearPressureEquationOfState linearEquationOfState;
const std::array views{eos::EquationOfStateView{polytrope}, eos::EquationOfStateView{linearEquationOfState}};
const eos::DensityValue density{0.7};
const auto polytropicPressure = pressureAtDensity(views[0], density);
const auto linearPressure = pressureAtDensity(views[1], density);
REQUIRE(polytropicPressure.has_value());
REQUIRE(linearPressure.has_value());
CHECK(polytropicPressure->value() == eos::evaluate<eos::quantity::Pressure>(polytrope, density).value());
CHECK(linearPressure->value() == 1.9);
}
TEST_CASE(
"Runtime EOS View Remains Valid When Stable Ownership Moves",
tags::equation_of_state_runtime_contract
) {
auto owner = std::make_unique<const eos::Polytrope>(3.0, 0.25);
const eos::EquationOfStateView view{*owner};
auto movedOwner = std::move(owner);
const auto pressure = view.tryEvaluate<eos::quantity::Pressure>(eos::DensityValue{0.7});
REQUIRE(movedOwner != nullptr);
REQUIRE(pressure.has_value());
CHECK(pressure->value() == eos::evaluate<eos::quantity::Pressure>(*movedOwner, eos::DensityValue{0.7}).value());
}

View File

@@ -0,0 +1,228 @@
#include <concepts>
#include <string_view>
#include <type_traits>
#include <catch2/catch_test_macros.hpp>
import mean_field;
import test_helpers;
namespace {
namespace eos = mean_field::eos;
struct Entropy final : eos::ThermodynamicQuantity { };
struct ElectronFraction final : eos::ThermodynamicQuantity { };
using SpecificEnthalpyFromPressureAndEntropy =
eos::Relation<eos::quantity::SpecificEnthalpy, eos::quantity::Pressure, Entropy>;
class CompleteEquationOfState final {
public:
using Relations = eos::RelationCatalog<
eos::PressureFromDensity,
eos::SpecificEnthalpyFromPressure,
SpecificEnthalpyFromPressureAndEntropy>;
[[nodiscard]] constexpr eos::PressureValue evaluate(
eos::PressureFromDensity,
const eos::DensityValue density
) const noexcept {
return eos::PressureValue{2.0 * density.value()};
}
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
eos::SpecificEnthalpyFromPressure,
const eos::PressureValue pressure
) const noexcept {
return eos::SpecificEnthalpyValue{3.0 * pressure.value()};
}
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromPressureAndEntropy,
const eos::PressureValue pressure,
const eos::QuantityValue<Entropy> entropy
) const noexcept {
return eos::SpecificEnthalpyValue{3.0 * pressure.value() + 5.0 * entropy.value()};
}
[[nodiscard]] constexpr eos::PartialDerivative<
eos::quantity::SpecificEnthalpy,
Entropy>
partialDerivative(
SpecificEnthalpyFromPressureAndEntropy,
eos::WithRespectTo<Entropy>,
eos::PressureValue,
eos::QuantityValue<Entropy>
) const noexcept {
return eos::PartialDerivative<eos::quantity::SpecificEnthalpy, Entropy>{5.0};
}
};
class MissingRelationImplementation final {
public:
using Relations = eos::RelationCatalog<eos::PressureFromDensity, eos::SpecificEnthalpyFromPressure>;
[[nodiscard]] eos::PressureValue evaluate(
eos::PressureFromDensity,
eos::DensityValue density
) const {
return eos::PressureValue{density.value()};
}
};
class IncorrectRelationOutput final {
public:
using Relations = eos::RelationCatalog<eos::PressureFromDensity>;
[[nodiscard]] eos::DensityValue evaluate(
eos::PressureFromDensity,
eos::DensityValue density
) const {
return density;
}
};
class InvalidRelationCatalog final {
public:
using Relations = eos::RelationCatalog<eos::Relation<double, eos::quantity::Density>>;
};
template <typename EquationOfState>
concept CanEvaluateDensityFromSpecificEnthalpy = requires(const EquationOfState &equationOfState) {
eos::evaluate<eos::quantity::Density>(equationOfState, eos::SpecificEnthalpyValue{1.0});
};
} // namespace
TEST_CASE(
"Thermodynamic Values Preserve Physical Quantity Types",
tags::equation_of_state_quantity_types
) {
STATIC_CHECK(eos::ThermodynamicQuantityType<eos::quantity::Density>);
STATIC_CHECK(eos::ThermodynamicQuantityType<eos::quantity::Pressure>);
STATIC_CHECK(eos::ThermodynamicQuantityType<eos::quantity::SpecificEnthalpy>);
STATIC_CHECK_FALSE(eos::ThermodynamicQuantityType<const eos::quantity::Pressure>);
STATIC_CHECK_FALSE(std::same_as<eos::DensityValue, eos::PressureValue>);
STATIC_CHECK_FALSE(std::same_as<eos::PressureValue, eos::SpecificEnthalpyValue>);
STATIC_CHECK_FALSE(std::is_convertible_v<double, eos::PressureValue>);
STATIC_CHECK_FALSE(std::is_constructible_v<eos::PressureValue, eos::DensityValue>);
STATIC_CHECK(std::is_trivially_copyable_v<eos::DensityValue>);
STATIC_CHECK(std::is_standard_layout_v<eos::DensityValue>);
STATIC_CHECK(sizeof(eos::DensityValue) == sizeof(double));
STATIC_CHECK(sizeof(eos::PressureValue) == sizeof(double));
STATIC_CHECK(sizeof(eos::SpecificEnthalpyValue) == sizeof(double));
STATIC_CHECK(std::is_empty_v<eos::PressureFromDensity>);
constexpr eos::DensityValue density{-0.25};
STATIC_CHECK(density.value() == -0.25);
}
TEST_CASE(
"Thermodynamic Derivatives Preserve Numerator And Denominator Types",
tags::equation_of_state_quantity_types
) {
using PressureByDensity = eos::PartialDerivative<eos::quantity::Pressure, eos::quantity::Density>;
using PressureBySpecificEnthalpy = eos::PartialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>;
STATIC_CHECK_FALSE(std::same_as<PressureByDensity, PressureBySpecificEnthalpy>);
STATIC_CHECK_FALSE(std::is_convertible_v<PressureByDensity, PressureBySpecificEnthalpy>);
STATIC_CHECK(std::is_trivially_copyable_v<PressureByDensity>);
STATIC_CHECK(std::is_standard_layout_v<PressureByDensity>);
STATIC_CHECK(sizeof(PressureByDensity) == sizeof(double));
constexpr PressureByDensity derivative{1.75};
STATIC_CHECK(derivative.value() == 1.75);
}
TEST_CASE(
"EOS Relation Catalog Rejects Invalid And Duplicate Relations",
tags::equation_of_state_relation_contract
) {
using ValidCatalog = eos::RelationCatalog<eos::PressureFromDensity, eos::SpecificEnthalpyFromPressure>;
using DuplicateCatalog = eos::RelationCatalog<eos::PressureFromDensity, eos::PressureFromDensity>;
using InvalidRelation = eos::Relation<double, eos::quantity::Density>;
using InvalidCatalog = eos::RelationCatalog<InvalidRelation>;
using RepeatedInputRelation =
eos::Relation<eos::quantity::Pressure, eos::quantity::Density, eos::quantity::Density>;
using RepeatedInputCatalog = eos::RelationCatalog<RepeatedInputRelation>;
STATIC_CHECK(eos::ValidRelationCatalog<ValidCatalog>);
STATIC_CHECK_FALSE(eos::ValidRelationCatalog<DuplicateCatalog>);
STATIC_CHECK_FALSE(eos::ValidRelationCatalog<InvalidCatalog>);
STATIC_CHECK_FALSE(eos::ValidRelationCatalog<RepeatedInputCatalog>);
STATIC_CHECK_FALSE(eos::ValidRelationCatalog<eos::RelationCatalog<>>);
STATIC_CHECK(eos::relationCatalogContains<ValidCatalog, eos::PressureFromDensity>);
STATIC_CHECK_FALSE(eos::relationCatalogContains<ValidCatalog, eos::DensityFromSpecificEnthalpy>);
STATIC_CHECK(eos::relationContainsInput<eos::PressureFromDensity, eos::quantity::Density>);
STATIC_CHECK_FALSE(eos::relationContainsInput<eos::PressureFromDensity, eos::quantity::Pressure>);
STATIC_CHECK(std::same_as<eos::RelationOutputT<eos::PressureFromDensity>, eos::quantity::Pressure>);
STATIC_CHECK(std::same_as<eos::RelationInputT<0, eos::PressureFromDensity>, eos::quantity::Density>);
}
TEST_CASE(
"EOS Model Contract Requires Every Declared Relation",
tags::equation_of_state_relation_contract
) {
STATIC_CHECK(eos::EquationOfStateModel<CompleteEquationOfState>);
STATIC_CHECK_FALSE(eos::EquationOfStateModel<MissingRelationImplementation>);
STATIC_CHECK_FALSE(eos::EquationOfStateModel<IncorrectRelationOutput>);
STATIC_CHECK_FALSE(eos::EquationOfStateModel<InvalidRelationCatalog>);
STATIC_CHECK(eos::SupportsRelation<CompleteEquationOfState, eos::PressureFromDensity>);
STATIC_CHECK_FALSE(eos::SupportsRelation<CompleteEquationOfState, eos::DensityFromSpecificEnthalpy>);
STATIC_CHECK_FALSE(CanEvaluateDensityFromSpecificEnthalpy<CompleteEquationOfState>);
STATIC_CHECK(
eos::SupportsPartialDerivative<CompleteEquationOfState, SpecificEnthalpyFromPressureAndEntropy, Entropy>
);
STATIC_CHECK_FALSE(
eos::SupportsPartialDerivative<
CompleteEquationOfState, SpecificEnthalpyFromPressureAndEntropy, ElectronFraction>
);
}
TEST_CASE(
"EOS Evaluation Selects Relations From Typed Inputs",
tags::equation_of_state_relation_contract
) {
constexpr CompleteEquationOfState equationOfState;
constexpr eos::PressureValue pressure =
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::DensityValue{1.25});
constexpr eos::SpecificEnthalpyValue specificEnthalpy = eos::evaluate<eos::quantity::SpecificEnthalpy>(
equationOfState, eos::PressureValue{0.5}, eos::QuantityValue<Entropy>{0.2}
);
constexpr auto entropyDerivative = eos::partialDerivative<eos::quantity::SpecificEnthalpy, Entropy>(
equationOfState, eos::PressureValue{0.5}, eos::QuantityValue<Entropy>{0.2}
);
STATIC_CHECK(noexcept(eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::DensityValue{1.25})));
STATIC_CHECK(
noexcept(eos::partialDerivative<eos::quantity::SpecificEnthalpy, Entropy>(
equationOfState, eos::PressureValue{0.5}, eos::QuantityValue<Entropy>{0.2}
))
);
STATIC_CHECK(pressure.value() == 2.5);
STATIC_CHECK(specificEnthalpy.value() == 2.5);
STATIC_CHECK(entropyDerivative.value() == 5.0);
}
TEST_CASE(
"EOS Evaluation Errors Retain A Structured Cause",
tags::equation_of_state_relation_contract
) {
const eos::EvaluationError error(
eos::EvaluationErrorCode::outside_domain, "Density is outside the relation domain."
);
CHECK(error.code() == eos::EvaluationErrorCode::outside_domain);
CHECK(std::string_view{error.what()} == "Density is outside the relation domain.");
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,156 @@
#include <array>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
import mean_field;
import test_helpers;
namespace eos = mean_field::eos;
TEST_CASE(
"Polytropic EOS Pressure To Specific Enthalpy Relation Is Characterized",
tags::polytropic_eos_characterization
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 3> polytropicConstants{0.25, 0.73, 2.0};
constexpr std::array<double, 5> pressures{0.0, 1.0e-12, 1.0e-4, 0.3, 5.0};
for (const double polytropicIndex : polytropicIndices) {
for (const double polytropicConstant : polytropicConstants) {
const mean_field::eos::Polytrope equationOfState(polytropicIndex, polytropicConstant);
for (const double pressure : pressures) {
CAPTURE(polytropicIndex, polytropicConstant, pressure);
const double indexPlusOne = polytropicIndex + 1.0;
const double expectedEnthalpy = indexPlusOne *
std::pow(polytropicConstant, polytropicIndex / indexPlusOne) *
std::pow(pressure, 1.0 / indexPlusOne);
const double enthalpy =
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::PressureValue{pressure})
.value();
if (pressure == 0.0) {
CHECK(enthalpy == 0.0);
} else {
CHECK_THAT(enthalpy, Catch::Matchers::WithinRel(expectedEnthalpy, 5.0e-14));
const double recoveredPressure =
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::SpecificEnthalpyValue{enthalpy})
.value();
CHECK_THAT(recoveredPressure, Catch::Matchers::WithinRel(pressure, 5.0e-13));
}
}
}
}
}
TEST_CASE(
"Polytropic EOS Domain Contract Covers Every Relation",
tags::polytropic_eos_characterization
) {
constexpr double infinity = std::numeric_limits<double>::infinity();
constexpr double quietNaN = std::numeric_limits<double>::quiet_NaN();
for (const double invalidIndex : std::array<double, 4>{0.999, infinity, -infinity, quietNaN}) {
CAPTURE(invalidIndex);
CHECK_THROWS_AS(mean_field::eos::Polytrope(invalidIndex, 1.0), std::invalid_argument);
}
for (const double invalidConstant : std::array<double, 5>{0.0, -0.1, infinity, -infinity, quietNaN}) {
CAPTURE(invalidConstant);
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, invalidConstant), std::invalid_argument);
}
const mean_field::eos::Polytrope equationOfState(3.0, 0.75);
constexpr double negativeDensity = -0.1;
CHECK_THROWS_AS(
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::DensityValue{negativeDensity}), std::domain_error
);
CHECK_THROWS_AS(
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::DensityValue{negativeDensity}),
std::domain_error
);
CHECK_THROWS_AS(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(
equationOfState, eos::DensityValue{negativeDensity}
)),
std::domain_error
);
CHECK_THROWS_AS(
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::PressureValue{-0.1}), std::domain_error
);
constexpr double exteriorEnthalpy = -0.1;
CHECK(
eos::evaluate<eos::quantity::Density>(equationOfState, eos::SpecificEnthalpyValue{exteriorEnthalpy}).value() ==
0.0
);
CHECK(
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::SpecificEnthalpyValue{exteriorEnthalpy}).value() ==
0.0
);
CHECK(
(eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
equationOfState, eos::SpecificEnthalpyValue{exteriorEnthalpy}
)
.value() == 0.0)
);
CHECK(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
equationOfState, eos::SpecificEnthalpyValue{exteriorEnthalpy}
)
.value() == 0.0)
);
for (const double nonfiniteValue : std::array<double, 3>{infinity, -infinity, quietNaN}) {
CAPTURE(nonfiniteValue);
CHECK_THROWS_AS(
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::DensityValue{nonfiniteValue}),
std::domain_error
);
CHECK_THROWS_AS(
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::DensityValue{nonfiniteValue}),
std::domain_error
);
CHECK_THROWS_AS(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(
equationOfState, eos::DensityValue{nonfiniteValue}
)),
std::domain_error
);
CHECK_THROWS_AS(
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::PressureValue{nonfiniteValue}),
std::domain_error
);
CHECK_THROWS_AS(
eos::evaluate<eos::quantity::Density>(equationOfState, eos::SpecificEnthalpyValue{nonfiniteValue}),
std::domain_error
);
CHECK_THROWS_AS(
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::SpecificEnthalpyValue{nonfiniteValue}),
std::domain_error
);
CHECK_THROWS_AS(
(eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
equationOfState, eos::SpecificEnthalpyValue{nonfiniteValue}
)),
std::domain_error
);
CHECK_THROWS_AS(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
equationOfState, eos::SpecificEnthalpyValue{nonfiniteValue}
)),
std::domain_error
);
}
}

View File

@@ -0,0 +1,200 @@
#include <array>
#include <cmath>
#include <concepts>
#include <limits>
#include <type_traits>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
import mean_field;
import test_helpers;
namespace {
namespace eos = mean_field::eos;
template <typename Candidate>
concept HasAnyUnaryEquationOfStateConversion =
requires(const Candidate &candidate, const double value) { candidate.pressure_from_density(value); } ||
requires(const Candidate &candidate, const double value) { candidate.pressure_from_enthalpy(value); } ||
requires(const Candidate &candidate, const double value) { candidate.enthalpy_from_density(value); } ||
requires(const Candidate &candidate, const double value) { candidate.enthalpy_from_pressure(value); } ||
requires(const Candidate &candidate, const double value) { candidate.density_from_enthalpy(value); } ||
requires(const Candidate &candidate, const double value) {
candidate.density_derivative_from_enthalpy(value);
} ||
requires(const Candidate &candidate, const double value) {
candidate.pressure_derivative_from_enthalpy(value);
} ||
requires(const Candidate &candidate, const double value) { candidate.pressure_derivative_from_density(value); };
} // namespace
TEST_CASE(
"Polytropic EOS Declares Its Thermodynamic Relation Contract",
tags::polytropic_eos_relation_contract
) {
using Polytrope = eos::Polytrope;
STATIC_CHECK(eos::EquationOfStateModel<Polytrope>);
STATIC_CHECK_FALSE(std::is_polymorphic_v<Polytrope>);
STATIC_CHECK_FALSE(HasAnyUnaryEquationOfStateConversion<Polytrope>);
STATIC_CHECK(Polytrope::Relations::size == 5);
STATIC_CHECK(eos::SupportsRelation<Polytrope, eos::PressureFromDensity>);
STATIC_CHECK(eos::SupportsRelation<Polytrope, eos::PressureFromSpecificEnthalpy>);
STATIC_CHECK(eos::SupportsRelation<Polytrope, eos::SpecificEnthalpyFromDensity>);
STATIC_CHECK(eos::SupportsRelation<Polytrope, eos::SpecificEnthalpyFromPressure>);
STATIC_CHECK(eos::SupportsRelation<Polytrope, eos::DensityFromSpecificEnthalpy>);
STATIC_CHECK(eos::SupportsPartialDerivative<Polytrope, eos::PressureFromDensity, eos::quantity::Density>);
STATIC_CHECK(
eos::SupportsPartialDerivative<Polytrope, eos::PressureFromSpecificEnthalpy, eos::quantity::SpecificEnthalpy>
);
STATIC_CHECK(
eos::SupportsPartialDerivative<Polytrope, eos::DensityFromSpecificEnthalpy, eos::quantity::SpecificEnthalpy>
);
STATIC_CHECK_FALSE(
eos::SupportsPartialDerivative<Polytrope, eos::SpecificEnthalpyFromPressure, eos::quantity::Pressure>
);
STATIC_CHECK_FALSE(
eos::SupportsPartialDerivative<Polytrope, eos::SpecificEnthalpyFromDensity, eos::quantity::Density>
);
}
TEST_CASE(
"Polytropic EOS Typed Relations Preserve Analytic Values",
tags::polytropic_eos_characterization
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 2> polytropicConstants{0.25, 0.73};
constexpr std::array<double, 4> densities{0.0, 1.0e-6, 0.2, 2.0};
constexpr std::array<double, 4> specificEnthalpies{-0.3, 0.0, 0.2, 1.7};
constexpr std::array<double, 4> pressures{0.0, 1.0e-8, 0.3, 4.0};
for (const double polytropicIndex : polytropicIndices) {
for (const double polytropicConstant : polytropicConstants) {
const eos::Polytrope equationOfState(polytropicIndex, polytropicConstant);
for (const double density : densities) {
CAPTURE(polytropicIndex, polytropicConstant, density);
const double expectedPressure = polytropicConstant * std::pow(density, 1.0 + 1.0 / polytropicIndex);
const double expectedSpecificEnthalpy =
(polytropicIndex + 1.0) * polytropicConstant * std::pow(density, 1.0 / polytropicIndex);
CHECK(
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::DensityValue{density}).value() ==
expectedPressure
);
CHECK(
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::DensityValue{density})
.value() == expectedSpecificEnthalpy
);
CHECK_THAT(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(
equationOfState, eos::DensityValue{density}
)
.value()),
Catch::Matchers::WithinRel(
density == 0.0 ? 0.0 : expectedSpecificEnthalpy / polytropicIndex, 2.0e-15
)
);
}
for (const double specificEnthalpy : specificEnthalpies) {
CAPTURE(polytropicIndex, polytropicConstant, specificEnthalpy);
const double expectedDensity =
specificEnthalpy <= 0.0
? 0.0
: std::pow(specificEnthalpy / ((polytropicIndex + 1.0) * polytropicConstant), polytropicIndex);
CHECK(
eos::evaluate<eos::quantity::Density>(equationOfState, eos::SpecificEnthalpyValue{specificEnthalpy})
.value() == expectedDensity
);
CHECK(
eos::evaluate<eos::quantity::Pressure>(
equationOfState, eos::SpecificEnthalpyValue{specificEnthalpy}
)
.value() ==
(specificEnthalpy <= 0.0 ? 0.0 : expectedDensity * specificEnthalpy / (polytropicIndex + 1.0))
);
CHECK(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
equationOfState, eos::SpecificEnthalpyValue{specificEnthalpy}
)
.value() == expectedDensity)
);
}
for (const double pressure : pressures) {
CAPTURE(polytropicIndex, polytropicConstant, pressure);
const double indexPlusOne = polytropicIndex + 1.0;
const double expectedSpecificEnthalpy = indexPlusOne *
std::pow(polytropicConstant, polytropicIndex / indexPlusOne) *
std::pow(pressure, 1.0 / indexPlusOne);
CHECK(
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::PressureValue{pressure})
.value() == expectedSpecificEnthalpy
);
}
}
}
}
TEST_CASE(
"Typed Polytropic EOS Preserves Domain And Exterior Semantics",
tags::polytropic_eos_relation_contract
) {
const eos::Polytrope equationOfState(3.0, 0.75);
try {
static_cast<void>(eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::DensityValue{-0.1}));
FAIL("A negative density must be rejected.");
} catch (const eos::EvaluationError &error) {
CHECK(error.code() == eos::EvaluationErrorCode::outside_domain);
}
try {
static_cast<void>(eos::evaluate<eos::quantity::SpecificEnthalpy>(
equationOfState, eos::PressureValue{std::numeric_limits<double>::quiet_NaN()}
));
FAIL("A nonfinite pressure must be rejected.");
} catch (const eos::EvaluationError &error) {
CHECK(error.code() == eos::EvaluationErrorCode::nonfinite_input);
}
constexpr double exteriorSpecificEnthalpy = -0.3;
CHECK(
eos::evaluate<eos::quantity::Density>(equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy})
.value() == 0.0
);
CHECK(
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy})
.value() == 0.0
);
CHECK(
(eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy}
)
.value() == 0.0)
);
CHECK(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy}
)
.value() == 0.0)
);
}

View File

@@ -0,0 +1,329 @@
#include <cmath>
#include <concepts>
#include <limits>
#include <string_view>
#include <type_traits>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
import mean_field;
import test_helpers;
namespace {
namespace eos = mean_field::eos;
namespace field = mean_field::field;
namespace surface = mean_field::surface;
struct Entropy final : eos::ThermodynamicQuantity {
static constexpr std::string_view identifier = "entropy";
};
struct ElectronFraction final : eos::ThermodynamicQuantity {
static constexpr std::string_view identifier = "electron_fraction";
};
struct EntropyField final {
static constexpr std::string_view name = "entropy";
};
struct ElectronFractionField final {
static constexpr std::string_view name = "electron_fraction";
};
using SpecificEnthalpyFromPressureEntropyAndElectronFraction =
eos::Relation<eos::quantity::SpecificEnthalpy, eos::quantity::Pressure, Entropy, ElectronFraction>;
class GeneralStellarMatterEquationOfState final {
public:
using Relations = eos::RelationCatalog<SpecificEnthalpyFromPressureEntropyAndElectronFraction>;
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromPressureEntropyAndElectronFraction,
const eos::PressureValue pressure,
const eos::QuantityValue<Entropy> entropy,
const eos::QuantityValue<ElectronFraction> electronFraction
) const noexcept {
return eos::SpecificEnthalpyValue{
2.0 * pressure.value() + 3.0 * entropy.value() + 5.0 * electronFraction.value()
};
}
[[nodiscard]] constexpr eos::PartialDerivative<
eos::quantity::SpecificEnthalpy,
Entropy>
partialDerivative(
SpecificEnthalpyFromPressureEntropyAndElectronFraction,
eos::WithRespectTo<Entropy>,
eos::PressureValue,
eos::QuantityValue<Entropy>,
eos::QuantityValue<ElectronFraction>
) const noexcept {
return eos::PartialDerivative<eos::quantity::SpecificEnthalpy, Entropy>{3.0};
}
[[nodiscard]] constexpr eos::PartialDerivative<
eos::quantity::SpecificEnthalpy,
ElectronFraction>
partialDerivative(
SpecificEnthalpyFromPressureEntropyAndElectronFraction,
eos::WithRespectTo<ElectronFraction>,
eos::PressureValue,
eos::QuantityValue<Entropy>,
eos::QuantityValue<ElectronFraction>
) const noexcept {
return eos::PartialDerivative<eos::quantity::SpecificEnthalpy, ElectronFraction>{5.0};
}
};
class GeneralEquationOfStateWithoutElectronFractionPartial final {
public:
using Relations = eos::RelationCatalog<SpecificEnthalpyFromPressureEntropyAndElectronFraction>;
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromPressureEntropyAndElectronFraction,
const eos::PressureValue pressure,
const eos::QuantityValue<Entropy> entropy,
const eos::QuantityValue<ElectronFraction> electronFraction
) const noexcept {
return eos::SpecificEnthalpyValue{pressure.value() + entropy.value() + electronFraction.value()};
}
[[nodiscard]] constexpr eos::PartialDerivative<
eos::quantity::SpecificEnthalpy,
Entropy>
partialDerivative(
SpecificEnthalpyFromPressureEntropyAndElectronFraction,
eos::WithRespectTo<Entropy>,
eos::PressureValue,
eos::QuantityValue<Entropy>,
eos::QuantityValue<ElectronFraction>
) const noexcept {
return eos::PartialDerivative<eos::quantity::SpecificEnthalpy, Entropy>{1.0};
}
};
using SpecificEnthalpyFromPressureAndEntropy =
eos::Relation<eos::quantity::SpecificEnthalpy, eos::quantity::Pressure, Entropy>;
class AmbiguousSurfaceEquationOfState final {
public:
using Relations =
eos::RelationCatalog<eos::SpecificEnthalpyFromPressure, SpecificEnthalpyFromPressureAndEntropy>;
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
eos::SpecificEnthalpyFromPressure,
const eos::PressureValue pressure
) const noexcept {
return eos::SpecificEnthalpyValue{pressure.value()};
}
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromPressureAndEntropy,
const eos::PressureValue pressure,
const eos::QuantityValue<Entropy> entropy
) const noexcept {
return eos::SpecificEnthalpyValue{pressure.value() + entropy.value()};
}
[[nodiscard]] constexpr eos::PartialDerivative<
eos::quantity::SpecificEnthalpy,
Entropy>
partialDerivative(
SpecificEnthalpyFromPressureAndEntropy,
eos::WithRespectTo<Entropy>,
eos::PressureValue,
eos::QuantityValue<Entropy>
) const noexcept {
return eos::PartialDerivative<eos::quantity::SpecificEnthalpy, Entropy>{1.0};
}
};
class DensityOnlyEquationOfState final {
public:
using Relations = eos::RelationCatalog<eos::PressureFromDensity>;
[[nodiscard]] constexpr eos::PressureValue evaluate(
eos::PressureFromDensity,
const eos::DensityValue density
) const noexcept {
return eos::PressureValue{density.value()};
}
};
using GeneralSurfaceFormulation = surface::SurfaceConstraintFormulation<
eos::quantity::SpecificEnthalpy,
field::Enthalpy,
surface::SurfaceStateBindings<
surface::SurfaceStateBinding<eos::quantity::SpecificEnthalpy, field::Enthalpy>,
surface::SurfaceStateBinding<Entropy, EntropyField>,
surface::SurfaceStateBinding<ElectronFraction, ElectronFractionField>>>;
struct PolytropicSurfaceState final {
double specificEnthalpy;
[[nodiscard]] eos::SpecificEnthalpyValue value(eos::quantity::SpecificEnthalpy) const noexcept {
return eos::SpecificEnthalpyValue{specificEnthalpy};
}
};
struct GeneralSurfaceState final {
double specificEnthalpy;
double entropy;
double electronFraction;
[[nodiscard]] eos::SpecificEnthalpyValue value(eos::quantity::SpecificEnthalpy) const noexcept {
return eos::SpecificEnthalpyValue{specificEnthalpy};
}
[[nodiscard]] eos::QuantityValue<Entropy> value(Entropy) const noexcept {
return eos::QuantityValue<Entropy>{entropy};
}
[[nodiscard]] eos::QuantityValue<ElectronFraction> value(ElectronFraction) const noexcept {
return eos::QuantityValue<ElectronFraction>{electronFraction};
}
};
template <typename Candidate>
concept HasTargetEnthalpy = requires(const Candidate &candidate) { candidate.targetEnthalpy; };
} // namespace
TEST_CASE(
"Constant Pressure Surface Prescribes Only A Pressure Quantity",
tags::surface_prescription_type_contract
) {
STATIC_CHECK(std::same_as<surface::ConstantPressureSurface::PhysicalQuantity, eos::quantity::Pressure>);
STATIC_CHECK(std::constructible_from<surface::ConstantPressureSurface, eos::PressureValue>);
STATIC_CHECK_FALSE(std::constructible_from<surface::ConstantPressureSurface, eos::SpecificEnthalpyValue>);
STATIC_CHECK_FALSE(std::constructible_from<surface::ConstantPressureSurface, double>);
STATIC_CHECK(std::same_as<surface::Isobaric, surface::ConstantPressureSurface>);
STATIC_CHECK(std::is_trivially_copyable_v<surface::ConstantPressureSurface>);
STATIC_CHECK(std::is_trivially_copyable_v<surface::PressureSurfaceDescriptor>);
STATIC_CHECK(std::is_trivially_copyable_v<surface::RuntimeSurfaceConstraintDependencies>);
const surface::ConstantPressureSurface pressureSurface{eos::PressureValue{0.03125}};
CHECK(pressureSurface.targetPressure() == eos::PressureValue{0.03125});
CHECK(pressureSurface.descriptor().targetPressure == 0.03125);
CHECK_THROWS_AS(surface::ConstantPressureSurface{eos::PressureValue{-0.1}}, std::invalid_argument);
CHECK_THROWS_AS(
surface::ConstantPressureSurface{eos::PressureValue{std::numeric_limits<double>::infinity()}},
std::invalid_argument
);
}
TEST_CASE(
"Polytropic EOS Resolves Constant Surface Pressure Through Its Enthalpy Relation",
tags::surface_constraint_compilation
) {
using Formulation = surface::BarotropicSurfaceFormulation;
STATIC_CHECK(surface::PressureSurfaceCompilable<Formulation, eos::Polytrope>);
STATIC_CHECK_FALSE(surface::PressureSurfaceCompilable<Formulation, DensityOnlyEquationOfState>);
const eos::Polytrope equationOfState(3.0, 0.25);
const surface::ConstantPressureSurface pressureSurface{eos::PressureValue{0.03125}};
const auto constraint = surface::compilePressureSurfaceConstraint<Formulation>(pressureSurface, equationOfState);
using Constraint = std::remove_cvref_t<decltype(constraint)>;
using Dependencies = Constraint::SurfaceDependencies;
STATIC_CHECK(std::is_trivially_copyable_v<Constraint>);
STATIC_CHECK(std::same_as<Constraint::Relation, eos::SpecificEnthalpyFromPressure>);
STATIC_CHECK(std::same_as<Dependencies::RowField, field::Enthalpy>);
STATIC_CHECK(std::same_as<Dependencies::StateFieldTypes, field::TypeList<field::Enthalpy>>);
STATIC_CHECK_FALSE(HasTargetEnthalpy<Constraint>);
const double requiredSpecificEnthalpy =
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, pressureSurface.targetPressure()).value();
const PolytropicSurfaceState state{requiredSpecificEnthalpy};
const PolytropicSurfaceState variation{-0.19};
CHECK(constraint.targetPressure() == eos::PressureValue{0.03125});
CHECK(constraint.residual(state) == 0.0);
CHECK(constraint.jacobianAction(state, variation) == -0.19);
const auto runtimeDependencies = constraint.runtimeDependencies();
REQUIRE(runtimeDependencies.stateFields.size() == 1);
CHECK(runtimeDependencies.residualRowField == surface::surfaceFieldId<field::Enthalpy>);
CHECK(runtimeDependencies.stateFields[0] == surface::surfaceFieldId<field::Enthalpy>);
}
TEST_CASE(
"General EOS Resolves Constant Surface Pressure With Local Composition",
tags::surface_constraint_compilation
) {
STATIC_CHECK(surface::PressureSurfaceCompilable<GeneralSurfaceFormulation, GeneralStellarMatterEquationOfState>);
STATIC_CHECK_FALSE(
surface::PressureSurfaceCompilable<surface::BarotropicSurfaceFormulation, GeneralStellarMatterEquationOfState>
);
STATIC_CHECK_FALSE(
surface::PressureSurfaceCompilable<
GeneralSurfaceFormulation, GeneralEquationOfStateWithoutElectronFractionPartial>
);
STATIC_CHECK_FALSE(surface::PressureSurfaceCompilable<GeneralSurfaceFormulation, AmbiguousSurfaceEquationOfState>);
const GeneralStellarMatterEquationOfState equationOfState;
const surface::ConstantPressureSurface pressureSurface{eos::PressureValue{0.4}};
const auto constraint =
surface::compilePressureSurfaceConstraint<GeneralSurfaceFormulation>(pressureSurface, equationOfState);
using Constraint = std::remove_cvref_t<decltype(constraint)>;
using Dependencies = Constraint::SurfaceDependencies;
STATIC_CHECK(std::same_as<Constraint::Relation, SpecificEnthalpyFromPressureEntropyAndElectronFraction>);
STATIC_CHECK(
std::same_as<
Dependencies::StateFieldTypes, field::TypeList<field::Enthalpy, EntropyField, ElectronFractionField>>
);
constexpr GeneralSurfaceState firstSurface{
.specificEnthalpy = 2.0 * 0.4 + 3.0 * 0.2 + 5.0 * 0.1, .entropy = 0.2, .electronFraction = 0.1
};
constexpr GeneralSurfaceState secondSurface{
.specificEnthalpy = 2.0 * 0.4 + 3.0 * 0.3 + 5.0 * 0.1, .entropy = 0.3, .electronFraction = 0.1
};
CHECK(firstSurface.specificEnthalpy != secondSurface.specificEnthalpy);
CHECK(constraint.residual(firstSurface) == 0.0);
CHECK(constraint.residual(secondSurface) == 0.0);
const auto runtimeDependencies = constraint.runtimeDependencies();
REQUIRE(runtimeDependencies.stateFields.size() == 3);
CHECK(runtimeDependencies.stateFields[0] == surface::surfaceFieldId<field::Enthalpy>);
CHECK(runtimeDependencies.stateFields[1] == surface::surfaceFieldId<EntropyField>);
CHECK(runtimeDependencies.stateFields[2] == surface::surfaceFieldId<ElectronFractionField>);
}
TEST_CASE(
"General EOS Pressure Surface Jacobian Includes Every Local State Dependency",
tags::surface_constraint_jacobian
) {
const GeneralStellarMatterEquationOfState equationOfState;
const surface::ConstantPressureSurface pressureSurface{eos::PressureValue{0.4}};
const auto constraint =
surface::compilePressureSurfaceConstraint<GeneralSurfaceFormulation>(pressureSurface, equationOfState);
constexpr GeneralSurfaceState state{.specificEnthalpy = 1.7, .entropy = 0.2, .electronFraction = 0.1};
constexpr GeneralSurfaceState variation{.specificEnthalpy = 0.7, .entropy = -0.2, .electronFraction = 0.05};
constexpr double step = 1.0e-7;
const GeneralSurfaceState forward{
.specificEnthalpy = state.specificEnthalpy + step * variation.specificEnthalpy,
.entropy = state.entropy + step * variation.entropy,
.electronFraction = state.electronFraction + step * variation.electronFraction
};
const GeneralSurfaceState backward{
.specificEnthalpy = state.specificEnthalpy - step * variation.specificEnthalpy,
.entropy = state.entropy - step * variation.entropy,
.electronFraction = state.electronFraction - step * variation.electronFraction
};
const double finiteDifference = (constraint.residual(forward) - constraint.residual(backward)) / (2.0 * step);
const double jacobianAction = constraint.jacobianAction(state, variation);
CHECK(jacobianAction == variation.specificEnthalpy - 3.0 * variation.entropy - 5.0 * variation.electronFraction);
CHECK_THAT(finiteDifference, Catch::Matchers::WithinAbs(jacobianAction, 2.0e-9));
}

View File

@@ -1,76 +0,0 @@
#include <cmath>
#include <limits>
#include <stdexcept>
#include <catch2/catch_test_macros.hpp>
import mean_field;
import test_helpers;
TEST_CASE(
"Isobaric Surface Resolves Zero Pressure To Zero Enthalpy",
tags::barotrope &tags::unit &tags::surface
) {
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mean_field::surface::Isobaric surface;
const mean_field::surface::ResolvedSurfaceCondition resolved = surface.resolve(equationOfState);
CHECK(surface.targetPressure() == 0.0);
CHECK(resolved.targetEnthalpy == 0.0);
CHECK(resolved.residual(0.0) == 0.0);
CHECK(resolved.residual(0.37) == 0.37);
CHECK(resolved.jacobianAction(-0.19) == -0.19);
}
TEST_CASE(
"Isobaric Surface Resolves Positive Pressure Through The EOS",
tags::barotrope &tags::unit &tags::surface
) {
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
constexpr double targetPressure = 0.03125;
const mean_field::surface::Isobaric surface(targetPressure);
const mean_field::surface::ResolvedSurfaceCondition resolved = surface.resolve(equationOfState);
const double recoveredPressure = equationOfState.pressure_from_enthalpy(resolved.targetEnthalpy);
INFO("Resolved surface enthalpy = " << resolved.targetEnthalpy);
INFO("Recovered surface pressure = " << recoveredPressure);
CHECK(resolved.targetEnthalpy > 0.0);
CHECK(std::abs(recoveredPressure - targetPressure) < 64.0 * std::numeric_limits<double>::epsilon());
CHECK(resolved.residual(resolved.targetEnthalpy) == 0.0);
}
TEST_CASE(
"Isobaric Surface Rejects Invalid Pressure Targets",
tags::barotrope &tags::unit &tags::surface
) {
CHECK_THROWS_AS(mean_field::surface::Isobaric(-1.0), std::invalid_argument);
CHECK_THROWS_AS(mean_field::surface::Isobaric(std::numeric_limits<double>::infinity()), std::invalid_argument);
CHECK_THROWS_AS(mean_field::surface::Isobaric(std::numeric_limits<double>::quiet_NaN()), std::invalid_argument);
}
TEST_CASE(
"Surface Base Dispatch Preserves The Isobaric Prescription",
tags::barotrope &tags::unit &tags::surface
) {
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mean_field::surface::Isobaric isobaric(0.02);
const mean_field::surface::SurfaceBase &surface = isobaric;
surface.validate(equationOfState);
const mean_field::surface::ResolvedSurfaceCondition resolved = surface.resolve(equationOfState);
CHECK(resolved.targetEnthalpy > 0.0);
CHECK(resolved.residual(resolved.targetEnthalpy) == 0.0);
}

View File

@@ -13,456 +13,469 @@ export module test_helpers;
import mean_field; import mean_field;
template <std::size_t N> struct Tag { template <std::size_t N> struct Tag {
std::array<char, N> chars{}; std::array<char, N> chars{};
// ReSharper disable once CppNonExplicitConvertingConstructor // ReSharper disable once CppNonExplicitConvertingConstructor
consteval Tag(std::array<char, N> arr) : chars(arr) {} consteval Tag(
std::array<
char,
N> arr
)
: chars(arr) {
}
// ReSharper disable once CppNonExplicitConversionOperator // ReSharper disable once CppNonExplicitConversionOperator
constexpr operator const char *() const { return chars.data(); } constexpr operator const char *() const {
return chars.data();
}
// ReSharper disable once CppNonExplicitConversionOperator // ReSharper disable once CppNonExplicitConversionOperator
constexpr operator Catch::StringRef() const { constexpr operator Catch::StringRef() const {
return Catch::StringRef(chars.data(), N - 1); return Catch::StringRef(chars.data(), N - 1);
} }
template <std::size_t M> template <std::size_t M> consteval Tag<N + M - 1> operator&(const Tag<M> &other) const {
consteval Tag<N + M - 1> operator&(const Tag<M> &other) const { std::array<char, N + M - 1> res{};
std::array<char, N + M - 1> res{}; std::ranges::copy(chars.begin(), chars.end() - 1, res.begin());
std::ranges::copy(chars.begin(), chars.end() - 1, res.begin()); std::ranges::copy(other.chars, res.begin() + (N - 1));
std::ranges::copy(other.chars, res.begin() + (N - 1)); return {res};
return {res}; }
}
}; };
template <std::size_t N> consteval auto make_tag(const char (&str)[N]) { template <std::size_t N> consteval auto make_tag(const char (&str)[N]) {
std::array<char, N + 2> res{}; std::array<char, N + 2> res{};
res[0] = '['; res[0] = '[';
std::ranges::copy(str, str + N - 1, res.begin() + 1); std::ranges::copy(str, str + N - 1, res.begin() + 1);
res[N] = ']'; res[N] = ']';
res[N + 1] = '\0'; res[N + 1] = '\0';
return Tag<N + 2>{res}; return Tag<N + 2>{res};
} }
template <std::size_t N, std::size_t M> template <
consteval auto sub_tag(const Tag<N> &parent, const char (&str)[M]) { std::size_t N,
return parent & make_tag(str); std::size_t M>
consteval auto sub_tag(
const Tag<N> &parent,
const char (&str)[M]
) {
return parent & make_tag(str);
} }
namespace test_utils::detail { namespace test_utils::detail {
std::optional<mean_field::utils::Args> configured_args; std::optional<mean_field::utils::Args> configured_args;
mean_field::utils::Args make_default_args() { mean_field::utils::Args make_default_args() {
mean_field::utils::Args args; mean_field::utils::Args args;
args.mesh_file = "sandbox.smesh"; args.mesh_file = "sandbox.smesh";
args.p.rtol = 1.0e-12; args.p.rtol = 1.0e-12;
args.p.atol = 1.0e-12; args.p.atol = 1.0e-12;
return args; return args;
} }
} // namespace test_utils::detail } // namespace test_utils::detail
export namespace test_utils { export namespace test_utils {
void set_args(mean_field::utils::Args args) { void set_args(mean_field::utils::Args args) {
detail::configured_args = std::move(args); detail::configured_args = std::move(args);
} }
mean_field::utils::Args setup_args() { mean_field::utils::Args setup_args() {
if (detail::configured_args.has_value()) { if (detail::configured_args.has_value()) {
return *detail::configured_args; return *detail::configured_args;
} }
return detail::make_default_args(); return detail::make_default_args();
} }
} // namespace test_utils } // namespace test_utils
export namespace gravity_prepared_test_utils { export namespace gravity_prepared_test_utils {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
template <typename FieldT> template <typename FieldT> inline mean_field::field::FieldDofMap make_field_map(const mean_field::fem::FEM &f) {
inline mean_field::field::FieldDofMap if constexpr (std::same_as<FieldT, mean_field::field::Density>) {
make_field_map(const mean_field::fem::FEM &f) { return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(*f.densityFes);
if constexpr (std::same_as<FieldT, mean_field::field::Density>) { } else if constexpr (std::same_as<FieldT, mean_field::field::Displacement>) {
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>( return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(*f.displacementFes);
*f.densityFes); } else {
} else if constexpr (std::same_as<FieldT, mean_field::field::Displacement>) { static_assert(std::same_as<FieldT, mean_field::field::Gravity>);
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>( return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(*f.gravityFluxFes);
*f.displacementFes); }
} else {
static_assert(std::same_as<FieldT, mean_field::field::Gravity>);
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(
*f.gravityFluxFes);
}
}
template <typename FieldT>
inline mfem::Vector gather_field(const mean_field::fem::FEM &f,
const mfem::Vector &true_vector) {
return make_field_map<FieldT>(f).gather(true_vector);
}
inline mfem::Vector make_deterministic_vector(const int size,
const double phase = 0.0) {
mfem::Vector vector(size);
for (int i = 0; i < size; ++i) {
const double index = static_cast<double>(i + 1);
vector(i) = std::sin(0.37 * index + phase) +
0.31 * std::cos(0.19 * index - 0.5 * phase);
}
return vector;
}
inline mfem::Vector make_displacement(const mean_field::fem::FEM &f,
const double scale) {
mfem::ParGridFunction displacement(f.displacementFes.get());
auto displacement_function = [scale](const mfem::Vector &position,
mfem::Vector &value) {
value.SetSize(3);
value(0) = scale * (0.04 * position(0) + 0.01 * position(1) * position(2));
value(1) =
scale * (-0.03 * position(1) + 0.008 * position(0) * position(2));
value(2) = scale * (0.02 * position(2) - 0.006 * position(0) * position(1));
};
mfem::VectorFunctionCoefficient coefficient(f.mesh->Dimension(),
displacement_function);
displacement.ProjectCoefficient(coefficient);
mfem::Vector displacement_true;
displacement.GetTrueDofs(displacement_true);
return displacement_true;
}
inline mfem::Vector make_domain_supported_density(const mean_field::fem::FEM &f,
const bool stellar) {
mfem::Vector attribute_values(f.mesh->attributes.Max());
attribute_values = 0.0;
using DomainSchema =
mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
for (int i = 0; i < f.mesh->attributes.Size(); ++i) {
const int attribute = f.mesh->attributes[i];
const bool is_stellar = DomainSchema::template attribute_belongs_to<
mean_field::utils::domain::Stellar>(attribute);
if (is_stellar == stellar) {
attribute_values(attribute - 1) = 1.0;
} }
}
mfem::PWConstCoefficient coefficient(attribute_values); template <typename FieldT>
mfem::ParGridFunction density(f.densityFes.get()); inline mfem::Vector gather_field(
density.ProjectCoefficient(coefficient); const mean_field::fem::FEM &f,
const mfem::Vector &true_vector
) {
return make_field_map<FieldT>(f).gather(true_vector);
}
mfem::Vector density_true; inline mfem::Vector make_deterministic_vector(
density.GetTrueDofs(density_true); const int size,
return density_true; const double phase = 0.0
} ) {
mfem::Vector vector(size);
inline mfem::Vector linear_combination(const mfem::Vector &first, for (int i = 0; i < size; ++i) {
const double first_scale, const double index = static_cast<double>(i + 1);
const mfem::Vector &second, vector(i) = std::sin(0.37 * index + phase) + 0.31 * std::cos(0.19 * index - 0.5 * phase);
const double second_scale) { }
MFEM_VERIFY(first.Size() == second.Size(),
"Cannot combine vectors with different sizes.");
mfem::Vector combination(first); return vector;
combination *= first_scale; }
combination.Add(second_scale, second);
return combination;
}
inline double global_norm(const mfem::Vector &vector, MPI_Comm communicator) { inline mfem::Vector make_displacement(
const double local_norm_squared = vector * vector; const mean_field::fem::FEM &f,
double global_norm_squared = 0.0; const double scale
MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, ) {
MPI_SUM, communicator); mfem::ParGridFunction displacement(f.displacementFes.get());
return std::sqrt(global_norm_squared);
}
inline double global_dot(const mfem::Vector &first, const mfem::Vector &second, auto displacement_function = [scale](const mfem::Vector &position, mfem::Vector &value) {
MPI_Comm communicator) { value.SetSize(3);
MFEM_VERIFY(first.Size() == second.Size(), value(0) = scale * (0.04 * position(0) + 0.01 * position(1) * position(2));
"Cannot take the dot product of vectors with different sizes."); value(1) = scale * (-0.03 * position(1) + 0.008 * position(0) * position(2));
value(2) = scale * (0.02 * position(2) - 0.006 * position(0) * position(1));
};
const double local_dot = first * second; mfem::VectorFunctionCoefficient coefficient(f.mesh->Dimension(), displacement_function);
double global_dot = 0.0; displacement.ProjectCoefficient(coefficient);
MPI_Allreduce(&local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator);
return global_dot;
}
inline double relative_error(const mfem::Vector &computed, mfem::Vector displacement_true;
const mfem::Vector &reference, displacement.GetTrueDofs(displacement_true);
MPI_Comm communicator) { return displacement_true;
MFEM_VERIFY(computed.Size() == reference.Size(), }
"Cannot compare vectors with different sizes.");
mfem::Vector difference(computed); inline mfem::Vector make_domain_supported_density(
difference -= reference; const mean_field::fem::FEM &f,
const bool stellar
) {
mfem::Vector attribute_values(f.mesh->attributes.Max());
attribute_values = 0.0;
return global_norm(difference, communicator) / using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
std::max(global_norm(reference, communicator),
std::numeric_limits<double>::epsilon());
}
inline double relative_scalar_error(const double computed, for (int i = 0; i < f.mesh->attributes.Size(); ++i) {
const double reference) { const int attribute = f.mesh->attributes[i];
return std::abs(computed - reference) / const bool is_stellar =
std::max(std::abs(reference), std::numeric_limits<double>::epsilon()); DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Stellar>(attribute);
}
if (is_stellar == stellar) {
attribute_values(attribute - 1) = 1.0;
}
}
mfem::PWConstCoefficient coefficient(attribute_values);
mfem::ParGridFunction density(f.densityFes.get());
density.ProjectCoefficient(coefficient);
mfem::Vector density_true;
density.GetTrueDofs(density_true);
return density_true;
}
inline mfem::Vector linear_combination(
const mfem::Vector &first,
const double first_scale,
const mfem::Vector &second,
const double second_scale
) {
MFEM_VERIFY(first.Size() == second.Size(), "Cannot combine vectors with different sizes.");
mfem::Vector combination(first);
combination *= first_scale;
combination.Add(second_scale, second);
return combination;
}
inline double global_norm(
const mfem::Vector &vector,
MPI_Comm communicator
) {
const double local_norm_squared = vector * vector;
double global_norm_squared = 0.0;
MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator);
return std::sqrt(global_norm_squared);
}
inline double global_dot(
const mfem::Vector &first,
const mfem::Vector &second,
MPI_Comm communicator
) {
MFEM_VERIFY(first.Size() == second.Size(), "Cannot take the dot product of vectors with different sizes.");
const double local_dot = first * second;
double global_dot = 0.0;
MPI_Allreduce(&local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator);
return global_dot;
}
inline double relative_error(
const mfem::Vector &computed,
const mfem::Vector &reference,
MPI_Comm communicator
) {
MFEM_VERIFY(computed.Size() == reference.Size(), "Cannot compare vectors with different sizes.");
mfem::Vector difference(computed);
difference -= reference;
return global_norm(difference, communicator) /
std::max(global_norm(reference, communicator), std::numeric_limits<double>::epsilon());
}
inline double relative_scalar_error(
const double computed,
const double reference
) {
return std::abs(computed - reference) / std::max(std::abs(reference), std::numeric_limits<double>::epsilon());
}
} // namespace gravity_prepared_test_utils } // namespace gravity_prepared_test_utils
export namespace field_dof_test_utils { export namespace field_dof_test_utils {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
inline mean_field::mapping::DomainMapper make_domain_mapper() { inline mean_field::mapping::DomainMapper make_domain_mapper() {
const mean_field::utils::Args args = test_utils::setup_args(); const mean_field::utils::Args args = test_utils::setup_args();
return mean_field::mapping::DomainMapper( return mean_field::mapping::DomainMapper(
args.domain_mapper_options, args.domain_mapper_options,
std::make_unique<const mean_field::mapping::compactification:: std::make_unique<const mean_field::mapping::compactification::KelvinCompactification>(args.kelvin_options)
KelvinCompactification>(args.kelvin_options)); );
} }
inline constexpr int vacuum_material_attribute = inline constexpr int vacuum_material_attribute =
DomainSchema::template material_attribute< DomainSchema::template material_attribute<mean_field::utils::domain::Vacuum>();
mean_field::utils::domain::Vacuum>();
template <typename FieldT> template <typename FieldT>
inline mean_field::field::FieldDofMap inline mean_field::field::FieldDofMap make_map(const mfem::ParFiniteElementSpace &finiteElementSpace) {
make_map(const mfem::ParFiniteElementSpace &finiteElementSpace) { return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(finiteElementSpace);
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>( }
finiteElementSpace);
}
template <typename FieldT> template <typename FieldT>
inline mfem::Vector make_deterministic_supported_vector( inline mfem::Vector make_deterministic_supported_vector(
const mfem::ParFiniteElementSpace &finiteElementSpace, const double phase) { const mfem::ParFiniteElementSpace &finiteElementSpace,
const mean_field::field::FieldDofMap map = const double phase
make_map<FieldT>(finiteElementSpace); ) {
const mfem::Vector full = const mean_field::field::FieldDofMap map = make_map<FieldT>(finiteElementSpace);
gravity_prepared_test_utils::make_deterministic_vector(map.full_size(), const mfem::Vector full = gravity_prepared_test_utils::make_deterministic_vector(map.full_size(), phase);
phase); return map.gather(full);
return map.gather(full); }
}
inline mfem::Vector make_supported_displacement(const mean_field::fem::FEM &f, inline mfem::Vector make_supported_displacement(
const double phase) { const mean_field::fem::FEM &f,
const mean_field::field::FieldDofMap map = const double phase
make_map<mean_field::field::Displacement>(*f.displacementFes); ) {
return map.gather(gravity_prepared_test_utils::make_displacement(f, phase)); const mean_field::field::FieldDofMap map = make_map<mean_field::field::Displacement>(*f.displacementFes);
} return map.gather(gravity_prepared_test_utils::make_displacement(f, phase));
}
inline void apply_hydrostatic_reference( inline void apply_hydrostatic_reference(
const mean_field::fem::FEM &f, const mean_field::fem::FEM &f,
const mean_field::physics::RigidRotation &rotation, const mean_field::physics::RigidRotation &rotation,
const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential, const mfem::Vector &enthalpy,
const mfem::Vector &displacement, const double bernoulliConstant, const mfem::Vector &gravityPotential,
mfem::Vector &residual) { const mfem::Vector &displacement,
const mean_field::field::FieldDofMap enthalpyMap = const double bernoulliConstant,
make_map<mean_field::field::Enthalpy>(*f.enthalpyFes); mfem::Vector &residual
const mean_field::field::FieldDofMap gravityPotentialMap = ) {
make_map<mean_field::field::Gravity>(*f.gravityPotentialFes); const mean_field::field::FieldDofMap enthalpyMap = make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
const mean_field::field::FieldDofMap displacementMap = const mean_field::field::FieldDofMap gravityPotentialMap =
make_map<mean_field::field::Displacement>(*f.displacementFes); make_map<mean_field::field::Gravity>(*f.gravityPotentialFes);
const mean_field::field::FieldDofMap displacementMap =
make_map<mean_field::field::Displacement>(*f.displacementFes);
mfem::Vector enthalpyTrue(enthalpyMap.full_size()); mfem::Vector enthalpyTrue(enthalpyMap.full_size());
mfem::Vector gravityPotentialTrue(gravityPotentialMap.full_size()); mfem::Vector gravityPotentialTrue(gravityPotentialMap.full_size());
mfem::Vector displacementTrue(displacementMap.full_size()); mfem::Vector displacementTrue(displacementMap.full_size());
mfem::Vector residualTrue; mfem::Vector residualTrue;
enthalpyMap.scatter(enthalpy, enthalpyTrue); enthalpyMap.scatter(enthalpy, enthalpyTrue);
gravityPotentialMap.scatter(gravityPotential, gravityPotentialTrue); gravityPotentialMap.scatter(gravityPotential, gravityPotentialTrue);
displacementMap.scatter(displacement, displacementTrue); displacementMap.scatter(displacement, displacementTrue);
mean_field::operators::kernels::apply_hydrostatic_equilibrium( mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpyTrue, gravityPotentialTrue, f, *f.domainMapperStateless, rotation, enthalpyTrue, gravityPotentialTrue, displacementTrue,
displacementTrue, bernoulliConstant, residualTrue); bernoulliConstant, residualTrue
);
residual.SetSize(enthalpyMap.reduced_size()); residual.SetSize(enthalpyMap.reduced_size());
enthalpyMap.gather(residualTrue, residual); enthalpyMap.gather(residualTrue, residual);
} }
} // namespace field_dof_test_utils } // namespace field_dof_test_utils
export namespace tags { export namespace tags {
inline constexpr auto geometry = make_tag("geometry"); inline constexpr auto geometry = make_tag("geometry");
inline constexpr auto physics = make_tag("physics"); inline constexpr auto physics = make_tag("physics");
inline constexpr auto unit = make_tag("unit"); inline constexpr auto unit = make_tag("unit");
inline constexpr auto mesh = make_tag("mesh"); inline constexpr auto mesh = make_tag("mesh");
inline constexpr auto integration = make_tag("integration"); inline constexpr auto integration = make_tag("integration");
inline constexpr auto solver = make_tag("solver"); inline constexpr auto solver = make_tag("solver");
inline constexpr auto integrator = make_tag("integrator"); inline constexpr auto integrator = make_tag("integrator");
inline constexpr auto mapping = make_tag("mapping"); inline constexpr auto mapping = make_tag("mapping");
inline constexpr auto utils = make_tag("utils"); inline constexpr auto utils = make_tag("utils");
inline constexpr auto mfem_operators = make_tag("operators"); inline constexpr auto mfem_operators = make_tag("operators");
inline constexpr auto initialization = make_tag("initialization"); inline constexpr auto initialization = make_tag("initialization");
inline constexpr auto accuracy = make_tag("accuracy"); inline constexpr auto accuracy = make_tag("accuracy");
inline constexpr auto closure = make_tag("closure"); inline constexpr auto closure = make_tag("closure");
inline constexpr auto kernels = make_tag("kernels"); inline constexpr auto kernels = make_tag("kernels");
inline constexpr auto surface = make_tag("surface"); inline constexpr auto surface = make_tag("surface");
inline constexpr auto model = make_tag("model"); inline constexpr auto model = make_tag("model");
inline constexpr auto field = sub_tag(mesh & physics, "field"); inline constexpr auto field = sub_tag(mesh & physics, "field");
inline constexpr auto field_dof = field & make_tag("dof"); inline constexpr auto field_dof = field & make_tag("dof");
inline constexpr auto field_dof_unit = field_dof & unit; inline constexpr auto field_dof_unit = field_dof & unit;
inline constexpr auto field_dof_integration = field_dof & integration; inline constexpr auto field_dof_integration = field_dof & integration;
inline constexpr auto pressure = sub_tag(physics, "pressure"); inline constexpr auto pressure = sub_tag(physics, "pressure");
inline constexpr auto hydro = sub_tag(physics, "hydro"); inline constexpr auto hydro = sub_tag(physics, "hydro");
inline constexpr auto jacobian = sub_tag(integration & physics, "jacobian"); inline constexpr auto jacobian = sub_tag(integration & physics, "jacobian");
inline constexpr auto residuals = sub_tag(integration & physics, "residuals"); inline constexpr auto residuals = sub_tag(integration & physics, "residuals");
inline constexpr auto volume = sub_tag(mesh & geometry, "volume"); inline constexpr auto volume = sub_tag(mesh & geometry, "volume");
inline constexpr auto quadrature = inline constexpr auto quadrature = sub_tag(mesh & geometry & solver, "quadrature");
sub_tag(mesh & geometry & solver, "quadrature"); inline constexpr auto convergence = sub_tag(solver, "convergence");
inline constexpr auto convergence = sub_tag(solver, "convergence"); inline constexpr auto transformations = sub_tag(mesh & geometry, "transformations");
inline constexpr auto transformations =
sub_tag(mesh & geometry, "transformations");
inline constexpr auto h_refinement = inline constexpr auto h_refinement = sub_tag(mesh & convergence, "h_refinement");
sub_tag(mesh & convergence, "h_refinement"); inline constexpr auto p_refinement = sub_tag(mesh & convergence, "p_refinement");
inline constexpr auto p_refinement =
sub_tag(mesh & convergence, "p_refinement");
inline constexpr auto analytic_comparison = inline constexpr auto analytic_comparison = sub_tag(solver & physics & residuals, "analytic_comparison");
sub_tag(solver & physics & residuals, "analytic_comparison"); inline constexpr auto self_consistency = sub_tag(solver & physics, "self_consistency");
inline constexpr auto self_consistency =
sub_tag(solver & physics, "self_consistency");
inline constexpr auto centrifugal = sub_tag(solver & physics, "centrifugal"); inline constexpr auto centrifugal = sub_tag(solver & physics, "centrifugal");
inline constexpr auto advection = sub_tag(solver & physics, "advection"); inline constexpr auto advection = sub_tag(solver & physics, "advection");
inline constexpr auto coriolis = sub_tag(solver & physics, "coriolis"); inline constexpr auto coriolis = sub_tag(solver & physics, "coriolis");
inline constexpr auto gravity = sub_tag(solver & physics, "gravity"); inline constexpr auto gravity = sub_tag(solver & physics, "gravity");
inline constexpr auto enthalpy = sub_tag(solver & physics, "enthalpy"); inline constexpr auto enthalpy = sub_tag(solver & physics, "enthalpy");
inline constexpr auto barotrope = sub_tag(physics, "barotrope"); inline constexpr auto barotrope = sub_tag(physics, "barotrope");
inline constexpr auto mass_continuity = inline constexpr auto mass_continuity = sub_tag(solver & physics, "mass_continuity");
sub_tag(solver & physics, "mass_continuity"); inline constexpr auto pressure_gradient = sub_tag(solver & physics, "pressure_gradient");
inline constexpr auto pressure_gradient = inline constexpr auto viscosity = sub_tag(solver & physics, "viscosity");
sub_tag(solver & physics, "pressure_gradient");
inline constexpr auto viscosity = sub_tag(solver & physics, "viscosity");
inline constexpr auto compactification = inline constexpr auto compactification = sub_tag(mesh & mapping, "compactification");
sub_tag(mesh & mapping, "compactification"); inline constexpr auto kelvin = sub_tag(compactification, "kelvin");
inline constexpr auto kelvin = sub_tag(compactification, "kelvin"); inline constexpr auto mapping_evaluator = mapping & make_tag("grid_function_evaluator");
inline constexpr auto mapping_evaluator = inline constexpr auto mapping_evaluator_unit = mapping_evaluator & unit;
mapping & make_tag("grid_function_evaluator");
inline constexpr auto mapping_evaluator_unit = mapping_evaluator & unit;
inline constexpr auto prepared = sub_tag(solver & physics, "prepared"); inline constexpr auto prepared = sub_tag(solver & physics, "prepared");
inline constexpr auto contexts = sub_tag(solver, "contexts"); inline constexpr auto contexts = sub_tag(solver, "contexts");
inline constexpr auto domain = sub_tag(mesh, "domain"); inline constexpr auto domain = sub_tag(mesh, "domain");
// Canonical gravity-suite tags. These intentionally compose leaf tags // Canonical gravity-suite tags. These intentionally compose leaf tags
// exactly once so Catch2 output remains useful and free of repeated // exactly once so Catch2 output remains useful and free of repeated
// [solver]/[physics] entries inherited from older composite tags. // [solver]/[physics] entries inherited from older composite tags.
inline constexpr auto gravity_unit = gravity & unit; inline constexpr auto gravity_unit = gravity & unit;
inline constexpr auto gravity_integration = gravity & integration; inline constexpr auto gravity_integration = gravity & integration;
inline constexpr auto gravity_operator = gravity & mfem_operators; inline constexpr auto gravity_operator = gravity & mfem_operators;
inline constexpr auto gravity_prepared = gravity & make_tag("prepared"); inline constexpr auto gravity_prepared = gravity & make_tag("prepared");
inline constexpr auto gravity_context = gravity & make_tag("context"); inline constexpr auto gravity_context = gravity & make_tag("context");
inline constexpr auto gravity_kernel = gravity & kernels; inline constexpr auto gravity_kernel = gravity & kernels;
inline constexpr auto gravity_accuracy = gravity & accuracy; inline constexpr auto gravity_accuracy = gravity & accuracy;
inline constexpr auto gravity_operator_unit = gravity_operator & unit; inline constexpr auto gravity_operator_unit = gravity_operator & unit;
inline constexpr auto gravity_operator_integration = inline constexpr auto gravity_operator_integration = gravity_operator & integration;
gravity_operator & integration; inline constexpr auto gravity_operator_convergence = gravity_operator & integration & make_tag("convergence");
inline constexpr auto gravity_operator_convergence = inline constexpr auto gravity_analytic = gravity & integration & make_tag("analytic_comparison");
gravity_operator & integration & make_tag("convergence"); inline constexpr auto gravity_consistency = gravity & integration & make_tag("self_consistency");
inline constexpr auto gravity_analytic = inline constexpr auto gravity_prepared_jacobian = gravity_prepared & integration & make_tag("jacobian");
gravity & integration & make_tag("analytic_comparison"); inline constexpr auto gravity_prepared_unit = gravity_prepared & unit;
inline constexpr auto gravity_consistency = inline constexpr auto gravity_prepared_jacobian_accuracy = gravity_prepared_jacobian & accuracy;
gravity & integration & make_tag("self_consistency"); inline constexpr auto gravity_kernel_accuracy = gravity_kernel & accuracy;
inline constexpr auto gravity_prepared_jacobian = inline constexpr auto gravity_kernel_integration = gravity_kernel & integration;
gravity_prepared & integration & make_tag("jacobian"); inline constexpr auto gravity_kernel_convergence = gravity_kernel & integration & make_tag("convergence");
inline constexpr auto gravity_prepared_unit = gravity_prepared & unit; inline constexpr auto gravity_analytic_accuracy = gravity_analytic & accuracy;
inline constexpr auto gravity_prepared_jacobian_accuracy = inline constexpr auto gravity_consistency_accuracy = gravity_consistency & accuracy;
gravity_prepared_jacobian & accuracy; inline constexpr auto gravity_integrator_unit = gravity & integrator & unit;
inline constexpr auto gravity_kernel_accuracy = gravity_kernel & accuracy;
inline constexpr auto gravity_kernel_integration = gravity_kernel & integration;
inline constexpr auto gravity_kernel_convergence =
gravity_kernel & integration & make_tag("convergence");
inline constexpr auto gravity_analytic_accuracy = gravity_analytic & accuracy;
inline constexpr auto gravity_consistency_accuracy =
gravity_consistency & accuracy;
inline constexpr auto gravity_integrator_unit = gravity & integrator & unit;
inline constexpr auto barotrope_prepared = inline constexpr auto barotrope_prepared = barotrope & solver & make_tag("prepared");
barotrope & solver & make_tag("prepared"); inline constexpr auto barotrope_eos_unit = barotrope & unit & make_tag("eos");
inline constexpr auto barotrope_eos_unit = barotrope & unit & make_tag("eos"); inline constexpr auto barotrope_eos_jacobian = barotrope_eos_unit & integration & make_tag("jacobian");
inline constexpr auto barotrope_eos_jacobian = inline constexpr auto polytropic_eos_characterization =
barotrope_eos_unit & integration & make_tag("jacobian"); barotrope & unit & make_tag("eos") & make_tag("characterization");
inline constexpr auto barotrope_pressure_quadrature = inline constexpr auto polytropic_eos_relation_contract =
barotrope & mesh & geometry & solver & make_tag("pressure") & barotrope & unit & make_tag("eos") & make_tag("relation_contract");
make_tag("pressure_gradient") & make_tag("quadrature"); inline constexpr auto polytropic_eos_compatibility = barotrope & unit & make_tag("eos") & make_tag("compatibility");
inline constexpr auto barotrope_pressure_quadrature_unit = inline constexpr auto equation_of_state = physics & make_tag("eos");
barotrope_pressure_quadrature & unit; inline constexpr auto equation_of_state_type_system = equation_of_state & unit & make_tag("type_system");
inline constexpr auto barotrope_pressure_quadrature_accuracy = inline constexpr auto equation_of_state_quantity_types = equation_of_state_type_system & make_tag("quantity_types");
barotrope_pressure_quadrature & accuracy; inline constexpr auto equation_of_state_relation_contract =
inline constexpr auto barotrope_prepared_jacobian = equation_of_state_type_system & make_tag("relation_contract");
barotrope_prepared & integration & make_tag("jacobian"); inline constexpr auto equation_of_state_runtime_view = equation_of_state & unit & make_tag("runtime_view");
inline constexpr auto barotrope_context = inline constexpr auto equation_of_state_runtime_contract =
barotrope & solver & make_tag("context"); equation_of_state_runtime_view & make_tag("relation_contract");
inline constexpr auto barotrope_context_integration = inline constexpr auto equation_of_state_runtime_compatibility =
barotrope_context & integration; equation_of_state_runtime_view & make_tag("compatibility");
inline constexpr auto barotrope_prepared_analytic = inline constexpr auto equation_of_state_consumer_contract =
barotrope_prepared & integration & make_tag("analytic_comparison"); equation_of_state & unit & make_tag("consumer_contract");
inline constexpr auto barotrope_prepared_jacobian_accuracy = inline constexpr auto barotropic_closure_equation_of_state_contract =
barotrope_prepared_jacobian & accuracy; equation_of_state_consumer_contract & make_tag("barotropic_closure");
inline constexpr auto barotrope_prepared_jacobian_geometry = inline constexpr auto pressure_force_equation_of_state_contract =
barotrope_prepared_jacobian & geometry; equation_of_state_consumer_contract & make_tag("pressure_force");
inline constexpr auto barotrope_prepared_jacobian_unit = inline constexpr auto structure_seed_equation_of_state_contract =
barotrope_prepared_jacobian & unit; equation_of_state_consumer_contract & make_tag("structure_seed");
inline constexpr auto stellar_model_type_contract = barotrope & model & unit & make_tag("type_contract");
inline constexpr auto stellar_model_runtime_view = barotrope & model & unit & make_tag("runtime_view");
inline constexpr auto surface_prescription_type_contract =
surface & physics & unit & make_tag("prescription") & make_tag("type_contract");
inline constexpr auto surface_constraint_compilation =
surface & physics & unit & make_tag("constraint_compilation");
inline constexpr auto surface_constraint_jacobian = surface_constraint_compilation & jacobian;
inline constexpr auto surface_constraint_lifetime = surface & model & unit & make_tag("constraint_lifetime");
inline constexpr auto surface_boundary_dof_topology =
surface & field_dof & integration & make_tag("boundary_topology");
inline constexpr auto surface_row_replacement =
surface & barotrope_prepared & integration & make_tag("row_replacement");
inline constexpr auto translational_centering = geometry & solver & make_tag("translational_centering");
inline constexpr auto translational_centering_topology =
translational_centering & field_dof & integration & make_tag("point_topology");
inline constexpr auto translational_centering_enforcement =
translational_centering & barotrope_prepared & integration & make_tag("row_replacement");
inline constexpr auto barotrope_pressure_quadrature = barotrope & mesh & geometry & solver & make_tag("pressure") &
make_tag("pressure_gradient") & make_tag("quadrature");
inline constexpr auto barotrope_pressure_quadrature_unit = barotrope_pressure_quadrature & unit;
inline constexpr auto barotrope_pressure_quadrature_accuracy = barotrope_pressure_quadrature & accuracy;
inline constexpr auto barotrope_prepared_jacobian = barotrope_prepared & integration & make_tag("jacobian");
inline constexpr auto barotrope_context = barotrope & solver & make_tag("context");
inline constexpr auto barotrope_context_integration = barotrope_context & integration;
inline constexpr auto barotrope_prepared_analytic =
barotrope_prepared & integration & make_tag("analytic_comparison");
inline constexpr auto barotrope_prepared_jacobian_accuracy = barotrope_prepared_jacobian & accuracy;
inline constexpr auto barotrope_prepared_jacobian_geometry = barotrope_prepared_jacobian & geometry;
inline constexpr auto barotrope_prepared_jacobian_unit = barotrope_prepared_jacobian & unit;
// Canonical hydrostatic-suite tags. The leaf tags are composed directly // Canonical hydrostatic-suite tags. The leaf tags are composed directly
// so inherited [physics]/[solver] tags appear only once. // so inherited [physics]/[solver] tags appear only once.
inline constexpr auto barotrope_hydrostatic = inline constexpr auto barotrope_hydrostatic = barotrope & solver & make_tag("hydro");
barotrope & solver & make_tag("hydro"); inline constexpr auto barotrope_hydrostatic_context = barotrope_hydrostatic & make_tag("context");
inline constexpr auto barotrope_hydrostatic_context = inline constexpr auto barotrope_hydrostatic_prepared = barotrope_hydrostatic & make_tag("prepared");
barotrope_hydrostatic & make_tag("context"); inline constexpr auto barotrope_hydrostatic_prepared_residual =
inline constexpr auto barotrope_hydrostatic_prepared = barotrope_hydrostatic_prepared & integration & make_tag("residual");
barotrope_hydrostatic & make_tag("prepared"); inline constexpr auto barotrope_hydrostatic_prepared_jacobian =
inline constexpr auto barotrope_hydrostatic_prepared_residual = barotrope_hydrostatic_prepared & integration & make_tag("jacobian");
barotrope_hydrostatic_prepared & integration & make_tag("residual"); inline constexpr auto barotrope_hydrostatic_prepared_analytic =
inline constexpr auto barotrope_hydrostatic_prepared_jacobian = barotrope_hydrostatic_prepared & integration & make_tag("analytic_comparison");
barotrope_hydrostatic_prepared & integration & make_tag("jacobian");
inline constexpr auto barotrope_hydrostatic_prepared_analytic =
barotrope_hydrostatic_prepared & integration &
make_tag("analytic_comparison");
inline constexpr auto barotrope_mass_normalization = inline constexpr auto barotrope_mass_normalization = barotrope & solver & make_tag("mass_normalization");
barotrope & solver & make_tag("mass_normalization"); inline constexpr auto barotrope_mass_normalization_context = barotrope_mass_normalization & make_tag("context");
inline constexpr auto barotrope_mass_normalization_context = inline constexpr auto barotrope_mass_normalization_prepared = barotrope_mass_normalization & make_tag("prepared");
barotrope_mass_normalization & make_tag("context"); inline constexpr auto barotrope_mass_normalization_jacobian =
inline constexpr auto barotrope_mass_normalization_prepared = barotrope_mass_normalization_prepared & integration & make_tag("jacobian");
barotrope_mass_normalization & make_tag("prepared"); inline constexpr auto barotrope_mass_normalization_analytic =
inline constexpr auto barotrope_mass_normalization_jacobian = barotrope_mass_normalization_prepared & integration & make_tag("analytic_comparison");
barotrope_mass_normalization_prepared & integration & make_tag("jacobian");
inline constexpr auto barotrope_mass_normalization_analytic =
barotrope_mass_normalization_prepared & integration &
make_tag("analytic_comparison");
inline constexpr auto rotation_prepared = centrifugal & make_tag("prepared"); inline constexpr auto rotation_prepared = centrifugal & make_tag("prepared");
inline constexpr auto rotation_context = centrifugal & make_tag("context"); inline constexpr auto rotation_context = centrifugal & make_tag("context");
inline constexpr auto rotation_analytic = inline constexpr auto rotation_analytic = centrifugal & integration & make_tag("analytic_comparison");
centrifugal & integration & make_tag("analytic_comparison"); inline constexpr auto rotation_context_unit = rotation_context & unit;
inline constexpr auto rotation_context_unit = rotation_context & unit; inline constexpr auto rotation_prepared_unit = rotation_prepared & unit;
inline constexpr auto rotation_prepared_unit = rotation_prepared & unit; inline constexpr auto rotation_prepared_jacobian = rotation_prepared & integration & make_tag("jacobian");
inline constexpr auto rotation_prepared_jacobian = inline constexpr auto rotation_prepared_jacobian_accuracy = rotation_prepared_jacobian & accuracy;
rotation_prepared & integration & make_tag("jacobian"); inline constexpr auto rotation_kernel_accuracy = centrifugal & kernels & accuracy;
inline constexpr auto rotation_prepared_jacobian_accuracy = inline constexpr auto rotation_integrator_unit = centrifugal & integrator & unit;
rotation_prepared_jacobian & accuracy; inline constexpr auto rotation_integrator_integration = centrifugal & integrator & integration;
inline constexpr auto rotation_kernel_accuracy = inline constexpr auto rotation_integrator_convergence =
centrifugal & kernels & accuracy; rotation_integrator_integration & convergence & h_refinement;
inline constexpr auto rotation_integrator_unit = centrifugal & integrator & unit; inline constexpr auto rotation_analytic_unit = rotation_analytic & unit;
inline constexpr auto rotation_integrator_integration = inline constexpr auto rotation_analytic_accuracy = rotation_analytic & accuracy;
centrifugal & integrator & integration; inline constexpr auto rotation_analytic_accuracy_geometry = rotation_analytic_accuracy & geometry;
inline constexpr auto rotation_integrator_convergence =
rotation_integrator_integration & convergence & h_refinement;
inline constexpr auto rotation_analytic_unit = rotation_analytic & unit;
inline constexpr auto rotation_analytic_accuracy = rotation_analytic & accuracy;
inline constexpr auto rotation_analytic_accuracy_geometry =
rotation_analytic_accuracy & geometry;
} // namespace tags } // namespace tags

View File

@@ -3,6 +3,7 @@
#include <catch2/catch_test_case_info.hpp> #include <catch2/catch_test_case_info.hpp>
#include <catch2/reporters/catch_reporter_registrars.hpp> #include <catch2/reporters/catch_reporter_registrars.hpp>
#include <catch2/reporters/catch_reporter_streaming_base.hpp> #include <catch2/reporters/catch_reporter_streaming_base.hpp>
#include <chrono>
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
#include <iostream> #include <iostream>
@@ -223,6 +224,7 @@ class CheckReporter : public Catch::StreamingReporterBase {
bool passed; bool passed;
std::size_t assertionsPassed; std::size_t assertionsPassed;
std::size_t assertionsFailed; std::size_t assertionsFailed;
double durationSeconds;
std::vector<std::string> failureMessages; std::vector<std::string> failureMessages;
std::vector<std::string> infoMessages; std::vector<std::string> infoMessages;
}; };
@@ -231,6 +233,7 @@ class CheckReporter : public Catch::StreamingReporterBase {
std::vector<std::string> m_currentInfos; std::vector<std::string> m_currentInfos;
std::unordered_set<unsigned int> m_currentInfoSequences; std::unordered_set<unsigned int> m_currentInfoSequences;
std::vector<TestCaseData> m_testRunData; std::vector<TestCaseData> m_testRunData;
std::chrono::time_point<std::chrono::steady_clock> m_testStartTime;
void captureInfoMessages(Catch::AssertionStats const &assertionStats) { void captureInfoMessages(Catch::AssertionStats const &assertionStats) {
for (auto const &message : assertionStats.infoMessages) { for (auto const &message : assertionStats.infoMessages) {
@@ -253,9 +256,8 @@ public:
} }
static std::string getDescription() { static std::string getDescription() {
return "Console reporter with wrapping, tags, and collapsible HTML " return "Console reporter with wrapping, tags, live test progress, and collapsible HTML "
"export " "export with ANSI color rendering.";
"with ANSI color rendering.";
} }
void testRunStarting(Catch::TestRunInfo const &_testRunInfo) override { void testRunStarting(Catch::TestRunInfo const &_testRunInfo) override {
@@ -263,8 +265,20 @@ public:
std::cout << '\n'; std::cout << '\n';
std::cout << std::left << std::setw(85) << "Test Case Name" std::cout << std::left << std::setw(85) << "Test Case Name"
<< "Status " << std::right << std::setw(8) << "Passed" << std::setw(8) << "Failed" << '\n'; << "Status " << std::right << std::setw(8) << "Passed" << std::setw(8) << "Failed" << std::setw(12)
std::cout << std::string(121, '-') << '\n'; << "Time (s)" << '\n';
std::cout << std::string(133, '-') << '\n';
}
void testCaseStarting(Catch::TestCaseInfo const &testInfo) override {
StreamingReporterBase::testCaseStarting(testInfo);
m_testStartTime = std::chrono::steady_clock::now();
std::string name = testInfo.name;
auto wrappedName = wrapText(name, 83);
// Print progress line, \r to overwrite later, \033[K to clear till end of line
std::cout << "\r\033[K" << std::left << std::setw(85) << (wrappedName[0] + " ...") << std::flush;
} }
void assertionEnded(Catch::AssertionStats const &assertionStats) override { void assertionEnded(Catch::AssertionStats const &assertionStats) override {
@@ -300,14 +314,20 @@ public:
void testCaseEnded(Catch::TestCaseStats const &stats) override { void testCaseEnded(Catch::TestCaseStats const &stats) override {
StreamingReporterBase::testCaseEnded(stats); StreamingReporterBase::testCaseEnded(stats);
bool passed = stats.totals.assertions.allPassed(); auto endTime = std::chrono::steady_clock::now();
std::string mark = passed ? "\033[32m✓\033[0m" : "\033[31m✗\033[0m"; std::chrono::duration<double> elapsed = endTime - m_testStartTime;
double duration_s = elapsed.count();
std::string name = stats.testInfo->name; bool passed = stats.totals.assertions.allPassed();
auto wrappedName = wrapText(name, 83); std::string mark = passed ? "\033[32m✓\033[0m" : "\033[31m✗\033[0m";
std::cout << std::left << std::setw(85) << wrappedName[0] << mark << " " << std::right << std::setw(8) std::string name = stats.testInfo->name;
<< stats.totals.assertions.passed << std::setw(8) << stats.totals.assertions.failed << '\n'; auto wrappedName = wrapText(name, 83);
// Overwrite the loading line with the actual result
std::cout << "\r\033[K" << std::left << std::setw(85) << wrappedName[0] << mark << " " << std::right
<< std::setw(8) << stats.totals.assertions.passed << std::setw(8) << stats.totals.assertions.failed
<< std::setw(11) << std::fixed << std::setprecision(3) << duration_s << "s\n";
for (size_t i = 1; i < wrappedName.size(); ++i) { for (size_t i = 1; i < wrappedName.size(); ++i) {
std::cout << " \033[90m↳ \033[0m" // Dim indent arrow std::cout << " \033[90m↳ \033[0m" // Dim indent arrow
@@ -327,12 +347,12 @@ public:
for (auto const &failure : m_currentFailures) { for (auto const &failure : m_currentFailures) {
std::cout << failure << '\n'; std::cout << failure << '\n';
} }
std::cout << std::string(121, '-') << '\n'; std::cout << std::string(133, '-') << '\n';
} }
m_testRunData.push_back( m_testRunData.push_back(
{name, tagsStr, passed, stats.totals.assertions.passed, stats.totals.assertions.failed, m_currentFailures, {name, tagsStr, passed, stats.totals.assertions.passed, stats.totals.assertions.failed, duration_s,
m_currentInfos} m_currentFailures, m_currentInfos}
); );
m_currentFailures.clear(); m_currentFailures.clear();
@@ -343,7 +363,7 @@ public:
void testRunEnded(Catch::TestRunStats const &_testRunStats) override { void testRunEnded(Catch::TestRunStats const &_testRunStats) override {
StreamingReporterBase::testRunEnded(_testRunStats); StreamingReporterBase::testRunEnded(_testRunStats);
std::cout << std::string(121, '=') << '\n'; std::cout << std::string(133, '=') << '\n';
auto const &tc = _testRunStats.totals.testCases; auto const &tc = _testRunStats.totals.testCases;
auto const &as = _testRunStats.totals.assertions; auto const &as = _testRunStats.totals.assertions;
@@ -444,7 +464,9 @@ private:
html << " </div>\n"; html << " </div>\n";
html << " <div class='stats'>\n"; html << " <div class='stats'>\n";
html << " <span class='text-green'>&#10003; " << test.assertionsPassed << "</span> | "; html << " <span class='text-green'>&#10003; " << test.assertionsPassed << "</span> | ";
html << " <span class='text-red'>&#10007; " << test.assertionsFailed << "</span>\n"; html << " <span class='text-red'>&#10007; " << test.assertionsFailed << "</span> | ";
html << " <span style='color: #34495e;'>&#8987; " << std::fixed << std::setprecision(3)
<< test.durationSeconds << "s</span>\n";
html << " </div>\n"; html << " </div>\n";
html << " </div>\n"; html << " </div>\n";
@@ -589,4 +611,4 @@ int main(
test_utils::set_args(std::move(test_args)); test_utils::set_args(std::move(test_args));
return session.run(); return session.run();
} }

File diff suppressed because it is too large Load Diff