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)
project(MeanField CXX)
project(MeanField C CXX)
set(CMAKE_CXX_STANDARD 23)
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/prepared_rotation_displacement_force.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/models/structure/structure_base.cppm
libmeanfield/interface/models/structure/polytropic.cppm
libmeanfield/interface/models/structure_profile.cppm
libmeanfield/interface/surface/surface_base.cppm
libmeanfield/interface/surface/isobaric.cppm
libmeanfield/interface/surface/constant.cppm
libmeanfield/interface/surface/dependencies.cppm
libmeanfield/interface/surface/compiled.cppm
libmeanfield/interface/surface/compiler.cppm
libmeanfield/interface/models/stellar_model.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
)
@@ -194,6 +203,12 @@ add_executable(tests
tests/operators/contexts/gravity_field_context.cpp
tests/physics/gravity_monopole_accuracy.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/prepared_barotropic_closure.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_affine_deformation.cpp
tests/operators/prepared_displacement_operator.cpp
tests/surface/isobaric.cpp
tests/models/stellar_model.cpp
tests/operators/prepared_mass_normalization.cpp
tests/operators/prepared_stellar_equilibrium.cpp
@@ -233,6 +247,7 @@ target_sources(experiment_mod
PUBLIC
FILE_SET CXX_MODULES FILES
experiments/experiment_results.cppm
experiments/stellar_null_space.cppm
)
target_link_libraries(experiment_mod
PUBLIC
@@ -247,6 +262,21 @@ add_executable(experiments
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 (Catch)
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]"
```
## 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
configuration registration as the existing Catch2 test executable. Add
`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) {
case mean_field::utils::DOMAINS::CORE:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Core, DomainSchema>(mesh);
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Core, DomainSchema>(
mesh
);
case mean_field::utils::DOMAINS::ENVELOPE:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Envelope, DomainSchema>(mesh);
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Envelope, DomainSchema>(
mesh
);
case mean_field::utils::DOMAINS::ALL:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::All, DomainSchema>(mesh);
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::All, DomainSchema>(mesh);
case mean_field::utils::DOMAINS::STELLAR:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Stellar, DomainSchema>(mesh);
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Stellar, DomainSchema>(
mesh
);
case mean_field::utils::DOMAINS::VACUUM:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Vacuum, DomainSchema>(mesh);
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Vacuum, DomainSchema>(
mesh
);
}
MFEM_ABORT("Unsupported integration domain.");
}
@@ -69,8 +72,7 @@ namespace mean_field::analysis {
if (fem.has_mapping() && coord_space == mapping::COORDINATE_SPACE::PHYSICAL) {
mapping::MappedScalarCoefficient mapped_gf_c(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate, gf_c
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, gf_c
);
// ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM
@@ -107,16 +109,14 @@ namespace mean_field::analysis {
) {
const int dim = fem.mesh->Dimension();
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
mfem::Vector local_com(dim);
local_com = 0.0;
double local_mass = 0.0;
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(
fem.mesh->GetAttribute(i)))
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(fem.mesh->GetAttribute(i)))
continue;
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir = get_density_rule<field::Density::Form::CenterOfMass>(
@@ -129,8 +129,7 @@ namespace mean_field::analysis {
mapping::VolumeMappingContext mapping_context;
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) ==
mapping::MappingStatus::valid,
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping::MappingStatus::valid,
"Center-of-mass integration encountered an invalid mapping."
);
const double weight = mapping_context.quadrature.weight;
@@ -183,8 +182,7 @@ namespace mean_field::analysis {
std::unique_ptr<mfem::Coefficient> s2_coeff;
if (fem.has_mapping()) {
s2_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate, s2_func
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, s2_func
);
} else {
s2_coeff = std::make_unique<mfem::FunctionCoefficient>(s2_func);
@@ -204,8 +202,7 @@ namespace mean_field::analysis {
double local_I = 0.0;
if (fem.has_mapping()) {
mapping::MappedScalarCoefficient mapped_integrand(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate, I_integrand
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, I_integrand
);
auto *integrator = new mfem::DomainLFIntegrator(mapped_integrand);
integrator->SetIntRule(&integration_rule);
@@ -239,14 +236,12 @@ namespace mean_field::analysis {
double local_volume = 0.0;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
for (int e = 0; e < mesh.GetNE(); ++e) {
const int attr = mesh.GetAttribute(e);
const bool selected =
domain == utils::DOMAINS::ALL ||
const bool selected = domain == utils::DOMAINS::ALL ||
(domain == utils::DOMAINS::STELLAR &&
DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(attr)) ||
(domain == utils::DOMAINS::VACUUM &&
@@ -266,8 +261,7 @@ namespace mean_field::analysis {
if (physical) {
mapping::VolumeMappingContext context;
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*T, ip, context) ==
mapping::MappingStatus::valid,
mapping_evaluator.EvaluateVolume(*T, ip, context) == mapping::MappingStatus::valid,
"Mesh-volume integration encountered an invalid mapping."
);
dV = context.quadrature.weight;

View File

@@ -21,8 +21,11 @@ import :utils.misc;
import :utils.user;
namespace mean_field::fem {
FEM setup_fem(const std::string &filename, const utils::Args &args,
const int extraRefine) {
FEM setup_fem(
const std::string &filename,
const utils::Args &args,
const int extraRefine
) {
FEM fem;
using GravityPotential = field::Gravity::Potential;
@@ -45,11 +48,9 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
int mpiSize = 1;
MPI_Comm_size(MPI_COMM_WORLD, &mpiSize);
const std::unique_ptr<int[]> meshPartitioning(
fem.smesh.mesh->GeneratePartitioning(mpiSize, 1));
const std::unique_ptr<int[]> meshPartitioning(fem.smesh.mesh->GeneratePartitioning(mpiSize, 1));
fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh,
meshPartitioning.get(), 1);
fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh, meshPartitioning.get(), 1);
fem.mesh->EnsureNodes();
@@ -69,22 +70,22 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
throw std::runtime_error("Values for exterior coordinate not set.");
}
const mfem::FiniteElementSpace &serialCoordinateSpace =
*fem.smesh.exterior_coordinate->space;
const mfem::FiniteElementSpace &serialCoordinateSpace = *fem.smesh.exterior_coordinate->space;
const mfem::GridFunction &serialCoordinate =
*fem.smesh.exterior_coordinate->values;
const mfem::GridFunction &serialCoordinate = *fem.smesh.exterior_coordinate->values;
if (serialCoordinate.FESpace() != &serialCoordinateSpace) {
throw std::runtime_error(
"Exterior coordinate values are not associated with the "
"supplied finite-element space.");
"supplied finite-element space."
);
}
if (serialCoordinateSpace.GetMesh() != fem.smesh.mesh.get()) {
throw std::runtime_error(
"Exterior coordinate space is not associated with the "
"loaded STROID mesh.");
"loaded STROID mesh."
);
}
if (serialCoordinateSpace.GetVDim() != 1) {
@@ -94,30 +95,29 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
if (serialCoordinate.Size() != serialCoordinateSpace.GetVSize()) {
throw std::runtime_error(
"Exterior coordinate value count does not match its "
"finite-element space.");
"finite-element space."
);
}
const int compactificationOrder = serialCoordinateSpace.GetMaxElementOrder();
const int dimension = fem.mesh->Dimension();
fem.compactificationFec =
std::make_unique<mfem::H1_FECollection>(compactificationOrder, dimension);
fem.compactificationFec = std::make_unique<mfem::H1_FECollection>(compactificationOrder, dimension);
fem.compactificationFes = std::make_unique<mfem::ParFiniteElementSpace>(
fem.mesh.get(), fem.compactificationFec.get());
fem.compactificationFes =
std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.compactificationFec.get());
mfem::ParGridFunction distributedCoordinate(fem.mesh.get(), &serialCoordinate,
meshPartitioning.get());
mfem::ParGridFunction distributedCoordinate(fem.mesh.get(), &serialCoordinate, meshPartitioning.get());
if (distributedCoordinate.Size() != fem.compactificationFes->GetVSize()) {
throw std::runtime_error(
"Distributed exterior coordinate does not match the "
"constructed parallel finite-element space.");
"constructed parallel finite-element space."
);
}
fem.compactificationCoordinate =
std::make_unique<mfem::ParGridFunction>(fem.compactificationFes.get());
fem.compactificationCoordinate = std::make_unique<mfem::ParGridFunction>(fem.compactificationFes.get());
*fem.compactificationCoordinate = distributedCoordinate;
@@ -129,8 +129,7 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
const double value = (*fem.compactificationCoordinate)(index);
if (!std::isfinite(value)) {
throw std::runtime_error(
"Exterior coordinate contains a non-finite value.");
throw std::runtime_error("Exterior coordinate contains a non-finite value.");
}
localMinimum = std::min(localMinimum, value);
@@ -141,18 +140,17 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
double globalMinimum = 0.0;
double globalMaximum = 0.0;
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN,
MPI_COMM_WORLD);
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD);
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX,
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].");
if (globalMinimum < -coordinateTolerance || globalMaximum > 1.0 + coordinateTolerance) {
throw std::runtime_error(
"Exterior coordinate lies outside the expected "
"interval [0, 1]."
);
}
// =====================================================================
@@ -165,8 +163,7 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
fem.gravityPotentialFec = GravityField::make_fec<GravityPotential>(dimension);
fem.gravityPotentialFes = GravityField::make_fespace<GravityPotential>(
*fem.mesh, *fem.gravityPotentialFec);
fem.gravityPotentialFes = GravityField::make_fespace<GravityPotential>(*fem.mesh, *fem.gravityPotentialFec);
// ---------------------------------------------------------------------
// Gravity flux: H(div)/RT. Basis choices are encoded by field.mfem.
@@ -174,21 +171,17 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
fem.gravityFluxFec = GravityField::make_fec<GravityFlux>(dimension);
fem.gravityFluxFes =
GravityField::make_fespace<GravityFlux>(*fem.mesh, *fem.gravityFluxFec);
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.displacementFec = DisplacementField::make_fec<DisplacementVector>(dimension);
fem.displacementFes = DisplacementField::make_fespace<DisplacementVector>(
*fem.mesh, *fem.displacementFec);
fem.displacementFes = DisplacementField::make_fespace<DisplacementVector>(*fem.mesh, *fem.displacementFec);
fem.displacement =
std::make_unique<mfem::ParGridFunction>(fem.displacementFes.get());
fem.displacement = std::make_unique<mfem::ParGridFunction>(fem.displacementFes.get());
*fem.displacement = 0.0;
@@ -198,8 +191,7 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
fem.densityFec = DensityField::make_fec<DensityScalar>(dimension);
fem.densityFes =
DensityField::make_fespace<DensityScalar>(*fem.mesh, *fem.densityFec);
fem.densityFes = DensityField::make_fespace<DensityScalar>(*fem.mesh, *fem.densityFec);
// ---------------------------------------------------------------------
// Specific enthalpy: scalar continuous H1
@@ -207,8 +199,7 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
fem.enthalpyFec = EnthalpyField::make_fec<EnthalpyScalar>(dimension);
fem.enthalpyFes =
EnthalpyField::make_fespace<EnthalpyScalar>(*fem.mesh, *fem.enthalpyFec);
fem.enthalpyFes = EnthalpyField::make_fespace<EnthalpyScalar>(*fem.mesh, *fem.enthalpyFec);
// =====================================================================
// Section 4: Multipole data
@@ -233,12 +224,9 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
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.inf_bounds[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
fem.boundaryContext
.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) -
1] = 1;
fem.boundaryContext.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1;
// =====================================================================
// Section 7: Quadrature policy
@@ -246,34 +234,30 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
const quadrature::QuadratureOptions &quadratureOptions = args.quadrature;
if (quadratureOptions.validation.reject_negative_boosts &&
quadratureOptions.global_boost < 0) {
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);
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.");
throw std::invalid_argument("Fallback quadrature order cannot be negative.");
}
quadratureRuleSet.fallback.fixed_order =
quadratureOptions.fallback_fixed_order;
quadratureRuleSet.fallback.fixed_order = quadratureOptions.fallback_fixed_order;
}
auto apply_quadrature_options = [&quadratureOptions](
quadrature::RuleControl &ruleControl,
const quadrature::QuadratureTermOptions
&termOptions) {
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) {
if (quadratureOptions.validation.reject_negative_boosts && termOptions.additional_boost < 0) {
throw std::invalid_argument("Term quadrature boost cannot be negative.");
}
@@ -284,97 +268,74 @@ FEM setup_fem(const std::string &filename, const utils::Args &args,
}
};
apply_quadrature_options(quadratureRuleSet.gravity_hdiv_mass,
quadratureOptions.gravity_hdiv_mass);
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_divergence, quadratureOptions.gravity_divergence);
apply_quadrature_options(quadratureRuleSet.gravity_source,
quadratureOptions.gravity_source);
apply_quadrature_options(quadratureRuleSet.gravity_source, quadratureOptions.gravity_source);
apply_quadrature_options(quadratureRuleSet.gravity_force,
quadratureOptions.gravity_force);
apply_quadrature_options(quadratureRuleSet.gravity_force, quadratureOptions.gravity_force);
apply_quadrature_options(quadratureRuleSet.gravity_boundary,
quadratureOptions.gravity_boundary);
apply_quadrature_options(quadratureRuleSet.gravity_boundary, quadratureOptions.gravity_boundary);
apply_quadrature_options(quadratureRuleSet.centrifugal,
quadratureOptions.centrifugal);
apply_quadrature_options(quadratureRuleSet.centrifugal, quadratureOptions.centrifugal);
apply_quadrature_options(quadratureRuleSet.density_projection,
quadratureOptions.density_projection);
apply_quadrature_options(quadratureRuleSet.density_projection, quadratureOptions.density_projection);
apply_quadrature_options(quadratureRuleSet.eos_closure,
quadratureOptions.eos_closure);
apply_quadrature_options(quadratureRuleSet.eos_closure, quadratureOptions.eos_closure);
apply_quadrature_options(quadratureRuleSet.hydrostatic_equilibrium,
quadratureOptions.hydrostatic_equilibrium);
apply_quadrature_options(quadratureRuleSet.hydrostatic_equilibrium, quadratureOptions.hydrostatic_equilibrium);
apply_quadrature_options(quadratureRuleSet.isobaric_surface,
quadratureOptions.isobaric_surface);
apply_quadrature_options(quadratureRuleSet.isobaric_surface, quadratureOptions.isobaric_surface);
apply_quadrature_options(quadratureRuleSet.mesh_extension,
quadratureOptions.mesh_extension);
apply_quadrature_options(quadratureRuleSet.mesh_extension, quadratureOptions.mesh_extension);
apply_quadrature_options(quadratureRuleSet.mass_conservation,
quadratureOptions.mass_conservation);
apply_quadrature_options(quadratureRuleSet.mass_conservation, quadratureOptions.mass_conservation);
apply_quadrature_options(quadratureRuleSet.mass_normalization,
quadratureOptions.mass_normalization);
apply_quadrature_options(quadratureRuleSet.mass_normalization, quadratureOptions.mass_normalization);
apply_quadrature_options(quadratureRuleSet.center_of_mass,
quadratureOptions.center_of_mass);
apply_quadrature_options(quadratureRuleSet.center_of_mass, quadratureOptions.center_of_mass);
apply_quadrature_options(quadratureRuleSet.quadrupole,
quadratureOptions.quadrupole);
apply_quadrature_options(quadratureRuleSet.quadrupole, quadratureOptions.quadrupole);
apply_quadrature_options(quadratureRuleSet.gravitational_energy,
quadratureOptions.gravitational_energy);
apply_quadrature_options(quadratureRuleSet.gravitational_energy, quadratureOptions.gravitational_energy);
apply_quadrature_options(quadratureRuleSet.pressure_integral,
quadratureOptions.pressure_integral);
apply_quadrature_options(quadratureRuleSet.pressure_integral, quadratureOptions.pressure_integral);
apply_quadrature_options(quadratureRuleSet.pressure_force,
quadratureOptions.pressure_force);
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.error_norm, quadratureOptions.error_norm);
apply_quadrature_options(quadratureRuleSet.roles.discretization,
quadratureOptions.roles.discretization);
apply_quadrature_options(quadratureRuleSet.roles.discretization, quadratureOptions.roles.discretization);
apply_quadrature_options(quadratureRuleSet.roles.preconditioner,
quadratureOptions.roles.preconditioner);
apply_quadrature_options(quadratureRuleSet.roles.preconditioner, quadratureOptions.roles.preconditioner);
apply_quadrature_options(quadratureRuleSet.roles.diagnostic,
quadratureOptions.roles.diagnostic);
apply_quadrature_options(quadratureRuleSet.roles.diagnostic, quadratureOptions.roles.diagnostic);
apply_quadrature_options(quadratureRuleSet.roles.projection,
quadratureOptions.roles.projection);
apply_quadrature_options(quadratureRuleSet.roles.projection, quadratureOptions.roles.projection);
fem.quadratureFactory = std::make_unique<quadrature::RuleFactory>(
quadrature::Policy(std::move(quadratureRuleSet)));
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);
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.");
"production domain schema."
);
fem.domainMapperStateless = std::make_unique<mapping::DomainMapper>(
args.domain_mapper_options, std::move(exteriorDomain));
fem.domainMapperStateless =
std::make_unique<mapping::DomainMapper>(args.domain_mapper_options, std::move(exteriorDomain));
return fem;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
)
: m_mapping(mapper, displacement, compactification_coordinate) { };
: m_mapping(
mapper,
displacement,
compactification_coordinate
) { };
void ContinuityVolumeIntegrator::AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
@@ -174,7 +178,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
)
: m_mapping(mapper, displacement, compactification_coordinate) {
: m_mapping(
mapper,
displacement,
compactification_coordinate
) {
}
void ContinuityFaceIntegrator::AssembleFaceVector(

View File

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

View File

@@ -15,7 +15,11 @@ namespace mean_field::mapping {
Coefficient &coeff,
const COORDINATE_SPACE coord_space
)
: m_mapping(mapper, displacement, compactification_coordinate),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_coeff(coeff),
m_coord_space(coord_space) { };
@@ -63,7 +67,11 @@ namespace mean_field::mapping {
const int dim
)
: MatrixCoefficient(dim),
m_mapping(mapper, displacement, compactification_coordinate),
m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_scalar(&sigma),
m_tensor(nullptr) { };
@@ -74,7 +82,11 @@ namespace mean_field::mapping {
MatrixCoefficient &sigma
)
: MatrixCoefficient(sigma.GetHeight()),
m_mapping(mapper, displacement, compactification_coordinate),
m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_scalar(nullptr),
m_tensor(&sigma) { };
@@ -120,7 +132,11 @@ namespace mean_field::mapping {
VectorCoefficient &coeff
)
: VectorCoefficient(coeff.GetVDim()),
m_mapping(mapper, displacement, compactification_coordinate),
m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_coeff(coeff) { };
void MappedVectorCoefficient::Eval(
@@ -157,7 +173,11 @@ namespace mean_field::mapping {
Func f // std::function<double(const mfem::Vector&)>
)
: m_f(std::move(f)),
m_mapping(mapper, displacement, compactification_coordinate) { };
m_mapping(
mapper,
displacement,
compactification_coordinate
) { };
double PhysicalPositionFunctionCoefficient::Eval(
mfem::ElementTransformation &T,
@@ -179,7 +199,11 @@ namespace mean_field::mapping {
const int dim
)
: MatrixCoefficient(dim),
m_mapping(mapper, displacement, compactification_coordinate) {
m_mapping(
mapper,
displacement,
compactification_coordinate
) {
}
void MappedHDivMassCoefficient::Eval(

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ namespace mean_field::models::structure {
validate();
}
const eos::EquationOfState &PolytropicStructure::equationOfState() const noexcept {
const eos::Polytrope &PolytropicStructure::equationOfState() const noexcept {
return m_equationOfState;
}
@@ -29,7 +29,9 @@ namespace mean_field::models::structure {
const double polytropicIndex = m_equationOfState.polytropic_index();
const std::vector<LaneEmdenPoint> laneEmdenSolution = solveLaneEmden(polytropicIndex);
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 =
centralEnthalpy / (4.0 * std::numbers::pi_v<double> * mean_field::utils::G * request.centralDensity);
@@ -65,7 +67,8 @@ namespace mean_field::models::structure {
seed.radius(sampleIndex) = radialScale * dimensionlessRadius;
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;

View File

@@ -80,13 +80,19 @@ namespace mean_field::operators::context::hydrostatic {
: m_f(f),
m_domainMapper(domainMapper),
m_enthalpyMap(
field::make_field_dof_map<field::Enthalpy, DomainSchema>(*f.enthalpyFes)
field::make_field_dof_map<
field::Enthalpy,
DomainSchema>(*f.enthalpyFes)
),
m_gravityPotentialMap(
field::make_field_dof_map<field::Gravity, DomainSchema>(*f.gravityPotentialFes)
field::make_field_dof_map<
field::Gravity,
DomainSchema>(*f.gravityPotentialFes)
),
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.");

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,8 @@ import :field.registry;
import :utils.domain;
namespace {
namespace eos = mean_field::eos;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using ClosureDomain = mean_field::field::FieldDomainT<mean_field::field::Density>;
@@ -336,11 +338,21 @@ namespace {
if (closureAction == ClosureAction::residual) {
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 {
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;
}
}
@@ -632,7 +644,10 @@ namespace mean_field::operators::kernels {
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;

View File

@@ -14,27 +14,24 @@ namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<
mean_field::utils::domain::Vacuum>(attribute);
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
enum class GravityDisplacementForceAction {
residual,
density,
gravityGradient,
displacement,
complete
};
enum class GravityDisplacementForceAction { residual, density, gravityGradient, displacement, complete };
void true_to_local(const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector, mfem::Vector &localVector) {
MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"The gravity-displacement-force true vector has the wrong size.");
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"The gravity-displacement-force true vector has the wrong size."
);
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
@@ -43,17 +40,20 @@ void true_to_local(const mfem::ParFiniteElementSpace &finiteElementSpace,
}
}
void local_to_true(const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector, mfem::Vector &trueVector) {
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
MFEM_VERIFY(
localVector.Size() == finiteElementSpace.GetVSize(),
"The gravity-displacement-force local vector has the wrong size.");
"The gravity-displacement-force local vector has the wrong size."
);
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
@@ -62,10 +62,13 @@ void local_to_true(const mfem::ParFiniteElementSpace &finiteElementSpace,
}
}
[[nodiscard]] int vector_dof_index(const mfem::Ordering::Type ordering,
const int scalarDof, const int component,
[[nodiscard]] int vector_dof_index(
const mfem::Ordering::Type ordering,
const int scalarDof,
const int component,
const int scalarDofCount,
const int dimension) {
const int dimension
) {
if (ordering == mfem::Ordering::byNODES) {
return scalarDof + component * scalarDofCount;
}
@@ -74,53 +77,61 @@ void local_to_true(const mfem::ParFiniteElementSpace &finiteElementSpace,
return scalarDof * dimension + component;
}
MFEM_ABORT("The gravity-displacement-force test space uses an unsupported "
"ordering.");
MFEM_ABORT(
"The gravity-displacement-force test space uses an unsupported "
"ordering."
);
return -1;
}
[[nodiscard]] const mfem::IntegrationRule &
get_gravity_force_rule(const mean_field::fem::FEM &f,
[[nodiscard]] const mfem::IntegrationRule &get_gravity_force_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &densityElement,
const mfem::FiniteElement &gravityGradientElement,
const mfem::FiniteElement &displacementElement,
const mfem::ElementTransformation &transformation) {
using DisplacementField =
mean_field::field::Field<mean_field::field::Displacement>;
const mfem::ElementTransformation &transformation
) {
using DisplacementField = mean_field::field::Field<mean_field::field::Displacement>;
MFEM_VERIFY(densityElement.GetOrder() ==
mean_field::field::Density::Scalar::familyOrder,
MFEM_VERIFY(
densityElement.GetOrder() == mean_field::field::Density::Scalar::familyOrder,
"The gravity-displacement-force density element does not match "
"the registered density field.");
"the registered density field."
);
MFEM_VERIFY(gravityGradientElement.GetOrder() ==
mean_field::field::Gravity::Flux::familyOrder + 1,
MFEM_VERIFY(
gravityGradientElement.GetOrder() == mean_field::field::Gravity::Flux::familyOrder + 1,
"The gravity-displacement-force RT element does not match the "
"registered gravity-gradient field.");
"registered gravity-gradient field."
);
MFEM_VERIFY(displacementElement.GetOrder() ==
mean_field::field::Displacement::Vector::familyOrder,
MFEM_VERIFY(
displacementElement.GetOrder() == mean_field::field::Displacement::Vector::familyOrder,
"The gravity-displacement-force test element does not match the "
"registered displacement field.");
"registered displacement field."
);
const mean_field::quadrature::Query query = DisplacementField::make_query<
mean_field::field::Displacement::Form::GravityForce>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
const mean_field::quadrature::Query query =
DisplacementField::make_query<mean_field::field::Displacement::Form::GravityForce>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
const mean_field::quadrature::MfemRule rule =
f.quadratureFactory->get(query, transformation.GetGeometryType());
const mean_field::quadrature::MfemRule rule = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(rule.integration_rule != nullptr,
"The quadrature policy did not return a gravity-displacement-"
"force integration rule.");
MFEM_VERIFY(
rule.integration_rule != nullptr, "The quadrature policy did not return a gravity-displacement-"
"force integration rule."
);
return *rule.integration_rule;
}
void validate_finite_vector(const mfem::Vector &vector, const char *message) {
void validate_finite_vector(
const mfem::Vector &vector,
const char *message
) {
for (int index = 0; index < vector.Size(); ++index) {
MFEM_VERIFY(std::isfinite(vector(index)), message);
}
@@ -129,60 +140,75 @@ void validate_finite_vector(const mfem::Vector &vector, const char *message) {
void validate_common_inputs(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domainMapper,
const mfem::Vector &displacementTrue) {
MFEM_VERIFY(f.mesh != nullptr,
"The gravity-displacement-force kernel requires a mesh.");
const mfem::Vector &displacementTrue
) {
MFEM_VERIFY(f.mesh != nullptr, "The gravity-displacement-force kernel requires a mesh.");
MFEM_VERIFY(f.densityFes != nullptr,
"The gravity-displacement-force kernel requires the density "
"finite-element space.");
MFEM_VERIFY(
f.densityFes != nullptr, "The gravity-displacement-force kernel requires the density "
"finite-element space."
);
MFEM_VERIFY(f.gravityFluxFes != nullptr,
"The gravity-displacement-force kernel requires the gravity-"
"gradient finite-element space.");
MFEM_VERIFY(
f.gravityFluxFes != nullptr, "The gravity-displacement-force kernel requires the gravity-"
"gradient finite-element space."
);
MFEM_VERIFY(f.displacementFes != nullptr,
"The gravity-displacement-force kernel requires the displacement "
"finite-element space.");
MFEM_VERIFY(
f.displacementFes != nullptr, "The gravity-displacement-force kernel requires the displacement "
"finite-element space."
);
MFEM_VERIFY(f.compactificationFes != nullptr &&
f.compactificationCoordinate != nullptr,
MFEM_VERIFY(
f.compactificationFes != nullptr && f.compactificationCoordinate != nullptr,
"The gravity-displacement-force kernel requires the "
"compactification coordinate.");
"compactification coordinate."
);
MFEM_VERIFY(f.quadratureFactory != nullptr,
"The gravity-displacement-force kernel requires the quadrature "
"rule factory.");
MFEM_VERIFY(
f.quadratureFactory != nullptr, "The gravity-displacement-force kernel requires the quadrature "
"rule factory."
);
MFEM_VERIFY(displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The gravity-displacement-force displacement vector has the "
"wrong size.");
"wrong size."
);
MFEM_VERIFY(domainMapper.GetDimension() == f.mesh->Dimension(),
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The gravity-displacement-force mapper dimension does not match "
"the mesh dimension.");
"the mesh dimension."
);
MFEM_VERIFY(f.displacementFes->GetVDim() == f.mesh->Dimension(),
MFEM_VERIFY(
f.displacementFes->GetVDim() == f.mesh->Dimension(),
"The gravity-displacement-force displacement dimension does not "
"match the mesh dimension.");
"match the mesh dimension."
);
validate_finite_vector(
displacementTrue,
"The gravity-displacement-force displacement contains a "
"non-finite value.");
displacementTrue, "The gravity-displacement-force displacement contains a "
"non-finite value."
);
}
void validate_density(const mean_field::fem::FEM &f,
const mfem::Vector &density, const char *message) {
void validate_density(
const mean_field::fem::FEM &f,
const mfem::Vector &density,
const char *message
) {
MFEM_VERIFY(density.Size() == f.densityFes->GetTrueVSize(), message);
validate_finite_vector(density, message);
}
void validate_gravity_gradient(const mean_field::fem::FEM &f,
void validate_gravity_gradient(
const mean_field::fem::FEM &f,
const mfem::Vector &gravityGradient,
const char *message) {
MFEM_VERIFY(gravityGradient.Size() == f.gravityFluxFes->GetTrueVSize(),
message);
const char *message
) {
MFEM_VERIFY(gravityGradient.Size() == f.gravityFluxFes->GetTrueVSize(), message);
validate_finite_vector(gravityGradient, message);
}
@@ -196,85 +222,90 @@ void apply_gravity_displacement_force_action(
const mfem::Vector *baseGravityGradientTrue,
const mfem::Vector *gravityGradientVariationTrue,
const mfem::Vector *displacementVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
validate_common_inputs(f, domainMapper, displacementTrue);
const bool needsBaseDensity =
requestedAction == GravityDisplacementForceAction::residual ||
const bool needsBaseDensity = requestedAction == GravityDisplacementForceAction::residual ||
requestedAction == GravityDisplacementForceAction::gravityGradient ||
requestedAction == GravityDisplacementForceAction::displacement ||
requestedAction == GravityDisplacementForceAction::complete;
const bool needsDensityVariation =
requestedAction == GravityDisplacementForceAction::density ||
const bool needsDensityVariation = requestedAction == GravityDisplacementForceAction::density ||
requestedAction == GravityDisplacementForceAction::complete;
const bool needsBaseGravityGradient =
requestedAction == GravityDisplacementForceAction::residual ||
const bool needsBaseGravityGradient = requestedAction == GravityDisplacementForceAction::residual ||
requestedAction == GravityDisplacementForceAction::density ||
requestedAction == GravityDisplacementForceAction::displacement ||
requestedAction == GravityDisplacementForceAction::complete;
const bool needsGravityGradientVariation =
requestedAction == GravityDisplacementForceAction::gravityGradient ||
const bool needsGravityGradientVariation = requestedAction == GravityDisplacementForceAction::gravityGradient ||
requestedAction == GravityDisplacementForceAction::complete;
const bool needsDisplacementVariation =
requestedAction == GravityDisplacementForceAction::displacement ||
const bool needsDisplacementVariation = requestedAction == GravityDisplacementForceAction::displacement ||
requestedAction == GravityDisplacementForceAction::complete;
if (needsBaseDensity) {
MFEM_VERIFY(baseDensityTrue != nullptr,
"The gravity-displacement-force action requires a base "
"density.");
MFEM_VERIFY(
baseDensityTrue != nullptr, "The gravity-displacement-force action requires a base "
"density."
);
validate_density(f, *baseDensityTrue,
"The gravity-displacement-force base density is invalid.");
validate_density(f, *baseDensityTrue, "The gravity-displacement-force base density is invalid.");
}
if (needsDensityVariation) {
MFEM_VERIFY(densityVariationTrue != nullptr,
"The gravity-displacement-force action requires a density "
"variation.");
MFEM_VERIFY(
densityVariationTrue != nullptr, "The gravity-displacement-force action requires a density "
"variation."
);
validate_density(f, *densityVariationTrue,
validate_density(
f, *densityVariationTrue,
"The gravity-displacement-force density variation is "
"invalid.");
"invalid."
);
}
if (needsBaseGravityGradient) {
MFEM_VERIFY(baseGravityGradientTrue != nullptr,
"The gravity-displacement-force action requires a base "
"gravity gradient.");
MFEM_VERIFY(
baseGravityGradientTrue != nullptr, "The gravity-displacement-force action requires a base "
"gravity gradient."
);
validate_gravity_gradient(
f, *baseGravityGradientTrue,
"The gravity-displacement-force base gravity gradient is "
"invalid.");
"invalid."
);
}
if (needsGravityGradientVariation) {
MFEM_VERIFY(gravityGradientVariationTrue != nullptr,
"The gravity-displacement-force action requires a gravity-"
"gradient variation.");
MFEM_VERIFY(
gravityGradientVariationTrue != nullptr, "The gravity-displacement-force action requires a gravity-"
"gradient variation."
);
validate_gravity_gradient(
f, *gravityGradientVariationTrue,
"The gravity-displacement-force gravity-gradient variation "
"is invalid.");
"is invalid."
);
}
if (needsDisplacementVariation) {
MFEM_VERIFY(displacementVariationTrue != nullptr &&
displacementVariationTrue->Size() ==
f.displacementFes->GetTrueVSize(),
MFEM_VERIFY(
displacementVariationTrue != nullptr &&
displacementVariationTrue->Size() == f.displacementFes->GetTrueVSize(),
"The gravity-displacement-force displacement variation is "
"invalid.");
"invalid."
);
validate_finite_vector(
*displacementVariationTrue,
"The gravity-displacement-force displacement variation "
"contains a non-finite value.");
*displacementVariationTrue, "The gravity-displacement-force displacement variation "
"contains a non-finite value."
);
}
mfem::Vector baseDensityLocal;
@@ -293,27 +324,23 @@ void apply_gravity_displacement_force_action(
}
if (needsBaseGravityGradient) {
true_to_local(*f.gravityFluxFes, *baseGravityGradientTrue,
baseGravityGradientLocal);
true_to_local(*f.gravityFluxFes, *baseGravityGradientTrue, baseGravityGradientLocal);
}
if (needsGravityGradientVariation) {
true_to_local(*f.gravityFluxFes, *gravityGradientVariationTrue,
gravityGradientVariationLocal);
true_to_local(*f.gravityFluxFes, *gravityGradientVariationTrue, gravityGradientVariationLocal);
}
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
if (needsDisplacementVariation) {
true_to_local(*f.displacementFes, *displacementVariationTrue,
displacementVariationLocal);
true_to_local(*f.displacementFes, *displacementVariationTrue, displacementVariationLocal);
}
mfem::Vector localAction(f.displacementFes->GetVSize());
localAction = 0.0;
mean_field::mapping::DomainMapper::Workspace workspace(
f.mesh->Dimension());
mean_field::mapping::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> densityDofs;
mfem::Array<int> gravityGradientDofs;
@@ -345,16 +372,15 @@ void apply_gravity_displacement_force_action(
const int dimension = f.mesh->Dimension();
const mfem::Ordering::Type displacementOrdering =
f.displacementFes->GetOrdering();
const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(transformation != nullptr,
"The gravity-displacement-force kernel received a null "
"element transformation.");
MFEM_VERIFY(
transformation != nullptr, "The gravity-displacement-force kernel received a null "
"element transformation."
);
if (is_vacuum_attribute(transformation->Attribute)) {
continue;
@@ -362,17 +388,13 @@ void apply_gravity_displacement_force_action(
const mfem::FiniteElement &densityElement = *f.densityFes->GetFE(elementId);
const mfem::FiniteElement &gravityGradientElement =
*f.gravityFluxFes->GetFE(elementId);
const mfem::FiniteElement &gravityGradientElement = *f.gravityFluxFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *densityDofTransformation =
f.densityFes->GetElementDofs(elementId, densityDofs);
mfem::DofTransformation *densityDofTransformation = f.densityFes->GetElementDofs(elementId, densityDofs);
mfem::DofTransformation *gravityGradientDofTransformation =
f.gravityFluxFes->GetElementVDofs(elementId, gravityGradientDofs);
@@ -392,24 +414,20 @@ void apply_gravity_displacement_force_action(
}
if (needsBaseGravityGradient) {
baseGravityGradientLocal.GetSubVector(gravityGradientDofs,
elementBaseGravityGradient);
baseGravityGradientLocal.GetSubVector(gravityGradientDofs, elementBaseGravityGradient);
}
if (needsGravityGradientVariation) {
gravityGradientVariationLocal.GetSubVector(
gravityGradientDofs, elementGravityGradientVariation);
gravityGradientVariationLocal.GetSubVector(gravityGradientDofs, elementGravityGradientVariation);
}
displacementLocal.GetSubVector(displacementDofs, elementDisplacement);
if (needsDisplacementVariation) {
displacementVariationLocal.GetSubVector(displacementDofs,
elementDisplacementVariation);
displacementVariationLocal.GetSubVector(displacementDofs, elementDisplacementVariation);
}
f.compactificationCoordinate->GetSubVector(compactificationDofs,
elementCompactification);
f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification);
if (densityDofTransformation != nullptr) {
if (needsBaseDensity) {
@@ -423,13 +441,11 @@ void apply_gravity_displacement_force_action(
if (gravityGradientDofTransformation != nullptr) {
if (needsBaseGravityGradient) {
gravityGradientDofTransformation->InvTransformPrimal(
elementBaseGravityGradient);
gravityGradientDofTransformation->InvTransformPrimal(elementBaseGravityGradient);
}
if (needsGravityGradientVariation) {
gravityGradientDofTransformation->InvTransformPrimal(
elementGravityGradientVariation);
gravityGradientDofTransformation->InvTransformPrimal(elementGravityGradientVariation);
}
}
@@ -437,42 +453,42 @@ void apply_gravity_displacement_force_action(
displacementDofTransformation->InvTransformPrimal(elementDisplacement);
if (needsDisplacementVariation) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacementVariation);
displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation);
}
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification);
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
const mean_field::mapping::ElementDisplacementData displacementData =
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement);
mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement);
const mean_field::mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification);
compactificationElement, elementCompactification
);
const mean_field::mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData};
.displacement = displacementData, .compactification = compactificationData
};
std::optional<mean_field::mapping::ElementDisplacementData>
displacementVariationData;
std::optional<mean_field::mapping::ElementDisplacementData> displacementVariationData;
if (needsDisplacementVariation) {
displacementVariationData.emplace(
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacementVariation));
displacementElement, elementDisplacementVariation
)
);
}
const int scalarDisplacementDofCount = displacementElement.GetDof();
MFEM_VERIFY(displacementDofs.Size() ==
scalarDisplacementDofCount * dimension,
MFEM_VERIFY(
displacementDofs.Size() == scalarDisplacementDofCount * dimension,
"The gravity-displacement-force element displacement vector "
"has the wrong size.");
"has the wrong size."
);
densityShape.SetSize(densityElement.GetDof());
displacementShape.SetSize(scalarDisplacementDofCount);
@@ -489,42 +505,38 @@ void apply_gravity_displacement_force_action(
elementAction = 0.0;
const mfem::IntegrationRule &integrationRule =
get_gravity_force_rule(f, densityElement, gravityGradientElement,
displacementElement, *transformation);
get_gravity_force_rule(f, densityElement, gravityGradientElement, displacementElement, *transformation);
for (int quadratureIndex = 0;
quadratureIndex < integrationRule.GetNPoints(); ++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadratureIndex);
for (int quadratureIndex = 0; quadratureIndex < integrationRule.GetNPoints(); ++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadratureIndex);
transformation->SetIntPoint(&integrationPoint);
const mean_field::mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(mappingData, *transformation,
integrationPoint, workspace,
mappingContext);
const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(mappingStatus == mean_field::mapping::MappingStatus::valid,
MFEM_VERIFY(
mappingStatus == mean_field::mapping::MappingStatus::valid,
"Stateless mapping failed in the gravity-displacement-"
"force kernel. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex
<< ", status: " << static_cast<int>(mappingStatus));
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex << ", status: " << static_cast<int>(mappingStatus)
);
if (needsDisplacementVariation) {
const mean_field::mapping::MappingStatus variationStatus =
domainMapper.EvaluateVolumeVariation(
mappingData, *displacementVariationData, *transformation,
integrationPoint, mappingContext, workspace, mappingVariation);
const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation(
mappingData, *displacementVariationData, *transformation, integrationPoint, mappingContext,
workspace, mappingVariation
);
MFEM_VERIFY(
variationStatus == mean_field::mapping::MappingStatus::valid,
"Stateless mapping variation failed in the gravity-"
"displacement-force kernel. Element: "
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex
<< ", status: " << static_cast<int>(variationStatus));
<< elementId << ", attribute: " << transformation->Attribute << ", quadrature point: "
<< quadratureIndex << ", status: " << static_cast<int>(variationStatus)
);
}
densityElement.CalcShape(integrationPoint, densityShape);
@@ -545,28 +557,27 @@ void apply_gravity_displacement_force_action(
}
if (needsBaseGravityGradient) {
gravityGradientShape.MultTranspose(elementBaseGravityGradient,
baseGravityReferenceValue);
gravityGradientShape.MultTranspose(elementBaseGravityGradient, baseGravityReferenceValue);
mappingContext.mapping.mapping_jacobian.Mult(baseGravityReferenceValue,
mappedBaseGravity);
mappingContext.mapping.mapping_jacobian.Mult(baseGravityReferenceValue, mappedBaseGravity);
} else {
mappedBaseGravity = 0.0;
}
if (needsGravityGradientVariation) {
gravityGradientShape.MultTranspose(elementGravityGradientVariation,
gravityVariationReferenceValue);
gravityGradientShape.MultTranspose(elementGravityGradientVariation, gravityVariationReferenceValue);
mappingContext.mapping.mapping_jacobian.Mult(
gravityVariationReferenceValue, mappedGravityVariation);
gravityVariationReferenceValue, mappedGravityVariation
);
} else {
mappedGravityVariation = 0.0;
}
if (needsDisplacementVariation) {
mappingVariation.mapping.mapping_jacobian_variation.Mult(
baseGravityReferenceValue, mappedGeometryVariation);
baseGravityReferenceValue, mappedGeometryVariation
);
} else {
mappedGeometryVariation = 0.0;
}
@@ -601,24 +612,22 @@ void apply_gravity_displacement_force_action(
* differentiating the Piola map and physical volume weight,
* but avoids a numerically pointless cancellation.
*/
const double referenceWeight =
integrationPoint.weight * transformation->Weight();
const double referenceWeight = integrationPoint.weight * transformation->Weight();
forceValue *= referenceWeight;
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount;
++scalarDof) {
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
for (int component = 0; component < dimension; ++component) {
const int vectorDof =
vector_dof_index(displacementOrdering, scalarDof, component,
scalarDisplacementDofCount, dimension);
const int vectorDof = vector_dof_index(
displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension
);
const double contribution =
displacementShape(scalarDof) * forceValue(component);
const double contribution = displacementShape(scalarDof) * forceValue(component);
MFEM_VERIFY(std::isfinite(contribution),
"The gravity-displacement-force kernel "
"encountered a non-finite contribution.");
MFEM_VERIFY(
std::isfinite(contribution), "The gravity-displacement-force kernel "
"encountered a non-finite contribution."
);
elementAction(vectorDof) += contribution;
}
@@ -638,61 +647,77 @@ void apply_gravity_displacement_force_action(
namespace mean_field::operators::kernels {
void apply_gravity_displacement_force_residual(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const mfem::Vector &densityTrue, const mfem::Vector &gravityGradientTrue,
const mfem::Vector &displacementTrue, mfem::Vector &residualTrue) {
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const mfem::Vector &densityTrue,
const mfem::Vector &gravityGradientTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &residualTrue
) {
apply_gravity_displacement_force_action(
f, domainMapper, GravityDisplacementForceAction::residual, &densityTrue,
nullptr, &gravityGradientTrue, nullptr, nullptr, displacementTrue,
residualTrue);
f, domainMapper, GravityDisplacementForceAction::residual, &densityTrue, nullptr, &gravityGradientTrue,
nullptr, nullptr, displacementTrue, residualTrue
);
}
void apply_gravity_displacement_force_density_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const mfem::Vector &densityVariationTrue,
const mfem::Vector &baseGravityGradientTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
apply_gravity_displacement_force_action(
f, domainMapper, GravityDisplacementForceAction::density, nullptr,
&densityVariationTrue, &baseGravityGradientTrue, nullptr, nullptr,
displacementTrue, actionTrue);
f, domainMapper, GravityDisplacementForceAction::density, nullptr, &densityVariationTrue,
&baseGravityGradientTrue, nullptr, nullptr, displacementTrue, actionTrue
);
}
void apply_gravity_displacement_force_gradient_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const mfem::Vector &baseDensityTrue,
const mfem::Vector &gravityGradientVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
apply_gravity_displacement_force_action(
f, domainMapper, GravityDisplacementForceAction::gravityGradient,
&baseDensityTrue, nullptr, nullptr, &gravityGradientVariationTrue,
nullptr, displacementTrue, actionTrue);
f, domainMapper, GravityDisplacementForceAction::gravityGradient, &baseDensityTrue, nullptr, nullptr,
&gravityGradientVariationTrue, nullptr, displacementTrue, actionTrue
);
}
void apply_gravity_displacement_force_displacement_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseGravityGradientTrue,
const mfem::Vector &displacementVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
apply_gravity_displacement_force_action(
f, domainMapper, GravityDisplacementForceAction::displacement,
&baseDensityTrue, nullptr, &baseGravityGradientTrue, nullptr,
&displacementVariationTrue, displacementTrue, actionTrue);
f, domainMapper, GravityDisplacementForceAction::displacement, &baseDensityTrue, nullptr,
&baseGravityGradientTrue, nullptr, &displacementVariationTrue, displacementTrue, actionTrue
);
}
void apply_gravity_displacement_force_complete_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const mfem::Vector &baseDensityTrue,
const mfem::Vector &densityVariationTrue,
const mfem::Vector &baseGravityGradientTrue,
const mfem::Vector &gravityGradientVariationTrue,
const mfem::Vector &displacementVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
apply_gravity_displacement_force_action(
f, domainMapper, GravityDisplacementForceAction::complete,
&baseDensityTrue, &densityVariationTrue, &baseGravityGradientTrue,
&gravityGradientVariationTrue, &displacementVariationTrue,
displacementTrue, actionTrue);
f, domainMapper, GravityDisplacementForceAction::complete, &baseDensityTrue, &densityVariationTrue,
&baseGravityGradientTrue, &gravityGradientVariationTrue, &displacementVariationTrue, displacementTrue,
actionTrue
);
}
} // namespace mean_field::operators::kernels

View File

@@ -9,20 +9,19 @@ namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<
mean_field::utils::domain::Vacuum>(attribute);
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
void true_to_local(const mfem::ParFiniteElementSpace &finite_element_space,
void true_to_local(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &true_vector,
mfem::Vector &local_vector) {
MFEM_VERIFY(true_vector.Size() == finite_element_space.GetTrueVSize(),
"True vector has the wrong size.");
mfem::Vector &local_vector
) {
MFEM_VERIFY(true_vector.Size() == finite_element_space.GetTrueVSize(), "True vector has the wrong size.");
local_vector.SetSize(finite_element_space.GetVSize());
const mfem::Operator *prolongation =
finite_element_space.GetProlongationMatrix();
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(true_vector, local_vector);
} else {
@@ -30,17 +29,17 @@ void true_to_local(const mfem::ParFiniteElementSpace &finite_element_space,
}
}
void local_to_true(const mfem::ParFiniteElementSpace &finite_element_space,
void local_to_true(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &local_vector,
mfem::Vector &true_vector) {
MFEM_VERIFY(local_vector.Size() == finite_element_space.GetVSize(),
"Local vector has the wrong size.");
mfem::Vector &true_vector
) {
MFEM_VERIFY(local_vector.Size() == finite_element_space.GetVSize(), "Local vector has the wrong size.");
true_vector.SetSize(finite_element_space.GetTrueVSize());
true_vector = 0.0;
const mfem::Operator *prolongation =
finite_element_space.GetProlongationMatrix();
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(local_vector, true_vector);
} else {
@@ -48,9 +47,11 @@ void local_to_true(const mfem::ParFiniteElementSpace &finite_element_space,
}
}
void add_local_to_true(const mfem::ParFiniteElementSpace &fes,
void add_local_to_true(
const mfem::ParFiniteElementSpace &fes,
const mfem::Vector &local_vector,
mfem::Vector &true_vector) {
mfem::Vector &true_vector
) {
const mfem::Operator *prolongation = fes.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->AddMultTranspose(local_vector, true_vector);
@@ -61,9 +62,9 @@ void add_local_to_true(const mfem::ParFiniteElementSpace &fes,
mean_field::quadrature::MappingKind get_mapping_kind(
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::ElementTransformation &transformation) {
return domain_mapper.IsCompactifiedElement(transformation)
? mean_field::quadrature::MappingKind::kelvin
const mfem::ElementTransformation &transformation
) {
return domain_mapper.IsCompactifiedElement(transformation) ? mean_field::quadrature::MappingKind::kelvin
: mean_field::quadrature::MappingKind::general;
}
@@ -71,87 +72,98 @@ const mfem::IntegrationRule &get_hdiv_mass_rule(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::FiniteElement &element,
const mfem::ElementTransformation &transformation) {
const mfem::ElementTransformation &transformation
) {
using GravityField = mean_field::field::Field<mean_field::field::Gravity>;
MFEM_VERIFY(element.GetOrder() ==
mean_field::field::Gravity::Flux::familyOrder + 1,
MFEM_VERIFY(
element.GetOrder() == mean_field::field::Gravity::Flux::familyOrder + 1,
"The H(div) kernel element does not match the registered gravity "
"flux.");
"flux."
);
const mean_field::quadrature::Query query =
GravityField::make_query<mean_field::field::Gravity::Form::HDivMass>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), {}, mean_field::utils::DOMAINS::ALL,
get_mapping_kind(domain_mapper, transformation));
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {},
mean_field::utils::DOMAINS::ALL, get_mapping_kind(domain_mapper, transformation)
);
const auto resolution =
f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(resolution.integration_rule != nullptr,
"The quadrature policy did not return an H(div) mass integration "
"rule.");
const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
resolution.integration_rule != nullptr, "The quadrature policy did not return an H(div) mass integration "
"rule."
);
return *resolution.integration_rule;
}
const mfem::IntegrationRule &
get_source_rule(const mean_field::fem::FEM &f,
const mfem::IntegrationRule &get_source_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &density_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() ==
mean_field::field::Density::Scalar::familyOrder,
MFEM_VERIFY(
density_element.GetOrder() == mean_field::field::Density::Scalar::familyOrder,
"The source-kernel trial element does not match the registered "
"density "
"field.");
MFEM_VERIFY(potential_element.GetOrder() ==
mean_field::field::Gravity::Potential::familyOrder,
"field."
);
MFEM_VERIFY(
potential_element.GetOrder() == mean_field::field::Gravity::Potential::familyOrder,
"The source-kernel test element does not match the registered "
"gravity "
"potential.");
const mean_field::quadrature::Query query = 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);
"potential."
);
const mean_field::quadrature::Query query =
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
);
const auto resolution =
f.quadratureFactory->get(query, transformation.GetGeometryType());
const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
resolution.integration_rule != nullptr,
"The quadrature policy did not return a gravity-source integration "
"rule.");
resolution.integration_rule != nullptr, "The quadrature policy did not return a gravity-source integration "
"rule."
);
return *resolution.integration_rule;
}
} // namespace
namespace mean_field::operators::kernels {
void apply_mapped_hdiv_mass(const fem::FEM &f,
void apply_mapped_hdiv_mass(
const fem::FEM &f,
const mapping::DomainMapper &domain_mapper,
const mfem::Vector &gravity_gradient_true,
const mfem::Vector &displacement_true,
mfem::Vector &action) {
MFEM_VERIFY(f.gravityFluxFes != nullptr,
"The H(div) mass kernel requires the "
"gravity-gradient finite-element space.");
MFEM_VERIFY(f.displacementFes != nullptr,
"The H(div) mass kernel requires the "
"displacement finite-element space.");
MFEM_VERIFY(f.compactificationFes != nullptr,
"The H(div) mass kernel requires the compactification "
mfem::Vector &action
) {
MFEM_VERIFY(
f.gravityFluxFes != nullptr, "The H(div) mass kernel requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr, "The H(div) mass kernel requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr, "The H(div) mass kernel requires the compactification "
"finite-element "
"space.");
MFEM_VERIFY(f.compactificationCoordinate != nullptr,
"The H(div) mass kernel requires the compactification field.");
MFEM_VERIFY(f.quadratureFactory != nullptr,
"The H(div) mass kernel requires the quadrature rule factory.");
MFEM_VERIFY(gravity_gradient_true.Size() == f.gravityFluxFes->GetTrueVSize(),
"The gravity-gradient vector has the wrong size.");
MFEM_VERIFY(displacement_true.Size() == f.displacementFes->GetTrueVSize(),
"The displacement vector has the wrong size.");
"space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr, "The H(div) mass kernel requires the compactification field."
);
MFEM_VERIFY(f.quadratureFactory != nullptr, "The H(div) mass kernel requires the quadrature rule factory.");
MFEM_VERIFY(
gravity_gradient_true.Size() == f.gravityFluxFes->GetTrueVSize(),
"The gravity-gradient vector has the wrong size."
);
MFEM_VERIFY(
displacement_true.Size() == f.displacementFes->GetTrueVSize(), "The displacement vector has the wrong size."
);
mfem::Vector gravity_gradient_local;
mfem::Vector displacement_local;
true_to_local(*f.gravityFluxFes, gravity_gradient_true,
gravity_gradient_local);
true_to_local(*f.gravityFluxFes, gravity_gradient_true, gravity_gradient_local);
true_to_local(*f.displacementFes, displacement_true, displacement_local);
mfem::Vector local_action(f.gravityFluxFes->GetVSize());
@@ -174,49 +186,42 @@ void apply_mapped_hdiv_mass(const fem::FEM &f,
mfem::DenseMatrix mapped_mass_tensor;
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
const mfem::FiniteElement &gravity_element =
*f.gravityFluxFes->GetFE(element_id);
const mfem::FiniteElement &displacement_element =
*f.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*f.compactificationFes->GetFE(element_id);
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(element_id);
const mfem::FiniteElement &gravity_element = *f.gravityFluxFes->GetFE(element_id);
const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id);
mfem::DofTransformation *gravity_dof_transformation =
f.gravityFluxFes->GetElementVDofs(element_id, gravity_dofs);
mfem::DofTransformation *displacement_dof_transformation =
f.displacementFes->GetElementVDofs(element_id, displacement_dofs);
mfem::DofTransformation *compactification_dof_transformation =
f.compactificationFes->GetElementDofs(element_id,
compactification_dofs);
f.compactificationFes->GetElementDofs(element_id, compactification_dofs);
gravity_gradient_local.GetSubVector(gravity_dofs, element_gravity_gradient);
displacement_local.GetSubVector(displacement_dofs, element_displacement);
f.compactificationCoordinate->GetSubVector(compactification_dofs,
element_compactification);
f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification);
if (gravity_dof_transformation != nullptr)
gravity_dof_transformation->InvTransformPrimal(element_gravity_gradient);
if (displacement_dof_transformation != nullptr)
displacement_dof_transformation->InvTransformPrimal(element_displacement);
if (compactification_dof_transformation != nullptr)
compactification_dof_transformation->InvTransformPrimal(
element_compactification);
compactification_dof_transformation->InvTransformPrimal(element_compactification);
// const mapping::ElementDisplacementData
// displacement_data(displacement_element, element_displacement,
// mfem::Ordering::byVDIM);
const mapping::ElementDisplacementData displacement_data =
mapping::ElementDisplacementDataFromElementVDofs(displacement_element,
element_displacement);
mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement);
const mapping::ElementCompactificationData compactification_data(
compactification_element, element_compactification);
compactification_element, element_compactification
);
const mapping::ElementMappingData mapping_data{
.displacement = displacement_data,
.compactification = compactification_data};
.displacement = displacement_data, .compactification = compactification_data
};
const int gravity_dof_count = gravity_element.GetDof();
const int dimension = transformation->GetSpaceDim();
@@ -232,39 +237,34 @@ void apply_mapped_hdiv_mass(const fem::FEM &f,
get_hdiv_mass_rule(f, domain_mapper, gravity_element, *transformation);
for (int q = 0; q < integration_rule.GetNPoints(); ++q) {
const mfem::IntegrationPoint &integration_point =
integration_rule.IntPoint(q);
const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point);
mapping::VolumeMappingContext mapping_context;
const mapping::MappingStatus status = domain_mapper.EvaluateVolume(
mapping_data, *transformation, integration_point, workspace,
mapping_context);
MFEM_VERIFY(status == mapping::MappingStatus::valid,
mapping_data, *transformation, integration_point, workspace, mapping_context
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid,
"Stateless mapping failed in the matrix-free H(div) mass "
"kernel. "
"Element: "
<< element_id
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << q
<< ", status: " << static_cast<int>(status));
<< element_id << ", attribute: " << transformation->Attribute << ", quadrature point: " << q
<< ", status: " << static_cast<int>(status)
);
gravity_element.CalcVShape(*transformation, vector_shape);
mapping::ComputeHDivMassTensor(mapping_context.mapping,
mapped_mass_tensor);
mapping::ComputeHDivMassTensor(mapping_context.mapping, mapped_mass_tensor);
vector_shape.MultTranspose(element_gravity_gradient,
gravity_gradient_value);
mapped_mass_tensor.Mult(gravity_gradient_value,
mapped_gravity_gradient_value);
vector_shape.MultTranspose(element_gravity_gradient, gravity_gradient_value);
mapped_mass_tensor.Mult(gravity_gradient_value, mapped_gravity_gradient_value);
const double weight = integration_point.weight * transformation->Weight();
for (int i = 0; i < gravity_dof_count; ++i) {
double value = 0.0;
for (int component = 0; component < dimension; ++component)
value += vector_shape(i, component) *
mapped_gravity_gradient_value(component);
value += vector_shape(i, component) * mapped_gravity_gradient_value(component);
element_action(i) += weight * value;
}
}
@@ -277,31 +277,37 @@ void apply_mapped_hdiv_mass(const fem::FEM &f,
local_to_true(*f.gravityFluxFes, local_action, action);
}
void apply_mapped_source(const fem::FEM &f,
void apply_mapped_source(
const fem::FEM &f,
const mapping::DomainMapper &domain_mapper,
const mfem::Vector &density_true,
const mfem::Vector &displacement_true,
mfem::Vector &action) {
MFEM_VERIFY(f.densityFes != nullptr, "The gravity-source kernel requires the "
"density finite-element space.");
MFEM_VERIFY(f.gravityPotentialFes != nullptr,
"The gravity-source kernel requires the gravity-potential "
"finite-element space.");
MFEM_VERIFY(f.displacementFes != nullptr,
"The gravity-source kernel requires the "
"displacement finite-element space.");
MFEM_VERIFY(f.compactificationFes != nullptr,
"The gravity-source kernel requires the compactification "
"finite-element space.");
MFEM_VERIFY(f.compactificationCoordinate != nullptr,
"The gravity-source kernel requires the compactification field.");
mfem::Vector &action
) {
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The gravity-source kernel requires the quadrature rule factory.");
MFEM_VERIFY(density_true.Size() == f.densityFes->GetTrueVSize(),
"The density vector has the wrong size.");
MFEM_VERIFY(displacement_true.Size() == f.displacementFes->GetTrueVSize(),
"The displacement vector has the wrong size.");
f.densityFes != nullptr, "The gravity-source kernel requires the "
"density finite-element space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr, "The gravity-source kernel requires the gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr, "The gravity-source kernel requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr, "The gravity-source kernel requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr, "The gravity-source kernel requires the compactification field."
);
MFEM_VERIFY(f.quadratureFactory != nullptr, "The gravity-source kernel requires the quadrature rule factory.");
MFEM_VERIFY(density_true.Size() == f.densityFes->GetTrueVSize(), "The density vector has the wrong size.");
MFEM_VERIFY(
displacement_true.Size() == f.displacementFes->GetTrueVSize(), "The displacement vector has the wrong size."
);
mfem::Vector density_local;
mfem::Vector displacement_local;
@@ -328,19 +334,14 @@ void apply_mapped_source(const fem::FEM &f,
constexpr double source_scale = 4.0 * M_PI * utils::G;
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(element_id);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id);
if (is_vacuum_attribute(transformation->Attribute))
continue;
const mfem::FiniteElement &density_element =
*f.densityFes->GetFE(element_id);
const mfem::FiniteElement &potential_element =
*f.gravityPotentialFes->GetFE(element_id);
const mfem::FiniteElement &displacement_element =
*f.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*f.compactificationFes->GetFE(element_id);
const mfem::FiniteElement &density_element = *f.densityFes->GetFE(element_id);
const mfem::FiniteElement &potential_element = *f.gravityPotentialFes->GetFE(element_id);
const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id);
mfem::DofTransformation *density_dof_transformation =
f.densityFes->GetElementDofs(element_id, density_dofs);
@@ -349,30 +350,27 @@ void apply_mapped_source(const fem::FEM &f,
mfem::DofTransformation *displacement_dof_transformation =
f.displacementFes->GetElementVDofs(element_id, displacement_dofs);
mfem::DofTransformation *compactification_dof_transformation =
f.compactificationFes->GetElementDofs(element_id,
compactification_dofs);
f.compactificationFes->GetElementDofs(element_id, compactification_dofs);
density_local.GetSubVector(density_dofs, element_density);
displacement_local.GetSubVector(displacement_dofs, element_displacement);
f.compactificationCoordinate->GetSubVector(compactification_dofs,
element_compactification);
f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification);
if (density_dof_transformation != nullptr)
density_dof_transformation->InvTransformPrimal(element_density);
if (displacement_dof_transformation != nullptr)
displacement_dof_transformation->InvTransformPrimal(element_displacement);
if (compactification_dof_transformation != nullptr)
compactification_dof_transformation->InvTransformPrimal(
element_compactification);
compactification_dof_transformation->InvTransformPrimal(element_compactification);
const mapping::ElementDisplacementData displacement_data =
mapping::ElementDisplacementDataFromElementVDofs(displacement_element,
element_displacement);
mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement);
const mapping::ElementCompactificationData compactification_data(
compactification_element, element_compactification);
compactification_element, element_compactification
);
const mapping::ElementMappingData mapping_data{
.displacement = displacement_data,
.compactification = compactification_data};
.displacement = displacement_data, .compactification = compactification_data
};
const int density_dof_count = density_element.GetDof();
const int potential_dof_count = potential_element.GetDof();
@@ -386,29 +384,27 @@ void apply_mapped_source(const fem::FEM &f,
get_source_rule(f, density_element, potential_element, *transformation);
for (int q = 0; q < integration_rule.GetNPoints(); ++q) {
const mfem::IntegrationPoint &integration_point =
integration_rule.IntPoint(q);
const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point);
mapping::VolumeMappingContext mapping_context;
const mapping::MappingStatus status = domain_mapper.EvaluateVolume(
mapping_data, *transformation, integration_point, workspace,
mapping_context);
MFEM_VERIFY(status == mapping::MappingStatus::valid,
mapping_data, *transformation, integration_point, workspace, mapping_context
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid,
"Stateless mapping failed in the matrix-free "
"gravity-source "
"kernel. Element: "
<< element_id
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << q
<< ", status: " << static_cast<int>(status));
<< element_id << ", attribute: " << transformation->Attribute << ", quadrature point: " << q
<< ", status: " << static_cast<int>(status)
);
density_element.CalcShape(integration_point, density_shape);
potential_element.CalcShape(integration_point, potential_shape);
const double density_value = element_density * density_shape;
const double weight =
source_scale * density_value * mapping_context.quadrature.weight;
const double weight = source_scale * density_value * mapping_context.quadrature.weight;
for (int i = 0; i < potential_dof_count; ++i)
element_action(i) += weight * potential_shape(i);
@@ -423,47 +419,57 @@ void apply_mapped_source(const fem::FEM &f,
}
void apply_mapped_hdiv_mass_variation(
const fem::FEM &f, const mapping::DomainMapper &domain_mapper,
const fem::FEM &f,
const mapping::DomainMapper &domain_mapper,
const mfem::Vector &gravity_gradient_true,
const mfem::Vector &displacement_true,
const mfem::Vector &displacement_variation_true,
mfem::Vector &action_variation) {
MFEM_VERIFY(f.mesh != nullptr,
"The H(div) mass-variation kernel requires a mesh.");
MFEM_VERIFY(f.gravityFluxFes != nullptr,
"The H(div) mass-variation kernel requires the "
"gravity-gradient finite-element space.");
MFEM_VERIFY(f.displacementFes != nullptr,
"The H(div) mass-variation kernel requires "
"the displacement finite-element space.");
MFEM_VERIFY(f.compactificationFes != nullptr,
"The H(div) mass-variation kernel requires the compactification "
"finite-element space.");
MFEM_VERIFY(f.compactificationCoordinate != nullptr,
"The H(div) mass-variation kernel requires the compactification "
"field.");
MFEM_VERIFY(f.quadratureFactory != nullptr,
"The H(div) mass-variation kernel requires the quadrature rule "
"factory.");
MFEM_VERIFY(gravity_gradient_true.Size() == f.gravityFluxFes->GetTrueVSize(),
"The gravity-gradient vector has the wrong size.");
MFEM_VERIFY(displacement_true.Size() == f.displacementFes->GetTrueVSize(),
"The displacement vector has the wrong size.");
MFEM_VERIFY(displacement_variation_true.Size() ==
f.displacementFes->GetTrueVSize(),
"The displacement-variation vector has the wrong size.");
MFEM_VERIFY(domain_mapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match the mesh dimension.");
mfem::Vector &action_variation
) {
MFEM_VERIFY(f.mesh != nullptr, "The H(div) mass-variation kernel requires a mesh.");
MFEM_VERIFY(
f.gravityFluxFes != nullptr, "The H(div) mass-variation kernel requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr, "The H(div) mass-variation kernel requires "
"the displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr, "The H(div) mass-variation kernel requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr, "The H(div) mass-variation kernel requires the compactification "
"field."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr, "The H(div) mass-variation kernel requires the quadrature rule "
"factory."
);
MFEM_VERIFY(
gravity_gradient_true.Size() == f.gravityFluxFes->GetTrueVSize(),
"The gravity-gradient vector has the wrong size."
);
MFEM_VERIFY(
displacement_true.Size() == f.displacementFes->GetTrueVSize(), "The displacement vector has the wrong size."
);
MFEM_VERIFY(
displacement_variation_true.Size() == f.displacementFes->GetTrueVSize(),
"The displacement-variation vector has the wrong size."
);
MFEM_VERIFY(
domain_mapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match the mesh dimension."
);
mfem::Vector gravity_gradient_local;
mfem::Vector displacement_local;
mfem::Vector displacement_variation_local;
true_to_local(*f.gravityFluxFes, gravity_gradient_true,
gravity_gradient_local);
true_to_local(*f.gravityFluxFes, gravity_gradient_true, gravity_gradient_local);
true_to_local(*f.displacementFes, displacement_true, displacement_local);
true_to_local(*f.displacementFes, displacement_variation_true,
displacement_variation_local);
true_to_local(*f.displacementFes, displacement_variation_true, displacement_variation_local);
mfem::Vector local_action(f.gravityFluxFes->GetVSize());
local_action = 0.0;
@@ -486,59 +492,49 @@ void apply_mapped_hdiv_mass_variation(
mfem::DenseMatrix mass_tensor_variation;
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
const mfem::FiniteElement &gravity_gradient_element =
*f.gravityFluxFes->GetFE(element_id);
const mfem::FiniteElement &displacement_element =
*f.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*f.compactificationFes->GetFE(element_id);
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(element_id);
const mfem::FiniteElement &gravity_gradient_element = *f.gravityFluxFes->GetFE(element_id);
const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id);
MFEM_VERIFY(transformation != nullptr,
"The H(div) mass-variation kernel "
"received a null element transformation.");
MFEM_VERIFY(
transformation != nullptr, "The H(div) mass-variation kernel "
"received a null element transformation."
);
mfem::DofTransformation *gravity_dof_transformation =
f.gravityFluxFes->GetElementVDofs(element_id, gravity_gradient_dofs);
mfem::DofTransformation *displacement_dof_transformation =
f.displacementFes->GetElementVDofs(element_id, displacement_dofs);
mfem::DofTransformation *compactification_dof_transformation =
f.compactificationFes->GetElementDofs(element_id,
compactification_dofs);
f.compactificationFes->GetElementDofs(element_id, compactification_dofs);
gravity_gradient_local.GetSubVector(gravity_gradient_dofs,
element_gravity_gradient);
gravity_gradient_local.GetSubVector(gravity_gradient_dofs, element_gravity_gradient);
displacement_local.GetSubVector(displacement_dofs, element_displacement);
displacement_variation_local.GetSubVector(displacement_dofs,
element_displacement_variation);
f.compactificationCoordinate->GetSubVector(compactification_dofs,
element_compactification);
displacement_variation_local.GetSubVector(displacement_dofs, element_displacement_variation);
f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification);
if (gravity_dof_transformation != nullptr)
gravity_dof_transformation->InvTransformPrimal(element_gravity_gradient);
if (displacement_dof_transformation != nullptr) {
displacement_dof_transformation->InvTransformPrimal(element_displacement);
displacement_dof_transformation->InvTransformPrimal(
element_displacement_variation);
displacement_dof_transformation->InvTransformPrimal(element_displacement_variation);
}
if (compactification_dof_transformation != nullptr)
compactification_dof_transformation->InvTransformPrimal(
element_compactification);
compactification_dof_transformation->InvTransformPrimal(element_compactification);
const mapping::ElementDisplacementData displacement_data =
mapping::ElementDisplacementDataFromElementVDofs(displacement_element,
element_displacement);
mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement);
const mapping::ElementDisplacementData displacement_variation_data =
mapping::ElementDisplacementDataFromElementVDofs(
displacement_element, element_displacement_variation);
mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement_variation);
const mapping::ElementCompactificationData compactification_data(
compactification_element, element_compactification);
compactification_element, element_compactification
);
const mapping::ElementMappingData mapping_data{
.displacement = displacement_data,
.compactification = compactification_data};
.displacement = displacement_data, .compactification = compactification_data
};
const int gravity_gradient_dof_count = gravity_gradient_element.GetDof();
const int dimension = transformation->GetSpaceDim();
@@ -552,52 +548,48 @@ void apply_mapped_hdiv_mass_variation(
gravity_gradient_shape.SetSize(gravity_gradient_dof_count, dimension);
mass_tensor_variation.SetSize(dimension, dimension);
const mfem::IntegrationRule &integration_rule = get_hdiv_mass_rule(
f, domain_mapper, gravity_gradient_element, *transformation);
const mfem::IntegrationRule &integration_rule =
get_hdiv_mass_rule(f, domain_mapper, gravity_gradient_element, *transformation);
for (int q = 0; q < integration_rule.GetNPoints(); ++q) {
const mfem::IntegrationPoint &integration_point =
integration_rule.IntPoint(q);
const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point);
mapping::VolumeMappingContext mapping_context;
const mapping::MappingStatus status = domain_mapper.EvaluateVolume(
mapping_data, *transformation, integration_point, workspace,
mapping_context);
MFEM_VERIFY(status == mapping::MappingStatus::valid,
mapping_data, *transformation, integration_point, workspace, mapping_context
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid,
"Stateless mapping failed in the matrix-free H(div) mass "
"kernel. "
"Element: "
<< element_id
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << q
<< ", status: " << static_cast<int>(status));
<< element_id << ", attribute: " << transformation->Attribute << ", quadrature point: " << q
<< ", status: " << static_cast<int>(status)
);
mapping::VolumeMappingVariation mapping_variation;
const mapping::MappingStatus variation_status =
domain_mapper.EvaluateVolumeVariation(
mapping_data, displacement_variation_data, *transformation,
integration_point, mapping_context, workspace, mapping_variation);
MFEM_VERIFY(variation_status == mapping::MappingStatus::valid,
const mapping::MappingStatus variation_status = domain_mapper.EvaluateVolumeVariation(
mapping_data, displacement_variation_data, *transformation, integration_point, mapping_context,
workspace, mapping_variation
);
MFEM_VERIFY(
variation_status == mapping::MappingStatus::valid,
"The mapping variation is invalid while applying the "
"H(div) mass "
"variation.");
"variation."
);
mapping::ComputeHDivMassTensorVariation(mapping_context.mapping,
mapping_variation.mapping,
mass_tensor_variation);
mapping::ComputeHDivMassTensorVariation(
mapping_context.mapping, mapping_variation.mapping, mass_tensor_variation
);
gravity_gradient_element.CalcVShape(*transformation,
gravity_gradient_shape);
gravity_gradient_shape.MultTranspose(element_gravity_gradient,
gravity_gradient_value);
mass_tensor_variation.Mult(gravity_gradient_value,
mass_tensor_variation_action);
const double reference_weight =
integration_point.weight * transformation->Weight();
gravity_gradient_element.CalcVShape(*transformation, gravity_gradient_shape);
gravity_gradient_shape.MultTranspose(element_gravity_gradient, gravity_gradient_value);
mass_tensor_variation.Mult(gravity_gradient_value, mass_tensor_variation_action);
const double reference_weight = integration_point.weight * transformation->Weight();
gravity_gradient_shape.AddMult(mass_tensor_variation_action,
element_action, reference_weight);
gravity_gradient_shape.AddMult(mass_tensor_variation_action, element_action, reference_weight);
}
if (gravity_dof_transformation != nullptr)
@@ -611,39 +603,48 @@ void apply_mapped_hdiv_mass_variation(
}
void apply_mapped_source_variation(
const fem::FEM &f, const mapping::DomainMapper &domain_mapper,
const mfem::Vector &density_true, const mfem::Vector &displacement_true,
const fem::FEM &f,
const mapping::DomainMapper &domain_mapper,
const mfem::Vector &density_true,
const mfem::Vector &displacement_true,
const mfem::Vector &displacement_variation_true,
mfem::Vector &action_variation) {
MFEM_VERIFY(f.mesh != nullptr,
"The source-variation kernel requires a mesh.");
MFEM_VERIFY(f.densityFes != nullptr,
"The source-variation kernel requires the density finite-element "
"space.");
MFEM_VERIFY(f.gravityPotentialFes != nullptr,
"The source-variation kernel requires the gravity-potential "
"finite-element space.");
MFEM_VERIFY(f.displacementFes != nullptr,
"The source-variation kernel requires the "
"displacement finite-element space.");
MFEM_VERIFY(f.compactificationFes != nullptr,
"The source-variation kernel requires the compactification "
"finite-element space.");
mfem::Vector &action_variation
) {
MFEM_VERIFY(f.mesh != nullptr, "The source-variation kernel requires a mesh.");
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The source-variation kernel requires the compactification field.");
f.densityFes != nullptr, "The source-variation kernel requires the density finite-element "
"space."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The source-variation kernel requires the quadrature rule factory.");
MFEM_VERIFY(density_true.Size() == f.densityFes->GetTrueVSize(),
"The density vector has the wrong size.");
MFEM_VERIFY(displacement_true.Size() == f.displacementFes->GetTrueVSize(),
"The displacement vector has the wrong size.");
MFEM_VERIFY(displacement_variation_true.Size() ==
f.displacementFes->GetTrueVSize(),
"The displacement-variation vector has the wrong size.");
MFEM_VERIFY(domain_mapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match the mesh dimension.");
f.gravityPotentialFes != nullptr, "The source-variation kernel requires the gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr, "The source-variation kernel requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr, "The source-variation kernel requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr, "The source-variation kernel requires the compactification field."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr, "The source-variation kernel requires the quadrature rule factory."
);
MFEM_VERIFY(density_true.Size() == f.densityFes->GetTrueVSize(), "The density vector has the wrong size.");
MFEM_VERIFY(
displacement_true.Size() == f.displacementFes->GetTrueVSize(), "The displacement vector has the wrong size."
);
MFEM_VERIFY(
displacement_variation_true.Size() == f.displacementFes->GetTrueVSize(),
"The displacement-variation vector has the wrong size."
);
MFEM_VERIFY(
domain_mapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match the mesh dimension."
);
mfem::Vector density_local;
mfem::Vector displacement_local;
@@ -651,8 +652,7 @@ void apply_mapped_source_variation(
true_to_local(*f.densityFes, density_true, density_local);
true_to_local(*f.displacementFes, displacement_true, displacement_local);
true_to_local(*f.displacementFes, displacement_variation_true,
displacement_variation_local);
true_to_local(*f.displacementFes, displacement_variation_true, displacement_variation_local);
mfem::Vector local_action(f.gravityPotentialFes->GetVSize());
local_action = 0.0;
@@ -678,23 +678,19 @@ void apply_mapped_source_variation(
constexpr double gravitational_source_scale = 4.0 * M_PI * utils::G;
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(element_id);
MFEM_VERIFY(transformation != nullptr,
"The source-variation kernel received a null element "
"transformation.");
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id);
MFEM_VERIFY(
transformation != nullptr, "The source-variation kernel received a null element "
"transformation."
);
if (is_vacuum_attribute(transformation->Attribute))
continue;
const mfem::FiniteElement &density_element =
*f.densityFes->GetFE(element_id);
const mfem::FiniteElement &potential_element =
*f.gravityPotentialFes->GetFE(element_id);
const mfem::FiniteElement &displacement_element =
*f.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*f.compactificationFes->GetFE(element_id);
const mfem::FiniteElement &density_element = *f.densityFes->GetFE(element_id);
const mfem::FiniteElement &potential_element = *f.gravityPotentialFes->GetFE(element_id);
const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id);
mfem::DofTransformation *density_dof_transformation =
f.densityFes->GetElementDofs(element_id, density_dofs);
@@ -703,40 +699,34 @@ void apply_mapped_source_variation(
mfem::DofTransformation *displacement_dof_transformation =
f.displacementFes->GetElementVDofs(element_id, displacement_dofs);
mfem::DofTransformation *compactification_dof_transformation =
f.compactificationFes->GetElementDofs(element_id,
compactification_dofs);
f.compactificationFes->GetElementDofs(element_id, compactification_dofs);
density_local.GetSubVector(density_dofs, element_density);
displacement_local.GetSubVector(displacement_dofs, element_displacement);
displacement_variation_local.GetSubVector(displacement_dofs,
element_displacement_variation);
f.compactificationCoordinate->GetSubVector(compactification_dofs,
element_compactification);
displacement_variation_local.GetSubVector(displacement_dofs, element_displacement_variation);
f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification);
if (density_dof_transformation != nullptr)
density_dof_transformation->InvTransformPrimal(element_density);
if (displacement_dof_transformation != nullptr) {
displacement_dof_transformation->InvTransformPrimal(element_displacement);
displacement_dof_transformation->InvTransformPrimal(
element_displacement_variation);
displacement_dof_transformation->InvTransformPrimal(element_displacement_variation);
}
if (compactification_dof_transformation != nullptr)
compactification_dof_transformation->InvTransformPrimal(
element_compactification);
compactification_dof_transformation->InvTransformPrimal(element_compactification);
const mapping::ElementDisplacementData displacement_data =
mapping::ElementDisplacementDataFromElementVDofs(displacement_element,
element_displacement);
mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement);
const mapping::ElementDisplacementData displacement_variation_data =
mapping::ElementDisplacementDataFromElementVDofs(
displacement_element, element_displacement_variation);
mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement_variation);
const mapping::ElementCompactificationData compactification_data(
compactification_element, element_compactification);
compactification_element, element_compactification
);
const mapping::ElementMappingData mapping_data{
.displacement = displacement_data,
.compactification = compactification_data};
.displacement = displacement_data, .compactification = compactification_data
};
element_action.SetSize(potential_element.GetDof());
element_action = 0.0;
@@ -748,35 +738,36 @@ void apply_mapped_source_variation(
get_source_rule(f, density_element, potential_element, *transformation);
for (int q = 0; q < integration_rule.GetNPoints(); ++q) {
const mfem::IntegrationPoint &integration_point =
integration_rule.IntPoint(q);
const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point);
const mapping::MappingStatus mapping_status =
domain_mapper.EvaluateVolume(mapping_data, *transformation,
integration_point, workspace,
mapping_context);
MFEM_VERIFY(mapping_status == mapping::MappingStatus::valid,
const mapping::MappingStatus mapping_status = domain_mapper.EvaluateVolume(
mapping_data, *transformation, integration_point, workspace, mapping_context
);
MFEM_VERIFY(
mapping_status == mapping::MappingStatus::valid,
"The base mapping is invalid while applying the source "
"variation.");
"variation."
);
const mapping::MappingStatus variation_status =
domain_mapper.EvaluateVolumeVariation(
mapping_data, displacement_variation_data, *transformation,
integration_point, mapping_context, workspace, mapping_variation);
const mapping::MappingStatus variation_status = domain_mapper.EvaluateVolumeVariation(
mapping_data, displacement_variation_data, *transformation, integration_point, mapping_context,
workspace, mapping_variation
);
MFEM_VERIFY(variation_status == mapping::MappingStatus::valid,
MFEM_VERIFY(
variation_status == mapping::MappingStatus::valid,
"The mapping variation is invalid while applying the "
"source "
"variation.");
"variation."
);
density_element.CalcShape(integration_point, density_shape);
potential_element.CalcShape(integration_point, potential_shape);
const double density_value = density_shape * element_density;
const double source_variation_value = gravitational_source_scale *
density_value *
mapping_variation.weight_variation;
const double source_variation_value =
gravitational_source_scale * density_value * mapping_variation.weight_variation;
element_action.Add(source_variation_value, potential_shape);
}

View File

@@ -14,19 +14,19 @@ namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<
mean_field::utils::domain::Vacuum>(attribute);
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
void true_to_local(const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector, mfem::Vector &localVector) {
MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"True vector has the wrong size.");
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(), "True vector has the wrong size.");
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
@@ -35,17 +35,18 @@ void true_to_local(const mfem::ParFiniteElementSpace &finiteElementSpace,
}
}
void local_to_true(const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector, mfem::Vector &trueVector) {
MFEM_VERIFY(localVector.Size() == finiteElementSpace.GetVSize(),
"Local vector has the wrong size.");
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
MFEM_VERIFY(localVector.Size() == finiteElementSpace.GetVSize(), "Local vector has the wrong size.");
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
@@ -56,93 +57,100 @@ void local_to_true(const mfem::ParFiniteElementSpace &finiteElementSpace,
void validate_fem(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domainMapper) {
const mean_field::mapping::DomainMapper &domainMapper
) {
MFEM_VERIFY(f.mesh != nullptr, "The hydrostatic kernel requires a mesh.");
MFEM_VERIFY(f.enthalpyFes != nullptr, "The hydrostatic kernel requires the "
"enthalpy finite-element space.");
MFEM_VERIFY(
f.enthalpyFes != nullptr, "The hydrostatic kernel requires the "
"enthalpy finite-element space."
);
MFEM_VERIFY(f.gravityPotentialFes != nullptr,
"The hydrostatic kernel requires the "
"gravity-potential finite-element space.");
MFEM_VERIFY(
f.gravityPotentialFes != nullptr, "The hydrostatic kernel requires the "
"gravity-potential finite-element space."
);
MFEM_VERIFY(f.displacementFes != nullptr,
"The hydrostatic kernel requires the "
"displacement finite-element space.");
MFEM_VERIFY(
f.displacementFes != nullptr, "The hydrostatic kernel requires the "
"displacement finite-element space."
);
MFEM_VERIFY(f.compactificationFes != nullptr,
"The hydrostatic kernel requires the "
"compactification finite-element space.");
MFEM_VERIFY(
f.compactificationFes != nullptr, "The hydrostatic kernel requires the "
"compactification finite-element space."
);
MFEM_VERIFY(f.compactificationCoordinate != nullptr,
"The hydrostatic kernel requires the "
"compactification coordinate.");
MFEM_VERIFY(
f.compactificationCoordinate != nullptr, "The hydrostatic kernel requires the "
"compactification coordinate."
);
MFEM_VERIFY(f.quadratureFactory != nullptr,
"The hydrostatic kernel requires the "
"quadrature-rule factory.");
MFEM_VERIFY(
f.quadratureFactory != nullptr, "The hydrostatic kernel requires the "
"quadrature-rule factory."
);
MFEM_VERIFY(f.mesh->Dimension() == 3,
"The rigid-rotation hydrostatic kernel "
"currently requires a three-dimensional mesh.");
MFEM_VERIFY(
f.mesh->Dimension() == 3, "The rigid-rotation hydrostatic kernel "
"currently requires a three-dimensional mesh."
);
MFEM_VERIFY(domainMapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match "
"the mesh dimension.");
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(), "The domain-mapper dimension does not match "
"the mesh dimension."
);
}
const mfem::IntegrationRule &
get_hydrostatic_rule(const mean_field::fem::FEM &f,
const mfem::IntegrationRule &get_hydrostatic_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &enthalpyElement,
const mfem::FiniteElement &potentialElement,
const mfem::ElementTransformation &transformation) {
const mfem::ElementTransformation &transformation
) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
MFEM_VERIFY(enthalpyElement.GetOrder() ==
mean_field::field::Enthalpy::Scalar::familyOrder,
MFEM_VERIFY(
enthalpyElement.GetOrder() == mean_field::field::Enthalpy::Scalar::familyOrder,
"The hydrostatic test element does not match "
"the registered enthalpy field.");
"the registered enthalpy field."
);
MFEM_VERIFY(potentialElement.GetOrder() ==
mean_field::field::Gravity::Potential::familyOrder,
MFEM_VERIFY(
potentialElement.GetOrder() == mean_field::field::Gravity::Potential::familyOrder,
"The hydrostatic potential element does not "
"match the registered gravity-potential field.");
"match the registered gravity-potential field."
);
const auto enthalpyQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumEnthalpy>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
const auto enthalpyQuery = EnthalpyField::make_query<mean_field::field::Enthalpy::Form::EquilibriumEnthalpy>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
const auto gravityQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumGravity>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
const auto gravityQuery = EnthalpyField::make_query<mean_field::field::Enthalpy::Form::EquilibriumGravity>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
const auto rotationQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumRotation>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), std::array<int, 1>{2},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
const auto rotationQuery = EnthalpyField::make_query<mean_field::field::Enthalpy::Form::EquilibriumRotation>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array<int, 1>{2},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
const auto constantQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumConstant>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
const auto constantQuery = EnthalpyField::make_query<mean_field::field::Enthalpy::Form::EquilibriumConstant>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
int integrationOrder = 0;
const auto update_order = [&f, &transformation, &integrationOrder](
const mean_field::quadrature::Query &query) {
const auto rule =
f.quadratureFactory->get(query, transformation.GetGeometryType());
const auto update_order = [&f, &transformation, &integrationOrder](const mean_field::quadrature::Query &query) {
const auto rule = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(rule.integration_rule != nullptr,
"The quadrature policy did not return "
"a hydrostatic-equilibrium rule.");
MFEM_VERIFY(
rule.integration_rule != nullptr, "The quadrature policy did not return "
"a hydrostatic-equilibrium rule."
);
integrationOrder = std::max(integrationOrder, rule.resolution.order);
};
@@ -175,64 +183,72 @@ void assemble_hydrostatic_form(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domainMapper,
const mfem::Vector &displacementTrue,
const HydrostaticAssemblyRequest &request, mfem::Vector &result) {
const HydrostaticAssemblyRequest &request,
mfem::Vector &result
) {
validate_fem(f, domainMapper);
MFEM_VERIFY(displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The hydrostatic displacement vector has "
"the wrong size.");
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(), "The hydrostatic displacement vector has "
"the wrong size."
);
MFEM_VERIFY(std::isfinite(request.bernoulliConstant),
"The Bernoulli constant is non-finite.");
MFEM_VERIFY(std::isfinite(request.bernoulliConstant), "The Bernoulli constant is non-finite.");
MFEM_VERIFY(std::isfinite(request.constantVariation),
"The Bernoulli-constant variation is non-finite.");
MFEM_VERIFY(std::isfinite(request.constantVariation), "The Bernoulli-constant variation is non-finite.");
const bool requiresBaseState =
request.buildResidual || request.displacementVariationTrue != nullptr;
const bool requiresBaseState = request.buildResidual || request.displacementVariationTrue != nullptr;
if (requiresBaseState) {
MFEM_VERIFY(request.rotation != nullptr,
"The hydrostatic residual or geometry "
"action requires the rotation model.");
MFEM_VERIFY(
request.rotation != nullptr, "The hydrostatic residual or geometry "
"action requires the rotation model."
);
MFEM_VERIFY(request.baseEnthalpyTrue != nullptr,
"The hydrostatic residual or geometry "
"action requires the base enthalpy.");
MFEM_VERIFY(
request.baseEnthalpyTrue != nullptr, "The hydrostatic residual or geometry "
"action requires the base enthalpy."
);
MFEM_VERIFY(request.basePotentialTrue != nullptr,
"The hydrostatic residual or geometry "
"action requires the base potential.");
MFEM_VERIFY(
request.basePotentialTrue != nullptr, "The hydrostatic residual or geometry "
"action requires the base potential."
);
}
if (request.baseEnthalpyTrue != nullptr) {
MFEM_VERIFY(request.baseEnthalpyTrue->Size() ==
f.enthalpyFes->GetTrueVSize(),
"The base enthalpy vector has the wrong size.");
MFEM_VERIFY(
request.baseEnthalpyTrue->Size() == f.enthalpyFes->GetTrueVSize(),
"The base enthalpy vector has the wrong size."
);
}
if (request.basePotentialTrue != nullptr) {
MFEM_VERIFY(request.basePotentialTrue->Size() ==
f.gravityPotentialFes->GetTrueVSize(),
"The base potential vector has the wrong size.");
MFEM_VERIFY(
request.basePotentialTrue->Size() == f.gravityPotentialFes->GetTrueVSize(),
"The base potential vector has the wrong size."
);
}
if (request.enthalpyVariationTrue != nullptr) {
MFEM_VERIFY(request.enthalpyVariationTrue->Size() ==
f.enthalpyFes->GetTrueVSize(),
"The enthalpy variation has the wrong size.");
MFEM_VERIFY(
request.enthalpyVariationTrue->Size() == f.enthalpyFes->GetTrueVSize(),
"The enthalpy variation has the wrong size."
);
}
if (request.potentialVariationTrue != nullptr) {
MFEM_VERIFY(request.potentialVariationTrue->Size() ==
f.gravityPotentialFes->GetTrueVSize(),
"The potential variation has the wrong size.");
MFEM_VERIFY(
request.potentialVariationTrue->Size() == f.gravityPotentialFes->GetTrueVSize(),
"The potential variation has the wrong size."
);
}
if (request.displacementVariationTrue != nullptr) {
MFEM_VERIFY(request.displacementVariationTrue->Size() ==
f.displacementFes->GetTrueVSize(),
"The displacement variation has the wrong size.");
MFEM_VERIFY(
request.displacementVariationTrue->Size() == f.displacementFes->GetTrueVSize(),
"The displacement variation has the wrong size."
);
}
mfem::Vector displacementLocal;
@@ -249,31 +265,26 @@ void assemble_hydrostatic_form(
}
if (request.basePotentialTrue != nullptr) {
true_to_local(*f.gravityPotentialFes, *request.basePotentialTrue,
basePotentialLocal);
true_to_local(*f.gravityPotentialFes, *request.basePotentialTrue, basePotentialLocal);
}
if (request.enthalpyVariationTrue != nullptr) {
true_to_local(*f.enthalpyFes, *request.enthalpyVariationTrue,
enthalpyVariationLocal);
true_to_local(*f.enthalpyFes, *request.enthalpyVariationTrue, enthalpyVariationLocal);
}
if (request.potentialVariationTrue != nullptr) {
true_to_local(*f.gravityPotentialFes, *request.potentialVariationTrue,
potentialVariationLocal);
true_to_local(*f.gravityPotentialFes, *request.potentialVariationTrue, potentialVariationLocal);
}
if (request.displacementVariationTrue != nullptr) {
true_to_local(*f.displacementFes, *request.displacementVariationTrue,
displacementVariationLocal);
true_to_local(*f.displacementFes, *request.displacementVariationTrue, displacementVariationLocal);
}
mfem::Vector localResult(f.enthalpyFes->GetVSize());
localResult = 0.0;
mean_field::mapping::DomainMapper::Workspace workspace(
f.mesh->Dimension());
mean_field::mapping::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> enthalpyDofs;
mfem::Array<int> potentialDofs;
@@ -293,31 +304,26 @@ void assemble_hydrostatic_form(
mfem::Vector potentialShape;
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(transformation != nullptr,
"The hydrostatic kernel received a null "
"element transformation.");
MFEM_VERIFY(
transformation != nullptr, "The hydrostatic kernel received a null "
"element transformation."
);
if (is_vacuum_attribute(transformation->Attribute)) {
continue;
}
const mfem::FiniteElement &enthalpyElement =
*f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &enthalpyElement = *f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &potentialElement =
*f.gravityPotentialFes->GetFE(elementId);
const mfem::FiniteElement &potentialElement = *f.gravityPotentialFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *enthalpyDofTransformation =
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *enthalpyDofTransformation = f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *potentialDofTransformation =
f.gravityPotentialFes->GetElementDofs(elementId, potentialDofs);
@@ -330,8 +336,7 @@ void assemble_hydrostatic_form(
displacementLocal.GetSubVector(displacementDofs, elementDisplacement);
f.compactificationCoordinate->GetSubVector(compactificationDofs,
elementCompactification);
f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification);
if (request.baseEnthalpyTrue != nullptr) {
baseEnthalpyLocal.GetSubVector(enthalpyDofs, elementBaseEnthalpy);
@@ -342,18 +347,15 @@ void assemble_hydrostatic_form(
}
if (request.enthalpyVariationTrue != nullptr) {
enthalpyVariationLocal.GetSubVector(enthalpyDofs,
elementEnthalpyVariation);
enthalpyVariationLocal.GetSubVector(enthalpyDofs, elementEnthalpyVariation);
}
if (request.potentialVariationTrue != nullptr) {
potentialVariationLocal.GetSubVector(potentialDofs,
elementPotentialVariation);
potentialVariationLocal.GetSubVector(potentialDofs, elementPotentialVariation);
}
if (request.displacementVariationTrue != nullptr) {
displacementVariationLocal.GetSubVector(displacementDofs,
elementDisplacementVariation);
displacementVariationLocal.GetSubVector(displacementDofs, elementDisplacementVariation);
}
if (enthalpyDofTransformation != nullptr) {
@@ -372,8 +374,7 @@ void assemble_hydrostatic_form(
}
if (request.potentialVariationTrue != nullptr) {
potentialDofTransformation->InvTransformPrimal(
elementPotentialVariation);
potentialDofTransformation->InvTransformPrimal(elementPotentialVariation);
}
}
@@ -381,34 +382,33 @@ void assemble_hydrostatic_form(
displacementDofTransformation->InvTransformPrimal(elementDisplacement);
if (request.displacementVariationTrue != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacementVariation);
displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation);
}
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification);
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
const mean_field::mapping::ElementDisplacementData displacementData =
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement);
mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement);
const mean_field::mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification);
compactificationElement, elementCompactification
);
const mean_field::mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData};
.displacement = displacementData, .compactification = compactificationData
};
std::optional<mean_field::mapping::ElementDisplacementData>
displacementVariationData;
std::optional<mean_field::mapping::ElementDisplacementData> displacementVariationData;
if (request.displacementVariationTrue != nullptr) {
displacementVariationData.emplace(
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacementVariation));
displacementElement, elementDisplacementVariation
)
);
}
elementResult.SetSize(enthalpyElement.GetDof());
@@ -419,28 +419,27 @@ void assemble_hydrostatic_form(
potentialShape.SetSize(potentialElement.GetDof());
const mfem::IntegrationRule &integrationRule = get_hydrostatic_rule(
f, enthalpyElement, potentialElement, *transformation);
const mfem::IntegrationRule &integrationRule =
get_hydrostatic_rule(f, enthalpyElement, potentialElement, *transformation);
for (int quadraturePoint = 0;
quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadraturePoint);
for (int quadraturePoint = 0; quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadraturePoint);
transformation->SetIntPoint(&integrationPoint);
mean_field::mapping::VolumeMappingContext mappingContext;
const mean_field::mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(mappingData, *transformation,
integrationPoint, workspace,
mappingContext);
const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(mappingStatus == mean_field::mapping::MappingStatus::valid,
MFEM_VERIFY(
mappingStatus == mean_field::mapping::MappingStatus::valid,
"The base mapping is invalid in the "
"hydrostatic kernel. Element: "
<< elementId << ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(mappingStatus));
<< ", status: " << static_cast<int>(mappingStatus)
);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
@@ -453,16 +452,14 @@ void assemble_hydrostatic_form(
const double potentialValue = elementBasePotential * potentialShape;
const double rotationPotential = request.rotation->potential(
mappingContext.mapping.physical_position);
const double rotationPotential =
request.rotation->potential(mappingContext.mapping.physical_position);
baseIntegrand = enthalpyValue + potentialValue - rotationPotential -
request.bernoulliConstant;
baseIntegrand = enthalpyValue + potentialValue - rotationPotential - request.bernoulliConstant;
}
if (request.buildResidual) {
elementResult.Add(mappingContext.quadrature.weight * baseIntegrand,
enthalpyShape);
elementResult.Add(mappingContext.quadrature.weight * baseIntegrand, enthalpyShape);
continue;
}
@@ -477,29 +474,27 @@ void assemble_hydrostatic_form(
materialVariation += elementPotentialVariation * potentialShape;
}
double weightedVariation =
mappingContext.quadrature.weight * materialVariation;
double weightedVariation = mappingContext.quadrature.weight * materialVariation;
if (request.displacementVariationTrue != nullptr) {
mean_field::mapping::VolumeMappingVariation mappingVariation;
const mean_field::mapping::MappingStatus variationStatus =
domainMapper.EvaluateVolumeVariation(
mappingData, *displacementVariationData, *transformation,
integrationPoint, mappingContext, workspace, mappingVariation);
const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation(
mappingData, *displacementVariationData, *transformation, integrationPoint, mappingContext,
workspace, mappingVariation
);
MFEM_VERIFY(variationStatus ==
mean_field::mapping::MappingStatus::valid,
MFEM_VERIFY(
variationStatus == mean_field::mapping::MappingStatus::valid,
"The mapping variation is invalid "
"in the hydrostatic kernel.");
"in the hydrostatic kernel."
);
const double rotationVariation =
request.rotation->potential_directional_derivative(
mappingContext.mapping.physical_position,
mappingVariation.mapping.physical_position_variation);
const double rotationVariation = request.rotation->potential_directional_derivative(
mappingContext.mapping.physical_position, mappingVariation.mapping.physical_position_variation
);
weightedVariation +=
baseIntegrand * mappingVariation.weight_variation -
weightedVariation += baseIntegrand * mappingVariation.weight_variation -
rotationVariation * mappingContext.quadrature.weight;
}
@@ -519,10 +514,15 @@ void assemble_hydrostatic_form(
namespace mean_field::operators::kernels {
void apply_hydrostatic_equilibrium(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation, const mfem::Vector &enthalpyTrue,
const mfem::Vector &potentialTrue, const mfem::Vector &displacementTrue,
const double bernoulliConstant, mfem::Vector &residual) {
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &potentialTrue,
const mfem::Vector &displacementTrue,
const double bernoulliConstant,
mfem::Vector &residual
) {
HydrostaticAssemblyRequest request;
request.rotation = &rotation;
@@ -531,14 +531,16 @@ void apply_hydrostatic_equilibrium(
request.bernoulliConstant = bernoulliConstant;
request.buildResidual = true;
assemble_hydrostatic_form(f, domainMapper, displacementTrue, request,
residual);
assemble_hydrostatic_form(f, domainMapper, displacementTrue, request, residual);
}
void apply_hydrostatic_equilibrium_enthalpy_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &action) {
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.enthalpyVariationTrue = &enthalpyVariationTrue;
@@ -547,9 +549,12 @@ void apply_hydrostatic_equilibrium_enthalpy_action(
}
void apply_hydrostatic_equilibrium_potential_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const mfem::Vector &potentialVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &action) {
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.potentialVariationTrue = &potentialVariationTrue;
@@ -558,9 +563,12 @@ void apply_hydrostatic_equilibrium_potential_action(
}
void apply_hydrostatic_equilibrium_constant_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const double constantVariation, const mfem::Vector &displacementTrue,
mfem::Vector &action) {
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const double constantVariation,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.constantVariation = constantVariation;
@@ -569,12 +577,16 @@ void apply_hydrostatic_equilibrium_constant_action(
}
void apply_hydrostatic_equilibrium_displacement_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &basePotentialTrue,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue,
const mfem::Vector &baseDisplacementTrue,
const double baseBernoulliConstant,
const mfem::Vector &displacementVariationTrue, mfem::Vector &action) {
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.rotation = &rotation;
@@ -583,19 +595,23 @@ void apply_hydrostatic_equilibrium_displacement_action(
request.displacementVariationTrue = &displacementVariationTrue;
request.bernoulliConstant = baseBernoulliConstant;
assemble_hydrostatic_form(f, domainMapper, baseDisplacementTrue, request,
action);
assemble_hydrostatic_form(f, domainMapper, baseDisplacementTrue, request, action);
}
void apply_hydrostatic_equilibrium_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &basePotentialTrue,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue,
const mfem::Vector &baseDisplacementTrue,
const double baseBernoulliConstant,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &potentialVariationTrue, const double constantVariation,
const mfem::Vector &displacementVariationTrue, mfem::Vector &action) {
const mfem::Vector &potentialVariationTrue,
const double constantVariation,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.rotation = &rotation;
@@ -607,7 +623,6 @@ void apply_hydrostatic_equilibrium_action(
request.bernoulliConstant = baseBernoulliConstant;
request.constantVariation = constantVariation;
assemble_hydrostatic_form(f, domainMapper, baseDisplacementTrue, request,
action);
assemble_hydrostatic_form(f, domainMapper, baseDisplacementTrue, request, action);
}
} // namespace mean_field::operators::kernels

View File

@@ -12,24 +12,28 @@ module mean_field;
import :operators.kernels.pressure_force;
namespace {
namespace eos = mean_field::eos;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<
mean_field::utils::domain::Vacuum>(attribute);
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
enum class PressureForceAction { residual, enthalpy, displacement };
void true_to_local(const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector, mfem::Vector &localVector) {
MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"The pressure-force true vector has the wrong size.");
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(
trueVector.Size() == finiteElementSpace.GetTrueVSize(), "The pressure-force true vector has the wrong size."
);
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
@@ -38,16 +42,19 @@ void true_to_local(const mfem::ParFiniteElementSpace &finiteElementSpace,
}
}
void local_to_true(const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector, mfem::Vector &trueVector) {
MFEM_VERIFY(localVector.Size() == finiteElementSpace.GetVSize(),
"The pressure-force local vector has the wrong size.");
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
MFEM_VERIFY(
localVector.Size() == finiteElementSpace.GetVSize(), "The pressure-force local vector has the wrong size."
);
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
@@ -56,10 +63,13 @@ void local_to_true(const mfem::ParFiniteElementSpace &finiteElementSpace,
}
}
[[nodiscard]] int vector_dof_index(const mfem::Ordering::Type ordering,
const int scalarDof, const int component,
[[nodiscard]] int vector_dof_index(
const mfem::Ordering::Type ordering,
const int scalarDof,
const int component,
const int scalarDofCount,
const int dimension) {
const int dimension
) {
if (ordering == mfem::Ordering::byNODES) {
return scalarDof + component * scalarDofCount;
}
@@ -72,8 +82,7 @@ void local_to_true(const mfem::ParFiniteElementSpace &finiteElementSpace,
return -1;
}
[[nodiscard]] int
get_pressure_extra_order(const mean_field::eos::Polytrope &barotrope) {
[[nodiscard]] int get_pressure_extra_order(const mean_field::eos::Polytrope &barotrope) {
/*
* Pressure has the enthalpy dependence
*
@@ -84,49 +93,51 @@ get_pressure_extra_order(const mean_field::eos::Polytrope &barotrope) {
* contribution is therefore n times that order.
*/
const double extraOrder =
barotrope.polytropic_index() *
static_cast<double>(mean_field::field::Enthalpy::Scalar::familyOrder);
barotrope.polytropic_index() * static_cast<double>(mean_field::field::Enthalpy::Scalar::familyOrder);
MFEM_VERIFY(std::isfinite(extraOrder) && extraOrder >= 0.0 &&
extraOrder <=
static_cast<double>(std::numeric_limits<int>::max()),
"The pressure EOS effective polynomial order is invalid.");
MFEM_VERIFY(
std::isfinite(extraOrder) && extraOrder >= 0.0 &&
extraOrder <= static_cast<double>(std::numeric_limits<int>::max()),
"The pressure EOS effective polynomial order is invalid."
);
return static_cast<int>(std::ceil(extraOrder));
}
[[nodiscard]] const mfem::IntegrationRule &
get_pressure_force_rule(const mean_field::fem::FEM &f,
[[nodiscard]] const mfem::IntegrationRule &get_pressure_force_rule(
const mean_field::fem::FEM &f,
const mean_field::eos::Polytrope &barotrope,
const mfem::FiniteElement &enthalpyElement,
const mfem::FiniteElement &displacementElement,
const mfem::ElementTransformation &transformation) {
const mfem::ElementTransformation &transformation
) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
MFEM_VERIFY(enthalpyElement.GetOrder() ==
mean_field::field::Enthalpy::Scalar::familyOrder,
MFEM_VERIFY(
enthalpyElement.GetOrder() == mean_field::field::Enthalpy::Scalar::familyOrder,
"The pressure-force enthalpy element does not match the "
"registered enthalpy field.");
"registered enthalpy field."
);
MFEM_VERIFY(displacementElement.GetOrder() ==
mean_field::field::Displacement::Vector::familyOrder,
MFEM_VERIFY(
displacementElement.GetOrder() == mean_field::field::Displacement::Vector::familyOrder,
"The pressure-force test element does not match the "
"registered displacement field.");
"registered displacement field."
);
const mean_field::quadrature::Query query = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(),
std::array<int, 1>{get_pressure_extra_order(barotrope)},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
const mean_field::quadrature::Query query =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(),
std::array<int, 1>{get_pressure_extra_order(barotrope)}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const mean_field::quadrature::MfemRule rule =
f.quadratureFactory->get(query, transformation.GetGeometryType());
const mean_field::quadrature::MfemRule rule = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(rule.integration_rule != nullptr,
"The quadrature policy did not return a pressure-force "
"integration rule.");
MFEM_VERIFY(
rule.integration_rule != nullptr, "The quadrature policy did not return a pressure-force "
"integration rule."
);
return *rule.integration_rule;
}
@@ -134,42 +145,56 @@ get_pressure_force_rule(const mean_field::fem::FEM &f,
void validate_inputs(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domainMapper,
const mfem::Vector &enthalpyTrue, const mfem::Vector &displacementTrue) {
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue
) {
MFEM_VERIFY(f.mesh != nullptr, "The pressure-force kernel requires a mesh.");
MFEM_VERIFY(f.enthalpyFes != nullptr,
"The pressure-force kernel requires the enthalpy "
"finite-element space.");
MFEM_VERIFY(
f.enthalpyFes != nullptr, "The pressure-force kernel requires the enthalpy "
"finite-element space."
);
MFEM_VERIFY(f.displacementFes != nullptr,
"The pressure-force kernel requires the displacement "
"finite-element space.");
MFEM_VERIFY(
f.displacementFes != nullptr, "The pressure-force kernel requires the displacement "
"finite-element space."
);
MFEM_VERIFY(f.compactificationFes != nullptr,
"The pressure-force kernel requires the compactification "
"finite-element space.");
MFEM_VERIFY(
f.compactificationFes != nullptr, "The pressure-force kernel requires the compactification "
"finite-element space."
);
MFEM_VERIFY(f.compactificationCoordinate != nullptr,
"The pressure-force kernel requires the compactification "
"coordinate.");
MFEM_VERIFY(
f.compactificationCoordinate != nullptr, "The pressure-force kernel requires the compactification "
"coordinate."
);
MFEM_VERIFY(f.quadratureFactory != nullptr,
"The pressure-force kernel requires the quadrature "
"rule factory.");
MFEM_VERIFY(
f.quadratureFactory != nullptr, "The pressure-force kernel requires the quadrature "
"rule factory."
);
MFEM_VERIFY(enthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(),
"The pressure-force enthalpy vector has the wrong size.");
MFEM_VERIFY(
enthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(),
"The pressure-force enthalpy vector has the wrong size."
);
MFEM_VERIFY(displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The pressure-force displacement vector has the wrong size.");
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The pressure-force displacement vector has the wrong size."
);
MFEM_VERIFY(domainMapper.GetDimension() == f.mesh->Dimension(),
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The pressure-force domain-mapper dimension does not match "
"the mesh dimension.");
"the mesh dimension."
);
MFEM_VERIFY(f.displacementFes->GetVDim() == f.mesh->Dimension(),
"The displacement vector dimension does not match the "
"mesh dimension.");
MFEM_VERIFY(
f.displacementFes->GetVDim() == f.mesh->Dimension(), "The displacement vector dimension does not match the "
"mesh dimension."
);
/*
* ElementDisplacementDataFromElementVDofs currently consumes the
@@ -177,9 +202,11 @@ void validate_inputs(
* registry change fails immediately rather than silently
* corrupting the geometry.
*/
MFEM_VERIFY(f.displacementFes->GetOrdering() == mfem::Ordering::byNODES,
MFEM_VERIFY(
f.displacementFes->GetOrdering() == mfem::Ordering::byNODES,
"The pressure-force kernel requires the registered byNODES "
"displacement ordering.");
"displacement ordering."
);
}
void apply_pressure_force_action(
@@ -190,22 +217,25 @@ void apply_pressure_force_action(
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector *enthalpyVariationTrue,
const mfem::Vector *displacementVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
validate_inputs(f, domainMapper, baseEnthalpyTrue, displacementTrue);
if (pressureForceAction == PressureForceAction::enthalpy) {
MFEM_VERIFY(enthalpyVariationTrue != nullptr &&
enthalpyVariationTrue->Size() ==
f.enthalpyFes->GetTrueVSize(),
"The pressure-force enthalpy variation has the wrong size.");
MFEM_VERIFY(
enthalpyVariationTrue != nullptr && enthalpyVariationTrue->Size() == f.enthalpyFes->GetTrueVSize(),
"The pressure-force enthalpy variation has the wrong size."
);
}
if (pressureForceAction == PressureForceAction::displacement) {
MFEM_VERIFY(displacementVariationTrue != nullptr &&
displacementVariationTrue->Size() ==
f.displacementFes->GetTrueVSize(),
MFEM_VERIFY(
displacementVariationTrue != nullptr &&
displacementVariationTrue->Size() == f.displacementFes->GetTrueVSize(),
"The pressure-force displacement variation has the wrong "
"size.");
"size."
);
}
mfem::Vector baseEnthalpyLocal;
@@ -216,22 +246,19 @@ void apply_pressure_force_action(
true_to_local(*f.enthalpyFes, baseEnthalpyTrue, baseEnthalpyLocal);
if (enthalpyVariationTrue != nullptr) {
true_to_local(*f.enthalpyFes, *enthalpyVariationTrue,
enthalpyVariationLocal);
true_to_local(*f.enthalpyFes, *enthalpyVariationTrue, enthalpyVariationLocal);
}
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
if (displacementVariationTrue != nullptr) {
true_to_local(*f.displacementFes, *displacementVariationTrue,
displacementVariationLocal);
true_to_local(*f.displacementFes, *displacementVariationTrue, displacementVariationLocal);
}
mfem::Vector localAction(f.displacementFes->GetVSize());
localAction = 0.0;
mean_field::mapping::DomainMapper::Workspace workspace(
f.mesh->Dimension());
mean_field::mapping::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> enthalpyDofsofs;
mfem::Array<int> displacementDofs;
@@ -255,16 +282,15 @@ void apply_pressure_force_action(
const int dimension = f.mesh->Dimension();
const mfem::Ordering::Type displacementOrdering =
f.displacementFes->GetOrdering();
const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(transformation != nullptr,
"The pressure-force kernel received a null element "
"transformation.");
MFEM_VERIFY(
transformation != nullptr, "The pressure-force kernel received a null element "
"transformation."
);
/*
* Skip vacuum before constructing or evaluating any mapping
@@ -274,17 +300,13 @@ void apply_pressure_force_action(
continue;
}
const mfem::FiniteElement &enthalpyElement =
*f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &enthalpyElement = *f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *enthalpyDofTransformation =
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *enthalpyDofTransformation = f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
@@ -295,19 +317,16 @@ void apply_pressure_force_action(
baseEnthalpyLocal.GetSubVector(enthalpyDofs, elementBaseEnthalpy);
if (enthalpyVariationTrue != nullptr) {
enthalpyVariationLocal.GetSubVector(enthalpyDofs,
elementEnthalpyVariation);
enthalpyVariationLocal.GetSubVector(enthalpyDofs, elementEnthalpyVariation);
}
displacementLocal.GetSubVector(displacementDofs, elementDisplacement);
if (displacementVariationTrue != nullptr) {
displacementVariationLocal.GetSubVector(displacementDofs,
elementDisplacementVariation);
displacementVariationLocal.GetSubVector(displacementDofs, elementDisplacementVariation);
}
f.compactificationCoordinate->GetSubVector(compactificationDofs,
elementCompactification);
f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification);
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(elementBaseEnthalpy);
@@ -321,42 +340,42 @@ void apply_pressure_force_action(
displacementDofTransformation->InvTransformPrimal(elementDisplacement);
if (displacementVariationTrue != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacementVariation);
displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation);
}
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification);
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
const mean_field::mapping::ElementDisplacementData displacementData =
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement);
mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement);
const mean_field::mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification);
compactificationElement, elementCompactification
);
const mean_field::mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData};
.displacement = displacementData, .compactification = compactificationData
};
std::optional<mean_field::mapping::ElementDisplacementData>
displacementVariationData;
std::optional<mean_field::mapping::ElementDisplacementData> displacementVariationData;
if (displacementVariationTrue != nullptr) {
displacementVariationData.emplace(
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacementVariation));
displacementElement, elementDisplacementVariation
)
);
}
const int scalarDisplacementDofCount = displacementElement.GetDof();
MFEM_VERIFY(displacementDofs.Size() ==
scalarDisplacementDofCount * dimension,
MFEM_VERIFY(
displacementDofs.Size() == scalarDisplacementDofCount * dimension,
"The pressure-force element displacement vector has "
"the wrong size.");
"the wrong size."
);
enthalpyShape.SetSize(enthalpyElement.GetDof());
@@ -364,34 +383,30 @@ void apply_pressure_force_action(
displacementDShapePhysical.SetSize(scalarDisplacementDofCount, dimension);
displacementDShapePhysicalVariation.SetSize(scalarDisplacementDofCount,
dimension);
displacementDShapePhysicalVariation.SetSize(scalarDisplacementDofCount, dimension);
elementAction.SetSize(displacementDofs.Size());
elementAction = 0.0;
const mfem::IntegrationRule &integrationRule = get_pressure_force_rule(
f, barotrope, enthalpyElement, displacementElement, *transformation);
const mfem::IntegrationRule &integrationRule =
get_pressure_force_rule(f, barotrope, enthalpyElement, displacementElement, *transformation);
for (int quadratureIndex = 0;
quadratureIndex < integrationRule.GetNPoints(); ++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadratureIndex);
for (int quadratureIndex = 0; quadratureIndex < integrationRule.GetNPoints(); ++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadratureIndex);
transformation->SetIntPoint(&integrationPoint);
const mean_field::mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(mappingData, *transformation,
integrationPoint, workspace,
mappingContext);
const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(mappingStatus == mean_field::mapping::MappingStatus::valid,
MFEM_VERIFY(
mappingStatus == mean_field::mapping::MappingStatus::valid,
"Stateless mapping failed in the pressure-force "
"kernel. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex
<< ", status: " << static_cast<int>(mappingStatus));
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex << ", status: " << static_cast<int>(mappingStatus)
);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
@@ -401,18 +416,20 @@ void apply_pressure_force_action(
if (pressureForceAction == PressureForceAction::residual ||
pressureForceAction == PressureForceAction::displacement) {
pressureFactor = barotrope.pressure_from_enthalpy(enthalpyValue);
} else {
const double enthalpyVariationValue =
elementEnthalpyVariation * enthalpyShape;
pressureFactor =
barotrope.pressure_derivative_from_enthalpy(enthalpyValue) *
eos::evaluate<eos::quantity::Pressure>(barotrope, eos::SpecificEnthalpyValue{enthalpyValue})
.value();
} else {
const double enthalpyVariationValue = elementEnthalpyVariation * enthalpyShape;
pressureFactor = eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{enthalpyValue}
)
.value() *
enthalpyVariationValue;
}
displacementElement.CalcDShape(integrationPoint,
displacementDShapeReference);
displacementElement.CalcDShape(integrationPoint, displacementDShapeReference);
/*
* Row i of DShape is grad_reference(N_i). Multiplication
@@ -421,27 +438,25 @@ void apply_pressure_force_action(
* grad_physical(N_i)
* = grad_reference(N_i) J^{-1}.
*/
mfem::Mult(displacementDShapeReference, mappingContext.quadrature.J_inv,
displacementDShapePhysical);
mfem::Mult(displacementDShapeReference, mappingContext.quadrature.J_inv, displacementDShapePhysical);
std::optional<mean_field::mapping::VolumeMappingVariation>
mappingVariation;
std::optional<mean_field::mapping::VolumeMappingVariation> mappingVariation;
if (pressureForceAction == PressureForceAction::displacement) {
mappingVariation.emplace();
const mean_field::mapping::MappingStatus variationStatus =
domainMapper.EvaluateVolumeVariation(
mappingData, *displacementVariationData, *transformation,
integrationPoint, mappingContext, workspace, *mappingVariation);
const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation(
mappingData, *displacementVariationData, *transformation, integrationPoint, mappingContext,
workspace, *mappingVariation
);
MFEM_VERIFY(
variationStatus == mean_field::mapping::MappingStatus::valid,
"Stateless mapping variation failed in the "
"pressure-force kernel. Element: "
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex
<< ", status: " << static_cast<int>(variationStatus));
<< elementId << ", attribute: " << transformation->Attribute << ", quadrature point: "
<< quadratureIndex << ", status: " << static_cast<int>(variationStatus)
);
/*
* Differentiating
@@ -452,18 +467,19 @@ void apply_pressure_force_action(
* test-gradient variation used by the geometric
* pressure block.
*/
mfem::Mult(displacementDShapeReference,
mappingVariation->inverse_element_jacobian_variation,
displacementDShapePhysicalVariation);
mfem::Mult(
displacementDShapeReference, mappingVariation->inverse_element_jacobian_variation,
displacementDShapePhysicalVariation
);
}
const double weightedPressureFactor =
pressureFactor * mappingContext.quadrature.weight;
const double weightedPressureFactor = pressureFactor * mappingContext.quadrature.weight;
MFEM_VERIFY(std::isfinite(pressureFactor) &&
std::isfinite(weightedPressureFactor),
MFEM_VERIFY(
std::isfinite(pressureFactor) && std::isfinite(weightedPressureFactor),
"The pressure-force kernel encountered a non-finite "
"quadrature value.");
"quadrature value."
);
/*
* For the vector basis N_i e_c,
@@ -475,12 +491,11 @@ void apply_pressure_force_action(
* R_(i,c)
* = -integral P partial_c N_i dV.
*/
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount;
++scalarDof) {
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
for (int component = 0; component < dimension; ++component) {
const int vectorDof =
vector_dof_index(displacementOrdering, scalarDof, component,
scalarDisplacementDofCount, dimension);
const int vectorDof = vector_dof_index(
displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension
);
if (pressureForceAction == PressureForceAction::displacement) {
/*
@@ -494,22 +509,20 @@ void apply_pressure_force_action(
const double gradientWeightVariation =
mappingContext.quadrature.weight *
displacementDShapePhysicalVariation(scalarDof, component) +
mappingVariation->weight_variation *
displacementDShapePhysical(scalarDof, component);
mappingVariation->weight_variation * displacementDShapePhysical(scalarDof, component);
const double contribution =
pressureFactor * gradientWeightVariation;
const double contribution = pressureFactor * gradientWeightVariation;
MFEM_VERIFY(std::isfinite(gradientWeightVariation) &&
std::isfinite(contribution),
MFEM_VERIFY(
std::isfinite(gradientWeightVariation) && std::isfinite(contribution),
"The pressure-force geometry action "
"encountered a non-finite contribution.");
"encountered a non-finite contribution."
);
elementAction(vectorDof) -= contribution;
} else {
elementAction(vectorDof) -=
weightedPressureFactor *
displacementDShapePhysical(scalarDof, component);
weightedPressureFactor * displacementDShapePhysical(scalarDof, component);
}
}
}
@@ -528,33 +541,46 @@ void apply_pressure_force_action(
namespace mean_field::operators::kernels {
void apply_pressure_force_residual(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue, mfem::Vector &residualTrue) {
apply_pressure_force_action(f, domainMapper, barotrope,
PressureForceAction::residual, enthalpyTrue,
nullptr, nullptr, displacementTrue, residualTrue);
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &residualTrue
) {
apply_pressure_force_action(
f, domainMapper, barotrope, PressureForceAction::residual, enthalpyTrue, nullptr, nullptr, displacementTrue,
residualTrue
);
}
void apply_pressure_force_enthalpy_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const mfem::Vector &baseEnthalpyTrue,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
apply_pressure_force_action(f, domainMapper, barotrope,
PressureForceAction::enthalpy, baseEnthalpyTrue,
&enthalpyVariationTrue, nullptr, displacementTrue,
actionTrue);
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
apply_pressure_force_action(
f, domainMapper, barotrope, PressureForceAction::enthalpy, baseEnthalpyTrue, &enthalpyVariationTrue,
nullptr, displacementTrue, actionTrue
);
}
void apply_pressure_force_displacement_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const mfem::Vector &baseEnthalpyTrue,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &displacementVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
apply_pressure_force_action(
f, domainMapper, barotrope, PressureForceAction::displacement,
baseEnthalpyTrue, nullptr, &displacementVariationTrue, displacementTrue,
actionTrue);
f, domainMapper, barotrope, PressureForceAction::displacement, baseEnthalpyTrue, nullptr,
&displacementVariationTrue, displacementTrue, actionTrue
);
}
} // namespace mean_field::operators::kernels

View File

@@ -14,27 +14,25 @@ namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<
mean_field::utils::domain::Vacuum>(attribute);
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
enum class RotationalDisplacementForceAction {
residual,
density,
displacement,
complete
};
enum class RotationalDisplacementForceAction { residual, density, displacement, complete };
void true_to_local(const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector, mfem::Vector &localVector) {
MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(),
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"The rotational-displacement-force true vector has the wrong "
"size.");
"size."
);
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
@@ -43,17 +41,21 @@ void true_to_local(const mfem::ParFiniteElementSpace &finiteElementSpace,
}
}
void local_to_true(const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector, mfem::Vector &trueVector) {
MFEM_VERIFY(localVector.Size() == finiteElementSpace.GetVSize(),
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
MFEM_VERIFY(
localVector.Size() == finiteElementSpace.GetVSize(),
"The rotational-displacement-force local vector has the wrong "
"size.");
"size."
);
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
@@ -62,10 +64,13 @@ void local_to_true(const mfem::ParFiniteElementSpace &finiteElementSpace,
}
}
[[nodiscard]] int vector_dof_index(const mfem::Ordering::Type ordering,
const int scalarDof, const int component,
[[nodiscard]] int vector_dof_index(
const mfem::Ordering::Type ordering,
const int scalarDof,
const int component,
const int scalarDofCount,
const int dimension) {
const int dimension
) {
if (ordering == mfem::Ordering::byNODES) {
return scalarDof + component * scalarDofCount;
}
@@ -74,52 +79,58 @@ void local_to_true(const mfem::ParFiniteElementSpace &finiteElementSpace,
return scalarDof * dimension + component;
}
MFEM_ABORT("The rotational-displacement-force test space uses an "
"unsupported ordering.");
MFEM_ABORT(
"The rotational-displacement-force test space uses an "
"unsupported ordering."
);
return -1;
}
[[nodiscard]] const mfem::IntegrationRule &
get_rotation_force_rule(const mean_field::fem::FEM &f,
[[nodiscard]] const mfem::IntegrationRule &get_rotation_force_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &densityElement,
const mfem::FiniteElement &displacementElement,
const mfem::ElementTransformation &transformation) {
using DisplacementField =
mean_field::field::Field<mean_field::field::Displacement>;
const mfem::ElementTransformation &transformation
) {
using DisplacementField = mean_field::field::Field<mean_field::field::Displacement>;
MFEM_VERIFY(densityElement.GetOrder() ==
mean_field::field::Density::Scalar::familyOrder,
MFEM_VERIFY(
densityElement.GetOrder() == mean_field::field::Density::Scalar::familyOrder,
"The rotational-displacement-force density element does not "
"match the registered density field.");
"match the registered density field."
);
MFEM_VERIFY(displacementElement.GetOrder() ==
mean_field::field::Displacement::Vector::familyOrder,
MFEM_VERIFY(
displacementElement.GetOrder() == mean_field::field::Displacement::Vector::familyOrder,
"The rotational-displacement-force test element does not match "
"the registered displacement field.");
"the registered displacement field."
);
/*
* grad(Psi_rotation) is linear in physical position, so it adds one
* dynamic polynomial-order contribution.
*/
const mean_field::quadrature::Query query = DisplacementField::make_query<
mean_field::field::Displacement::Form::CentrifugalForce>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), std::array<int, 1>{1},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
const mean_field::quadrature::Query query =
DisplacementField::make_query<mean_field::field::Displacement::Form::CentrifugalForce>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array<int, 1>{1},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
const mean_field::quadrature::MfemRule rule =
f.quadratureFactory->get(query, transformation.GetGeometryType());
const mean_field::quadrature::MfemRule rule = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(rule.integration_rule != nullptr,
"The quadrature policy did not return a rotational-"
"displacement-force integration rule.");
MFEM_VERIFY(
rule.integration_rule != nullptr, "The quadrature policy did not return a rotational-"
"displacement-force integration rule."
);
return *rule.integration_rule;
}
void validate_finite_vector(const mfem::Vector &vector, const char *message) {
void validate_finite_vector(
const mfem::Vector &vector,
const char *message
) {
for (int index = 0; index < vector.Size(); ++index) {
MFEM_VERIFY(std::isfinite(vector(index)), message);
}
@@ -128,51 +139,65 @@ void validate_finite_vector(const mfem::Vector &vector, const char *message) {
void validate_common_inputs(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domainMapper,
const mfem::Vector &displacementTrue) {
MFEM_VERIFY(f.mesh != nullptr,
"The rotational-displacement-force kernel requires a mesh.");
const mfem::Vector &displacementTrue
) {
MFEM_VERIFY(f.mesh != nullptr, "The rotational-displacement-force kernel requires a mesh.");
MFEM_VERIFY(f.mesh->Dimension() == 3,
"The rotational-displacement-force kernel requires a "
"three-dimensional mesh.");
MFEM_VERIFY(
f.mesh->Dimension() == 3, "The rotational-displacement-force kernel requires a "
"three-dimensional mesh."
);
MFEM_VERIFY(f.densityFes != nullptr,
"The rotational-displacement-force kernel requires the density "
"finite-element space.");
MFEM_VERIFY(
f.densityFes != nullptr, "The rotational-displacement-force kernel requires the density "
"finite-element space."
);
MFEM_VERIFY(f.displacementFes != nullptr,
MFEM_VERIFY(
f.displacementFes != nullptr, "The rotational-displacement-force kernel requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr && f.compactificationCoordinate != nullptr,
"The rotational-displacement-force kernel requires the "
"displacement finite-element space.");
"compactification coordinate."
);
MFEM_VERIFY(f.compactificationFes != nullptr &&
f.compactificationCoordinate != nullptr,
"The rotational-displacement-force kernel requires the "
"compactification coordinate.");
MFEM_VERIFY(
f.quadratureFactory != nullptr, "The rotational-displacement-force kernel requires the "
"quadrature-rule factory."
);
MFEM_VERIFY(f.quadratureFactory != nullptr,
"The rotational-displacement-force kernel requires the "
"quadrature-rule factory.");
MFEM_VERIFY(displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The rotational-displacement-force displacement vector has the "
"wrong size.");
"wrong size."
);
MFEM_VERIFY(domainMapper.GetDimension() == f.mesh->Dimension(),
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The rotational-displacement-force mapper dimension does not "
"match the mesh dimension.");
"match the mesh dimension."
);
MFEM_VERIFY(f.displacementFes->GetVDim() == f.mesh->Dimension(),
MFEM_VERIFY(
f.displacementFes->GetVDim() == f.mesh->Dimension(),
"The rotational-displacement-force displacement dimension does "
"not match the mesh dimension.");
"not match the mesh dimension."
);
validate_finite_vector(
displacementTrue,
"The rotational-displacement-force displacement contains a "
"non-finite value.");
displacementTrue, "The rotational-displacement-force displacement contains a "
"non-finite value."
);
}
void validate_density(const mean_field::fem::FEM &f,
const mfem::Vector &density, const char *message) {
void validate_density(
const mean_field::fem::FEM &f,
const mfem::Vector &density,
const char *message
) {
MFEM_VERIFY(density.Size() == f.densityFes->GetTrueVSize(), message);
validate_finite_vector(density, message);
}
@@ -185,53 +210,55 @@ void apply_rotational_displacement_force_action(
const mfem::Vector *baseDensityTrue,
const mfem::Vector *densityVariationTrue,
const mfem::Vector *displacementVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
validate_common_inputs(f, domainMapper, displacementTrue);
const bool needsBaseDensity =
requestedAction == RotationalDisplacementForceAction::residual ||
const bool needsBaseDensity = requestedAction == RotationalDisplacementForceAction::residual ||
requestedAction == RotationalDisplacementForceAction::displacement ||
requestedAction == RotationalDisplacementForceAction::complete;
const bool needsDensityVariation =
requestedAction == RotationalDisplacementForceAction::density ||
const bool needsDensityVariation = requestedAction == RotationalDisplacementForceAction::density ||
requestedAction == RotationalDisplacementForceAction::complete;
const bool needsDisplacementVariation =
requestedAction == RotationalDisplacementForceAction::displacement ||
const bool needsDisplacementVariation = requestedAction == RotationalDisplacementForceAction::displacement ||
requestedAction == RotationalDisplacementForceAction::complete;
if (needsBaseDensity) {
MFEM_VERIFY(baseDensityTrue != nullptr,
"The rotational-displacement-force action requires a base "
"density.");
MFEM_VERIFY(
baseDensityTrue != nullptr, "The rotational-displacement-force action requires a base "
"density."
);
validate_density(
f, *baseDensityTrue,
"The rotational-displacement-force base density is invalid.");
validate_density(f, *baseDensityTrue, "The rotational-displacement-force base density is invalid.");
}
if (needsDensityVariation) {
MFEM_VERIFY(densityVariationTrue != nullptr,
"The rotational-displacement-force action requires a "
"density variation.");
MFEM_VERIFY(
densityVariationTrue != nullptr, "The rotational-displacement-force action requires a "
"density variation."
);
validate_density(f, *densityVariationTrue,
validate_density(
f, *densityVariationTrue,
"The rotational-displacement-force density variation is "
"invalid.");
"invalid."
);
}
if (needsDisplacementVariation) {
MFEM_VERIFY(displacementVariationTrue != nullptr &&
displacementVariationTrue->Size() ==
f.displacementFes->GetTrueVSize(),
MFEM_VERIFY(
displacementVariationTrue != nullptr &&
displacementVariationTrue->Size() == f.displacementFes->GetTrueVSize(),
"The rotational-displacement-force displacement variation "
"is invalid.");
"is invalid."
);
validate_finite_vector(
*displacementVariationTrue,
"The rotational-displacement-force displacement variation "
"contains a non-finite value.");
*displacementVariationTrue, "The rotational-displacement-force displacement variation "
"contains a non-finite value."
);
}
mfem::Vector baseDensityLocal;
@@ -250,15 +277,13 @@ void apply_rotational_displacement_force_action(
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
if (needsDisplacementVariation) {
true_to_local(*f.displacementFes, *displacementVariationTrue,
displacementVariationLocal);
true_to_local(*f.displacementFes, *displacementVariationTrue, displacementVariationLocal);
}
mfem::Vector localAction(f.displacementFes->GetVSize());
localAction = 0.0;
mean_field::mapping::DomainMapper::Workspace workspace(
f.mesh->Dimension());
mean_field::mapping::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> densityDofs;
mfem::Array<int> displacementDofs;
@@ -284,16 +309,15 @@ void apply_rotational_displacement_force_action(
const int dimension = f.mesh->Dimension();
const mfem::Ordering::Type displacementOrdering =
f.displacementFes->GetOrdering();
const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(transformation != nullptr,
"The rotational-displacement-force kernel received a null "
"element transformation.");
MFEM_VERIFY(
transformation != nullptr, "The rotational-displacement-force kernel received a null "
"element transformation."
);
if (is_vacuum_attribute(transformation->Attribute)) {
continue;
@@ -301,14 +325,11 @@ void apply_rotational_displacement_force_action(
const mfem::FiniteElement &densityElement = *f.densityFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *densityDofTransformation =
f.densityFes->GetElementDofs(elementId, densityDofs);
mfem::DofTransformation *densityDofTransformation = f.densityFes->GetElementDofs(elementId, densityDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
@@ -327,12 +348,10 @@ void apply_rotational_displacement_force_action(
displacementLocal.GetSubVector(displacementDofs, elementDisplacement);
if (needsDisplacementVariation) {
displacementVariationLocal.GetSubVector(displacementDofs,
elementDisplacementVariation);
displacementVariationLocal.GetSubVector(displacementDofs, elementDisplacementVariation);
}
f.compactificationCoordinate->GetSubVector(compactificationDofs,
elementCompactification);
f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification);
if (densityDofTransformation != nullptr) {
if (needsBaseDensity) {
@@ -348,42 +367,42 @@ void apply_rotational_displacement_force_action(
displacementDofTransformation->InvTransformPrimal(elementDisplacement);
if (needsDisplacementVariation) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacementVariation);
displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation);
}
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification);
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
const mean_field::mapping::ElementDisplacementData displacementData =
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement);
mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement);
const mean_field::mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification);
compactificationElement, elementCompactification
);
const mean_field::mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData};
.displacement = displacementData, .compactification = compactificationData
};
std::optional<mean_field::mapping::ElementDisplacementData>
displacementVariationData;
std::optional<mean_field::mapping::ElementDisplacementData> displacementVariationData;
if (needsDisplacementVariation) {
displacementVariationData.emplace(
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacementVariation));
displacementElement, elementDisplacementVariation
)
);
}
const int scalarDisplacementDofCount = displacementElement.GetDof();
MFEM_VERIFY(displacementDofs.Size() ==
scalarDisplacementDofCount * dimension,
MFEM_VERIFY(
displacementDofs.Size() == scalarDisplacementDofCount * dimension,
"The rotational-displacement-force element displacement "
"vector has the wrong size.");
"vector has the wrong size."
);
densityShape.SetSize(densityElement.GetDof());
displacementShape.SetSize(scalarDisplacementDofCount);
@@ -396,42 +415,39 @@ void apply_rotational_displacement_force_action(
elementAction.SetSize(displacementDofs.Size());
elementAction = 0.0;
const mfem::IntegrationRule &integrationRule = get_rotation_force_rule(
f, densityElement, displacementElement, *transformation);
const mfem::IntegrationRule &integrationRule =
get_rotation_force_rule(f, densityElement, displacementElement, *transformation);
for (int quadratureIndex = 0;
quadratureIndex < integrationRule.GetNPoints(); ++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadratureIndex);
for (int quadratureIndex = 0; quadratureIndex < integrationRule.GetNPoints(); ++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadratureIndex);
transformation->SetIntPoint(&integrationPoint);
const mean_field::mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(mappingData, *transformation,
integrationPoint, workspace,
mappingContext);
const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(mappingStatus == mean_field::mapping::MappingStatus::valid,
MFEM_VERIFY(
mappingStatus == mean_field::mapping::MappingStatus::valid,
"Stateless mapping failed in the rotational-"
"displacement-force kernel. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex
<< ", status: " << static_cast<int>(mappingStatus));
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex << ", status: " << static_cast<int>(mappingStatus)
);
if (needsDisplacementVariation) {
const mean_field::mapping::MappingStatus variationStatus =
domainMapper.EvaluateVolumeVariation(
mappingData, *displacementVariationData, *transformation,
integrationPoint, mappingContext, workspace, mappingVariation);
const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation(
mappingData, *displacementVariationData, *transformation, integrationPoint, mappingContext,
workspace, mappingVariation
);
MFEM_VERIFY(
variationStatus == mean_field::mapping::MappingStatus::valid,
"Stateless mapping variation failed in the "
"rotational-displacement-force kernel. Element: "
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex
<< ", status: " << static_cast<int>(variationStatus));
<< elementId << ", attribute: " << transformation->Attribute << ", quadrature point: "
<< quadratureIndex << ", status: " << static_cast<int>(variationStatus)
);
}
densityElement.CalcShape(integrationPoint, densityShape);
@@ -449,16 +465,15 @@ void apply_rotational_displacement_force_action(
densityVariationValue = elementDensityVariation * densityShape;
}
rotation.potential_gradient(mappingContext.mapping.physical_position,
potentialGradient);
rotation.potential_gradient(mappingContext.mapping.physical_position, potentialGradient);
centrifugalAcceleration = potentialGradient;
centrifugalAcceleration *= -1.0;
if (needsDisplacementVariation) {
rotation.potential_gradient_directional_derivative(
mappingVariation.mapping.physical_position_variation,
potentialGradientVariation);
mappingVariation.mapping.physical_position_variation, potentialGradientVariation
);
centrifugalAccelerationVariation = potentialGradientVariation;
@@ -470,38 +485,37 @@ void apply_rotational_displacement_force_action(
weightedForce = 0.0;
if (requestedAction == RotationalDisplacementForceAction::residual) {
weightedForce.Add(baseDensityValue * mappingContext.quadrature.weight,
centrifugalAcceleration);
weightedForce.Add(baseDensityValue * mappingContext.quadrature.weight, centrifugalAcceleration);
} else {
if (needsDensityVariation) {
weightedForce.Add(densityVariationValue *
mappingContext.quadrature.weight,
centrifugalAcceleration);
weightedForce.Add(
densityVariationValue * mappingContext.quadrature.weight, centrifugalAcceleration
);
}
if (needsDisplacementVariation) {
weightedForce.Add(baseDensityValue * mappingContext.quadrature.weight,
centrifugalAccelerationVariation);
weightedForce.Add(
baseDensityValue * mappingContext.quadrature.weight, centrifugalAccelerationVariation
);
weightedForce.Add(baseDensityValue *
mappingVariation.weight_variation,
centrifugalAcceleration);
weightedForce.Add(
baseDensityValue * mappingVariation.weight_variation, centrifugalAcceleration
);
}
}
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount;
++scalarDof) {
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
for (int component = 0; component < dimension; ++component) {
const int vectorDof =
vector_dof_index(displacementOrdering, scalarDof, component,
scalarDisplacementDofCount, dimension);
const int vectorDof = vector_dof_index(
displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension
);
const double contribution =
displacementShape(scalarDof) * weightedForce(component);
const double contribution = displacementShape(scalarDof) * weightedForce(component);
MFEM_VERIFY(std::isfinite(contribution),
"The rotational-displacement-force kernel "
"encountered a non-finite contribution.");
MFEM_VERIFY(
std::isfinite(contribution), "The rotational-displacement-force kernel "
"encountered a non-finite contribution."
);
elementAction(vectorDof) += contribution;
}
@@ -521,44 +535,61 @@ void apply_rotational_displacement_force_action(
namespace mean_field::operators::kernels {
void apply_rotational_displacement_force_residual(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation, const mfem::Vector &densityTrue,
const mfem::Vector &displacementTrue, mfem::Vector &residualTrue) {
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &densityTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &residualTrue
) {
apply_rotational_displacement_force_action(
f, domainMapper, rotation, RotationalDisplacementForceAction::residual,
&densityTrue, nullptr, nullptr, displacementTrue, residualTrue);
f, domainMapper, rotation, RotationalDisplacementForceAction::residual, &densityTrue, nullptr, nullptr,
displacementTrue, residualTrue
);
}
void apply_rotational_displacement_force_density_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
apply_rotational_displacement_force_action(
f, domainMapper, rotation, RotationalDisplacementForceAction::density,
nullptr, &densityVariationTrue, nullptr, displacementTrue, actionTrue);
f, domainMapper, rotation, RotationalDisplacementForceAction::density, nullptr, &densityVariationTrue,
nullptr, displacementTrue, actionTrue
);
}
void apply_rotational_displacement_force_displacement_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation, const mfem::Vector &baseDensityTrue,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseDensityTrue,
const mfem::Vector &displacementVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
apply_rotational_displacement_force_action(
f, domainMapper, rotation,
RotationalDisplacementForceAction::displacement, &baseDensityTrue,
nullptr, &displacementVariationTrue, displacementTrue, actionTrue);
f, domainMapper, rotation, RotationalDisplacementForceAction::displacement, &baseDensityTrue, nullptr,
&displacementVariationTrue, displacementTrue, actionTrue
);
}
void apply_rotational_displacement_force_complete_action(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation, const mfem::Vector &baseDensityTrue,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseDensityTrue,
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,
const mfem::Vector &displacementTrue, mfem::Vector &actionTrue) {
const mfem::Vector &displacementTrue,
mfem::Vector &actionTrue
) {
apply_rotational_displacement_force_action(
f, domainMapper, rotation, RotationalDisplacementForceAction::complete,
&baseDensityTrue, &densityVariationTrue, &displacementVariationTrue,
displacementTrue, actionTrue);
f, domainMapper, rotation, RotationalDisplacementForceAction::complete, &baseDensityTrue,
&densityVariationTrue, &displacementVariationTrue, displacementTrue, actionTrue
);
}
} // namespace mean_field::operators::kernels

View File

@@ -401,8 +401,14 @@ namespace mean_field::operators {
const double density = elementBaseDensity * densityShape;
const double enthalpy = elementBaseEnthalpy * enthalpyShape;
const double quadratureWeight = mappingContext.quadrature.weight;
const double eosDensity = m_equationOfState.density_from_enthalpy(enthalpy);
const double enthalpyDerivative = m_equationOfState.density_derivative_from_enthalpy(enthalpy);
const eos::SpecificEnthalpyValue specificEnthalpy{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(
std::isfinite(quadratureWeight) && quadratureWeight > 0.0 && std::isfinite(eosDensity) &&

View File

@@ -12,32 +12,32 @@ namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
int get_operator_height(const mean_field::fem::FEM &f) {
MFEM_VERIFY(f.gravityPotentialFes != nullptr,
"PreparedMappedGravitySourceOperator requires the "
MFEM_VERIFY(
f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the "
"gravity-potential "
"finite-element space.");
return mean_field::field::make_field_dof_map<mean_field::field::Gravity,
DomainSchema>(
*f.gravityPotentialFes)
"finite-element space."
);
return mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes)
.reduced_size();
}
int get_operator_width(const mean_field::fem::FEM &f) {
MFEM_VERIFY(f.densityFes != nullptr,
"PreparedMappedGravitySourceOperator requires the density "
"finite-element space.");
return mean_field::field::make_field_dof_map<mean_field::field::Density,
DomainSchema>(*f.densityFes)
MFEM_VERIFY(
f.densityFes != nullptr, "PreparedMappedGravitySourceOperator requires the density "
"finite-element space."
);
return mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*f.densityFes)
.reduced_size();
}
void true_to_local(const mfem::ParFiniteElementSpace &finite_element_space,
void true_to_local(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &true_vector,
mfem::Vector &local_vector) {
mfem::Vector &local_vector
) {
local_vector.SetSize(finite_element_space.GetVSize());
const mfem::Operator *prolongation =
finite_element_space.GetProlongationMatrix();
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(true_vector, local_vector);
@@ -46,17 +46,17 @@ void true_to_local(const mfem::ParFiniteElementSpace &finite_element_space,
}
}
void local_to_true(const mfem::ParFiniteElementSpace &finite_element_space,
void local_to_true(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &local_vector,
mfem::Vector &true_vector) {
MFEM_VERIFY(local_vector.Size() == finite_element_space.GetVSize(),
"Local vector has the wrong size.");
mfem::Vector &true_vector
) {
MFEM_VERIFY(local_vector.Size() == finite_element_space.GetVSize(), "Local vector has the wrong size.");
true_vector.SetSize(finite_element_space.GetTrueVSize());
true_vector = 0.0;
const mfem::Operator *prolongation =
finite_element_space.GetProlongationMatrix();
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(local_vector, true_vector);
@@ -65,28 +65,30 @@ void local_to_true(const mfem::ParFiniteElementSpace &finite_element_space,
}
}
const mfem::IntegrationRule &
get_source_rule(const mean_field::fem::FEM &f,
const mfem::IntegrationRule &get_source_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &density_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() ==
mean_field::field::Density::Scalar::familyOrder,
MFEM_VERIFY(
density_element.GetOrder() == mean_field::field::Density::Scalar::familyOrder,
"The prepared source trial element does not match the registered "
"density field.");
MFEM_VERIFY(potential_element.GetOrder() ==
mean_field::field::Gravity::Potential::familyOrder,
"density field."
);
MFEM_VERIFY(
potential_element.GetOrder() == mean_field::field::Gravity::Potential::familyOrder,
"The prepared source test element does not match the registered "
"gravity potential.");
const mean_field::quadrature::Query query = 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);
"gravity potential."
);
const mean_field::quadrature::Query query =
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())
.integration_rule;
return *f.quadratureFactory->get(query, transformation.GetGeometryType()).integration_rule;
}
class FrozenMappedGravitySourceCoefficient final : public mfem::Coefficient {
@@ -94,43 +96,46 @@ public:
FrozenMappedGravitySourceCoefficient(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::Vector &displacement_true)
: m_fem(f), m_domain_mapper(domain_mapper),
const mfem::Vector &displacement_true
)
: m_fem(f),
m_domain_mapper(domain_mapper),
m_workspace(domain_mapper.GetDimension()) {
true_to_local(*m_fem.displacementFes, displacement_true,
m_displacement_local);
true_to_local(*m_fem.displacementFes, displacement_true, m_displacement_local);
}
double Eval(mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point) override {
double Eval(
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(),
MFEM_VERIFY(
element_id >= 0 && element_id < m_fem.mesh->GetNE(),
"Mapped gravity source coefficient received an invalid element "
"ID.");
if (DomainSchema::template attribute_belongs_to<
mean_field::utils::domain::Vacuum>(transformation.Attribute)) {
"ID."
);
if (DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(
transformation.Attribute
)) {
return 0.0;
}
LoadElement(element_id);
const mean_field::mapping::ElementMappingData mapping_data{
.displacement = *m_displacement_data,
.compactification = *m_compactification_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);
const mean_field::mapping::MappingStatus status = m_domain_mapper.EvaluateVolume(
mapping_data, transformation, integration_point, m_workspace, mapping_context
);
if (status != mean_field::mapping::MappingStatus::valid) {
const mfem::FiniteElement &displacement_element =
*m_fem.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*m_fem.compactificationFes->GetFE(element_id);
const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id);
mfem::Vector displacement_shape(displacement_element.GetDof());
mfem::Vector compactification_shape(compactification_element.GetDof());
@@ -138,41 +143,36 @@ public:
mfem::Vector displacement_value(m_domain_mapper.GetDimension());
displacement_element.CalcShape(integration_point, displacement_shape);
compactification_element.CalcShape(integration_point,
compactification_shape);
compactification_element.CalcShape(integration_point, compactification_shape);
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 =
m_compactification_data->GetDofs() * compactification_shape;
const double compactification_coordinate = m_compactification_data->GetDofs() * compactification_shape;
MFEM_ABORT(
"Stateless domain mapping failed while preparing the "
"gravity "
"source operator."
<< "\nMapping status = " << static_cast<int>(status)
<< "\nElement ID = " << element_id
<< "\nMapping status = " << static_cast<int>(status) << "\nElement ID = " << element_id
<< "\nElement attribute = " << transformation.Attribute
<< "\nIntegration-point index = " << integration_point.index
<< "\nIntegration point = <" << integration_point.x << ", "
<< integration_point.y << ", " << integration_point.z << ">"
<< "\nReference position = <" << reference_position(0) << ", "
<< reference_position(1) << ", " << reference_position(2) << ">"
<< "\nReference radius = " << reference_position.Norml2()
<< "\nDisplacement value = <" << displacement_value(0) << ", "
<< displacement_value(1) << ", " << displacement_value(2) << ">"
<< "\nIntegration-point index = " << integration_point.index << "\nIntegration point = <"
<< integration_point.x << ", " << integration_point.y << ", " << integration_point.z << ">"
<< "\nReference position = <" << reference_position(0) << ", " << reference_position(1) << ", "
<< reference_position(2) << ">"
<< "\nReference radius = " << reference_position.Norml2() << "\nDisplacement value = <"
<< displacement_value(0) << ", " << displacement_value(1) << ", " << displacement_value(2) << ">"
<< "\nDisplacement magnitude = " << displacement_value.Norml2()
<< "\nCompactification coordinate = " << compactification_coordinate
<< "\nDisplacement ordering = "
<< static_cast<int>(m_fem.displacementFes->GetOrdering()));
<< "\nDisplacement ordering = " << static_cast<int>(m_fem.displacementFes->GetOrdering())
);
}
const double mapping_determinant =
mapping_context.mapping.mapping_determinant;
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 "
"or "
"non-finite mapping determinant.");
"non-finite mapping determinant."
);
return 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant;
}
@@ -183,40 +183,34 @@ private:
return;
}
const mfem::FiniteElement &displacement_element =
*m_fem.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*m_fem.compactificationFes->GetFE(element_id);
const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id);
mfem::DofTransformation *displacement_dof_transformation =
m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs);
mfem::DofTransformation *compactification_dof_transformation =
m_fem.compactificationFes->GetElementDofs(element_id,
m_compactification_dofs);
m_fem.compactificationFes->GetElementDofs(element_id, m_compactification_dofs);
m_displacement_local.GetSubVector(m_displacement_dofs,
m_element_displacement);
m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs,
m_element_compactification);
m_displacement_local.GetSubVector(m_displacement_dofs, m_element_displacement);
m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs, m_element_compactification);
if (displacement_dof_transformation != nullptr) {
displacement_dof_transformation->InvTransformPrimal(
m_element_displacement);
displacement_dof_transformation->InvTransformPrimal(m_element_displacement);
}
if (compactification_dof_transformation != nullptr) {
compactification_dof_transformation->InvTransformPrimal(
m_element_compactification);
compactification_dof_transformation->InvTransformPrimal(m_element_compactification);
}
m_displacement_data =
std::make_unique<mean_field::mapping::ElementDisplacementData>(
m_displacement_data = std::make_unique<mean_field::mapping::ElementDisplacementData>(
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacement_element, m_element_displacement));
displacement_element, m_element_displacement
)
);
m_compactification_data =
std::make_unique<mean_field::mapping::ElementCompactificationData>(
compactification_element, m_element_compactification);
m_compactification_data = std::make_unique<mean_field::mapping::ElementCompactificationData>(
compactification_element, m_element_compactification
);
m_cached_element_id = element_id;
}
@@ -232,10 +226,8 @@ private:
mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification;
std::unique_ptr<mean_field::mapping::ElementDisplacementData>
m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
m_compactification_data;
std::unique_ptr<mean_field::mapping::ElementDisplacementData> m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData> m_compactification_data;
mean_field::mapping::DomainMapper::Workspace m_workspace;
int m_cached_element_id{-1};
@@ -244,59 +236,79 @@ private:
namespace mean_field::operators {
PreparedMappedGravitySourceOperator::PreparedMappedGravitySourceOperator(
const fem::FEM &f, const mapping::DomainMapper &domain_mapper)
: Operator(get_operator_height(f), get_operator_width(f)), m_fem(f),
const fem::FEM &f,
const mapping::DomainMapper &domain_mapper
)
: Operator(
get_operator_height(f),
get_operator_width(f)
),
m_fem(f),
m_domain_mapper(domain_mapper),
m_density_map(field::make_field_dof_map<field::Density, DomainSchema>(
*f.densityFes)),
m_potential_map(field::make_field_dof_map<field::Gravity, DomainSchema>(
*f.gravityPotentialFes)),
m_density_map(
field::make_field_dof_map<
field::Density,
DomainSchema>(*f.densityFes)
),
m_potential_map(
field::make_field_dof_map<
field::Gravity,
DomainSchema>(*f.gravityPotentialFes)
),
m_displacement_map(
field::make_field_dof_map<field::Displacement, DomainSchema>(
*f.displacementFes)) {
MFEM_VERIFY(f.mesh != nullptr,
"PreparedMappedGravitySourceOperator requires a mesh.");
MFEM_VERIFY(f.densityFes != nullptr,
"PreparedMappedGravitySourceOperator requires the density "
"finite-element space.");
MFEM_VERIFY(f.gravityPotentialFes != nullptr,
"PreparedMappedGravitySourceOperator requires the "
"gravity-potential "
"finite-element space.");
MFEM_VERIFY(f.displacementFes != nullptr,
"PreparedMappedGravitySourceOperator requires "
"the displacement finite-element space.");
field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
) {
MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedGravitySourceOperator requires a mesh.");
MFEM_VERIFY(
f.compactificationFes != nullptr,
"PreparedMappedGravitySourceOperator requires the compactification "
"finite-element space.");
f.densityFes != nullptr, "PreparedMappedGravitySourceOperator requires the density "
"finite-element space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the "
"gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
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(),
"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.");
"dimension."
);
m_stellar_marker =
utils::domain::make_attribute_marker<utils::domain::Stellar,
DomainSchema>(*f.mesh);
m_stellar_marker = 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(),
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.");
"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.");
MFEM_VERIFY(
std::isfinite(displacement(i)), "PreparedMappedGravitySourceOperator received a non-finite "
"displacement value."
);
}
m_is_prepared = false;
@@ -305,14 +317,12 @@ void PreparedMappedGravitySourceOperator::Prepare(
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
FrozenMappedGravitySourceCoefficient source_coefficient(
m_fem, m_domain_mapper, m_displacement_true);
FrozenMappedGravitySourceCoefficient source_coefficient(m_fem, m_domain_mapper, m_displacement_true);
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) {
if (attribute <= 0 || attribute > m_stellar_marker.Size() || m_stellar_marker[attribute - 1] == 0) {
continue;
}
@@ -321,24 +331,19 @@ void PreparedMappedGravitySourceOperator::Prepare(
data.element_id = element_id;
data.density_dof_transformation =
m_fem.densityFes->GetElementDofs(element_id, data.density_dofs);
data.density_dof_transformation = m_fem.densityFes->GetElementDofs(element_id, data.density_dofs);
data.potential_dof_transformation =
m_fem.gravityPotentialFes->GetElementDofs(element_id,
data.potential_dofs);
m_fem.gravityPotentialFes->GetElementDofs(element_id, data.potential_dofs);
const mfem::FiniteElement &density_element =
*m_fem.densityFes->GetFE(element_id);
const mfem::FiniteElement &density_element = *m_fem.densityFes->GetFE(element_id);
const mfem::FiniteElement &potential_element =
*m_fem.gravityPotentialFes->GetFE(element_id);
const mfem::FiniteElement &potential_element = *m_fem.gravityPotentialFes->GetFE(element_id);
mfem::ElementTransformation &transformation =
*m_fem.mesh->GetElementTransformation(element_id);
mfem::ElementTransformation &transformation = *m_fem.mesh->GetElementTransformation(element_id);
const mfem::IntegrationRule &integration_rule = get_source_rule(
m_fem, density_element, potential_element, transformation);
const mfem::IntegrationRule &integration_rule =
get_source_rule(m_fem, density_element, potential_element, transformation);
const int quadrature_point_count = integration_rule.GetNPoints();
@@ -355,10 +360,8 @@ void PreparedMappedGravitySourceOperator::Prepare(
mfem::Vector density_shape(density_dof_count);
mfem::Vector potential_shape(potential_dof_count);
for (int quadrature_point = 0; quadrature_point < quadrature_point_count;
++quadrature_point) {
const mfem::IntegrationPoint &integration_point =
integration_rule.IntPoint(quadrature_point);
for (int quadrature_point = 0; quadrature_point < quadrature_point_count; ++quadrature_point) {
const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(quadrature_point);
transformation.SetIntPoint(&integration_point);
@@ -376,40 +379,41 @@ void PreparedMappedGravitySourceOperator::Prepare(
data.potential_basis(quadrature_point, i) = potential_shape(i);
}
const double coefficient_value =
source_coefficient.Eval(transformation, integration_point);
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;
const double quadrature_value = integration_point.weight * transformation.Weight() * coefficient_value;
MFEM_VERIFY(std::isfinite(quadrature_value) && quadrature_value > 0.0,
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
<< ".");
<< element_id << ", quadrature point " << quadrature_point << "."
);
data.quadrature_data(quadrature_point) = quadrature_value;
}
}
MFEM_VERIFY(!m_elements.empty(),
"PreparedMappedGravitySourceOperator found no stellar elements.");
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.");
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(),
"PreparedMappedGravitySourceOperator received a density vector "
"with the wrong size.");
MFEM_VERIFY(
density.Size() == Width(), "PreparedMappedGravitySourceOperator received a density vector "
"with the wrong size."
);
m_density_true.SetSize(m_density_map.full_size());
m_density_map.scatter(density, m_density_true);
@@ -460,14 +464,18 @@ void PreparedMappedGravitySourceOperator::Mult(const mfem::Vector &density,
}
void PreparedMappedGravitySourceOperator::MultTranspose(
const mfem::Vector &potential, mfem::Vector &action) const {
MFEM_VERIFY(m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before "
"MultTranspose is called.");
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.");
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);
@@ -517,23 +525,19 @@ bool PreparedMappedGravitySourceOperator::IsPrepared() const noexcept {
return m_is_prepared;
}
std::uint64_t
PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
std::uint64_t PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetDensityMap() const noexcept {
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetDensityMap() const noexcept {
return m_density_map;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetPotentialMap() const noexcept {
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetPotentialMap() const noexcept {
return m_potential_map;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetDisplacementMap() const noexcept {
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetDisplacementMap() const noexcept {
return m_displacement_map;
}
} // namespace mean_field::operators

View File

@@ -11,21 +11,22 @@ namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
int get_operator_size(const mean_field::fem::FEM &f) {
MFEM_VERIFY(f.gravityFluxFes != nullptr,
"PreparedMappedHDivMassOperator requires the "
"gravity-gradient finite-element space.");
return mean_field::field::make_field_dof_map<mean_field::field::Gravity,
DomainSchema>(*f.gravityFluxFes)
MFEM_VERIFY(
f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the "
"gravity-gradient finite-element space."
);
return mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityFluxFes)
.reduced_size();
}
void true_to_local(const mfem::ParFiniteElementSpace &finite_element_space,
void true_to_local(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &true_vector,
mfem::Vector &local_vector) {
mfem::Vector &local_vector
) {
local_vector.SetSize(finite_element_space.GetVSize());
const mfem::Operator *prolongation =
finite_element_space.GetProlongationMatrix();
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(true_vector, local_vector);
@@ -34,13 +35,14 @@ void true_to_local(const mfem::ParFiniteElementSpace &finite_element_space,
}
}
int find_representative_element(const mean_field::fem::FEM &f,
const mfem::Array<int> &marker) {
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) {
if (attribute > 0 && attribute <= marker.Size() && marker[attribute - 1] != 0) {
return element_id;
}
}
@@ -49,36 +51,40 @@ int find_representative_element(const mean_field::fem::FEM &f,
}
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 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) {
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);
const mfem::ElementTransformation &transformation = *f.mesh->GetElementTransformation(element_id);
MFEM_VERIFY(element.GetGeomType() == representative_element.GetGeomType(),
MFEM_VERIFY(
element.GetGeomType() == representative_element.GetGeomType(),
"Prepared H(div) mass domains currently require a uniform "
"element "
"geometry.");
MFEM_VERIFY(element.GetOrder() == representative_element.GetOrder(),
"geometry."
);
MFEM_VERIFY(
element.GetOrder() == representative_element.GetOrder(),
"Prepared H(div) mass domains currently require a uniform "
"finite-element order.");
MFEM_VERIFY(transformation.OrderW() ==
representative_transformation.OrderW(),
"finite-element order."
);
MFEM_VERIFY(
transformation.OrderW() == representative_transformation.OrderW(),
"Prepared H(div) mass domains currently require a uniform "
"geometry-weight order.");
"geometry-weight order."
);
}
}
@@ -87,27 +93,34 @@ 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),
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);
true_to_local(*m_fem.displacementFes, displacement_true, m_displacement_local);
}
void Eval(mfem::DenseMatrix &mass_tensor,
void Eval(
mfem::DenseMatrix &mass_tensor,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point) override {
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.");
"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);
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());
@@ -118,34 +131,33 @@ public:
LoadElement(element_id);
const mean_field::mapping::ElementMappingData mapping_data{
.displacement = *m_displacement_data,
.compactification = *m_compactification_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);
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,
MFEM_VERIFY(
status == mean_field::mapping::MappingStatus::valid,
"Stateless domain mapping failed while preparing the H(div) "
"mass "
"operator. Mapping status = "
<< static_cast<int>(status)
<< ", element ID = " << element_id
<< static_cast<int>(status) << ", element ID = " << element_id
<< ", element attribute = " << transformation.Attribute
<< ", coefficient domain = "
<< (m_elevates_vacuum ? "vacuum" : "stellar"));
<< ", coefficient domain = " << (m_elevates_vacuum ? "vacuum" : "stellar")
);
const mfem::DenseMatrix &mapping_jacobian =
mapping_context.mapping.mapping_jacobian;
const double mapping_determinant =
mapping_context.mapping.mapping_determinant;
const mfem::DenseMatrix &mapping_jacobian = mapping_context.mapping.mapping_jacobian;
const double mapping_determinant = mapping_context.mapping.mapping_determinant;
MFEM_VERIFY(std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
MFEM_VERIFY(
std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
"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);
mass_tensor *= 1.0 / mapping_determinant;
@@ -157,40 +169,34 @@ private:
return;
}
const mfem::FiniteElement &displacement_element =
*m_fem.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*m_fem.compactificationFes->GetFE(element_id);
const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id);
mfem::DofTransformation *displacement_dof_transformation =
m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs);
mfem::DofTransformation *compactification_dof_transformation =
m_fem.compactificationFes->GetElementDofs(element_id,
m_compactification_dofs);
m_fem.compactificationFes->GetElementDofs(element_id, m_compactification_dofs);
m_displacement_local.GetSubVector(m_displacement_dofs,
m_element_displacement);
m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs,
m_element_compactification);
m_displacement_local.GetSubVector(m_displacement_dofs, m_element_displacement);
m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs, m_element_compactification);
if (displacement_dof_transformation != nullptr) {
displacement_dof_transformation->InvTransformPrimal(
m_element_displacement);
displacement_dof_transformation->InvTransformPrimal(m_element_displacement);
}
if (compactification_dof_transformation != nullptr) {
compactification_dof_transformation->InvTransformPrimal(
m_element_compactification);
compactification_dof_transformation->InvTransformPrimal(m_element_compactification);
}
m_displacement_data =
std::make_unique<mean_field::mapping::ElementDisplacementData>(
m_displacement_data = std::make_unique<mean_field::mapping::ElementDisplacementData>(
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacement_element, m_element_displacement));
displacement_element, m_element_displacement
)
);
m_compactification_data =
std::make_unique<mean_field::mapping::ElementCompactificationData>(
compactification_element, m_element_compactification);
m_compactification_data = std::make_unique<mean_field::mapping::ElementCompactificationData>(
compactification_element, m_element_compactification
);
m_cached_element_id = element_id;
}
@@ -206,10 +212,8 @@ private:
mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification;
std::unique_ptr<mean_field::mapping::ElementDisplacementData>
m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
m_compactification_data;
std::unique_ptr<mean_field::mapping::ElementDisplacementData> m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData> m_compactification_data;
mean_field::mapping::DomainMapper::Workspace m_workspace;
int m_cached_element_id{-1};
@@ -219,87 +223,95 @@ private:
namespace mean_field::operators {
PreparedMappedHDivMassOperator::PreparedMappedHDivMassOperator(
const fem::FEM &f, const mapping::DomainMapper &domain_mapper)
: Operator(get_operator_size(f)), m_fem(f), m_domain_mapper(domain_mapper),
m_flux_map(field::make_field_dof_map<field::Gravity, DomainSchema>(
*f.gravityFluxFes)),
const fem::FEM &f,
const mapping::DomainMapper &domain_mapper
)
: Operator(get_operator_size(f)),
m_fem(f),
m_domain_mapper(domain_mapper),
m_flux_map(
field::make_field_dof_map<
field::Gravity,
DomainSchema>(*f.gravityFluxFes)
),
m_displacement_map(
field::make_field_dof_map<field::Displacement, DomainSchema>(
*f.displacementFes)) {
MFEM_VERIFY(f.mesh != nullptr,
"PreparedMappedHDivMassOperator requires a mesh.");
MFEM_VERIFY(f.gravityFluxFes != nullptr,
"PreparedMappedHDivMassOperator requires the "
"gravity-gradient finite-element space.");
MFEM_VERIFY(f.displacementFes != nullptr,
"PreparedMappedHDivMassOperator requires the "
"displacement finite-element space.");
MFEM_VERIFY(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(),
field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
) {
MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedHDivMassOperator requires a mesh.");
MFEM_VERIFY(
f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr, "PreparedMappedHDivMassOperator requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
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.");
"dimension."
);
m_stellar_marker =
utils::domain::make_attribute_marker<utils::domain::Stellar,
DomainSchema>(*f.mesh);
m_vacuum_marker =
utils::domain::make_attribute_marker<utils::domain::Vacuum, DomainSchema>(
*f.mesh);
m_stellar_marker = utils::domain::make_attribute_marker<utils::domain::Stellar, DomainSchema>(*f.mesh);
m_vacuum_marker = utils::domain::make_attribute_marker<utils::domain::Vacuum, DomainSchema>(*f.mesh);
const int stellar_element_id =
find_representative_element(f, m_stellar_marker);
const int stellar_element_id = find_representative_element(f, m_stellar_marker);
const int vacuum_element_id = find_representative_element(f, m_vacuum_marker);
MFEM_VERIFY(stellar_element_id >= 0,
"PreparedMappedHDivMassOperator requires "
"at least one stellar element.");
MFEM_VERIFY(vacuum_element_id >= 0,
"PreparedMappedHDivMassOperator requires at "
"least one compactified vacuum element.");
MFEM_VERIFY(
stellar_element_id >= 0, "PreparedMappedHDivMassOperator requires "
"at least one stellar element."
);
MFEM_VERIFY(
vacuum_element_id >= 0, "PreparedMappedHDivMassOperator requires at "
"least one compactified vacuum element."
);
validate_uniform_domain_discretization(f, m_stellar_marker,
stellar_element_id);
validate_uniform_domain_discretization(f, m_stellar_marker, stellar_element_id);
validate_uniform_domain_discretization(f, m_vacuum_marker, vacuum_element_id);
}
void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) {
MFEM_VERIFY(displacement.Size() == m_displacement_map.reduced_size(),
MFEM_VERIFY(
displacement.Size() == m_displacement_map.reduced_size(),
"PreparedMappedHDivMassOperator received a displacement vector "
"with "
"the wrong size.");
"the wrong size."
);
for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY(std::isfinite(displacement(i)),
"PreparedMappedHDivMassOperator received a non-finite "
MFEM_VERIFY(
std::isfinite(displacement(i)), "PreparedMappedHDivMassOperator received a non-finite "
"displacement "
"value.");
"value."
);
}
m_displacement_true.SetSize(m_displacement_map.full_size());
m_displacement_map.scatter(displacement, m_displacement_true);
const int stellar_element_id =
find_representative_element(m_fem, m_stellar_marker);
const int vacuum_element_id =
find_representative_element(m_fem, m_vacuum_marker);
const int stellar_element_id = find_representative_element(m_fem, m_stellar_marker);
const int vacuum_element_id = find_representative_element(m_fem, m_vacuum_marker);
const mfem::FiniteElement &stellar_element =
*m_fem.gravityFluxFes->GetFE(stellar_element_id);
const mfem::FiniteElement &vacuum_element =
*m_fem.gravityFluxFes->GetFE(vacuum_element_id);
const mfem::FiniteElement &stellar_element = *m_fem.gravityFluxFes->GetFE(stellar_element_id);
const mfem::FiniteElement &vacuum_element = *m_fem.gravityFluxFes->GetFE(vacuum_element_id);
mfem::ElementTransformation &stellar_transformation =
*m_fem.mesh->GetElementTransformation(stellar_element_id);
mfem::ElementTransformation &vacuum_transformation =
*m_fem.mesh->GetElementTransformation(vacuum_element_id);
mfem::ElementTransformation &stellar_transformation = *m_fem.mesh->GetElementTransformation(stellar_element_id);
mfem::ElementTransformation &vacuum_transformation = *m_fem.mesh->GetElementTransformation(vacuum_element_id);
m_stellar_mass_form.reset();
m_vacuum_mass_form.reset();
@@ -307,37 +319,30 @@ void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) {
m_vacuum_mass_coefficient.reset();
m_stellar_mass_coefficient =
std::make_unique<FrozenMappedHDivMassCoefficient>(
m_fem, m_domain_mapper, m_displacement_true, false);
m_vacuum_mass_coefficient = std::make_unique<FrozenMappedHDivMassCoefficient>(
m_fem, m_domain_mapper, m_displacement_true, true);
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, false);
m_vacuum_mass_coefficient =
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, true);
m_stellar_mass_form =
std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
m_vacuum_mass_form =
std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
m_stellar_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
m_vacuum_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
m_stellar_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
m_vacuum_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
auto stellar_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(
*m_stellar_mass_coefficient);
auto vacuum_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(
*m_vacuum_mass_coefficient);
auto stellar_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*m_stellar_mass_coefficient);
auto vacuum_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*m_vacuum_mass_coefficient);
m_fem.quadratureFactory->configure_gravity_hdiv_mass(
*stellar_integrator, quadrature::QuadratureRole::discretization,
stellar_element, stellar_transformation, utils::DOMAINS::STELLAR,
quadrature::MappingKind::general);
*stellar_integrator, quadrature::QuadratureRole::discretization, stellar_element, stellar_transformation,
utils::DOMAINS::STELLAR, quadrature::MappingKind::general
);
m_fem.quadratureFactory->configure_gravity_hdiv_mass(
*vacuum_integrator, quadrature::QuadratureRole::discretization,
vacuum_element, vacuum_transformation, utils::DOMAINS::VACUUM,
quadrature::MappingKind::kelvin);
*vacuum_integrator, quadrature::QuadratureRole::discretization, vacuum_element, vacuum_transformation,
utils::DOMAINS::VACUUM, quadrature::MappingKind::kelvin
);
m_stellar_mass_form->AddDomainIntegrator(stellar_integrator.release(),
m_stellar_marker);
m_vacuum_mass_form->AddDomainIntegrator(vacuum_integrator.release(),
m_vacuum_marker);
m_stellar_mass_form->AddDomainIntegrator(stellar_integrator.release(), m_stellar_marker);
m_vacuum_mass_form->AddDomainIntegrator(vacuum_integrator.release(), m_vacuum_marker);
m_stellar_mass_form->Assemble();
m_vacuum_mass_form->Assemble();
@@ -345,17 +350,22 @@ void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) {
++m_preparation_count;
}
void PreparedMappedHDivMassOperator::Mult(const mfem::Vector &gravity_gradient,
mfem::Vector &action) const {
MFEM_VERIFY(m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
"before Mult is called.");
void PreparedMappedHDivMassOperator::Mult(
const mfem::Vector &gravity_gradient,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
"before Mult is called."
);
MFEM_VERIFY(
m_stellar_mass_form != nullptr && m_vacuum_mass_form != nullptr,
"PreparedMappedHDivMassOperator has incomplete domain mass forms.");
"PreparedMappedHDivMassOperator has incomplete domain mass forms."
);
MFEM_VERIFY(
gravity_gradient.Size() == Width(),
"PreparedMappedHDivMassOperator received a gravity-gradient vector "
"with the wrong size.");
gravity_gradient.Size() == Width(), "PreparedMappedHDivMassOperator received a gravity-gradient vector "
"with the wrong size."
);
m_flux_true.SetSize(m_flux_map.full_size());
m_action_true.SetSize(m_flux_map.full_size());
@@ -368,21 +378,22 @@ void PreparedMappedHDivMassOperator::Mult(const mfem::Vector &gravity_gradient,
m_flux_map.gather(m_action_true, action);
}
void PreparedMappedHDivMassOperator::AssembleDiagonal(
mfem::Vector &diagonal) const {
void PreparedMappedHDivMassOperator::AssembleDiagonal(mfem::Vector &diagonal) const {
mfem::Vector true_diagonal;
AssembleTrueDiagonal(true_diagonal);
diagonal.SetSize(Height());
m_flux_map.gather(true_diagonal, diagonal);
}
void PreparedMappedHDivMassOperator::AssembleTrueDiagonal(
mfem::Vector &diagonal) const {
MFEM_VERIFY(m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
"before assembling its diagonal.");
void PreparedMappedHDivMassOperator::AssembleTrueDiagonal(mfem::Vector &diagonal) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
"before assembling its diagonal."
);
MFEM_VERIFY(
m_stellar_mass_form != nullptr && m_vacuum_mass_form != nullptr,
"PreparedMappedHDivMassOperator has incomplete domain mass forms.");
"PreparedMappedHDivMassOperator has incomplete domain mass forms."
);
diagonal.SetSize(m_flux_map.full_size());
mfem::Vector domain_diagonal(m_flux_map.full_size());
@@ -395,18 +406,15 @@ bool PreparedMappedHDivMassOperator::IsPrepared() const noexcept {
return m_is_prepared;
}
std::uint64_t
PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept {
std::uint64_t PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
}
const field::FieldDofMap &
PreparedMappedHDivMassOperator::GetFluxMap() const noexcept {
const field::FieldDofMap &PreparedMappedHDivMassOperator::GetFluxMap() const noexcept {
return m_flux_map;
}
const field::FieldDofMap &
PreparedMappedHDivMassOperator::GetDisplacementMap() const noexcept {
const field::FieldDofMap &PreparedMappedHDivMassOperator::GetDisplacementMap() const noexcept {
return m_displacement_map;
}
} // namespace mean_field::operators

View File

@@ -12,25 +12,28 @@ namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<
mean_field::utils::domain::Vacuum>(attribute);
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
void validate_finite_vector(const mfem::Vector &vector, const char *message) {
void validate_finite_vector(
const mfem::Vector &vector,
const char *message
) {
for (int index = 0; index < vector.Size(); ++index) {
MFEM_VERIFY(std::isfinite(vector(index)), message);
}
}
void true_to_local(const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector, mfem::Vector &localVector) {
MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"True vector has the wrong size.");
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(), "True vector has the wrong size.");
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
@@ -39,41 +42,42 @@ void true_to_local(const mfem::ParFiniteElementSpace &finiteElementSpace,
}
}
const mfem::IntegrationRule &
get_mass_normalization_rule(const mean_field::fem::FEM &f,
const mfem::IntegrationRule &get_mass_normalization_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &densityElement,
const mfem::ElementTransformation &transformation) {
const mfem::ElementTransformation &transformation
) {
using DensityField = mean_field::field::Field<mean_field::field::Density>;
MFEM_VERIFY(densityElement.GetOrder() ==
mean_field::field::Density::Scalar::familyOrder,
MFEM_VERIFY(
densityElement.GetOrder() == mean_field::field::Density::Scalar::familyOrder,
"The mass-normalization element does not match the registered "
"density field.");
"density field."
);
const mean_field::quadrature::Query query = DensityField::make_query<
mean_field::field::Density::Form::MassNormalization>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), std::array<int, 0>{},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
const mean_field::quadrature::Query query =
DensityField::make_query<mean_field::field::Density::Form::MassNormalization>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array<int, 0>{},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
const auto resolution =
f.quadratureFactory->get(query, transformation.GetGeometryType());
const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
resolution.integration_rule != nullptr,
"The quadrature policy did not return a mass-normalization rule.");
resolution.integration_rule != nullptr, "The quadrature policy did not return a mass-normalization rule."
);
return *resolution.integration_rule;
}
void validate_shared_gravity_revisions(
const mean_field::operators::context::gravity_field::
GravityFieldLinearizationContext &gravityContext,
const mean_field::operators::MassNormalizationDependencies &dependencies) {
MFEM_VERIFY(gravityContext.IsPrepared(),
"PreparedMassNormalizationOperator requires the shared gravity "
"linearization context to be prepared first.");
const mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &gravityContext,
const mean_field::operators::MassNormalizationDependencies &dependencies
) {
MFEM_VERIFY(
gravityContext.IsPrepared(), "PreparedMassNormalizationOperator requires the shared gravity "
"linearization context to be prepared first."
);
const auto &revisions = gravityContext.GetRevisions();
@@ -82,55 +86,61 @@ void validate_shared_gravity_revisions(
revisions.density.value == dependencies.density.revision &&
revisions.displacement.value == dependencies.displacement.revision,
"PreparedMassNormalizationOperator received dependency revisions "
"that do not match the shared gravity context.");
"that do not match the shared gravity context."
);
}
void validate_shared_identity_transition(
const mean_field::operators::MassNormalizationDependencyStamp &prepared,
const mean_field::operators::MassNormalizationDependencyStamp &requested,
const char *message) {
MFEM_VERIFY(prepared.identity == requested.identity ||
prepared.revision != requested.revision,
message);
const char *message
) {
MFEM_VERIFY(prepared.identity == requested.identity || prepared.revision != requested.revision, message);
}
} // namespace
namespace mean_field::operators {
PreparedMassNormalizationOperator::PreparedMassNormalizationOperator(
const fem::FEM &f, const mapping::DomainMapper &domainMapper,
const context::gravity_field::GravityFieldLinearizationContext
&gravityContext)
: m_fem(f), m_domainMapper(domainMapper), m_gravityContext(gravityContext) {
MFEM_VERIFY(m_fem.mesh != nullptr,
"PreparedMassNormalizationOperator requires a mesh.");
MFEM_VERIFY(m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr &&
m_fem.compactificationFes != nullptr &&
m_fem.compactificationCoordinate != nullptr &&
m_fem.quadratureFactory != nullptr,
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const context::gravity_field::GravityFieldLinearizationContext &gravityContext
)
: m_fem(f),
m_domainMapper(domainMapper),
m_gravityContext(gravityContext) {
MFEM_VERIFY(m_fem.mesh != nullptr, "PreparedMassNormalizationOperator requires a mesh.");
MFEM_VERIFY(
m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr && m_fem.compactificationFes != nullptr &&
m_fem.compactificationCoordinate != nullptr && m_fem.quadratureFactory != nullptr,
"PreparedMassNormalizationOperator requires density, "
"displacement, compactification, and quadrature data.");
MFEM_VERIFY(m_domainMapper.GetDimension() == m_fem.mesh->Dimension(),
"displacement, compactification, and quadrature data."
);
MFEM_VERIFY(
m_domainMapper.GetDimension() == m_fem.mesh->Dimension(),
"PreparedMassNormalizationOperator received a mapper with the "
"wrong dimension.");
"wrong dimension."
);
MFEM_VERIFY(m_gravityContext.GetDensityMap().full_size() ==
m_fem.densityFes->GetTrueVSize() &&
m_gravityContext.GetDisplacementMap().full_size() ==
m_fem.displacementFes->GetTrueVSize(),
MFEM_VERIFY(
m_gravityContext.GetDensityMap().full_size() == m_fem.densityFes->GetTrueVSize() &&
m_gravityContext.GetDisplacementMap().full_size() == m_fem.displacementFes->GetTrueVSize(),
"PreparedMassNormalizationOperator received incompatible shared "
"FieldDof maps.");
"FieldDof maps."
);
m_densityVariationTrue.SetSize(m_gravityContext.GetDensityMap().full_size());
m_displacementVariationTrue.SetSize(
m_gravityContext.GetDisplacementMap().full_size());
m_displacementVariationTrue.SetSize(m_gravityContext.GetDisplacementMap().full_size());
}
PreparedMassNormalizationReport PreparedMassNormalizationOperator::Prepare(
const MassNormalizationStateView &state,
const MassNormalizationDependencies &dependencies) {
MFEM_VERIFY(std::isfinite(state.targetMass) && state.targetMass > 0.0,
const MassNormalizationDependencies &dependencies
) {
MFEM_VERIFY(
std::isfinite(state.targetMass) && state.targetMass > 0.0,
"PreparedMassNormalizationOperator requires a finite, positive "
"target mass.");
"target mass."
);
validate_shared_gravity_revisions(m_gravityContext, dependencies);
@@ -138,32 +148,29 @@ PreparedMassNormalizationReport PreparedMassNormalizationOperator::Prepare(
validate_shared_identity_transition(
m_preparedDependencies.discretization, dependencies.discretization,
"A new mass-normalization discretization identity must also "
"change the shared gravity revision.");
"change the shared gravity revision."
);
validate_shared_identity_transition(
m_preparedDependencies.density, dependencies.density,
"A new mass-normalization density identity must also change "
"the shared gravity revision.");
"the shared gravity revision."
);
validate_shared_identity_transition(
m_preparedDependencies.displacement, dependencies.displacement,
"A new mass-normalization displacement identity must also "
"change the shared gravity revision.");
"change the shared gravity revision."
);
}
const bool rebuildStaticPlan =
!m_isPrepared ||
dependencies.discretization != m_preparedDependencies.discretization;
!m_isPrepared || dependencies.discretization != m_preparedDependencies.discretization;
const bool refreshGeometry =
rebuildStaticPlan ||
dependencies.displacement != m_preparedDependencies.displacement;
rebuildStaticPlan || dependencies.displacement != m_preparedDependencies.displacement;
const bool refreshDensity =
rebuildStaticPlan ||
dependencies.density != m_preparedDependencies.density;
const bool refreshDensity = rebuildStaticPlan || dependencies.density != m_preparedDependencies.density;
const bool updateTargetMass =
!m_isPrepared ||
dependencies.targetMass != m_preparedDependencies.targetMass ||
const bool updateTargetMass = !m_isPrepared || dependencies.targetMass != m_preparedDependencies.targetMass ||
state.targetMass != m_targetMass;
m_isPrepared = false;
@@ -176,8 +183,7 @@ PreparedMassNormalizationReport PreparedMassNormalizationOperator::Prepare(
}
if (refreshGeometry) {
RefreshGeometry(
m_gravityContext.GetGeometryContext().GetDisplacementTrue());
RefreshGeometry(m_gravityContext.GetGeometryContext().GetDisplacementTrue());
report.refreshedGeometry = true;
}
@@ -213,12 +219,12 @@ void PreparedMassNormalizationOperator::BuildStaticPlan() {
int localStellarElementCount = 0;
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
m_fem.mesh->GetElementTransformation(elementId);
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(transformation != nullptr,
"PreparedMassNormalizationOperator received a null element "
"transformation.");
MFEM_VERIFY(
transformation != nullptr, "PreparedMassNormalizationOperator received a null element "
"transformation."
);
if (is_vacuum_attribute(transformation->Attribute)) {
continue;
@@ -229,26 +235,22 @@ void PreparedMassNormalizationOperator::BuildStaticPlan() {
ElementPAData &data = m_elements.back();
data.elementId = elementId;
data.densityDofTransformation =
m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
data.displacementDofTransformation = m_fem.displacementFes->GetElementVDofs(
elementId, data.displacementDofs);
data.displacementDofTransformation =
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
data.compactificationDofTransformation =
m_fem.compactificationFes->GetElementDofs(elementId,
data.compactificationDofs);
m_fem.compactificationFes->GetElementDofs(elementId, data.compactificationDofs);
const mfem::FiniteElement &densityElement =
*m_fem.densityFes->GetFE(elementId);
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(elementId);
const mfem::IntegrationRule &integrationRule =
get_mass_normalization_rule(m_fem, densityElement, *transformation);
data.quadraturePoints.resize(integrationRule.GetNPoints());
for (int quadraturePoint = 0;
quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) {
for (int quadraturePoint = 0; quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) {
QuadraturePointData &point = data.quadraturePoints[quadraturePoint];
point.integrationPoint = integrationRule.IntPoint(quadraturePoint);
@@ -259,21 +261,23 @@ void PreparedMassNormalizationOperator::BuildStaticPlan() {
}
int globalStellarElementCount = 0;
MPI_Allreduce(&localStellarElementCount, &globalStellarElementCount, 1,
MPI_INT, MPI_SUM, m_fem.mesh->GetComm());
MPI_Allreduce(
&localStellarElementCount, &globalStellarElementCount, 1, MPI_INT, MPI_SUM, m_fem.mesh->GetComm()
);
MFEM_VERIFY(globalStellarElementCount > 0,
"PreparedMassNormalizationOperator found no stellar elements.");
MFEM_VERIFY(globalStellarElementCount > 0, "PreparedMassNormalizationOperator found no stellar elements.");
}
void PreparedMassNormalizationOperator::RefreshGeometry(
const mfem::Vector &displacement) {
MFEM_VERIFY(displacement.Size() == m_fem.displacementFes->GetTrueVSize(),
void PreparedMassNormalizationOperator::RefreshGeometry(const mfem::Vector &displacement) {
MFEM_VERIFY(
displacement.Size() == m_fem.displacementFes->GetTrueVSize(),
"PreparedMassNormalizationOperator received a displacement "
"vector with the wrong size.");
"vector with the wrong size."
);
validate_finite_vector(
displacement, "PreparedMassNormalizationOperator received a non-finite "
"displacement value.");
"displacement value."
);
mfem::Vector displacementLocal;
true_to_local(*m_fem.displacementFes, displacement, displacementLocal);
@@ -281,66 +285,61 @@ void PreparedMassNormalizationOperator::RefreshGeometry(
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
for (ElementPAData &data : m_elements) {
displacementLocal.GetSubVector(data.displacementDofs,
data.baseDisplacement);
displacementLocal.GetSubVector(data.displacementDofs, data.baseDisplacement);
m_fem.compactificationCoordinate->GetSubVector(data.compactificationDofs,
data.compactification);
m_fem.compactificationCoordinate->GetSubVector(data.compactificationDofs, data.compactification);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(
data.baseDisplacement);
data.displacementDofTransformation->InvTransformPrimal(data.baseDisplacement);
}
if (data.compactificationDofTransformation != nullptr) {
data.compactificationDofTransformation->InvTransformPrimal(
data.compactification);
data.compactificationDofTransformation->InvTransformPrimal(data.compactification);
}
const mfem::FiniteElement &displacementElement =
*m_fem.displacementFes->GetFE(data.elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mfem::FiniteElement &compactificationElement =
*m_fem.compactificationFes->GetFE(data.elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement,
data.baseDisplacement);
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, data.compactification);
compactificationElement, data.compactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData};
.displacement = displacementData, .compactification = compactificationData
};
mfem::ElementTransformation *transformation =
m_fem.mesh->GetElementTransformation(data.elementId);
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
for (QuadraturePointData &point : data.quadraturePoints) {
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
mappingData, *transformation, point.integrationPoint, workspace,
point.mappingContext);
mappingData, *transformation, point.integrationPoint, workspace, point.mappingContext
);
MFEM_VERIFY(status == mapping::MappingStatus::valid,
"Stateless mapping failed while preparing mass "
MFEM_VERIFY(
status == mapping::MappingStatus::valid, "Stateless mapping failed while preparing mass "
"normalization. Element: "
<< data.elementId
<< ", attribute: " << transformation->Attribute
<< ", status: " << static_cast<int>(status));
<< ", status: " << static_cast<int>(status)
);
}
}
}
void PreparedMassNormalizationOperator::RefreshDensity(
const mfem::Vector &density) {
MFEM_VERIFY(density.Size() == m_fem.densityFes->GetTrueVSize(),
void PreparedMassNormalizationOperator::RefreshDensity(const mfem::Vector &density) {
MFEM_VERIFY(
density.Size() == m_fem.densityFes->GetTrueVSize(),
"PreparedMassNormalizationOperator received a density vector "
"with the wrong size.");
"with the wrong size."
);
validate_finite_vector(
density,
"PreparedMassNormalizationOperator received a non-finite density "
"value.");
density, "PreparedMassNormalizationOperator received a non-finite density "
"value."
);
mfem::Vector densityLocal;
true_to_local(*m_fem.densityFes, density, densityLocal);
@@ -356,9 +355,10 @@ void PreparedMassNormalizationOperator::RefreshDensity(
for (QuadraturePointData &point : data.quadraturePoints) {
point.density = elementDensity * point.densityShape;
MFEM_VERIFY(std::isfinite(point.density),
"PreparedMassNormalizationOperator produced a non-finite "
"quadrature density.");
MFEM_VERIFY(
std::isfinite(point.density), "PreparedMassNormalizationOperator produced a non-finite "
"quadrature density."
);
}
}
}
@@ -373,29 +373,26 @@ void PreparedMassNormalizationOperator::AssembleResidual() {
}
m_currentMass = GlobalSum(localMass);
MFEM_VERIFY(std::isfinite(m_currentMass),
"PreparedMassNormalizationOperator assembled a non-finite mass.");
MFEM_VERIFY(std::isfinite(m_currentMass), "PreparedMassNormalizationOperator assembled a non-finite mass.");
m_cachedResidual.SetSize(1);
m_cachedResidual(0) = m_currentMass - m_targetMass;
++m_preparationCount;
}
void PreparedMassNormalizationOperator::BuildResidual(
mfem::Vector &residual) const {
void PreparedMassNormalizationOperator::BuildResidual(mfem::Vector &residual) const {
VerifyPrepared();
residual = m_cachedResidual;
++m_residualApplicationCount;
}
double PreparedMassNormalizationOperator::EvaluateDensityActionLocal(
const mfem::Vector &densityVariation) const {
MFEM_VERIFY(densityVariation.Size() == m_fem.densityFes->GetTrueVSize(),
double PreparedMassNormalizationOperator::EvaluateDensityActionLocal(const mfem::Vector &densityVariation) const {
MFEM_VERIFY(
densityVariation.Size() == m_fem.densityFes->GetTrueVSize(),
"Mass-normalization density action received a vector with the "
"wrong size.");
validate_finite_vector(
densityVariation,
"Mass-normalization density action received a non-finite value.");
"wrong size."
);
validate_finite_vector(densityVariation, "Mass-normalization density action received a non-finite value.");
mfem::Vector densityVariationLocal;
true_to_local(*m_fem.densityFes, densityVariation, densityVariationLocal);
@@ -404,17 +401,14 @@ double PreparedMassNormalizationOperator::EvaluateDensityActionLocal(
double localAction = 0.0;
for (const ElementPAData &data : m_elements) {
densityVariationLocal.GetSubVector(data.densityDofs,
elementDensityVariation);
densityVariationLocal.GetSubVector(data.densityDofs, elementDensityVariation);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(
elementDensityVariation);
data.densityDofTransformation->InvTransformPrimal(elementDensityVariation);
}
for (const QuadraturePointData &point : data.quadraturePoints) {
localAction += (elementDensityVariation * point.densityShape) *
point.mappingContext.quadrature.weight;
localAction += (elementDensityVariation * point.densityShape) * point.mappingContext.quadrature.weight;
}
}
@@ -422,19 +416,20 @@ double PreparedMassNormalizationOperator::EvaluateDensityActionLocal(
}
double PreparedMassNormalizationOperator::EvaluateDisplacementActionLocal(
const mfem::Vector &displacementVariation) const {
MFEM_VERIFY(displacementVariation.Size() ==
m_fem.displacementFes->GetTrueVSize(),
const mfem::Vector &displacementVariation
) const {
MFEM_VERIFY(
displacementVariation.Size() == m_fem.displacementFes->GetTrueVSize(),
"Mass-normalization displacement action received a vector with "
"the wrong size.");
"the wrong size."
);
validate_finite_vector(
displacementVariation,
"Mass-normalization displacement action received a non-finite "
"value.");
displacementVariation, "Mass-normalization displacement action received a non-finite "
"value."
);
mfem::Vector displacementVariationLocal;
true_to_local(*m_fem.displacementFes, displacementVariation,
displacementVariationLocal);
true_to_local(*m_fem.displacementFes, displacementVariation, displacementVariationLocal);
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
@@ -442,52 +437,46 @@ double PreparedMassNormalizationOperator::EvaluateDisplacementActionLocal(
double localAction = 0.0;
for (const ElementPAData &data : m_elements) {
displacementVariationLocal.GetSubVector(data.displacementDofs,
elementDisplacementVariation);
displacementVariationLocal.GetSubVector(data.displacementDofs, elementDisplacementVariation);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(
elementDisplacementVariation);
data.displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation);
}
const mfem::FiniteElement &displacementElement =
*m_fem.displacementFes->GetFE(data.elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mfem::FiniteElement &compactificationElement =
*m_fem.compactificationFes->GetFE(data.elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
const mapping::ElementDisplacementData baseDisplacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement,
data.baseDisplacement);
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacementVariation);
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacementVariation);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, data.compactification);
compactificationElement, data.compactification
);
const mapping::ElementMappingData mappingData{
.displacement = baseDisplacementData,
.compactification = compactificationData};
.displacement = baseDisplacementData, .compactification = compactificationData
};
mfem::ElementTransformation *transformation =
m_fem.mesh->GetElementTransformation(data.elementId);
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
for (const QuadraturePointData &point : data.quadraturePoints) {
mapping::VolumeMappingVariation variation;
const mapping::MappingStatus status =
m_domainMapper.EvaluateVolumeVariation(
mappingData, directionData, *transformation,
point.integrationPoint, point.mappingContext, workspace,
variation);
const mapping::MappingStatus status = m_domainMapper.EvaluateVolumeVariation(
mappingData, directionData, *transformation, point.integrationPoint, point.mappingContext,
workspace, variation
);
MFEM_VERIFY(status == mapping::MappingStatus::valid,
"Stateless mapping variation failed in the "
MFEM_VERIFY(
status == mapping::MappingStatus::valid, "Stateless mapping variation failed in the "
"mass-normalization displacement action. Element: "
<< data.elementId
<< ", status: " << static_cast<int>(status));
<< ", status: " << static_cast<int>(status)
);
localAction += point.density * variation.weight_variation;
}
@@ -497,18 +486,18 @@ double PreparedMassNormalizationOperator::EvaluateDisplacementActionLocal(
}
void PreparedMassNormalizationOperator::ApplyDensityJacobianAction(
const mfem::Vector &densityVariation, mfem::Vector &action) const {
const mfem::Vector &densityVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(densityVariation.Size() ==
m_gravityContext.GetDensityMap().reduced_size(),
MFEM_VERIFY(
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size(),
"Mass-normalization density action received a supported vector "
"with the wrong size.");
validate_finite_vector(
densityVariation,
"Mass-normalization density action received a non-finite value.");
m_gravityContext.GetDensityMap().scatter(densityVariation,
m_densityVariationTrue);
"with the wrong size."
);
validate_finite_vector(densityVariation, "Mass-normalization density action received a non-finite value.");
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
action.SetSize(1);
action(0) = GlobalSum(EvaluateDensityActionLocal(m_densityVariationTrue));
@@ -516,52 +505,52 @@ void PreparedMassNormalizationOperator::ApplyDensityJacobianAction(
}
void PreparedMassNormalizationOperator::ApplyDisplacementJacobianAction(
const mfem::Vector &displacementVariation, mfem::Vector &action) const {
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(displacementVariation.Size() ==
m_gravityContext.GetDisplacementMap().reduced_size(),
MFEM_VERIFY(
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
"Mass-normalization displacement action received a supported "
"vector with the wrong size.");
"vector with the wrong size."
);
validate_finite_vector(
displacementVariation,
"Mass-normalization displacement action received a non-finite value.");
m_gravityContext.GetDisplacementMap().scatter(displacementVariation,
m_displacementVariationTrue);
displacementVariation, "Mass-normalization displacement action received a non-finite value."
);
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
action.SetSize(1);
action(0) =
GlobalSum(EvaluateDisplacementActionLocal(m_displacementVariationTrue));
action(0) = GlobalSum(EvaluateDisplacementActionLocal(m_displacementVariationTrue));
++m_actionStatistics.displacementApplications;
}
void PreparedMassNormalizationOperator::ApplyCompleteJacobianAction(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation, mfem::Vector &action) const {
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(densityVariation.Size() ==
m_gravityContext.GetDensityMap().reduced_size(),
MFEM_VERIFY(
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size(),
"Mass-normalization complete action received a supported density "
"vector with the wrong size.");
MFEM_VERIFY(displacementVariation.Size() ==
m_gravityContext.GetDisplacementMap().reduced_size(),
"vector with the wrong size."
);
MFEM_VERIFY(
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
"Mass-normalization complete action received a supported "
"displacement vector with the wrong size.");
"displacement vector with the wrong size."
);
validate_finite_vector(densityVariation, "Mass-normalization complete action received a non-finite density.");
validate_finite_vector(
densityVariation,
"Mass-normalization complete action received a non-finite density.");
validate_finite_vector(
displacementVariation,
"Mass-normalization complete action received a non-finite displacement.");
displacementVariation, "Mass-normalization complete action received a non-finite displacement."
);
m_gravityContext.GetDensityMap().scatter(densityVariation,
m_densityVariationTrue);
m_gravityContext.GetDisplacementMap().scatter(displacementVariation,
m_displacementVariationTrue);
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
const double localAction =
EvaluateDensityActionLocal(m_densityVariationTrue) +
const double localAction = EvaluateDensityActionLocal(m_densityVariationTrue) +
EvaluateDisplacementActionLocal(m_displacementVariationTrue);
action.SetSize(1);
@@ -569,11 +558,9 @@ void PreparedMassNormalizationOperator::ApplyCompleteJacobianAction(
++m_actionStatistics.completeApplications;
}
double
PreparedMassNormalizationOperator::GlobalSum(const double localValue) const {
double PreparedMassNormalizationOperator::GlobalSum(const double localValue) const {
double globalValue = 0.0;
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM,
m_fem.mesh->GetComm());
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, m_fem.mesh->GetComm());
return globalValue;
}
@@ -583,11 +570,9 @@ bool PreparedMassNormalizationOperator::IsPrepared() const noexcept {
}
const auto &revisions = m_gravityContext.GetRevisions();
return revisions.discretization.value ==
m_preparedDependencies.discretization.revision &&
return revisions.discretization.value == m_preparedDependencies.discretization.revision &&
revisions.density.value == m_preparedDependencies.density.revision &&
revisions.displacement.value ==
m_preparedDependencies.displacement.revision;
revisions.displacement.value == m_preparedDependencies.displacement.revision;
}
double PreparedMassNormalizationOperator::GetCurrentMass() const {
@@ -600,13 +585,11 @@ double PreparedMassNormalizationOperator::GetTargetMass() const {
return m_targetMass;
}
std::uint64_t
PreparedMassNormalizationOperator::GetPreparationCount() const noexcept {
std::uint64_t PreparedMassNormalizationOperator::GetPreparationCount() const noexcept {
return m_preparationCount;
}
std::uint64_t PreparedMassNormalizationOperator::GetResidualApplicationCount()
const noexcept {
std::uint64_t PreparedMassNormalizationOperator::GetResidualApplicationCount() const noexcept {
return m_residualApplicationCount;
}
@@ -625,126 +608,119 @@ PreparedMassNormalizationOperator::GetGravityContext() const noexcept {
}
void PreparedMassNormalizationOperator::VerifyPrepared() const {
MFEM_VERIFY(IsPrepared(),
"PreparedMassNormalizationOperator must be prepared for the "
"current shared gravity-context revisions.");
MFEM_VERIFY(
IsPrepared(), "PreparedMassNormalizationOperator must be prepared for the "
"current shared gravity-context revisions."
);
}
PreparedMassNormalizationJacobianOperator::
PreparedMassNormalizationJacobianOperator(
PreparedMassNormalizationJacobianOperator::PreparedMassNormalizationJacobianOperator(
const MassNormalizationLayout &layout,
const PreparedMassNormalizationOperator &preparedOperator)
: mfem::Operator(layout.residual_offsets().Last(),
layout.value_offsets().Last()),
m_layout(layout), m_preparedOperator(preparedOperator) {
const PreparedMassNormalizationOperator &preparedOperator
)
: mfem::Operator(
layout.residual_offsets().Last(),
layout.value_offsets().Last()
),
m_layout(layout),
m_preparedOperator(preparedOperator) {
const fem::FEM &f = m_preparedOperator.GetFEM();
MFEM_VERIFY(f.densityFes != nullptr && f.displacementFes != nullptr &&
f.gravityFluxFes != nullptr &&
MFEM_VERIFY(
f.densityFes != nullptr && f.displacementFes != nullptr && f.gravityFluxFes != nullptr &&
f.gravityPotentialFes != nullptr && f.enthalpyFes != nullptr,
"Prepared mass-normalization MFEM adapter requires every "
"finite-element space in the barotropic equilibrium layout.");
"finite-element space in the barotropic equilibrium layout."
);
using Form = utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue = utils::blocks::get_value_block<Form>(
utils::blocks::density_field.mass_term);
constexpr auto displacementValue = utils::blocks::get_value_block<Form>(
utils::blocks::displacement_field.geometry_term);
constexpr auto gravityGradientValue = utils::blocks::get_value_block<Form>(
utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue = utils::blocks::get_value_block<Form>(
utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue = utils::blocks::get_value_block<Form>(
utils::blocks::enthalpy_field.specific_term);
constexpr auto barotropicConstantValue = utils::blocks::get_value_block<Form>(
utils::blocks::barotropic_constant_field.mass_normalization_term);
constexpr auto densityValue = utils::blocks::get_value_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto gravityGradientValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue =
utils::blocks::get_value_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto barotropicConstantValue =
utils::blocks::get_value_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
constexpr auto gravityGradientResidual =
utils::blocks::get_residual_block<Form>(
utils::blocks::gravity_field.gradient_term);
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialResidual =
utils::blocks::get_residual_block<Form>(
utils::blocks::gravity_field.poisson_term);
constexpr auto densityResidual = utils::blocks::get_residual_block<Form>(
utils::blocks::density_field.mass_term);
constexpr auto displacementResidual = utils::blocks::get_residual_block<Form>(
utils::blocks::displacement_field.geometry_term);
constexpr auto enthalpyResidual = utils::blocks::get_residual_block<Form>(
utils::blocks::enthalpy_field.specific_term);
constexpr auto massResidual = utils::blocks::get_residual_block<Form>(
utils::blocks::barotropic_constant_field.mass_normalization_term);
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.poisson_term);
constexpr auto densityResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto enthalpyResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto massResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
const auto &gravityContext = m_preparedOperator.GetGravityContext();
const field::FieldDofMap enthalpyMap =
field::make_field_dof_map<field::Enthalpy, DomainSchema>(*f.enthalpyFes);
const field::FieldDofMap enthalpyMap = field::make_field_dof_map<field::Enthalpy, DomainSchema>(*f.enthalpyFes);
MFEM_VERIFY(m_layout.size(densityValue) ==
gravityContext.GetDensityMap().reduced_size() &&
m_layout.size(displacementValue) ==
gravityContext.GetDisplacementMap().reduced_size() &&
m_layout.size(gravityGradientValue) ==
gravityContext.GetGravityGradientMap().reduced_size() &&
m_layout.size(gravityPotentialValue) ==
gravityContext.GetGravityPotentialMap().reduced_size() &&
MFEM_VERIFY(
m_layout.size(densityValue) == gravityContext.GetDensityMap().reduced_size() &&
m_layout.size(displacementValue) == gravityContext.GetDisplacementMap().reduced_size() &&
m_layout.size(gravityGradientValue) == gravityContext.GetGravityGradientMap().reduced_size() &&
m_layout.size(gravityPotentialValue) == gravityContext.GetGravityPotentialMap().reduced_size() &&
m_layout.size(enthalpyValue) == enthalpyMap.reduced_size() &&
m_layout.size(barotropicConstantValue) == 1 &&
m_layout.size(gravityGradientResidual) ==
gravityContext.GetGravityGradientMap().reduced_size() &&
m_layout.size(gravityPotentialResidual) ==
gravityContext.GetGravityPotentialMap().reduced_size() &&
m_layout.size(densityResidual) ==
gravityContext.GetDensityMap().reduced_size() &&
m_layout.size(displacementResidual) ==
gravityContext.GetDisplacementMap().reduced_size() &&
m_layout.size(enthalpyResidual) ==
enthalpyMap.reduced_size() &&
m_layout.size(massResidual) == 1,
m_layout.size(gravityGradientResidual) == gravityContext.GetGravityGradientMap().reduced_size() &&
m_layout.size(gravityPotentialResidual) == gravityContext.GetGravityPotentialMap().reduced_size() &&
m_layout.size(densityResidual) == gravityContext.GetDensityMap().reduced_size() &&
m_layout.size(displacementResidual) == gravityContext.GetDisplacementMap().reduced_size() &&
m_layout.size(enthalpyResidual) == enthalpyMap.reduced_size() && m_layout.size(massResidual) == 1,
"Prepared mass-normalization MFEM adapter received incompatible "
"barotropic block sizes.");
"barotropic block sizes."
);
}
void PreparedMassNormalizationJacobianOperator::Mult(
const mfem::Vector &direction, mfem::Vector &action) const {
MFEM_VERIFY(m_preparedOperator.IsPrepared(),
"Prepared mass-normalization MFEM adapter requires a prepared "
"row operator.");
MFEM_VERIFY(direction.Size() == Width(),
"Prepared mass-normalization MFEM adapter received a direction "
"with the wrong size.");
const mfem::Vector &direction,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_preparedOperator.IsPrepared(), "Prepared mass-normalization MFEM adapter requires a prepared "
"row operator."
);
MFEM_VERIFY(
direction.Size() == Width(), "Prepared mass-normalization MFEM adapter received a direction "
"with the wrong size."
);
using Form = utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue = utils::blocks::get_value_block<Form>(
utils::blocks::density_field.mass_term);
constexpr auto displacementValue = utils::blocks::get_value_block<Form>(
utils::blocks::displacement_field.geometry_term);
constexpr auto massResidual = utils::blocks::get_residual_block<Form>(
utils::blocks::barotropic_constant_field.mass_normalization_term);
constexpr auto densityValue = utils::blocks::get_value_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto massResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
const mfem::Vector densityVariation(
const_cast<mfem::real_t *>(direction.GetData()) +
m_layout.offset(densityValue),
m_layout.size(densityValue));
const_cast<mfem::real_t *>(direction.GetData()) + m_layout.offset(densityValue), m_layout.size(densityValue)
);
const mfem::Vector displacementVariation(
const_cast<mfem::real_t *>(direction.GetData()) +
m_layout.offset(displacementValue),
m_layout.size(displacementValue));
const_cast<mfem::real_t *>(direction.GetData()) + m_layout.offset(displacementValue),
m_layout.size(displacementValue)
);
mfem::Vector massAction;
m_preparedOperator.ApplyCompleteJacobianAction(
densityVariation, displacementVariation, massAction);
m_preparedOperator.ApplyCompleteJacobianAction(densityVariation, displacementVariation, massAction);
action.SetSize(Height());
action = 0.0;
action(m_layout.offset(massResidual)) = massAction(0);
}
const MassNormalizationLayout &
PreparedMassNormalizationJacobianOperator::GetLayout() const noexcept {
const MassNormalizationLayout &PreparedMassNormalizationJacobianOperator::GetLayout() const noexcept {
return m_layout;
}
} // namespace mean_field::operators

View File

@@ -619,9 +619,15 @@ namespace mean_field::operators {
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++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);

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(
const mean_field::field::FieldDofMap &densityMap,
const mean_field::field::FieldDofMap &displacementMap,
@@ -253,6 +259,8 @@ namespace mean_field::operators {
field::FieldDofMap gravityFluxMap;
field::FieldDofMap gravityPotentialMap;
field::FieldDofMap enthalpyMap;
field::FieldBoundaryDofMap pressureSurfaceRows;
field::FieldPointDofMap centerDisplacementRows;
StellarEquilibriumLayout layout;
mfem::Array<int> gravityStateOffsets;
@@ -284,6 +292,23 @@ namespace mean_field::operators {
field::Enthalpy,
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(
densityMap,
displacementMap,
@@ -314,27 +339,15 @@ namespace mean_field::operators {
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
const models::StellarModel &stellarModel
)
: PreparedStellarEquilibriumOperator(
f,
domainMapper,
equationOfState,
stellarModel.targetMass()
) {
}
PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
const double targetMass
const double targetMass,
const PressureSurfaceConstraintView surfaceConstraint
)
: PreparedStellarEquilibriumOperator(
f,
domainMapper,
equationOfState,
targetMass,
surfaceConstraint,
MakeConstructionData(f)
) {
}
@@ -344,6 +357,7 @@ namespace mean_field::operators {
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
const double targetMass,
const PressureSurfaceConstraintView surfaceConstraint,
ConstructionData constructionData
)
: mfem::Operator(
@@ -390,6 +404,11 @@ namespace mean_field::operators {
domainMapper,
m_gravityContext
),
m_surfaceConstraintOperator(
constructionData.pressureSurfaceRows,
surfaceConstraint
),
m_centeringConstraintOperator(constructionData.centerDisplacementRows),
m_targetMass(targetMass) {
MFEM_VERIFY(
std::isfinite(m_targetMass) && m_targetMass > 0.0,
@@ -506,6 +525,14 @@ namespace mean_field::operators {
report.massNormalization =
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;
if (dependenciesChanged || report.DidAnyChildWork()) {
AssembleResidual();
@@ -542,7 +569,9 @@ namespace mean_field::operators {
m_gravityOperator.Mult(m_gravityState, gravity);
m_barotropicClosureOperator.BuildResidual(closure);
m_displacementOperator.BuildResidual(displacement);
m_centeringConstraintOperator.ApplyResidualRows(displacement);
m_hydrostaticOperator.BuildResidual(hydrostatic);
m_surfaceConstraintOperator.ApplyResidualRows(hydrostatic);
m_massNormalizationOperator.BuildResidual(mass);
m_cachedResidual.SetSize(Height());
@@ -659,11 +688,13 @@ namespace mean_field::operators {
reducedDensityDirection, displacementDirection, gravityGradientDirection, reducedEnthalpyDirection,
displacementAction
);
m_centeringConstraintOperator.ApplyJacobianRows(displacementDirection, displacementAction);
m_hydrostaticOperator.ApplyCompleteJacobianAction(
reducedEnthalpyDirection, gravityPotentialDirection, bernoulliDirection(0), displacementDirection,
hydrostaticAction
);
m_surfaceConstraintOperator.ApplyJacobianRows(reducedEnthalpyDirection, hydrostaticAction);
m_massNormalizationOperator.ApplyCompleteJacobianAction(
reducedDensityDirection, displacementDirection, massAction
@@ -712,7 +743,8 @@ namespace mean_field::operators {
bool PreparedStellarEquilibriumOperator::IsPrepared() const noexcept {
return m_isPrepared && m_gravityContext.IsPrepared() && m_barotropicClosureOperator.IsPrepared() &&
m_hydrostaticOperator.IsPrepared() && m_displacementOperator.IsPrepared() &&
m_massNormalizationOperator.IsPrepared();
m_massNormalizationOperator.IsPrepared() && m_surfaceConstraintOperator.IsPrepared() &&
m_centeringConstraintOperator.IsPrepared();
}
double PreparedStellarEquilibriumOperator::GetTargetMass() const noexcept {
@@ -771,6 +803,16 @@ namespace mean_field::operators {
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 {
MFEM_VERIFY(
IsPrepared(), "PreparedStellarEquilibriumOperator must be prepared before residual or Jacobian application."

View File

@@ -16,13 +16,11 @@ namespace mean_field::physics {
local_Q = 0.0;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(
fem.mesh->GetAttribute(i)))
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(fem.mesh->GetAttribute(i)))
continue;
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
@@ -40,8 +38,7 @@ namespace mean_field::physics {
mapping::VolumeMappingContext mapping_context;
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) ==
mapping::MappingStatus::valid,
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping::MappingStatus::valid,
"Quadrupole integration encountered an invalid mapping."
);
const double weight = mapping_context.quadrature.weight;
@@ -220,12 +217,8 @@ namespace mean_field::physics {
GravitySolution solution(f);
gravity_flux_adapter.scatter(
gravity_state.GetBlock(gravity_gradient_residual_block), solution.gradPhi
);
gravity_potential_adapter.scatter(
gravity_state.GetBlock(gravity_poisson_residual_block), solution.phi
);
gravity_flux_adapter.scatter(gravity_state.GetBlock(gravity_gradient_residual_block), solution.gradPhi);
gravity_potential_adapter.scatter(gravity_state.GetBlock(gravity_poisson_residual_block), solution.phi);
return solution;
}

View File

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

View File

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

View File

@@ -4,14 +4,18 @@ module;
module mean_field;
namespace mean_field::utils {
DOMAINS operator|(DOMAINS lhs, DOMAINS rhs) {
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) |
static_cast<uint8_t>(rhs));
DOMAINS operator|(
DOMAINS lhs,
DOMAINS rhs
) {
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
}
DOMAINS operator&(DOMAINS lhs, DOMAINS rhs) {
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) &
static_cast<uint8_t>(rhs));
DOMAINS operator&(
DOMAINS lhs,
DOMAINS rhs
) {
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
}
int get_mesh_order(const mfem::Mesh &mesh) {

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

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;
#include <array>
#include <cmath>
#include <concepts>
#include <cstddef>
#include <memory>
@@ -888,6 +889,282 @@ export namespace mean_field::field {
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
* vector.
@@ -1015,9 +1292,6 @@ export namespace mean_field::field {
[[nodiscard]]
FieldDofGridFunctionAdapter
make_field_dof_grid_function_adapter(const mfem::ParFiniteElementSpace &finiteElementSpace) {
return FieldDofGridFunctionAdapter(
make_field_dof_map<FieldT, SchemaT>(finiteElementSpace),
finiteElementSpace
);
return FieldDofGridFunctionAdapter(make_field_dof_map<FieldT, SchemaT>(finiteElementSpace), finiteElementSpace);
}
} // namespace mean_field::field

View File

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

View File

@@ -13,8 +13,10 @@ enum class FaceElementSide : uint8_t { element_1, element_2 };
class ElementDisplacementData {
public:
ElementDisplacementData(
const mfem::FiniteElement &element, const mfem::Vector &displacement_dofs,
mfem::Ordering::Type ordering = mfem::Ordering::byNODES);
const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs,
mfem::Ordering::Type ordering = mfem::Ordering::byNODES
);
[[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept;
[[nodiscard]] const mfem::DenseMatrix &GetDofMatrix() const noexcept;
@@ -34,14 +36,17 @@ struct CompactificationPointData {
mfem::Vector coordinate_gradient;
};
[[nodiscard]] ElementDisplacementData
ElementDisplacementDataFromElementVDofs(const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs);
[[nodiscard]] ElementDisplacementData ElementDisplacementDataFromElementVDofs(
const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs
);
class ElementCompactificationData {
public:
ElementCompactificationData(const mfem::FiniteElement &element,
const mfem::Vector &dofs);
ElementCompactificationData(
const mfem::FiniteElement &element,
const mfem::Vector &dofs
);
[[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept;
[[nodiscard]] const mfem::Vector &GetDofs() const noexcept;
@@ -96,86 +101,103 @@ public:
public:
DomainMapper(
utils::DomainMapperOptions options,
std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map);
std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map
);
DomainMapper(const DomainMapper &) = delete;
DomainMapper &operator=(const DomainMapper &) = delete;
DomainMapper(DomainMapper &&) = default;
DomainMapper &operator=(DomainMapper &&) = default;
[[nodiscard]] MappingStatus
EvaluatePoint(const ElementMappingData &element_data,
[[nodiscard]] MappingStatus EvaluatePoint(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace, MappingPointContext &context) const;
Workspace &workspace,
MappingPointContext &context
) const;
[[nodiscard]] MappingStatus
EvaluateVolume(const ElementMappingData &element_data,
[[nodiscard]] MappingStatus EvaluateVolume(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace, VolumeMappingContext &context) const;
Workspace &workspace,
VolumeMappingContext &context
) const;
[[nodiscard]] MappingStatus
EvaluateFace(const ElementMappingData &element_data,
[[nodiscard]] MappingStatus EvaluateFace(
const ElementMappingData &element_data,
mfem::FaceElementTransformations &transformation,
FaceElementSide side,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace, FaceMappingContext &context) const;
Workspace &workspace,
FaceMappingContext &context
) const;
[[nodiscard]] MappingStatus
EvaluatePointVariation(const ElementMappingData &element_data,
[[nodiscard]] MappingStatus EvaluatePointVariation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const MappingPointContext &base_context,
Workspace &workspace,
MappingPointVariation &variation) const;
MappingPointVariation &variation
) const;
[[nodiscard]] MappingStatus
EvaluateVolumeVariation(const ElementMappingData &element_data,
[[nodiscard]] MappingStatus EvaluateVolumeVariation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const VolumeMappingContext &base_context,
Workspace &workspace,
VolumeMappingVariation &variation) const;
VolumeMappingVariation &variation
) const;
[[nodiscard]] MappingStatus EvaluateFaceVariation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::FaceElementTransformations &transformation, FaceElementSide side,
mfem::FaceElementTransformations &transformation,
FaceElementSide side,
const mfem::IntegrationPoint &integration_point,
const FaceMappingContext &base_context, Workspace &workspace,
FaceMappingVariation &variation) const;
const FaceMappingContext &base_context,
Workspace &workspace,
FaceMappingVariation &variation
) const;
[[nodiscard]] bool IsCompactifiedElement(
const mfem::ElementTransformation &transformation) const noexcept;
[[nodiscard]] bool IsCompactifiedElement(const mfem::ElementTransformation &transformation) const noexcept;
[[nodiscard]] int GetDimension() const noexcept;
[[nodiscard]] const compactification::ExteriorDomainMap &
GetExteriorMap() const noexcept;
[[nodiscard]] const compactification::ExteriorDomainMap &GetExteriorMap() const noexcept;
private:
void ValidateElementData(const ElementMappingData &element_data) const;
void EvaluateField(const ElementDisplacementData &field,
void EvaluateField(
const ElementDisplacementData &field,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace, mfem::Vector &value,
mfem::DenseMatrix &jacobian) const;
Workspace &workspace,
mfem::Vector &value,
mfem::DenseMatrix &jacobian
) const;
[[nodiscard]] MappingStatus EvaluateCompactificationCoordinate(
const ElementCompactificationData &compactification,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, Workspace &workspace,
CompactificationPointData &point_data) const;
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
CompactificationPointData &point_data
) const;
[[nodiscard]] static mfem::ElementTransformation &
SelectFaceElementTransformation(
mfem::FaceElementTransformations &transformation, FaceElementSide side);
[[nodiscard]] static mfem::ElementTransformation &SelectFaceElementTransformation(
mfem::FaceElementTransformations &transformation,
FaceElementSide side
);
[[nodiscard]] static const mfem::IntegrationPoint &
SelectFaceElementIntegrationPoint(
mfem::FaceElementTransformations &transformation, FaceElementSide side);
[[nodiscard]] static const mfem::IntegrationPoint &SelectFaceElementIntegrationPoint(
mfem::FaceElementTransformations &transformation,
FaceElementSide side
);
utils::DomainMapperOptions m_options;
std::unique_ptr<const compactification::ExteriorDomainMap> m_exterior_map;
@@ -194,7 +216,8 @@ public:
GridFunctionMappingEvaluator(
const DomainMapper &mapper,
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
@@ -210,35 +233,41 @@ public:
*/
void Refresh();
[[nodiscard]] MappingStatus
EvaluatePoint(mfem::ElementTransformation &transformation,
[[nodiscard]] MappingStatus EvaluatePoint(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
MappingPointContext &context);
MappingPointContext &context
);
[[nodiscard]] MappingStatus
EvaluateVolume(mfem::ElementTransformation &transformation,
[[nodiscard]] MappingStatus EvaluateVolume(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
VolumeMappingContext &context);
VolumeMappingContext &context
);
[[nodiscard]] MappingStatus
EvaluateFace(mfem::FaceElementTransformations &transformation,
[[nodiscard]] MappingStatus EvaluateFace(
mfem::FaceElementTransformations &transformation,
FaceElementSide side,
const mfem::IntegrationPoint &integration_point,
FaceMappingContext &context);
FaceMappingContext &context
);
[[nodiscard]] VolumeQuadratureContext
GetQuadratureContext(mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point);
[[nodiscard]] VolumeQuadratureContext GetQuadratureContext(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point
);
[[nodiscard]] FaceQuadratureContext
GetFaceQuadratureContext(
[[nodiscard]] FaceQuadratureContext GetFaceQuadratureContext(
mfem::FaceElementTransformations &transformation,
const mfem::IntegrationPoint &integration_point,
FaceElementSide side = FaceElementSide::element_1);
FaceElementSide side = FaceElementSide::element_1
);
void GetPhysicalPoint(mfem::ElementTransformation &transformation,
void GetPhysicalPoint(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
mfem::Vector &physical_position);
mfem::Vector &physical_position
);
private:
void ValidateFieldBindings() const;

View File

@@ -54,10 +54,19 @@ export import :operators.prepared_displacement_residual;
export import :model.structure_profile;
export import :model.structure.base;
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 :surface.base;
export import :surface.isobaric;
export import :surface.constant;
export import :surface.dependencies;
export import :surface.compiled;
export import :surface.compiler;
export import :model.stellar;
export import :operators.prepared_mass_normalization;
export import :operators.prepared_centering_constraint;
export import :operators.prepared_surface_constraint;
export import :operators.prepared_stellar_equilibrium;

View File

@@ -7,62 +7,84 @@ module;
export module mean_field:model.stellar;
export import :eos.base;
export import :eos.runtime;
export import :model.structure.base;
export import :surface.base;
export import :surface.compiler;
export namespace mean_field::models {
namespace detail {
template <typename Candidate>
concept StructurePrescription =
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>
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>;
};
/*
* Public ownership facade for a physical structure prescription and its
* stellar-surface prescription.
*
* The concrete prescriptions are allocated once at construction. Their
* stable addresses allow future prepared operators and contexts to borrow
* references without making ownership part of the user-facing API.
*/
template <StructurePrescription Candidate>
using StructureEquationOfStateT =
std::remove_cvref_t<decltype(std::declval<const std::remove_cvref_t<Candidate> &>().equationOfState())>;
template <typename Candidate, typename EquationOfState>
concept SurfacePrescription =
surface::ConstantPressureSurfaceType<Candidate> &&
surface::PressureSurfaceCompilable<surface::BarotropicSurfaceFormulation, std::remove_cvref_t<EquationOfState>>;
template <StructurePrescription Structure>
requires SurfacePrescription<surface::ConstantPressureSurface, StructureEquationOfStateT<Structure>>
class StellarModel final {
public:
template <
StructurePrescription StructureType,
SurfacePrescription SurfaceType>
using StructurePrescriptionType = Structure;
using SurfacePrescriptionType = surface::ConstantPressureSurface;
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(
StructureType &&structurePrescription,
SurfaceType &&surfacePrescription
StructureArgument &&structurePrescription,
const surface::ConstantPressureSurface surfacePrescription
)
: StellarModel(
std::make_unique<std::remove_cvref_t<StructureType>>(
std::forward<StructureType>(structurePrescription)
: m_structurePrescription(
std::make_unique<Structure>(std::forward<StructureArgument>(structurePrescription))
),
std::make_unique<std::remove_cvref_t<SurfaceType>>(std::forward<SurfaceType>(surfacePrescription))
m_surfacePrescription(std::make_unique<surface::ConstantPressureSurface>(surfacePrescription)),
m_compiledSurfaceConstraint(
std::make_unique<SurfaceConstraintType>(validateAndCompileSurface(
*m_structurePrescription,
*m_surfacePrescription
))
) {
}
~StellarModel() = default;
StellarModel(const StellarModel &) = delete;
StellarModel &operator=(const StellarModel &) = delete;
StellarModel(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;
}
[[nodiscard]] const mean_field::surface::SurfaceBase &surfacePrescription() const noexcept {
[[nodiscard]] const surface::ConstantPressureSurface &surfacePrescription() const noexcept {
return *m_surfacePrescription;
}
[[nodiscard]] const mean_field::eos::EquationOfState &equationOfState() const noexcept {
[[nodiscard]] const EquationOfStateType &equationOfState() const noexcept {
return m_structurePrescription->equationOfState();
}
@@ -70,45 +92,99 @@ export namespace mean_field::models {
return m_structurePrescription->targetMass();
}
[[nodiscard]] mean_field::models::structure::StructureSeed
makeInitialSeed(const mean_field::models::structure::StructureSeedRequest &request) const {
[[nodiscard]] structure::StructureSeed makeInitialSeed(const structure::StructureSeedRequest &request) const {
return m_structurePrescription->makeInitialSeed(request);
}
[[nodiscard]] const mean_field::surface::ResolvedSurfaceCondition &resolvedSurfaceCondition() const noexcept {
return m_resolvedSurfaceCondition;
[[nodiscard]] const SurfaceConstraintType &compiledSurfaceConstraint() const noexcept {
return *m_compiledSurfaceConstraint;
}
private:
explicit StellarModel(
std::unique_ptr<mean_field::models::structure::StructureBase> structurePrescription,
std::unique_ptr<mean_field::surface::SurfaceBase> 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
[[nodiscard]] static SurfaceConstraintType validateAndCompileSurface(
const Structure &structurePrescription,
const surface::ConstantPressureSurface &surfacePrescription
) {
structurePrescription.validate();
const mean_field::eos::EquationOfState &equationOfState = structurePrescription.equationOfState();
surfacePrescription.validate(equationOfState);
return surfacePrescription.resolve(equationOfState);
return surface::compilePressureSurfaceConstraint<surface::BarotropicSurfaceFormulation>(
surfacePrescription, structurePrescription.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

View File

@@ -12,20 +12,20 @@ export import :model.structure.base;
import :utils.misc;
export namespace mean_field::models::structure {
class PolytropicStructure final : public StructureBase {
class PolytropicStructure final {
public:
explicit PolytropicStructure(
eos::Polytrope equationOfState,
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:
struct LaneEmdenPoint {

View File

@@ -1,7 +1,7 @@
module;
#include <mfem.hpp>
export module mean_field:model.structure.base;
export import :eos.base;
export import :eos.runtime;
export namespace mean_field::models::structure {
struct StructureSeed {
@@ -23,7 +23,7 @@ export namespace mean_field::models::structure {
public:
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;

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

View File

@@ -45,8 +45,7 @@ template <IsDomain... DomainTs> struct DomainSet {};
template <typename T> constexpr bool is_domain_set_v = false;
template <IsDomain... DomainTs>
constexpr bool is_domain_set_v<DomainSet<DomainTs...>> = true;
template <IsDomain... DomainTs> constexpr bool is_domain_set_v<DomainSet<DomainTs...>> = true;
template <typename T>
concept IsDomainSet = is_domain_set_v<T>;
@@ -59,8 +58,7 @@ using All = DomainSet<Core, Envelope, Vacuum>;
struct DomainRelation { };
template <IsDomainOrSet A, IsDomainOrSet B>
struct Inscribed final : public DomainRelation {
template <IsDomainOrSet A, IsDomainOrSet B> struct Inscribed final : public DomainRelation {
using inner_type = A;
using outer_type = B;
@@ -90,16 +88,14 @@ template <IsBoundary B, int Id> struct BoundaryAttribute {
template <typename T> constexpr bool is_material_v = false;
template <IsDomain D, int Id>
constexpr bool is_material_v<Material<D, Id>> = true;
template <IsDomain D, int Id> constexpr bool is_material_v<Material<D, Id>> = true;
template <typename T>
concept IsMaterial = is_material_v<T>;
template <typename T> constexpr bool is_boundary_attr_v = false;
template <IsBoundary B, int Id>
constexpr bool is_boundary_attr_v<BoundaryAttribute<B, Id>> = true;
template <IsBoundary B, int Id> constexpr bool is_boundary_attr_v<BoundaryAttribute<B, Id>> = true;
template <typename T>
concept IsBoundaryAttr = is_boundary_attr_v<T>;
@@ -117,13 +113,10 @@ struct BoundaryDescriptor {
template <IsMaterial... MaterialTs>
[[nodiscard]]
consteval bool material_ids_are_unique() noexcept {
constexpr std::array<int, sizeof...(MaterialTs)> materialIds{
MaterialTs::id...};
constexpr std::array<int, sizeof...(MaterialTs)> materialIds{MaterialTs::id...};
for (std::size_t firstIndex = 0; firstIndex < materialIds.size();
++firstIndex) {
for (std::size_t secondIndex = firstIndex + 1;
secondIndex < materialIds.size(); ++secondIndex) {
for (std::size_t firstIndex = 0; firstIndex < materialIds.size(); ++firstIndex) {
for (std::size_t secondIndex = firstIndex + 1; secondIndex < materialIds.size(); ++secondIndex) {
if (materialIds[firstIndex] == materialIds[secondIndex]) {
return false;
}
@@ -136,13 +129,10 @@ consteval bool material_ids_are_unique() noexcept {
template <IsBoundaryAttr... BoundaryTs>
[[nodiscard]]
consteval bool boundary_ids_are_unique() noexcept {
constexpr std::array<int, sizeof...(BoundaryTs)> boundaryIds{
BoundaryTs::id...};
constexpr std::array<int, sizeof...(BoundaryTs)> boundaryIds{BoundaryTs::id...};
for (std::size_t firstIndex = 0; firstIndex < boundaryIds.size();
++firstIndex) {
for (std::size_t secondIndex = firstIndex + 1;
secondIndex < boundaryIds.size(); ++secondIndex) {
for (std::size_t firstIndex = 0; firstIndex < boundaryIds.size(); ++firstIndex) {
for (std::size_t secondIndex = firstIndex + 1; secondIndex < boundaryIds.size(); ++secondIndex) {
if (boundaryIds[firstIndex] == boundaryIds[secondIndex]) {
return false;
}
@@ -156,14 +146,12 @@ template <typename... MaterialTs> struct MaterialDomainsAreUnique;
template <> struct MaterialDomainsAreUnique<> : std::true_type { };
template <typename MaterialT>
struct MaterialDomainsAreUnique<MaterialT> : std::true_type {};
template <typename MaterialT> struct MaterialDomainsAreUnique<MaterialT> : std::true_type { };
template <typename FirstMaterialT, typename... RemainingMaterialTs>
struct MaterialDomainsAreUnique<FirstMaterialT, RemainingMaterialTs...>
: std::bool_constant<
(!std::is_same_v<typename FirstMaterialT::domain_type,
typename RemainingMaterialTs::domain_type> &&
(!std::is_same_v<typename FirstMaterialT::domain_type, typename RemainingMaterialTs::domain_type> &&
...) &&
MaterialDomainsAreUnique<RemainingMaterialTs...>::value> { };
@@ -171,14 +159,12 @@ template <typename... BoundaryTs> struct BoundaryTypesAreUnique;
template <> struct BoundaryTypesAreUnique<> : std::true_type { };
template <typename BoundaryT>
struct BoundaryTypesAreUnique<BoundaryT> : std::true_type {};
template <typename BoundaryT> struct BoundaryTypesAreUnique<BoundaryT> : std::true_type { };
template <typename FirstBoundaryT, typename... RemainingBoundaryTs>
struct BoundaryTypesAreUnique<FirstBoundaryT, RemainingBoundaryTs...>
: std::bool_constant<
(!std::is_same_v<typename FirstBoundaryT::boundary_type,
typename RemainingBoundaryTs::boundary_type> &&
(!std::is_same_v<typename FirstBoundaryT::boundary_type, typename RemainingBoundaryTs::boundary_type> &&
...) &&
BoundaryTypesAreUnique<RemainingBoundaryTs...>::value> { };
@@ -186,8 +172,7 @@ template <typename... MaterialTs>
concept HaveUniqueMaterialIds = material_ids_are_unique<MaterialTs...>();
template <typename... MaterialTs>
concept HaveUniqueMaterialDomains =
MaterialDomainsAreUnique<MaterialTs...>::value;
concept HaveUniqueMaterialDomains = MaterialDomainsAreUnique<MaterialTs...>::value;
template <typename... BoundaryTs>
concept HaveUniqueBoundaryIds = boundary_ids_are_unique<BoundaryTs...>();
@@ -196,104 +181,88 @@ template <typename... BoundaryTs>
concept HaveUniqueBoundaryTypes = BoundaryTypesAreUnique<BoundaryTs...>::value;
template <IsMaterial... MaterialTs>
requires(HaveUniqueMaterialIds<MaterialTs...> &&
HaveUniqueMaterialDomains<MaterialTs...>)
requires(HaveUniqueMaterialIds<MaterialTs...> && HaveUniqueMaterialDomains<MaterialTs...>)
struct MaterialList {
static constexpr std::size_t count = sizeof...(MaterialTs);
[[nodiscard]]
static constexpr std::array<MaterialDescriptor, count>
descriptors() noexcept {
return {MaterialDescriptor{.name = MaterialTs::domain_type::name,
.id = MaterialTs::id}...};
static constexpr std::array<
MaterialDescriptor,
count> descriptors() noexcept {
return {MaterialDescriptor{.name = MaterialTs::domain_type::name, .id = MaterialTs::id}...};
}
};
template <IsBoundaryAttr... BoundaryTs>
requires(HaveUniqueBoundaryIds<BoundaryTs...> &&
HaveUniqueBoundaryTypes<BoundaryTs...>)
requires(HaveUniqueBoundaryIds<BoundaryTs...> && HaveUniqueBoundaryTypes<BoundaryTs...>)
struct BoundaryList {
static constexpr std::size_t count = sizeof...(BoundaryTs);
[[nodiscard]]
static constexpr std::array<BoundaryDescriptor, count>
descriptors() noexcept {
return {BoundaryDescriptor{.name = BoundaryTs::boundary_type::name,
.id = BoundaryTs::id}...};
static constexpr std::array<
BoundaryDescriptor,
count> descriptors() noexcept {
return {BoundaryDescriptor{.name = BoundaryTs::boundary_type::name, .id = BoundaryTs::id}...};
}
};
template <typename DomainT, typename MaterialListT>
struct DomainMaterialResolver;
template <typename DomainT, typename MaterialListT> struct DomainMaterialResolver;
template <IsDomain DomainT, IsMaterial... MaterialTs>
struct DomainMaterialResolver<DomainT, MaterialList<MaterialTs...>> {
static constexpr bool registered =
(std::is_same_v<DomainT, typename MaterialTs::domain_type> || ...);
static constexpr bool registered = (std::is_same_v<DomainT, typename MaterialTs::domain_type> || ...);
[[nodiscard]]
static constexpr bool contains_attribute(int materialId) noexcept {
return ((std::is_same_v<DomainT, typename MaterialTs::domain_type> &&
MaterialTs::id == materialId) ||
...);
return ((std::is_same_v<DomainT, typename MaterialTs::domain_type> && MaterialTs::id == materialId) || ...);
}
[[nodiscard]]
static consteval int attribute() {
static_assert(registered,
"Requested domain is not registered in this schema.");
static_assert(registered, "Requested domain is not registered in this schema.");
int result = 0;
((std::is_same_v<DomainT, typename MaterialTs::domain_type>
? result = MaterialTs::id
: result),
...);
((std::is_same_v<DomainT, typename MaterialTs::domain_type> ? result = MaterialTs::id : result), ...);
return result;
}
};
template <IsDomain... DomainTs, IsMaterial... MaterialTs>
struct DomainMaterialResolver<DomainSet<DomainTs...>,
MaterialList<MaterialTs...>> {
struct DomainMaterialResolver<DomainSet<DomainTs...>, MaterialList<MaterialTs...>> {
static constexpr bool registered =
(DomainMaterialResolver<DomainTs,
MaterialList<MaterialTs...>>::registered &&
...);
(DomainMaterialResolver<DomainTs, MaterialList<MaterialTs...>>::registered && ...);
[[nodiscard]]
static constexpr bool contains_attribute(int materialId) noexcept {
return (DomainMaterialResolver<DomainTs, MaterialList<MaterialTs...>>::
contains_attribute(materialId) ||
...);
return (
DomainMaterialResolver<DomainTs, MaterialList<MaterialTs...>>::contains_attribute(materialId) || ...
);
}
};
template <typename BoundaryT, typename BoundaryListT>
struct BoundaryAttributeResolver;
template <typename BoundaryT, typename BoundaryListT> struct BoundaryAttributeResolver;
template <IsBoundary BoundaryT, IsBoundaryAttr... BoundaryTs>
struct BoundaryAttributeResolver<BoundaryT, BoundaryList<BoundaryTs...>> {
static constexpr bool registered =
(std::is_same_v<BoundaryT, typename BoundaryTs::boundary_type> || ...);
static constexpr bool registered = (std::is_same_v<BoundaryT, typename BoundaryTs::boundary_type> || ...);
[[nodiscard]]
static constexpr bool matches_attribute(int boundaryId) noexcept {
return ((std::is_same_v<BoundaryT, typename BoundaryTs::boundary_type> &&
BoundaryTs::id == boundaryId) ||
...);
return (
(std::is_same_v<BoundaryT, typename BoundaryTs::boundary_type> && BoundaryTs::id == boundaryId) || ...
);
}
[[nodiscard]]
static consteval int attribute() {
static_assert(registered, "Requested boundary is not registered "
"in this schema.");
static_assert(
registered, "Requested boundary is not registered "
"in this schema."
);
int result = 0;
((std::is_same_v<BoundaryT, typename BoundaryTs::boundary_type>
? result = BoundaryTs::id
: result),
...);
((std::is_same_v<BoundaryT, typename BoundaryTs::boundary_type> ? result = BoundaryTs::id : result), ...);
return result;
}
@@ -301,16 +270,14 @@ struct BoundaryAttributeResolver<BoundaryT, BoundaryList<BoundaryTs...>> {
template <typename T> constexpr bool is_material_list_v = false;
template <IsMaterial... MaterialTs>
constexpr bool is_material_list_v<MaterialList<MaterialTs...>> = true;
template <IsMaterial... MaterialTs> constexpr bool is_material_list_v<MaterialList<MaterialTs...>> = true;
template <typename T>
concept IsMaterialList = is_material_list_v<T>;
template <typename T> constexpr bool is_boundary_list_v = false;
template <IsBoundaryAttr... BoundaryTs>
constexpr bool is_boundary_list_v<BoundaryList<BoundaryTs...>> = true;
template <IsBoundaryAttr... BoundaryTs> constexpr bool is_boundary_list_v<BoundaryList<BoundaryTs...>> = true;
template <typename T>
concept IsBoundaryList = is_boundary_list_v<T>;
@@ -337,8 +304,7 @@ template <IsRelation... RelationTs> struct RelationList {
template <typename T> constexpr bool is_relation_list_v = false;
template <IsRelation... RelationTs>
constexpr bool is_relation_list_v<RelationList<RelationTs...>> = true;
template <IsRelation... RelationTs> constexpr bool is_relation_list_v<RelationList<RelationTs...>> = true;
template <typename T>
concept IsRelationList = is_relation_list_v<T>;
@@ -351,51 +317,36 @@ concept IsRelationList = is_relation_list_v<T>;
* DomainBoundary references both a boundary and one or
* two domains.
*/
template <IsRelation RelationT, IsMaterialList MaterialsT,
IsBoundaryList BoundariesT>
template <IsRelation RelationT, IsMaterialList MaterialsT, IsBoundaryList BoundariesT>
struct RelationUsesRegisteredEntities;
template <IsDomainOrSet DomainT, IsMaterialList MaterialsT,
IsBoundaryList BoundariesT>
struct RelationUsesRegisteredEntities<Connected<DomainT>, MaterialsT,
BoundariesT>
: std::bool_constant<
DomainMaterialResolver<DomainT, MaterialsT>::registered> {};
template <IsDomainOrSet DomainT, IsMaterialList MaterialsT, IsBoundaryList BoundariesT>
struct RelationUsesRegisteredEntities<Connected<DomainT>, MaterialsT, BoundariesT>
: std::bool_constant<DomainMaterialResolver<DomainT, MaterialsT>::registered> { };
template <IsDomainOrSet InnerT, IsDomainOrSet OuterT, IsMaterialList MaterialsT,
IsBoundaryList BoundariesT>
struct RelationUsesRegisteredEntities<Inscribed<InnerT, OuterT>, MaterialsT,
BoundariesT>
template <IsDomainOrSet InnerT, IsDomainOrSet OuterT, IsMaterialList MaterialsT, IsBoundaryList BoundariesT>
struct RelationUsesRegisteredEntities<Inscribed<InnerT, OuterT>, MaterialsT, BoundariesT>
: std::bool_constant<
DomainMaterialResolver<InnerT, MaterialsT>::registered &&
DomainMaterialResolver<OuterT, MaterialsT>::registered> { };
template <IsBoundary BoundaryT, IsDomainOrSet... DomainTs,
IsMaterialList MaterialsT, IsBoundaryList BoundariesT>
struct RelationUsesRegisteredEntities<DomainBoundary<BoundaryT, DomainTs...>,
MaterialsT, BoundariesT>
template <IsBoundary BoundaryT, IsDomainOrSet... DomainTs, IsMaterialList MaterialsT, IsBoundaryList BoundariesT>
struct RelationUsesRegisteredEntities<DomainBoundary<BoundaryT, DomainTs...>, MaterialsT, BoundariesT>
: std::bool_constant<
BoundaryAttributeResolver<BoundaryT, BoundariesT>::registered &&
(DomainMaterialResolver<DomainTs, MaterialsT>::registered && ...)> { };
template <IsMaterialList MaterialsT, IsBoundaryList BoundariesT,
IsRelationList RelationsT>
template <IsMaterialList MaterialsT, IsBoundaryList BoundariesT, IsRelationList RelationsT>
struct RelationsUseRegisteredEntities;
template <IsMaterialList MaterialsT, IsBoundaryList BoundariesT,
IsRelation... RelationTs>
struct RelationsUseRegisteredEntities<MaterialsT, BoundariesT,
RelationList<RelationTs...>>
: std::bool_constant<(RelationUsesRegisteredEntities<RelationTs, MaterialsT,
BoundariesT>::value &&
...)> {};
template <IsMaterialList MaterialsT, IsBoundaryList BoundariesT, IsRelation... RelationTs>
struct RelationsUseRegisteredEntities<MaterialsT, BoundariesT, RelationList<RelationTs...>>
: std::bool_constant<(RelationUsesRegisteredEntities<RelationTs, MaterialsT, BoundariesT>::value && ...)> { };
template <typename MaterialsT, typename BoundariesT, typename RelationsT>
concept HaveValidRelationEntities =
RelationsUseRegisteredEntities<MaterialsT, BoundariesT, RelationsT>::value;
concept HaveValidRelationEntities = RelationsUseRegisteredEntities<MaterialsT, BoundariesT, RelationsT>::value;
template <IsMaterialList Materials, IsBoundaryList Boundaries,
IsRelationList Relations>
template <IsMaterialList Materials, IsBoundaryList Boundaries, IsRelationList Relations>
requires HaveValidRelationEntities<Materials, Boundaries, Relations>
struct DomainSchema {
using materials_type = Materials;
@@ -427,19 +378,18 @@ struct DomainSchema {
template <IsDomainOrSet DomainT>
[[nodiscard]]
static constexpr bool attribute_belongs_to(int materialId) noexcept {
static_assert(contains_domain<DomainT>(),
"Requested domain is not completely "
"registered in this schema.");
static_assert(
contains_domain<DomainT>(), "Requested domain is not completely "
"registered in this schema."
);
return DomainMaterialResolver<DomainT, Materials>::contains_attribute(
materialId);
return DomainMaterialResolver<DomainT, Materials>::contains_attribute(materialId);
}
template <IsDomain DomainT>
[[nodiscard]]
static consteval int material_attribute() noexcept {
static_assert(contains_domain<DomainT>(),
"Requested domain is not registered in this schema.");
static_assert(contains_domain<DomainT>(), "Requested domain is not registered in this schema.");
return DomainMaterialResolver<DomainT, Materials>::attribute();
}
@@ -458,21 +408,19 @@ struct DomainSchema {
template <IsBoundary BoundaryT>
[[nodiscard]]
static constexpr bool boundary_attribute_matches(int boundaryId) noexcept {
static_assert(contains_boundary<BoundaryT>(),
"Requested boundary is not registered "
"in this schema.");
static_assert(
contains_boundary<BoundaryT>(), "Requested boundary is not registered "
"in this schema."
);
return BoundaryAttributeResolver<BoundaryT, Boundaries>::matches_attribute(
boundaryId);
return BoundaryAttributeResolver<BoundaryT, Boundaries>::matches_attribute(boundaryId);
}
};
template <typename S> constexpr bool is_schema_v = false;
template <IsMaterialList Materials, IsBoundaryList Boundaries,
IsRelationList Relations>
constexpr bool is_schema_v<DomainSchema<Materials, Boundaries, Relations>> =
true;
template <IsMaterialList Materials, IsBoundaryList Boundaries, IsRelationList Relations>
constexpr bool is_schema_v<DomainSchema<Materials, Boundaries, Relations>> = true;
template <typename T>
concept IsSchema = is_schema_v<T>;
@@ -533,8 +481,7 @@ struct RelationValidationResult {
std::optional<int> secondMaterialId = std::nullopt;
};
std::optional<DomainBoundaryDiagnostics> domainBoundaryDiagnostics =
std::nullopt;
std::optional<DomainBoundaryDiagnostics> domainBoundaryDiagnostics = std::nullopt;
[[nodiscard]]
bool valid() const noexcept {
@@ -549,18 +496,19 @@ struct RelationValidationResult {
template <IsRelation RelationT> struct RelationValidator;
template <IsDomainOrSet InnerT, IsDomainOrSet OuterT>
struct RelationValidator<Inscribed<InnerT, OuterT>> {
template <IsDomainOrSet InnerT, IsDomainOrSet OuterT> struct RelationValidator<Inscribed<InnerT, OuterT>> {
template <IsSchema SchemaT>
[[nodiscard]]
static RelationValidationResult validate(const mfem::Mesh &mesh) {
static_assert(SchemaT::template contains_domain<InnerT>(),
"The inner domain of Inscribed is not "
"registered in the supplied schema.");
static_assert(
SchemaT::template contains_domain<InnerT>(), "The inner domain of Inscribed is not "
"registered in the supplied schema."
);
static_assert(SchemaT::template contains_domain<OuterT>(),
"The outer domain of Inscribed is not "
"registered in the supplied schema.");
static_assert(
SchemaT::template contains_domain<OuterT>(), "The outer domain of Inscribed is not "
"registered in the supplied schema."
);
bool foundInnerElement = false;
bool foundOuterElement = false;
@@ -569,13 +517,9 @@ struct RelationValidator<Inscribed<InnerT, OuterT>> {
for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) {
const int materialId = mesh.GetAttribute(elementId);
foundInnerElement =
foundInnerElement ||
SchemaT::template attribute_belongs_to<InnerT>(materialId);
foundInnerElement = foundInnerElement || SchemaT::template attribute_belongs_to<InnerT>(materialId);
foundOuterElement =
foundOuterElement ||
SchemaT::template attribute_belongs_to<OuterT>(materialId);
foundOuterElement = foundOuterElement || SchemaT::template attribute_belongs_to<OuterT>(materialId);
}
if (!foundInnerElement) {
@@ -593,12 +537,12 @@ struct RelationValidator<Inscribed<InnerT, OuterT>> {
mesh.GetFaceElements(faceId, &firstElementId, &secondElementId);
const bool firstIsInner =
firstElementId >= 0 && SchemaT::template attribute_belongs_to<InnerT>(
mesh.GetAttribute(firstElementId));
firstElementId >= 0 &&
SchemaT::template attribute_belongs_to<InnerT>(mesh.GetAttribute(firstElementId));
const bool secondIsInner = secondElementId >= 0 &&
SchemaT::template attribute_belongs_to<InnerT>(
mesh.GetAttribute(secondElementId));
const bool secondIsInner =
secondElementId >= 0 &&
SchemaT::template attribute_belongs_to<InnerT>(mesh.GetAttribute(secondElementId));
if (firstIsInner == secondIsInner) {
continue;
@@ -606,32 +550,31 @@ struct RelationValidator<Inscribed<InnerT, OuterT>> {
foundInnerBoundary = true;
const int innerElementId =
firstIsInner ? firstElementId : secondElementId;
const int innerElementId = firstIsInner ? firstElementId : secondElementId;
const int adjacentElementId =
firstIsInner ? secondElementId : firstElementId;
const int adjacentElementId = firstIsInner ? secondElementId : firstElementId;
if (adjacentElementId < 0) {
return {.failure =
RelationValidationFailure::InnerDomainTouchesMeshBoundary,
.inscribedDiagnostics = std::make_optional<
RelationValidationResult::InscribedDiagnostics>(
{.faceId = faceId, .innerElementId = innerElementId})};
return {
.failure = RelationValidationFailure::InnerDomainTouchesMeshBoundary,
.inscribedDiagnostics = std::make_optional<RelationValidationResult::InscribedDiagnostics>(
{.faceId = faceId, .innerElementId = innerElementId}
)
};
}
const int adjacentMaterialId = mesh.GetAttribute(adjacentElementId);
if (!SchemaT::template attribute_belongs_to<OuterT>(adjacentMaterialId)) {
return {
.failure =
RelationValidationFailure::InnerDomainTouchesUnexpectedMaterial,
.inscribedDiagnostics = std::make_optional<
RelationValidationResult::InscribedDiagnostics>(
.failure = RelationValidationFailure::InnerDomainTouchesUnexpectedMaterial,
.inscribedDiagnostics = std::make_optional<RelationValidationResult::InscribedDiagnostics>(
{.faceId = faceId,
.innerElementId = innerElementId,
.adjacentElementId = adjacentElementId,
.adjacentMaterialId = adjacentMaterialId})};
.adjacentMaterialId = adjacentMaterialId}
)
};
}
}
@@ -647,13 +590,13 @@ template <IsDomainOrSet DomainT> struct RelationValidator<Connected<DomainT>> {
template <IsSchema SchemaT>
[[nodiscard]]
static RelationValidationResult validate(const mfem::Mesh &mesh) {
static_assert(SchemaT::template contains_domain<DomainT>(),
"Connected refers to a domain which is "
static_assert(
SchemaT::template contains_domain<DomainT>(), "Connected refers to a domain which is "
"not completely registered in the "
"supplied DomainSchema.");
"supplied DomainSchema."
);
std::vector<bool> belongsToDomain(static_cast<std::size_t>(mesh.GetNE()),
false);
std::vector<bool> belongsToDomain(static_cast<std::size_t>(mesh.GetNE()), false);
int domainElementCount = 0;
int firstDomainElement = -1;
@@ -661,8 +604,7 @@ template <IsDomainOrSet DomainT> struct RelationValidator<Connected<DomainT>> {
for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) {
const int materialId = mesh.GetAttribute(elementId);
const bool belongs =
SchemaT::template attribute_belongs_to<DomainT>(materialId);
const bool belongs = SchemaT::template attribute_belongs_to<DomainT>(materialId);
belongsToDomain[static_cast<std::size_t>(elementId)] = belongs;
@@ -678,14 +620,15 @@ template <IsDomainOrSet DomainT> struct RelationValidator<Connected<DomainT>> {
}
if (domainElementCount == 0) {
return {.failure = RelationValidationFailure::DomainAbsent,
.connectedDiagnostics = std::make_optional<
RelationValidationResult::ConnectedDiagnostics>(
{.domainElementCount = 0, .visitedElementCount = 0})};
return {
.failure = RelationValidationFailure::DomainAbsent,
.connectedDiagnostics = std::make_optional<RelationValidationResult::ConnectedDiagnostics>(
{.domainElementCount = 0, .visitedElementCount = 0}
)
};
}
std::vector<std::vector<int>> adjacency(
static_cast<std::size_t>(mesh.GetNE()));
std::vector<std::vector<int>> adjacency(static_cast<std::size_t>(mesh.GetNE()));
for (int faceId = 0; faceId < mesh.GetNumFaces(); ++faceId) {
int firstElementId = -1;
@@ -697,21 +640,17 @@ template <IsDomainOrSet DomainT> struct RelationValidator<Connected<DomainT>> {
continue;
}
const bool firstBelongs =
belongsToDomain[static_cast<std::size_t>(firstElementId)];
const bool firstBelongs = belongsToDomain[static_cast<std::size_t>(firstElementId)];
const bool secondBelongs =
belongsToDomain[static_cast<std::size_t>(secondElementId)];
const bool secondBelongs = belongsToDomain[static_cast<std::size_t>(secondElementId)];
if (!(firstBelongs && secondBelongs)) {
continue;
}
adjacency[static_cast<std::size_t>(firstElementId)].push_back(
secondElementId);
adjacency[static_cast<std::size_t>(firstElementId)].push_back(secondElementId);
adjacency[static_cast<std::size_t>(secondElementId)].push_back(
firstElementId);
adjacency[static_cast<std::size_t>(secondElementId)].push_back(firstElementId);
}
std::vector<bool> visited(static_cast<std::size_t>(mesh.GetNE()), false);
@@ -737,8 +676,7 @@ template <IsDomainOrSet DomainT> struct RelationValidator<Connected<DomainT>> {
++visitedElementCount;
for (const int neighborElementId :
adjacency[static_cast<std::size_t>(elementId)]) {
for (const int neighborElementId : adjacency[static_cast<std::size_t>(elementId)]) {
if (!visited[static_cast<std::size_t>(neighborElementId)]) {
pending.push_back(neighborElementId);
}
@@ -746,10 +684,11 @@ template <IsDomainOrSet DomainT> struct RelationValidator<Connected<DomainT>> {
}
if (visitedElementCount == domainElementCount) {
return {.connectedDiagnostics = std::make_optional<
RelationValidationResult::ConnectedDiagnostics>(
{.domainElementCount = domainElementCount,
.visitedElementCount = visitedElementCount})};
return {
.connectedDiagnostics = std::make_optional<RelationValidationResult::ConnectedDiagnostics>(
{.domainElementCount = domainElementCount, .visitedElementCount = visitedElementCount}
)
};
}
int disconnectedElementId = -1;
@@ -766,11 +705,12 @@ template <IsDomainOrSet DomainT> struct RelationValidator<Connected<DomainT>> {
return {
.failure = RelationValidationFailure::DomainDisconnected,
.connectedDiagnostics =
std::make_optional<RelationValidationResult::ConnectedDiagnostics>(
.connectedDiagnostics = std::make_optional<RelationValidationResult::ConnectedDiagnostics>(
{.elementId = disconnectedElementId,
.domainElementCount = domainElementCount,
.visitedElementCount = visitedElementCount})};
.visitedElementCount = visitedElementCount}
)
};
}
};
@@ -779,19 +719,23 @@ struct RelationValidator<DomainBoundary<BoundaryT, DomainTs...>> {
template <IsSchema SchemaT>
[[nodiscard]]
static RelationValidationResult validate(const mfem::Mesh &mesh) {
static_assert(sizeof...(DomainTs) == 1 || sizeof...(DomainTs) == 2,
"DomainBoundary requires exactly one or two domains.");
static_assert(
sizeof...(DomainTs) == 1 || sizeof...(DomainTs) == 2,
"DomainBoundary requires exactly one or two domains."
);
static_assert(SchemaT::template contains_boundary<BoundaryT>(),
"DomainBoundary refers to a boundary which is not "
"registered in the supplied DomainSchema.");
static_assert(
SchemaT::template contains_boundary<BoundaryT>(), "DomainBoundary refers to a boundary which is not "
"registered in the supplied DomainSchema."
);
static_assert((SchemaT::template contains_domain<DomainTs>() && ...),
static_assert(
(SchemaT::template contains_domain<DomainTs>() && ...),
"DomainBoundary refers to a domain which is not "
"completely registered in the supplied DomainSchema.");
"completely registered in the supplied DomainSchema."
);
constexpr int expectedBoundaryAttribute =
SchemaT::template boundary_attribute<BoundaryT>();
constexpr int expectedBoundaryAttribute = SchemaT::template boundary_attribute<BoundaryT>();
using DomainsTuple = std::tuple<DomainTs...>;
@@ -804,47 +748,42 @@ struct RelationValidator<DomainBoundary<BoundaryT, DomainTs...>> {
* one here; instead, every boundary element on an expected
* face must carry the expected semantic boundary attribute.
*/
std::vector<std::vector<int>> boundaryElementsByFace(
static_cast<std::size_t>(mesh.GetNumFaces()));
std::vector<std::vector<int>> boundaryElementsByFace(static_cast<std::size_t>(mesh.GetNumFaces()));
for (int boundaryElementId = 0; boundaryElementId < mesh.GetNBE();
++boundaryElementId) {
for (int boundaryElementId = 0; boundaryElementId < mesh.GetNBE(); ++boundaryElementId) {
const int faceId = mesh.GetBdrElementFaceIndex(boundaryElementId);
if (faceId >= 0 && faceId < mesh.GetNumFaces()) {
boundaryElementsByFace[static_cast<std::size_t>(faceId)].push_back(
boundaryElementId);
boundaryElementsByFace[static_cast<std::size_t>(faceId)].push_back(boundaryElementId);
}
}
/*
* Build detailed diagnostics for one face.
*/
const auto make_diagnostics =
[&mesh, expectedBoundaryAttribute](
const auto make_diagnostics = [&mesh, expectedBoundaryAttribute](
int faceId, int boundaryElementId,
std::optional<int> actualBoundaryAttribute) {
std::optional<int> actualBoundaryAttribute
) {
RelationValidationResult::DomainBoundaryDiagnostics diagnostics{
.faceId = faceId,
.boundaryElementId = boundaryElementId,
.expectedBoundaryAttribute = expectedBoundaryAttribute,
.actualBoundaryAttribute = actualBoundaryAttribute};
.actualBoundaryAttribute = actualBoundaryAttribute
};
if (faceId < 0 || faceId >= mesh.GetNumFaces()) {
return diagnostics;
}
mesh.GetFaceElements(faceId, &diagnostics.firstElementId,
&diagnostics.secondElementId);
mesh.GetFaceElements(faceId, &diagnostics.firstElementId, &diagnostics.secondElementId);
if (diagnostics.firstElementId >= 0) {
diagnostics.firstMaterialId =
mesh.GetAttribute(diagnostics.firstElementId);
diagnostics.firstMaterialId = mesh.GetAttribute(diagnostics.firstElementId);
}
if (diagnostics.secondElementId >= 0) {
diagnostics.secondMaterialId =
mesh.GetAttribute(diagnostics.secondElementId);
diagnostics.secondMaterialId = mesh.GetAttribute(diagnostics.secondElementId);
}
return diagnostics;
@@ -866,8 +805,7 @@ struct RelationValidator<DomainBoundary<BoundaryT, DomainTs...>> {
*
* Both adjacent volume elements must exist.
*/
const auto has_required_topology = [](int firstElementId,
int secondElementId) {
const auto has_required_topology = [](int firstElementId, int secondElementId) {
if constexpr (sizeof...(DomainTs) == 1) {
const bool firstExists = firstElementId >= 0;
@@ -885,8 +823,7 @@ struct RelationValidator<DomainBoundary<BoundaryT, DomainTs...>> {
*
* For two domains, ordering is intentionally irrelevant.
*/
const auto face_matches_domains = [&mesh](int firstElementId,
int secondElementId) {
const auto face_matches_domains = [&mesh](int firstElementId, int secondElementId) {
if constexpr (sizeof...(DomainTs) == 1) {
using DomainT = std::tuple_element_t<0, DomainsTuple>;
@@ -916,17 +853,11 @@ struct RelationValidator<DomainBoundary<BoundaryT, DomainTs...>> {
const int secondMaterialId = mesh.GetAttribute(secondElementId);
const bool forwardMatch =
SchemaT::template attribute_belongs_to<FirstDomainT>(
firstMaterialId) &&
SchemaT::template attribute_belongs_to<SecondDomainT>(
secondMaterialId);
const bool forwardMatch = SchemaT::template attribute_belongs_to<FirstDomainT>(firstMaterialId) &&
SchemaT::template attribute_belongs_to<SecondDomainT>(secondMaterialId);
const bool reverseMatch =
SchemaT::template attribute_belongs_to<SecondDomainT>(
firstMaterialId) &&
SchemaT::template attribute_belongs_to<FirstDomainT>(
secondMaterialId);
const bool reverseMatch = SchemaT::template attribute_belongs_to<SecondDomainT>(firstMaterialId) &&
SchemaT::template attribute_belongs_to<FirstDomainT>(secondMaterialId);
return forwardMatch || reverseMatch;
}
@@ -941,8 +872,7 @@ struct RelationValidator<DomainBoundary<BoundaryT, DomainTs...>> {
* exactly the topology/material interface declared by
* DomainBoundary.
*/
for (int boundaryElementId = 0; boundaryElementId < mesh.GetNBE();
++boundaryElementId) {
for (int boundaryElementId = 0; boundaryElementId < mesh.GetNBE(); ++boundaryElementId) {
const int boundaryAttribute = mesh.GetBdrAttribute(boundaryElementId);
if (boundaryAttribute != expectedBoundaryAttribute) {
@@ -959,21 +889,23 @@ struct RelationValidator<DomainBoundary<BoundaryT, DomainTs...>> {
mesh.GetFaceElements(faceId, &firstElementId, &secondElementId);
if (!has_required_topology(firstElementId, secondElementId)) {
return {.failure = RelationValidationFailure::
DomainBoundaryTaggedFaceHasWrongTopology,
.domainBoundaryDiagnostics = std::make_optional<
RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, boundaryElementId,
boundaryAttribute))};
return {
.failure = RelationValidationFailure::DomainBoundaryTaggedFaceHasWrongTopology,
.domainBoundaryDiagnostics =
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, boundaryElementId, boundaryAttribute)
)
};
}
if (!face_matches_domains(firstElementId, secondElementId)) {
return {.failure = RelationValidationFailure::
DomainBoundaryTaggedFaceTouchesUnexpectedMaterial,
.domainBoundaryDiagnostics = std::make_optional<
RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, boundaryElementId,
boundaryAttribute))};
return {
.failure = RelationValidationFailure::DomainBoundaryTaggedFaceTouchesUnexpectedMaterial,
.domainBoundaryDiagnostics =
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, boundaryElementId, boundaryAttribute)
)
};
}
}
@@ -1001,32 +933,32 @@ struct RelationValidator<DomainBoundary<BoundaryT, DomainTs...>> {
foundExpectedFace = true;
const auto &boundaryElementIds =
boundaryElementsByFace[static_cast<std::size_t>(faceId)];
const auto &boundaryElementIds = boundaryElementsByFace[static_cast<std::size_t>(faceId)];
if (boundaryElementIds.empty()) {
return {
.failure =
RelationValidationFailure::DomainBoundaryExpectedFaceIsUntagged,
.domainBoundaryDiagnostics = std::make_optional<
RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, -1, std::nullopt))};
.failure = RelationValidationFailure::DomainBoundaryExpectedFaceIsUntagged,
.domainBoundaryDiagnostics =
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, -1, std::nullopt)
)
};
}
for (const int boundaryElementId : boundaryElementIds) {
const int actualBoundaryAttribute =
mesh.GetBdrAttribute(boundaryElementId);
const int actualBoundaryAttribute = mesh.GetBdrAttribute(boundaryElementId);
if (actualBoundaryAttribute == expectedBoundaryAttribute) {
continue;
}
return {.failure = RelationValidationFailure::
DomainBoundaryExpectedFaceHasWrongAttribute,
.domainBoundaryDiagnostics = std::make_optional<
RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, boundaryElementId,
actualBoundaryAttribute))};
return {
.failure = RelationValidationFailure::DomainBoundaryExpectedFaceHasWrongAttribute,
.domainBoundaryDiagnostics =
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, boundaryElementId, actualBoundaryAttribute)
)
};
}
}
@@ -1039,10 +971,13 @@ struct RelationValidator<DomainBoundary<BoundaryT, DomainTs...>> {
* have returned a more specific diagnostic.
*/
if (!foundTaggedBoundary || !foundExpectedFace) {
return {.failure = RelationValidationFailure::DomainBoundaryAbsent,
.domainBoundaryDiagnostics = std::make_optional<
RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(-1, -1, std::nullopt))};
return {
.failure = RelationValidationFailure::DomainBoundaryAbsent,
.domainBoundaryDiagnostics =
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(-1, -1, std::nullopt)
)
};
}
return {};
@@ -1109,8 +1044,7 @@ struct SchemaValidationResult {
[[nodiscard]]
std::optional<std::size_t> first_failed_relation_index() const noexcept {
for (std::size_t relationIndex = 0; relationIndex < relationResults.size();
++relationIndex) {
for (std::size_t relationIndex = 0; relationIndex < relationResults.size(); ++relationIndex) {
if (!relationResults[relationIndex].valid()) {
return relationIndex;
}
@@ -1120,8 +1054,7 @@ struct SchemaValidationResult {
}
};
template <IsSchema SchemaT, typename RelationListT>
struct SchemaRelationValidator;
template <IsSchema SchemaT, typename RelationListT> struct SchemaRelationValidator;
template <IsSchema SchemaT, IsRelation... RelationTs>
struct SchemaRelationValidator<SchemaT, RelationList<RelationTs...>> {
@@ -1133,11 +1066,13 @@ struct SchemaRelationValidator<SchemaT, RelationList<RelationTs...>> {
std::size_t relationIndex = 0;
(schemaResult.relationResults.push_back(SchemaRelationValidationResult{
(schemaResult.relationResults.push_back(
SchemaRelationValidationResult{
.relationIndex = relationIndex++,
.relationName = RelationTs::name,
.result =
RelationValidator<RelationTs>::template validate<SchemaT>(mesh)}),
.result = RelationValidator<RelationTs>::template validate<SchemaT>(mesh)
}
),
...);
return schemaResult;
@@ -1152,18 +1087,20 @@ SchemaValidationResult validate_schema(const mfem::Mesh &mesh) {
return SchemaRelationValidator<SchemaT, RelationsT>::validate(mesh);
}
template <IsDomainOrSet DomainT, IsSchema SchemaT>
template <
IsDomainOrSet DomainT,
IsSchema SchemaT>
[[nodiscard]]
mfem::Array<int> make_attribute_marker(const mfem::Mesh &mesh) {
static_assert(SchemaT::template contains_domain<DomainT>(),
"Requested marker domain is not completely registered in the "
"supplied DomainSchema.");
static_assert(
SchemaT::template contains_domain<DomainT>(), "Requested marker domain is not completely registered in the "
"supplied DomainSchema."
);
mfem::Array<int> marker(mesh.attributes.Max());
for (int attribute = 1; attribute <= marker.Size(); ++attribute) {
marker[attribute - 1] =
SchemaT::template attribute_belongs_to<DomainT>(attribute) ? 1 : 0;
marker[attribute - 1] = SchemaT::template attribute_belongs_to<DomainT>(attribute) ? 1 : 0;
}
return marker;
@@ -1171,16 +1108,18 @@ mfem::Array<int> make_attribute_marker(const mfem::Mesh &mesh) {
using CoreEnvelopeVacuumDomainSchema = DomainSchema<
MaterialList<Material<Core, 1>, Material<Envelope, 2>, Material<Vacuum, 3>>,
BoundaryList<BoundaryAttribute<StellarSurface, 1>,
BoundaryAttribute<InfinitySurface, 2>>,
BoundaryList<BoundaryAttribute<StellarSurface, 1>, BoundaryAttribute<InfinitySurface, 2>>,
RelationList<
// All Domains must be fully connected
Connected<Core>, Connected<Envelope>, Connected<Vacuum>,
Connected<Core>,
Connected<Envelope>,
Connected<Vacuum>,
// Describe the topology of the mesh (core must be within envelope and
// the stellar domain (core + envelope) must be inscribed within vacuum
// region
Inscribed<Core, Envelope>, Inscribed<Stellar, Vacuum>,
Inscribed<Core, Envelope>,
Inscribed<Stellar, Vacuum>,
// The stellar surface sits between the stellar and vacuum domain and
// the infinity surface sits at the outside of the vacuum domain

View File

@@ -13,8 +13,10 @@ import :utils.domain;
export namespace mean_field::utils {
constexpr double APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING = 1e-4;
bool is_vacuum(const mfem::ElementTransformation &Tr,
mfem::Array<mfem::Vector *> elvec) {
bool is_vacuum(
const mfem::ElementTransformation &Tr,
mfem::Array<mfem::Vector *> elvec
) {
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
if (Schema::template attribute_belongs_to<domain::Vacuum>(Tr.Attribute)) {
@@ -29,8 +31,10 @@ bool is_vacuum(const mfem::ElementTransformation &Tr,
return false;
}
bool is_vacuum(const mfem::ElementTransformation &Tr,
const mfem::Array2D<mfem::DenseMatrix *> &elmats) {
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)) {
@@ -65,15 +69,13 @@ constexpr double RADIUS = 1.0;
[[maybe_unused]] constexpr int PORT = 19916;
template <typename T>
concept is_xad = std::is_same_v<T, xad::AReal<long double>> ||
std::is_same_v<T, xad::AReal<double>> ||
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>>;
template <typename T>
concept is_real = std::is_floating_point_v<T> || is_xad<T>;
template <is_real T>
using EOS_P = std::function<T(const T &rho, const T &temp)>;
template <is_real T> using EOS_P = std::function<T(const T &rho, const T &temp)>;
enum class DOMAINS : uint8_t {
CORE = 1 << 0,
@@ -83,9 +85,15 @@ enum class DOMAINS : uint8_t {
ALL = CORE | ENVELOPE | VACUUM
};
DOMAINS operator|(DOMAINS lhs, DOMAINS rhs);
DOMAINS operator|(
DOMAINS lhs,
DOMAINS rhs
);
DOMAINS operator&(DOMAINS lhs, DOMAINS rhs);
DOMAINS operator&(
DOMAINS lhs,
DOMAINS rhs
);
int get_mesh_order(const mfem::Mesh &mesh);

View File

@@ -94,6 +94,65 @@ namespace field_dof_map_test_utils {
domain::RelationList<>>;
} // 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(
"Field DOF Map Preserves Canonical Bidirectional Indexing",
tags::field_dof_unit
@@ -523,9 +582,7 @@ TEST_CASE(
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Displacement>);
STATIC_REQUIRE_FALSE(
field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::BarotropicConstant>
);
STATIC_REQUIRE_FALSE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::BarotropicConstant>);
CHECK(true);
}
@@ -751,8 +808,7 @@ TEST_CASE(
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
@@ -797,8 +853,7 @@ TEST_CASE(
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Enthalpy>::make_fec<field::Enthalpy::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
auto finiteElementSpace = field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
@@ -850,8 +905,7 @@ TEST_CASE(
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
@@ -901,8 +955,7 @@ TEST_CASE(
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Displacement>::make_fec<field::Displacement::Vector>(2);
auto finiteElementSpace =
field::Field<field::Displacement>::make_fespace<field::Displacement::Vector>(mesh, *fec);
auto finiteElementSpace = field::Field<field::Displacement>::make_fespace<field::Displacement::Vector>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
@@ -940,12 +993,10 @@ TEST_CASE(
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
auto otherFec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto otherFiniteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *otherFec);
auto otherFiniteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *otherFec);
REQUIRE(finiteElementSpace != nullptr);
REQUIRE(otherFiniteElementSpace != nullptr);
@@ -959,8 +1010,7 @@ TEST_CASE(
const mfem::Array<int> empty;
CHECK_THROWS_AS(
(field::FieldDofGridFunctionAdapter(
field::FieldDofMap(finiteElementSpace->GetTrueVSize() + 1, empty),
*finiteElementSpace
field::FieldDofMap(finiteElementSpace->GetTrueVSize() + 1, empty), *finiteElementSpace
)),
std::invalid_argument
);

View File

@@ -11,7 +11,10 @@ using namespace mean_field;
namespace {
struct SerialMappingData {
explicit SerialMappingData(mfem::Mesh &mesh)
: compactification_fes(&mesh, &compactification_fec),
: compactification_fes(
&mesh,
&compactification_fec
),
compactification_coordinate(&compactification_fes),
mapper(field_dof_test_utils::make_domain_mapper()) {
compactification_coordinate = 0.0;

View File

@@ -129,8 +129,7 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator(
domain_mapper, displacement, compactification_coordinate,
integrators::GravityForceJacobianMode::field_coupled
domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
);
const int maximum_order = std::max(
@@ -300,9 +299,7 @@ TEST_CASE(
compactification_coordinate = 0.0;
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
mapping::GridFunctionMappingEvaluator mapping_evaluator(
domain_mapper, displacement, compactification_coordinate
);
mapping::GridFunctionMappingEvaluator mapping_evaluator(domain_mapper, displacement, compactification_coordinate);
auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); };
@@ -381,8 +378,7 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator(
domain_mapper, displacement, compactification_coordinate,
integrators::GravityForceJacobianMode::field_coupled
domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
);
const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8);
@@ -473,9 +469,7 @@ TEST_CASE(
mfem::GridFunction compactification_coordinate(&compactification_fes);
compactification_coordinate = 0.0;
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
mapping::GridFunctionMappingEvaluator mapping_evaluator(
domain_mapper, displacement, compactification_coordinate
);
mapping::GridFunctionMappingEvaluator mapping_evaluator(domain_mapper, displacement, compactification_coordinate);
auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) {
gravity.SetSize(3);
@@ -553,8 +547,7 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator(
domain_mapper, displacement, compactification_coordinate,
integrators::GravityForceJacobianMode::field_coupled
domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
);
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 <concepts>
#include <limits>
#include <memory>
#include <type_traits>
@@ -12,17 +14,11 @@ import test_helpers;
namespace {
struct StellarModelExtensionTracker final {
int structureValidationCount{0};
int surfaceValidationCount{0};
int surfaceResolutionCount{0};
const mean_field::eos::EquationOfState *structureEquationOfState{nullptr};
const mean_field::eos::EquationOfState *surfaceValidationEquationOfState{nullptr};
const mean_field::eos::EquationOfState *surfaceResolutionEquationOfState{nullptr};
const mean_field::eos::Polytrope *structureEquationOfState{nullptr};
};
class StellarModelTestStructure final : public mean_field::models::structure::StructureBase {
class StellarModelTestStructure final {
public:
explicit StellarModelTestStructure(std::shared_ptr<StellarModelExtensionTracker> 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;
return m_equationOfState;
}
[[nodiscard]] double targetMass() const noexcept override {
[[nodiscard]] double targetMass() const noexcept {
return 2.5;
}
[[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;
seed.radius.SetSize(2);
@@ -65,7 +61,7 @@ namespace {
return seed;
}
void validate() const override {
void validate() const {
++m_tracker->structureValidationCount;
}
@@ -74,59 +70,85 @@ namespace {
mean_field::eos::Polytrope m_equationOfState;
};
class StellarModelTestSurface final : public mean_field::surface::SurfaceBase {
class StructureWithoutSeed final {
public:
explicit StellarModelTestSurface(std::shared_ptr<StellarModelExtensionTracker> tracker)
: m_tracker(std::move(tracker)) {
}
[[nodiscard]] const mean_field::eos::Polytrope &equationOfState() const noexcept;
[[nodiscard]]
mean_field::surface::ResolvedSurfaceCondition
resolve(const mean_field::eos::EquationOfState &equationOfState) const override {
++m_tracker->surfaceResolutionCount;
[[nodiscard]] double targetMass() const noexcept;
m_tracker->surfaceResolutionEquationOfState = &equationOfState;
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;
void validate() const;
};
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
TEST_CASE(
"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::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);
CHECK(model.resolvedSurfaceCondition().targetEnthalpy == 0.0);
CHECK(
dynamic_cast<const mean_field::models::structure::PolytropicStructure *>(&model.structurePrescription()) !=
nullptr
STATIC_CHECK(std::same_as<decltype(model), PolytropicStellarModel>);
STATIC_CHECK(
std::same_as<
decltype(model.structurePrescription()), const mean_field::models::structure::PolytropicStructure &>
);
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(
@@ -135,7 +157,7 @@ TEST_CASE(
) {
mean_field::models::StellarModel model{
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 =
@@ -153,45 +175,49 @@ TEST_CASE(
TEST_CASE(
"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::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)};
CHECK(&movedModel.structurePrescription() == structureAddress);
CHECK(&movedModel.surfacePrescription() == surfaceAddress);
CHECK(&movedModel.equationOfState() == equationOfStateAddress);
CHECK(&movedModel.compiledSurfaceConstraint() == compiledSurfaceConstraintAddress);
CHECK(movedModel.targetMass() == 1.0);
}
TEST_CASE(
"Stellar Model Resolves A Positive Isobaric Surface",
"Stellar Model Compiles A Positive Constant Pressure Surface",
tags::barotrope &tags::unit &tags::model
) {
constexpr double targetPressure = 0.03125;
mean_field::models::StellarModel model{
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(
std::abs(model.equationOfState().pressure_from_enthalpy(targetEnthalpy) - targetPressure) <
64.0 * std::numeric_limits<double>::epsilon()
);
CHECK(requiredSpecificEnthalpy > 0.0);
CHECK(model.compiledSurfaceConstraint().targetPressure() == mean_field::eos::PressureValue{targetPressure});
CHECK(model.compiledSurfaceConstraint().residual(ModelSurfaceState{requiredSpecificEnthalpy}) == 0.0);
}
TEST_CASE(
@@ -200,27 +226,22 @@ TEST_CASE(
) {
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->surfaceValidationCount == 1);
REQUIRE(tracker->surfaceResolutionCount == 1);
CHECK(dynamic_cast<const StellarModelTestStructure *>(&model.structurePrescription()) != nullptr);
CHECK(dynamic_cast<const StellarModelTestSurface *>(&model.surfacePrescription()) != nullptr);
const mean_field::eos::EquationOfState *ownedEquationOfState = &model.equationOfState();
const mean_field::eos::Polytrope *ownedEquationOfState = &model.equationOfState();
CHECK(tracker->structureEquationOfState == ownedEquationOfState);
CHECK(tracker->surfaceValidationEquationOfState == ownedEquationOfState);
CHECK(tracker->surfaceResolutionEquationOfState == ownedEquationOfState);
CHECK(model.targetMass() == 2.5);
CHECK(model.resolvedSurfaceCondition().targetEnthalpy == 0.375);
CHECK(model.compiledSurfaceConstraint().targetPressure() == mean_field::eos::PressureValue{0.375});
}
TEST_CASE(
@@ -229,21 +250,23 @@ TEST_CASE(
) {
mean_field::models::StellarModel sourceModel{
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::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);
@@ -255,5 +278,83 @@ TEST_CASE(
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.updatedBernoulliConstant);
CHECK(
context.GetBaseGravityPotentialTrue()(context.GetGravityPotentialMap().true_dof(0)) == gravityPotential(0)
);
CHECK(context.GetBaseGravityPotentialTrue()(context.GetGravityPotentialMap().true_dof(0)) == gravityPotential(0));
++dependencies.bernoulliConstant.revision;

View File

@@ -14,95 +14,83 @@ namespace gravity_displacement_force_test_utils {
using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::density_field.mass_term);
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term);
constexpr auto displacementValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto gravityGradientValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term);
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term);
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term);
constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto barotropicConstantValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field
.mass_normalization_term);
constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
constexpr auto gravityGradientResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term
);
constexpr auto gravityPotentialResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term
);
constexpr auto densityResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::density_field.mass_term);
mean_field::utils::blocks::get_residual_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term);
constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto enthalpyResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term);
constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto massResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field
.mass_normalization_term);
constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
[[nodiscard]] mean_field::operators::GravityDisplacementForceLayout
make_layout(const mean_field::fem::FEM &f) {
[[nodiscard]] mean_field::operators::GravityDisplacementForceLayout make_layout(const mean_field::fem::FEM &f) {
using DomainSchema = gravity_prepared_test_utils::DomainSchema;
const auto densityMap =
gravity_prepared_test_utils::make_field_map<mean_field::field::Density>(
f);
const auto displacementMap = gravity_prepared_test_utils::make_field_map<
mean_field::field::Displacement>(f);
const auto densityMap = gravity_prepared_test_utils::make_field_map<mean_field::field::Density>(f);
const auto displacementMap = gravity_prepared_test_utils::make_field_map<mean_field::field::Displacement>(f);
const auto gravityFluxMap =
mean_field::field::make_field_dof_map<mean_field::field::Gravity,
DomainSchema>(*f.gravityFluxFes);
const auto gravityPotentialMap = mean_field::field::make_field_dof_map<
mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes);
mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityFluxFes);
const auto gravityPotentialMap =
mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes);
const auto enthalpyMap =
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy,
DomainSchema>(*f.enthalpyFes);
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy, DomainSchema>(*f.enthalpyFes);
const std::array<int, CoupledForm::value_block_count> valueSizes{
densityMap.reduced_size(), displacementMap.reduced_size(),
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(),
enthalpyMap.reduced_size(), 1};
densityMap.reduced_size(), displacementMap.reduced_size(), gravityFluxMap.reduced_size(),
gravityPotentialMap.reduced_size(), enthalpyMap.reduced_size(), 1
};
const std::array<int, CoupledForm::residual_block_count> residualSizes{
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(),
densityMap.reduced_size(), displacementMap.reduced_size(),
enthalpyMap.reduced_size(), 1};
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), densityMap.reduced_size(),
displacementMap.reduced_size(), enthalpyMap.reduced_size(), 1
};
return {valueSizes, residualSizes};
}
[[nodiscard]] mfem::Vector make_density(const mean_field::fem::FEM &f,
const double phase) {
[[nodiscard]] mfem::Vector make_density(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction densityField(f.densityFes.get());
mfem::FunctionCoefficient densityCoefficient(
[phase](const mfem::Vector &position) {
mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) {
return 0.82 + 0.07 * std::sin(0.8 * position(0) + phase) +
0.05 * std::cos(0.6 * position(1) - 0.3 * phase) +
0.03 * position(2) * position(2);
0.05 * std::cos(0.6 * position(1) - 0.3 * phase) + 0.03 * position(2) * position(2);
});
densityField.ProjectCoefficient(densityCoefficient);
@@ -112,14 +100,15 @@ make_layout(const mean_field::fem::FEM &f) {
return densityTrue;
}
[[nodiscard]] mfem::Vector make_density_direction(const mean_field::fem::FEM &f,
const double phase) {
[[nodiscard]] mfem::Vector make_density_direction(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction densityField(f.densityFes.get());
mfem::FunctionCoefficient densityCoefficient(
[phase](const mfem::Vector &position) {
return 0.19 * std::sin(0.9 * position(0) + phase) -
0.13 * std::cos(0.7 * position(1) - phase) + 0.08 * position(2);
mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) {
return 0.19 * std::sin(0.9 * position(0) + phase) - 0.13 * std::cos(0.7 * position(1) - phase) +
0.08 * position(2);
});
densityField.ProjectCoefficient(densityCoefficient);
@@ -129,12 +118,13 @@ make_layout(const mean_field::fem::FEM &f) {
return densityTrue;
}
[[nodiscard]] mfem::Vector make_gravity_gradient(const mean_field::fem::FEM &f,
const double phase) {
[[nodiscard]] mfem::Vector make_gravity_gradient(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
auto gravityFunction = [phase](const mfem::Vector &position,
mfem::Vector &value) {
auto gravityFunction = [phase](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(3);
value(0) = 0.31 + 0.08 * position(0) + 0.03 * phase * position(1);
@@ -153,13 +143,13 @@ make_layout(const mean_field::fem::FEM &f) {
return gravityTrue;
}
[[nodiscard]] mfem::Vector
make_gravity_gradient_direction(const mean_field::fem::FEM &f,
const double phase) {
[[nodiscard]] mfem::Vector make_gravity_gradient_direction(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
auto gravityFunction = [phase](const mfem::Vector &position,
mfem::Vector &value) {
auto gravityFunction = [phase](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(3);
value(0) = 0.14 * std::sin(position(0) + phase) + 0.03 * position(1);
@@ -178,20 +168,16 @@ make_gravity_gradient_direction(const mean_field::fem::FEM &f,
return gravityTrue;
}
[[nodiscard]] mfem::Vector
make_displacement_direction(const mean_field::fem::FEM &f) {
mfem::Vector direction =
gravity_prepared_test_utils::make_displacement(f, 0.83);
[[nodiscard]] mfem::Vector make_displacement_direction(const mean_field::fem::FEM &f) {
mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, 0.83);
const mfem::Vector second =
gravity_prepared_test_utils::make_displacement(f, 0.29);
const mfem::Vector second = gravity_prepared_test_utils::make_displacement(f, 0.29);
direction -= second;
return direction;
}
[[nodiscard]] mfem::Vector
make_vacuum_only_density(const mean_field::fem::FEM &f) {
[[nodiscard]] mfem::Vector make_vacuum_only_density(const mean_field::fem::FEM &f) {
mfem::ParGridFunction densityField(f.densityFes.get());
densityField = 0.0;
@@ -201,8 +187,7 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
int localVacuumElements = 0;
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
REQUIRE(transformation != nullptr);
@@ -219,8 +204,7 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
}
int globalVacuumElements = 0;
MPI_Allreduce(&localVacuumElements, &globalVacuumElements, 1, MPI_INT,
MPI_SUM, f.mesh->GetComm());
MPI_Allreduce(&localVacuumElements, &globalVacuumElements, 1, MPI_INT, MPI_SUM, f.mesh->GetComm());
REQUIRE(globalVacuumElements > 0);
@@ -229,59 +213,65 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
return densityTrue;
}
[[nodiscard]] mean_field::operators::context::gravity_field::
GravityFieldRevisions
make_revisions() {
return {.discretization = {.value = 3},
[[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions make_revisions() {
return {
.discretization = {.value = 3},
.displacement = {.value = 5},
.density = {.value = 7},
.gravity_gradient = {.value = 11},
.gravity_potential = {.value = 13}};
.gravity_potential = {.value = 13}
};
}
void prepare_gravity_context(
mean_field::operators::context::gravity_field::
GravityFieldLinearizationContext &context,
const mfem::Vector &density, const mfem::Vector &displacement,
const mfem::Vector &gravityGradient, const mfem::Vector &gravityPotential,
const mean_field::operators::context::gravity_field::GravityFieldRevisions
&revisions) {
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &context,
const mfem::Vector &density,
const mfem::Vector &displacement,
const mfem::Vector &gravityGradient,
const mfem::Vector &gravityPotential,
const mean_field::operators::context::gravity_field::GravityFieldRevisions &revisions
) {
context.Prepare(
{.density = context.GetDensityMap().gather(density),
.displacement = context.GetDisplacementMap().gather(displacement),
.gravity_gradient =
context.GetGravityGradientMap().gather(gravityGradient),
.gravity_potential =
context.GetGravityPotentialMap().gather(gravityPotential)},
revisions);
.gravity_gradient = context.GetGravityGradientMap().gather(gravityGradient),
.gravity_potential = context.GetGravityPotentialMap().gather(gravityPotential)},
revisions
);
}
[[nodiscard]] double relative_difference(const mfem::Vector &left,
[[nodiscard]] double relative_difference(
const mfem::Vector &left,
const mfem::Vector &right,
const MPI_Comm communicator) {
MFEM_VERIFY(left.Size() == right.Size(),
"Cannot compare gravity-displacement-force vectors with "
"different sizes.");
const MPI_Comm communicator
) {
MFEM_VERIFY(
left.Size() == right.Size(), "Cannot compare gravity-displacement-force vectors with "
"different sizes."
);
mfem::Vector difference(left);
difference -= right;
const double scale =
std::max({gravity_prepared_test_utils::global_norm(left, communicator),
const double scale = std::max(
{gravity_prepared_test_utils::global_norm(left, communicator),
gravity_prepared_test_utils::global_norm(right, communicator),
100.0 * std::numeric_limits<double>::epsilon()});
100.0 * std::numeric_limits<double>::epsilon()}
);
return gravity_prepared_test_utils::global_norm(difference, communicator) /
scale;
return gravity_prepared_test_utils::global_norm(difference, communicator) / scale;
}
[[nodiscard]] mfem::Vector centered_difference(
const mean_field::fem::FEM &f, const mfem::Vector &baseDensity,
const mean_field::fem::FEM &f,
const mfem::Vector &baseDensity,
const mfem::Vector &densityDirection,
const mfem::Vector &baseGravityGradient,
const mfem::Vector &gravityGradientDirection,
const mfem::Vector &baseDisplacement,
const mfem::Vector &displacementDirection, const double step) {
const mfem::Vector &displacementDirection,
const double step
) {
mfem::Vector plusDensity(baseDensity);
plusDensity.Add(step, densityDirection);
@@ -304,12 +294,12 @@ void prepare_gravity_context(
mfem::Vector minusResidual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, plusDensity, plusGravity, plusDisplacement,
plusResidual);
f, *f.domainMapperStateless, plusDensity, plusGravity, plusDisplacement, plusResidual
);
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, minusDensity, minusGravity,
minusDisplacement, minusResidual);
f, *f.domainMapperStateless, minusDensity, minusGravity, minusDisplacement, minusResidual
);
plusResidual -= minusResidual;
plusResidual /= 2.0 * step;
@@ -320,7 +310,8 @@ template <int index>
[[nodiscard]] mfem::Vector copy_residual_block(
const mfem::Vector &action,
const mean_field::operators::GravityDisplacementForceLayout &layout,
const mean_field::utils::blocks::residual_block<index> block) {
const mean_field::utils::blocks::residual_block<index> block
) {
mfem::Vector result(layout.size(block));
const int offset = layout.offset(block);
@@ -332,18 +323,19 @@ template <int index>
}
} // namespace gravity_displacement_force_test_utils
TEST_CASE("Gravity Displacement Force Query Includes Every Registered Operand",
tags::gravity_unit) {
using DisplacementField =
mean_field::field::Field<mean_field::field::Displacement>;
TEST_CASE(
"Gravity Displacement Force Query Includes Every Registered Operand",
tags::gravity_unit
) {
using DisplacementField = mean_field::field::Field<mean_field::field::Displacement>;
constexpr int geometryWeightOrder = 4;
constexpr mean_field::quadrature::Query query = DisplacementField::make_query<
mean_field::field::Displacement::Form::GravityForce>(
mean_field::quadrature::QuadratureRole::discretization,
geometryWeightOrder, {}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
constexpr mean_field::quadrature::Query query =
DisplacementField::make_query<mean_field::field::Displacement::Form::GravityForce>(
mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder, {},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
/*
* rho: 2
@@ -356,8 +348,7 @@ TEST_CASE("Gravity Displacement Force Query Includes Every Registered Operand",
STATIC_REQUIRE(query.term == mean_field::quadrature::Term::gravity_force);
STATIC_REQUIRE(query.role ==
mean_field::quadrature::QuadratureRole::discretization);
STATIC_REQUIRE(query.role == mean_field::quadrature::QuadratureRole::discretization);
STATIC_REQUIRE(query.domain == mean_field::utils::DOMAINS::STELLAR);
@@ -367,9 +358,11 @@ TEST_CASE("Gravity Displacement Force Query Includes Every Registered Operand",
STATIC_REQUIRE(*query.base_order == expectedBaseOrder);
}
TEST_CASE("Gravity Displacement Force Uses Positive Grad-Phi Sign And Excludes "
TEST_CASE(
"Gravity Displacement Force Uses Positive Grad-Phi Sign And Excludes "
"Vacuum",
tags::gravity_kernel_accuracy) {
tags::gravity_kernel_accuracy
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -391,8 +384,7 @@ TEST_CASE("Gravity Displacement Force Uses Positive Grad-Phi Sign And Excludes "
value(0) = 1.0;
};
mfem::VectorFunctionCoefficient gravityCoefficient(3,
constantGravityFunction);
mfem::VectorFunctionCoefficient gravityCoefficient(3, constantGravityFunction);
gravityField.ProjectCoefficient(gravityCoefficient);
@@ -405,8 +397,8 @@ TEST_CASE("Gravity Displacement Force Uses Positive Grad-Phi Sign And Excludes "
mfem::Vector residual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement,
residual);
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
);
mfem::ParGridFunction testField(f.displacementFes.get());
testField.ProjectCoefficient(gravityCoefficient);
@@ -414,57 +406,54 @@ TEST_CASE("Gravity Displacement Force Uses Positive Grad-Phi Sign And Excludes "
mfem::Vector testDirection;
testField.GetTrueDofs(testDirection);
const double signedWork = gravity_prepared_test_utils::global_dot(
residual, testDirection, f.mesh->GetComm());
const double signedWork = gravity_prepared_test_utils::global_dot(residual, testDirection, f.mesh->GetComm());
INFO("Constant +x gravity-force work = " << signedWork);
CHECK(signedWork > 0.0);
const mfem::Vector vacuumDensity =
gravity_displacement_force_test_utils::make_vacuum_only_density(f);
const mfem::Vector vacuumDensity = gravity_displacement_force_test_utils::make_vacuum_only_density(f);
mfem::Vector vacuumResidual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, vacuumDensity, gravityGradient, displacement,
vacuumResidual);
f, *f.domainMapperStateless, vacuumDensity, gravityGradient, displacement, vacuumResidual
);
CHECK(gravity_prepared_test_utils::global_norm(vacuumResidual,
f.mesh->GetComm()) == 0.0);
CHECK(gravity_prepared_test_utils::global_norm(vacuumResidual, f.mesh->GetComm()) == 0.0);
}
TEST_CASE("Prepared Gravity Displacement Force Reuses Shared Gravity Revisions",
tags::gravity_prepared) {
TEST_CASE(
"Prepared Gravity Displacement Force Reuses Shared Gravity Revisions",
tags::gravity_prepared
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
mfem::Vector density =
gravity_displacement_force_test_utils::make_density(f, 0.31);
mfem::Vector density = gravity_displacement_force_test_utils::make_density(f, 0.31);
const mfem::Vector gravityGradient =
gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.47);
const mfem::Vector gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.47);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.61);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.61);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
auto revisions = gravity_displacement_force_test_utils::make_revisions();
mean_field::operators::context::gravity_field::
GravityFieldLinearizationContext gravityContext(f,
*f.domainMapperStateless);
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential,
revisions);
gravityContext, density, displacement, gravityGradient, gravityPotential, revisions
);
mean_field::operators::PreparedGravityDisplacementForceOperator
preparedOperator(f, *f.domainMapperStateless, gravityContext);
mean_field::operators::PreparedGravityDisplacementForceOperator preparedOperator(
f, *f.domainMapperStateless, gravityContext
);
const auto initialReport = preparedOperator.Prepare();
REQUIRE(initialReport.DidAnyWork());
@@ -476,23 +465,24 @@ TEST_CASE("Prepared Gravity Displacement Force Reuses Shared Gravity Revisions",
preparedOperator.BuildResidual(preparedResidual);
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement,
kernelResidual);
f, *f.domainMapperStateless, density, gravityGradient, displacement, kernelResidual
);
const mfem::Vector kernelResidualReduced =
gravityContext.GetDisplacementMap().gather(kernelResidual);
const mfem::Vector kernelResidualReduced = gravityContext.GetDisplacementMap().gather(kernelResidual);
CHECK(gravity_displacement_force_test_utils::relative_difference(
preparedResidual, kernelResidualReduced, f.mesh->GetComm()) <
2.0e-12);
CHECK(
gravity_displacement_force_test_utils::relative_difference(
preparedResidual, kernelResidualReduced, f.mesh->GetComm()
) < 2.0e-12
);
CHECK_FALSE(preparedOperator.Prepare().DidAnyWork());
++revisions.gravity_potential.value;
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential,
revisions);
gravityContext, density, displacement, gravityGradient, gravityPotential, revisions
);
CHECK(preparedOperator.IsPrepared());
CHECK_FALSE(preparedOperator.Prepare().DidAnyWork());
@@ -501,8 +491,8 @@ TEST_CASE("Prepared Gravity Displacement Force Reuses Shared Gravity Revisions",
++revisions.density.value;
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential,
revisions);
gravityContext, density, displacement, gravityGradient, gravityPotential, revisions
);
CHECK_FALSE(preparedOperator.IsPrepared());
@@ -516,79 +506,73 @@ TEST_CASE("Prepared Gravity Displacement Force Reuses Shared Gravity Revisions",
TEST_CASE(
"Gravity Displacement Force Jacobian Matches All Columns And Centered "
"Differences",
tags::gravity_prepared_jacobian_accuracy) {
tags::gravity_prepared_jacobian_accuracy
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const mfem::Vector density =
gravity_displacement_force_test_utils::make_density(f, 0.37);
const mfem::Vector density = gravity_displacement_force_test_utils::make_density(f, 0.37);
const mfem::Vector densityDirection =
gravity_displacement_force_test_utils::make_density_direction(f, 0.53);
const mfem::Vector densityDirection = gravity_displacement_force_test_utils::make_density_direction(f, 0.53);
const mfem::Vector gravityGradient =
gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.67);
const mfem::Vector gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.67);
const mfem::Vector gravityGradientDirection =
gravity_displacement_force_test_utils::make_gravity_gradient_direction(
f, 0.71);
gravity_displacement_force_test_utils::make_gravity_gradient_direction(f, 0.71);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.59);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.59);
const mfem::Vector displacementDirection =
gravity_displacement_force_test_utils::make_displacement_direction(f);
const mfem::Vector displacementDirection = gravity_displacement_force_test_utils::make_displacement_direction(f);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
mean_field::operators::context::gravity_field::
GravityFieldLinearizationContext gravityContext(f,
*f.domainMapperStateless);
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential,
gravity_displacement_force_test_utils::make_revisions());
gravity_displacement_force_test_utils::make_revisions()
);
mean_field::operators::PreparedGravityDisplacementForceOperator
preparedOperator(f, *f.domainMapperStateless, gravityContext);
mean_field::operators::PreparedGravityDisplacementForceOperator preparedOperator(
f, *f.domainMapperStateless, gravityContext
);
preparedOperator.Prepare();
const mfem::Vector densityDirectionReduced =
gravityContext.GetDensityMap().gather(densityDirection);
const mfem::Vector densityDirectionReduced = gravityContext.GetDensityMap().gather(densityDirection);
const mfem::Vector gravityGradientDirectionReduced =
gravityContext.GetGravityGradientMap().gather(gravityGradientDirection);
const mfem::Vector displacementDirectionReduced =
gravityContext.GetDisplacementMap().gather(displacementDirection);
const mfem::Vector displacementDirectionReduced = gravityContext.GetDisplacementMap().gather(displacementDirection);
mfem::Vector densityAction;
mfem::Vector gravityAction;
mfem::Vector displacementAction;
mfem::Vector completeAction;
preparedOperator.ApplyDensityJacobianAction(densityDirectionReduced,
densityAction);
preparedOperator.ApplyDensityJacobianAction(densityDirectionReduced, densityAction);
preparedOperator.ApplyGravityGradientJacobianAction(
gravityGradientDirectionReduced, gravityAction);
preparedOperator.ApplyGravityGradientJacobianAction(gravityGradientDirectionReduced, gravityAction);
preparedOperator.ApplyDisplacementJacobianAction(displacementDirectionReduced,
displacementAction);
preparedOperator.ApplyDisplacementJacobianAction(displacementDirectionReduced, displacementAction);
preparedOperator.ApplyCompleteJacobianAction(
densityDirectionReduced, displacementDirectionReduced,
gravityGradientDirectionReduced, completeAction);
densityDirectionReduced, displacementDirectionReduced, gravityGradientDirectionReduced, completeAction
);
mfem::Vector summedColumns(densityAction);
summedColumns += gravityAction;
summedColumns += displacementAction;
CHECK(gravity_displacement_force_test_utils::relative_difference(
completeAction, summedColumns, f.mesh->GetComm()) < 2.0e-12);
CHECK(
gravity_displacement_force_test_utils::relative_difference(completeAction, summedColumns, f.mesh->GetComm()) <
2.0e-12
);
mfem::Vector zeroDensity(densityDirection.Size());
mfem::Vector zeroGravity(gravityGradientDirection.Size());
@@ -599,50 +583,41 @@ TEST_CASE(
constexpr double step = 1.0e-5;
const mfem::Vector densityDifferenceTrue =
gravity_displacement_force_test_utils::centered_difference(
f, density, densityDirection, gravityGradient, zeroGravity,
displacement, zeroDisplacement, step);
const mfem::Vector densityDifferenceTrue = gravity_displacement_force_test_utils::centered_difference(
f, density, densityDirection, gravityGradient, zeroGravity, displacement, zeroDisplacement, step
);
const mfem::Vector gravityDifferenceTrue =
gravity_displacement_force_test_utils::centered_difference(
f, density, zeroDensity, gravityGradient, gravityGradientDirection,
displacement, zeroDisplacement, step);
const mfem::Vector gravityDifferenceTrue = gravity_displacement_force_test_utils::centered_difference(
f, density, zeroDensity, gravityGradient, gravityGradientDirection, displacement, zeroDisplacement, step
);
const mfem::Vector displacementDifferenceTrue =
gravity_displacement_force_test_utils::centered_difference(
f, density, zeroDensity, gravityGradient, zeroGravity, displacement,
displacementDirection, step);
const mfem::Vector displacementDifferenceTrue = gravity_displacement_force_test_utils::centered_difference(
f, density, zeroDensity, gravityGradient, zeroGravity, displacement, displacementDirection, step
);
const mfem::Vector completeDifferenceTrue =
gravity_displacement_force_test_utils::centered_difference(
f, density, densityDirection, gravityGradient,
gravityGradientDirection, displacement, displacementDirection, step);
const mfem::Vector completeDifferenceTrue = gravity_displacement_force_test_utils::centered_difference(
f, density, densityDirection, gravityGradient, gravityGradientDirection, displacement, displacementDirection,
step
);
const mfem::Vector densityDifference =
gravityContext.GetDisplacementMap().gather(densityDifferenceTrue);
const mfem::Vector gravityDifference =
gravityContext.GetDisplacementMap().gather(gravityDifferenceTrue);
const mfem::Vector displacementDifference =
gravityContext.GetDisplacementMap().gather(displacementDifferenceTrue);
const mfem::Vector completeDifference =
gravityContext.GetDisplacementMap().gather(completeDifferenceTrue);
const mfem::Vector densityDifference = gravityContext.GetDisplacementMap().gather(densityDifferenceTrue);
const mfem::Vector gravityDifference = gravityContext.GetDisplacementMap().gather(gravityDifferenceTrue);
const mfem::Vector displacementDifference = gravityContext.GetDisplacementMap().gather(displacementDifferenceTrue);
const mfem::Vector completeDifference = gravityContext.GetDisplacementMap().gather(completeDifferenceTrue);
const double densityError =
gravity_displacement_force_test_utils::relative_difference(
densityAction, densityDifference, f.mesh->GetComm());
gravity_displacement_force_test_utils::relative_difference(densityAction, densityDifference, f.mesh->GetComm());
const double gravityError =
gravity_displacement_force_test_utils::relative_difference(
gravityAction, gravityDifference, f.mesh->GetComm());
gravity_displacement_force_test_utils::relative_difference(gravityAction, gravityDifference, f.mesh->GetComm());
const double displacementError =
gravity_displacement_force_test_utils::relative_difference(
displacementAction, displacementDifference, f.mesh->GetComm());
const double displacementError = gravity_displacement_force_test_utils::relative_difference(
displacementAction, displacementDifference, f.mesh->GetComm()
);
const double completeError =
gravity_displacement_force_test_utils::relative_difference(
completeAction, completeDifference, f.mesh->GetComm());
const double completeError = gravity_displacement_force_test_utils::relative_difference(
completeAction, completeDifference, f.mesh->GetComm()
);
INFO("Density-column centered-difference error = " << densityError);
INFO("Gravity-column centered-difference error = " << gravityError);
@@ -655,82 +630,70 @@ TEST_CASE(
CHECK(completeError < 3.0e-8);
}
TEST_CASE("Prepared Gravity Displacement Force MFEM Adapter Routes Only R-d",
tags::gravity_prepared_unit) {
TEST_CASE(
"Prepared Gravity Displacement Force MFEM Adapter Routes Only R-d",
tags::gravity_prepared_unit
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const mfem::Vector density =
gravity_displacement_force_test_utils::make_density(f, 0.41);
const mfem::Vector density = gravity_displacement_force_test_utils::make_density(f, 0.41);
const mfem::Vector densityDirection =
gravity_displacement_force_test_utils::make_density_direction(f, 0.57);
const mfem::Vector densityDirection = gravity_displacement_force_test_utils::make_density_direction(f, 0.57);
const mfem::Vector gravityGradient =
gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.63);
const mfem::Vector gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.63);
const mfem::Vector gravityGradientDirection =
gravity_displacement_force_test_utils::make_gravity_gradient_direction(
f, 0.77);
gravity_displacement_force_test_utils::make_gravity_gradient_direction(f, 0.77);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.51);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.51);
const mfem::Vector displacementDirection =
gravity_displacement_force_test_utils::make_displacement_direction(f);
const mfem::Vector displacementDirection = gravity_displacement_force_test_utils::make_displacement_direction(f);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
mean_field::operators::context::gravity_field::
GravityFieldLinearizationContext gravityContext(f,
*f.domainMapperStateless);
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential,
gravity_displacement_force_test_utils::make_revisions());
gravity_displacement_force_test_utils::make_revisions()
);
mean_field::operators::PreparedGravityDisplacementForceOperator
preparedOperator(f, *f.domainMapperStateless, gravityContext);
mean_field::operators::PreparedGravityDisplacementForceOperator preparedOperator(
f, *f.domainMapperStateless, gravityContext
);
preparedOperator.Prepare();
const mfem::Vector densityDirectionReduced =
gravityContext.GetDensityMap().gather(densityDirection);
const mfem::Vector densityDirectionReduced = gravityContext.GetDensityMap().gather(densityDirection);
const mfem::Vector gravityGradientDirectionReduced =
gravityContext.GetGravityGradientMap().gather(gravityGradientDirection);
const mfem::Vector displacementDirectionReduced =
gravityContext.GetDisplacementMap().gather(displacementDirection);
const mfem::Vector displacementDirectionReduced = gravityContext.GetDisplacementMap().gather(displacementDirection);
const auto layout = gravity_displacement_force_test_utils::make_layout(f);
mean_field::operators::PreparedGravityDisplacementForceJacobianOperator
adapter(layout, preparedOperator);
mean_field::operators::PreparedGravityDisplacementForceJacobianOperator adapter(layout, preparedOperator);
mfem::BlockVector direction(layout.value_offsets());
direction = 0.0;
direction.GetBlock(gravity_displacement_force_test_utils::densityValue) =
densityDirectionReduced;
direction.GetBlock(gravity_displacement_force_test_utils::densityValue) = densityDirectionReduced;
direction.GetBlock(gravity_displacement_force_test_utils::displacementValue) =
displacementDirectionReduced;
direction.GetBlock(gravity_displacement_force_test_utils::displacementValue) = displacementDirectionReduced;
direction.GetBlock(
gravity_displacement_force_test_utils::gravityGradientValue) =
gravityGradientDirectionReduced;
direction.GetBlock(gravity_displacement_force_test_utils::gravityGradientValue) = gravityGradientDirectionReduced;
direction.GetBlock(
gravity_displacement_force_test_utils::gravityPotentialValue) = 0.29;
direction.GetBlock(gravity_displacement_force_test_utils::gravityPotentialValue) = 0.29;
direction.GetBlock(gravity_displacement_force_test_utils::enthalpyValue) =
-0.37;
direction.GetBlock(gravity_displacement_force_test_utils::enthalpyValue) = -0.37;
direction.GetBlock(
gravity_displacement_force_test_utils::barotropicConstantValue) = 0.43;
direction.GetBlock(gravity_displacement_force_test_utils::barotropicConstantValue) = 0.43;
mfem::Vector action;
adapter.Mult(direction, action);
@@ -738,36 +701,39 @@ TEST_CASE("Prepared Gravity Displacement Force MFEM Adapter Routes Only R-d",
mfem::Vector expectedDisplacementAction;
preparedOperator.ApplyCompleteJacobianAction(
densityDirectionReduced, displacementDirectionReduced,
gravityGradientDirectionReduced, expectedDisplacementAction);
densityDirectionReduced, displacementDirectionReduced, gravityGradientDirectionReduced,
expectedDisplacementAction
);
const mfem::Vector actualDisplacementAction =
gravity_displacement_force_test_utils::copy_residual_block(
action, layout,
gravity_displacement_force_test_utils::displacementResidual);
const mfem::Vector actualDisplacementAction = gravity_displacement_force_test_utils::copy_residual_block(
action, layout, gravity_displacement_force_test_utils::displacementResidual
);
CHECK(gravity_displacement_force_test_utils::relative_difference(
actualDisplacementAction, expectedDisplacementAction,
f.mesh->GetComm()) < 2.0e-12);
CHECK(
gravity_displacement_force_test_utils::relative_difference(
actualDisplacementAction, expectedDisplacementAction, f.mesh->GetComm()
) < 2.0e-12
);
const std::array<mfem::Vector, 5> zeroRows{
gravity_displacement_force_test_utils::copy_residual_block(
action, layout,
gravity_displacement_force_test_utils::gravityGradientResidual),
action, layout, gravity_displacement_force_test_utils::gravityGradientResidual
),
gravity_displacement_force_test_utils::copy_residual_block(
action, layout,
gravity_displacement_force_test_utils::gravityPotentialResidual),
action, layout, gravity_displacement_force_test_utils::gravityPotentialResidual
),
gravity_displacement_force_test_utils::copy_residual_block(
action, layout,
gravity_displacement_force_test_utils::densityResidual),
action, layout, gravity_displacement_force_test_utils::densityResidual
),
gravity_displacement_force_test_utils::copy_residual_block(
action, layout,
gravity_displacement_force_test_utils::enthalpyResidual),
action, layout, gravity_displacement_force_test_utils::enthalpyResidual
),
gravity_displacement_force_test_utils::copy_residual_block(
action, layout, gravity_displacement_force_test_utils::massResidual)};
action, layout, gravity_displacement_force_test_utils::massResidual
)
};
for (const mfem::Vector &row : zeroRows) {
CHECK(gravity_prepared_test_utils::global_norm(row, f.mesh->GetComm()) ==
0.0);
CHECK(gravity_prepared_test_utils::global_norm(row, f.mesh->GetComm()) == 0.0);
}
}

View File

@@ -20,17 +20,25 @@ struct AffineCase {
return (4.0 / 3.0) * std::numbers::pi * radius * radius * radius;
}
[[nodiscard]] double determinant(const std::array<double, 3> &scales) {
[[nodiscard]] double determinant(
const std::array<
double,
3> &scales
) {
return scales[0] * scales[1] * scales[2];
}
[[nodiscard]] double relative_scalar_error(const double computed,
const double expected) {
[[nodiscard]] double relative_scalar_error(
const double computed,
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,
const double densityValue) {
[[nodiscard]] mfem::Vector make_constant_density(
const mean_field::fem::FEM &f,
const double densityValue
) {
mfem::ParGridFunction densityField(f.densityFes.get());
mfem::ConstantCoefficient densityCoefficient(densityValue);
densityField.ProjectCoefficient(densityCoefficient);
@@ -40,21 +48,23 @@ struct AffineCase {
return densityTrue;
}
[[nodiscard]] mfem::Vector
make_reference_gravity(const mean_field::fem::FEM &f,
const std::array<double, 3> &referenceGravity) {
[[nodiscard]] mfem::Vector make_reference_gravity(
const mean_field::fem::FEM &f,
const std::array<
double,
3> &referenceGravity
) {
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
mfem::VectorFunctionCoefficient gravityCoefficient(
f.mesh->Dimension(),
[referenceGravity](const mfem::Vector &, mfem::Vector &value) {
f.mesh->Dimension(), [referenceGravity](const mfem::Vector &, mfem::Vector &value) {
value.SetSize(3);
for (int component = 0; component < 3; ++component) {
value(component) =
referenceGravity[static_cast<std::size_t>(component)];
value(component) = referenceGravity[static_cast<std::size_t>(component)];
}
});
}
);
gravityField.ProjectCoefficient(gravityCoefficient);
@@ -63,19 +73,21 @@ make_reference_gravity(const mean_field::fem::FEM &f,
return gravityTrue;
}
[[nodiscard]] mfem::Vector make_radial_gravity(const mean_field::fem::FEM &f,
const double radialCoefficient) {
[[nodiscard]] mfem::Vector make_radial_gravity(
const mean_field::fem::FEM &f,
const double radialCoefficient
) {
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
mfem::VectorFunctionCoefficient gravityCoefficient(
f.mesh->Dimension(),
[radialCoefficient](const mfem::Vector &position, mfem::Vector &value) {
f.mesh->Dimension(), [radialCoefficient](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int component = 0; component < position.Size(); ++component) {
value(component) = radialCoefficient * position(component);
}
});
}
);
gravityField.ProjectCoefficient(gravityCoefficient);
@@ -84,22 +96,23 @@ make_reference_gravity(const mean_field::fem::FEM &f,
return gravityTrue;
}
[[nodiscard]] mfem::Vector
make_affine_displacement(const mean_field::fem::FEM &f,
const std::array<double, 3> &scales) {
[[nodiscard]] mfem::Vector make_affine_displacement(
const mean_field::fem::FEM &f,
const std::array<
double,
3> &scales
) {
mfem::ParGridFunction displacementField(f.displacementFes.get());
mfem::VectorFunctionCoefficient displacementCoefficient(
f.mesh->Dimension(),
[scales](const mfem::Vector &position, mfem::Vector &value) {
f.mesh->Dimension(), [scales](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int component = 0; component < position.Size(); ++component) {
value(component) =
(scales[static_cast<std::size_t>(component)] - 1.0) *
position(component);
value(component) = (scales[static_cast<std::size_t>(component)] - 1.0) * position(component);
}
});
}
);
displacementField.ProjectCoefficient(displacementCoefficient);
@@ -108,18 +121,19 @@ make_affine_displacement(const mean_field::fem::FEM &f,
return displacementTrue;
}
[[nodiscard]] mfem::Vector
make_constant_test_direction(const mean_field::fem::FEM &f,
const int selectedComponent) {
[[nodiscard]] mfem::Vector make_constant_test_direction(
const mean_field::fem::FEM &f,
const int selectedComponent
) {
mfem::ParGridFunction testField(f.displacementFes.get());
mfem::VectorFunctionCoefficient testCoefficient(
f.mesh->Dimension(),
[selectedComponent](const mfem::Vector &position, mfem::Vector &value) {
f.mesh->Dimension(), [selectedComponent](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
value = 0.0;
value(selectedComponent) = 1.0;
});
}
);
testField.ProjectCoefficient(testCoefficient);
@@ -128,13 +142,12 @@ make_constant_test_direction(const mean_field::fem::FEM &f,
return testTrue;
}
[[nodiscard]] mfem::Vector
make_dilation_test_direction(const mean_field::fem::FEM &f) {
[[nodiscard]] mfem::Vector make_dilation_test_direction(const mean_field::fem::FEM &f) {
mfem::ParGridFunction testField(f.displacementFes.get());
mfem::VectorFunctionCoefficient testCoefficient(
f.mesh->Dimension(), [](const mfem::Vector &position,
mfem::Vector &value) { value = position; });
f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) { value = position; }
);
testField.ProjectCoefficient(testCoefficient);
@@ -143,28 +156,28 @@ make_dilation_test_direction(const mean_field::fem::FEM &f) {
return testTrue;
}
void set_mass_normalized_density(mean_field::fem::FEM &f,
void set_mass_normalized_density(
mean_field::fem::FEM &f,
const double targetMass,
mfem::ParGridFunction &densityField) {
const mfem::Vector stellarDensityTrue =
gravity_prepared_test_utils::make_domain_supported_density(f, true);
mfem::ParGridFunction &densityField
) {
const mfem::Vector stellarDensityTrue = gravity_prepared_test_utils::make_domain_supported_density(f, true);
densityField.SetFromTrueDofs(stellarDensityTrue);
const double unnormalizedMass =
mean_field::analysis::domain_integrate_grid_function(
f, densityField, mean_field::utils::DOMAINS::STELLAR);
mean_field::analysis::domain_integrate_grid_function(f, densityField, mean_field::utils::DOMAINS::STELLAR);
MFEM_VERIFY(unnormalizedMass > 0.0,
"The analytic gravity-force test obtained non-positive mass.");
MFEM_VERIFY(unnormalizedMass > 0.0, "The analytic gravity-force test obtained non-positive mass.");
densityField *= targetMass / unnormalizedMass;
}
} // namespace gravity_displacement_force_analytic_test_utils
TEST_CASE("Gravity Displacement Force Matches Analytic Affine Resultants",
tags::gravity &tags::accuracy &tags::analytic_comparison
&tags::integration) {
TEST_CASE(
"Gravity Displacement Force Matches Analytic Affine Resultants",
tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -177,30 +190,23 @@ TEST_CASE("Gravity Displacement Force Matches Analytic Affine Resultants",
constexpr std::array<double, 3> physicalGravity{0.31, -0.47, 0.22};
constexpr std::array<
gravity_displacement_force_analytic_test_utils::AffineCase, 3>
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-changing affine geometry",
.scales = {1.11, 0.96, 1.07}}}};
constexpr std::array<gravity_displacement_force_analytic_test_utils::AffineCase, 3> 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-changing affine geometry", .scales = {1.11, 0.96, 1.07}}}
};
const mfem::Vector density =
gravity_displacement_force_analytic_test_utils::make_constant_density(
f, densityValue);
const mfem::Vector density = gravity_displacement_force_analytic_test_utils::make_constant_density(f, densityValue);
const double referenceVolume =
gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(
mean_field::utils::RADIUS);
gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(mean_field::utils::RADIUS);
constexpr double relativeTolerance = 5.0e-6;
for (const gravity_displacement_force_analytic_test_utils::AffineCase
&affineCase : affineCases) {
for (const gravity_displacement_force_analytic_test_utils::AffineCase &affineCase : affineCases) {
DYNAMIC_SECTION(affineCase.name) {
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);
@@ -216,42 +222,35 @@ TEST_CASE("Gravity Displacement Force Matches Analytic Affine Resultants",
*/
for (int component = 0; component < 3; ++component) {
referenceGravity[static_cast<std::size_t>(component)] =
mapDeterminant *
physicalGravity[static_cast<std::size_t>(component)] /
mapDeterminant * physicalGravity[static_cast<std::size_t>(component)] /
affineCase.scales[static_cast<std::size_t>(component)];
}
const mfem::Vector gravityGradient =
gravity_displacement_force_analytic_test_utils::
make_reference_gravity(f, referenceGravity);
gravity_displacement_force_analytic_test_utils::make_reference_gravity(f, referenceGravity);
const mfem::Vector displacement =
gravity_displacement_force_analytic_test_utils::
make_affine_displacement(f, affineCase.scales);
gravity_displacement_force_analytic_test_utils::make_affine_displacement(f, affineCase.scales);
mfem::Vector residual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement,
residual);
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
);
for (int component = 0; component < 3; ++component) {
const mfem::Vector testDirection =
gravity_displacement_force_analytic_test_utils::
make_constant_test_direction(f, component);
gravity_displacement_force_analytic_test_utils::make_constant_test_direction(f, component);
const double computedResultant =
gravity_prepared_test_utils::global_dot(residual, testDirection,
f.mesh->GetComm());
gravity_prepared_test_utils::global_dot(residual, testDirection, f.mesh->GetComm());
const double expectedResultant =
densityValue *
physicalGravity[static_cast<std::size_t>(component)] *
const double expectedResultant = densityValue * physicalGravity[static_cast<std::size_t>(component)] *
mapDeterminant * referenceVolume;
const double relativeError =
gravity_displacement_force_analytic_test_utils::
relative_scalar_error(computedResultant, expectedResultant);
const double relativeError = gravity_displacement_force_analytic_test_utils::relative_scalar_error(
computedResultant, expectedResultant
);
CAPTURE(component);
INFO("Map determinant = " << mapDeterminant);
@@ -267,8 +266,8 @@ TEST_CASE("Gravity Displacement Force Matches Analytic Affine Resultants",
TEST_CASE(
"Gravity Displacement Force Reproduces Analytic Homogeneous Sphere Work",
tags::gravity &tags::accuracy &tags::analytic_comparison
&tags::integration) {
tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -278,21 +277,15 @@ TEST_CASE(
const double radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS;
const double volume =
gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(
radius);
const double volume = gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(radius);
const double densityValue = mass / volume;
const double radialGravityCoefficient =
mean_field::utils::G * mass / (radius * radius * radius);
const double radialGravityCoefficient = mean_field::utils::G * mass / (radius * radius * radius);
const mfem::Vector density =
gravity_displacement_force_analytic_test_utils::make_constant_density(
f, densityValue);
const mfem::Vector density = gravity_displacement_force_analytic_test_utils::make_constant_density(f, densityValue);
const mfem::Vector gravityGradient =
gravity_displacement_force_analytic_test_utils::make_radial_gravity(
f, radialGravityCoefficient);
gravity_displacement_force_analytic_test_utils::make_radial_gravity(f, radialGravityCoefficient);
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0;
@@ -300,22 +293,18 @@ TEST_CASE(
mfem::Vector residual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement,
residual);
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
);
const mfem::Vector dilationDirection =
gravity_displacement_force_analytic_test_utils::
make_dilation_test_direction(f);
gravity_displacement_force_analytic_test_utils::make_dilation_test_direction(f);
const double computedWork = gravity_prepared_test_utils::global_dot(
residual, dilationDirection, f.mesh->GetComm());
const double computedWork = gravity_prepared_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm());
const double analyticWork =
(3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
const double analyticWork = (3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
const double relativeError =
gravity_displacement_force_analytic_test_utils::relative_scalar_error(
computedWork, analyticWork);
gravity_displacement_force_analytic_test_utils::relative_scalar_error(computedWork, analyticWork);
INFO("Computed positive gravity work = " << computedWork);
INFO("Analytic positive gravity work = " << analyticWork);
@@ -327,9 +316,10 @@ TEST_CASE(
CHECK(relativeError < 1.0e-5);
}
TEST_CASE("Solved Homogeneous Sphere Gravity Force Matches Analytic Virial",
tags::gravity &tags::accuracy &tags::analytic_comparison
&tags::integration &tags::initialization) {
TEST_CASE(
"Solved Homogeneous Sphere Gravity Force Matches Analytic Virial",
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;
args.p.max_iters = std::max(args.p.max_iters, 1000);
@@ -350,12 +340,10 @@ TEST_CASE("Solved Homogeneous Sphere Gravity Force Matches Analytic Virial",
mfem::ParGridFunction densityField(f.densityFes.get());
gravity_displacement_force_analytic_test_utils::set_mass_normalized_density(
f, mass, densityField);
gravity_displacement_force_analytic_test_utils::set_mass_normalized_density(f, mass, densityField);
const mean_field::physics::GravitySolution gravitySolution =
mean_field::physics::solve_gravity_field(f, args, densityField,
displacementField);
mean_field::physics::solve_gravity_field(f, args, densityField, displacementField);
mfem::Vector densityTrue;
mfem::Vector gravityGradientTrue;
@@ -368,22 +356,18 @@ TEST_CASE("Solved Homogeneous Sphere Gravity Force Matches Analytic Virial",
mfem::Vector residual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, densityTrue, gravityGradientTrue,
displacementTrue, residual);
f, *f.domainMapperStateless, densityTrue, gravityGradientTrue, displacementTrue, residual
);
const mfem::Vector dilationDirection =
gravity_displacement_force_analytic_test_utils::
make_dilation_test_direction(f);
gravity_displacement_force_analytic_test_utils::make_dilation_test_direction(f);
const double computedWork = gravity_prepared_test_utils::global_dot(
residual, dilationDirection, f.mesh->GetComm());
const double computedWork = gravity_prepared_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm());
const double analyticWork =
(3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
const double analyticWork = (3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
const double relativeError =
gravity_displacement_force_analytic_test_utils::relative_scalar_error(
computedWork, analyticWork);
gravity_displacement_force_analytic_test_utils::relative_scalar_error(computedWork, analyticWork);
INFO("Solved-field positive gravity work = " << computedWork);
INFO("Analytic positive gravity work = " << analyticWork);

File diff suppressed because it is too large Load Diff

View File

@@ -72,7 +72,10 @@ TEST_CASE(
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);

View File

@@ -9,8 +9,10 @@ import mean_field;
import test_helpers;
namespace hydrostatic_kernel_test_utils {
mfem::Vector project_scalar(mfem::ParFiniteElementSpace &finiteElementSpace,
mfem::Coefficient &coefficient) {
mfem::Vector project_scalar(
mfem::ParFiniteElementSpace &finiteElementSpace,
mfem::Coefficient &coefficient
) {
mfem::ParGridFunction field(&finiteElementSpace);
field.ProjectCoefficient(coefficient);
@@ -21,9 +23,10 @@ mfem::Vector project_scalar(mfem::ParFiniteElementSpace &finiteElementSpace,
return trueVector;
}
mfem::Vector
make_constant_field(mfem::ParFiniteElementSpace &finiteElementSpace,
const double value) {
mfem::Vector make_constant_field(
mfem::ParFiniteElementSpace &finiteElementSpace,
const double value
) {
mfem::ConstantCoefficient coefficient(value);
return project_scalar(finiteElementSpace, coefficient);
@@ -31,8 +34,7 @@ make_constant_field(mfem::ParFiniteElementSpace &finiteElementSpace,
mfem::Vector make_enthalpy(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
return 1.10 + 0.035 * position(0) - 0.021 * position(1) +
0.014 * position(2);
return 1.10 + 0.035 * position(0) - 0.021 * position(1) + 0.014 * position(2);
});
return project_scalar(*f.enthalpyFes, coefficient);
@@ -40,8 +42,7 @@ mfem::Vector make_enthalpy(const mean_field::fem::FEM &f) {
mfem::Vector make_potential(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
return -0.72 + 0.018 * position(0) + 0.011 * position(1) -
0.025 * position(2);
return -0.72 + 0.018 * position(0) + 0.011 * position(1) - 0.025 * position(2);
});
return project_scalar(*f.gravityPotentialFes, coefficient);
@@ -73,9 +74,11 @@ mean_field::physics::RigidRotation make_zero_rotation() {
return mean_field::physics::RigidRotation(angularVelocity, center);
}
mfem::Vector centered_difference(const mfem::Vector &plusResidual,
mfem::Vector centered_difference(
const mfem::Vector &plusResidual,
const mfem::Vector &minusResidual,
const double epsilon) {
const double epsilon
) {
mfem::Vector difference(plusResidual);
difference -= minusResidual;
@@ -84,10 +87,12 @@ mfem::Vector centered_difference(const mfem::Vector &plusResidual,
return difference;
}
double sum_normalized_error(const mfem::Vector &computed,
double sum_normalized_error(
const mfem::Vector &computed,
const mfem::Vector &reference,
const double normalization,
const MPI_Comm communicator) {
const MPI_Comm communicator
) {
mfem::Vector difference(computed);
difference -= reference;
@@ -102,8 +107,7 @@ mfem::Vector make_vacuum_supported_potential(const mean_field::fem::FEM &f) {
const int vacuumAttribute = field_dof_test_utils::vacuum_material_attribute;
for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size();
++attributeIndex) {
for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size(); ++attributeIndex) {
const int attribute = f.mesh->attributes[attributeIndex];
if (attribute == vacuumAttribute) {
@@ -121,14 +125,21 @@ public:
HydrostaticEnthalpyMassOperator(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domainMapper,
const mfem::Vector &displacementTrue)
: mfem::Operator(f.enthalpyFes->GetTrueVSize()), f_(f),
domainMapper_(domainMapper), displacementTrue_(displacementTrue) {}
const mfem::Vector &displacementTrue
)
: mfem::Operator(f.enthalpyFes->GetTrueVSize()),
f_(f),
domainMapper_(domainMapper),
displacementTrue_(displacementTrue) {
}
void Mult(const mfem::Vector &input, mfem::Vector &output) const override {
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_enthalpy_action(
f_, domainMapper_, input, displacementTrue_, output);
void Mult(
const mfem::Vector &input,
mfem::Vector &output
) const override {
mean_field::operators::kernels::apply_hydrostatic_equilibrium_enthalpy_action(
f_, domainMapper_, input, displacementTrue_, output
);
}
private:
@@ -140,11 +151,11 @@ private:
};
} // namespace hydrostatic_kernel_test_utils
TEST_CASE("Rigid Rotation Potential Derivative Matches Centered Differences",
tags::barotrope &tags::hydro &tags::jacobian &tags::physics
&tags::unit &tags::kernels) {
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_rotation();
TEST_CASE(
"Rigid Rotation Potential Derivative Matches Centered Differences",
tags::barotrope &tags::hydro &tags::jacobian &tags::physics &tags::unit &tags::kernels
) {
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
mfem::Vector position(3);
mfem::Vector direction(3);
@@ -166,52 +177,43 @@ TEST_CASE("Rigid Rotation Potential Derivative Matches Centered Differences",
minusPosition.Add(-epsilon, direction);
const double centeredDerivative =
(rotation.potential(plusPosition) - rotation.potential(minusPosition)) /
(2.0 * epsilon);
(rotation.potential(plusPosition) - rotation.potential(minusPosition)) / (2.0 * epsilon);
const double analyticDerivative =
rotation.potential_directional_derivative(position, direction);
const double analyticDerivative = rotation.potential_directional_derivative(position, direction);
const double relativeError =
std::abs(centeredDerivative - analyticDerivative) /
std::max(std::abs(analyticDerivative),
std::numeric_limits<double>::epsilon());
const double relativeError = std::abs(centeredDerivative - analyticDerivative) /
std::max(std::abs(analyticDerivative), std::numeric_limits<double>::epsilon());
INFO("Rigid-rotation derivative error = " << relativeError);
CHECK(relativeError < 2.0e-9);
}
TEST_CASE("Hydrostatic Residual Vanishes For A Manufactured Rotating State",
tags::barotrope &tags::hydro &tags::integration &tags::kernels
&tags::physics &tags::residuals) {
TEST_CASE(
"Hydrostatic Residual Vanishes For A Manufactured Rotating State",
tags::barotrope &tags::hydro &tags::integration &tags::kernels &tags::physics &tags::residuals
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_rotation();
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
constexpr double bernoulliConstant = 0.73;
constexpr double potentialValue = -0.21;
constexpr double constantOffset = 0.40;
mfem::FunctionCoefficient enthalpyCoefficient(
[&rotation](const mfem::Vector &position) {
return bernoulliConstant - potentialValue +
rotation.potential(position);
mfem::FunctionCoefficient enthalpyCoefficient([&rotation](const mfem::Vector &position) {
return bernoulliConstant - potentialValue + rotation.potential(position);
});
const mfem::Vector interpolatedEnthalpy =
hydrostatic_kernel_test_utils::project_scalar(*f.enthalpyFes,
enthalpyCoefficient);
hydrostatic_kernel_test_utils::project_scalar(*f.enthalpyFes, enthalpyCoefficient);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes,
potentialValue);
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, potentialValue);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.0);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.0);
const MPI_Comm communicator = f.mesh->GetComm();
@@ -226,33 +228,32 @@ TEST_CASE("Hydrostatic Residual Vanishes For A Manufactured Rotating State",
mfem::Vector interpolatedReferenceResidual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential,
displacement, bernoulliConstant, interpolatedResidual);
f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential, displacement, bernoulliConstant,
interpolatedResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential,
displacement, bernoulliConstant + constantOffset,
interpolatedReferenceResidual);
f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential, displacement,
bernoulliConstant + constantOffset, interpolatedReferenceResidual
);
const double interpolatedResidualNorm =
gravity_prepared_test_utils::global_norm(interpolatedResidual,
communicator);
gravity_prepared_test_utils::global_norm(interpolatedResidual, communicator);
const double interpolatedReferenceNorm =
gravity_prepared_test_utils::global_norm(interpolatedReferenceResidual,
communicator);
gravity_prepared_test_utils::global_norm(interpolatedReferenceResidual, communicator);
REQUIRE(interpolatedReferenceNorm > 1.0e-12);
const double representationFloor =
interpolatedResidualNorm / interpolatedReferenceNorm;
const double representationFloor = interpolatedResidualNorm / interpolatedReferenceNorm;
INFO("Interpolated rotating-state residual norm = "
<< interpolatedResidualNorm);
INFO("Interpolated rotating-state residual norm = " << interpolatedResidualNorm);
INFO("Interpolated rotating-state relative "
INFO(
"Interpolated rotating-state relative "
"representation floor = "
<< representationFloor);
<< representationFloor
);
/*
* This remains an independent physical/sign check. A wrong
@@ -275,8 +276,9 @@ TEST_CASE("Hydrostatic Residual Vanishes For A Manufactured Rotating State",
* side is in the range of M_h. Starting CG from zero keeps the
* iteration in the active stellar subspace.
*/
hydrostatic_kernel_test_utils::HydrostaticEnthalpyMassOperator
enthalpyMassOperator(f, *f.domainMapperStateless, displacement);
hydrostatic_kernel_test_utils::HydrostaticEnthalpyMassOperator enthalpyMassOperator(
f, *f.domainMapperStateless, displacement
);
mfem::Vector correctionRightHandSide(interpolatedResidual);
@@ -297,14 +299,11 @@ TEST_CASE("Hydrostatic Residual Vanishes For A Manufactured Rotating State",
projectionSolver.Mult(correctionRightHandSide, enthalpyCorrection);
INFO("Discrete-equilibrium projection converged = "
<< projectionSolver.GetConverged());
INFO("Discrete-equilibrium projection converged = " << projectionSolver.GetConverged());
INFO("Discrete-equilibrium projection iterations = "
<< projectionSolver.GetNumIterations());
INFO("Discrete-equilibrium projection iterations = " << projectionSolver.GetNumIterations());
INFO("Discrete-equilibrium projection final norm = "
<< projectionSolver.GetFinalNorm());
INFO("Discrete-equilibrium projection final norm = " << projectionSolver.GetFinalNorm());
REQUIRE(projectionSolver.GetConverged());
@@ -315,15 +314,15 @@ TEST_CASE("Hydrostatic Residual Vanishes For A Manufactured Rotating State",
correctionEquationResidual -= correctionRightHandSide;
const double correctionEquationNorm =
gravity_prepared_test_utils::global_norm(correctionEquationResidual,
communicator);
gravity_prepared_test_utils::global_norm(correctionEquationResidual, communicator);
INFO("Discrete-equilibrium correction-equation "
INFO(
"Discrete-equilibrium correction-equation "
"residual norm = "
<< correctionEquationNorm);
<< correctionEquationNorm
);
CHECK(correctionEquationNorm <=
std::max(5.0e-12 * interpolatedResidualNorm, 5.0e-15));
CHECK(correctionEquationNorm <= std::max(5.0e-12 * interpolatedResidualNorm, 5.0e-15));
mfem::Vector discreteEnthalpy(interpolatedEnthalpy);
@@ -333,21 +332,20 @@ TEST_CASE("Hydrostatic Residual Vanishes For A Manufactured Rotating State",
mfem::Vector referenceResidual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential,
displacement, bernoulliConstant, exactResidual);
f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential, displacement, bernoulliConstant,
exactResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential,
displacement, bernoulliConstant + constantOffset, referenceResidual);
f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential, displacement,
bernoulliConstant + constantOffset, referenceResidual
);
const double exactNorm =
gravity_prepared_test_utils::global_norm(exactResidual, communicator);
const double exactNorm = gravity_prepared_test_utils::global_norm(exactResidual, communicator);
const double referenceNorm =
gravity_prepared_test_utils::global_norm(referenceResidual, communicator);
const double referenceNorm = gravity_prepared_test_utils::global_norm(referenceResidual, communicator);
const double correctionNorm = gravity_prepared_test_utils::global_norm(
enthalpyCorrection, communicator);
const double correctionNorm = gravity_prepared_test_utils::global_norm(enthalpyCorrection, communicator);
INFO("Enthalpy representation correction norm = " << correctionNorm);
@@ -362,37 +360,31 @@ TEST_CASE("Hydrostatic Residual Vanishes For A Manufactured Rotating State",
TEST_CASE(
"Exact Constant Hydrostatic Equilibrium Remains Zero Under Deformation",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian
&tags::kernels &tags::mapping &tags::physics) {
tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::kernels &tags::mapping &tags::physics
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_zero_rotation();
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_zero_rotation();
constexpr double enthalpyValue = 1.20;
constexpr double potentialValue = -0.35;
constexpr double bernoulliConstant = enthalpyValue + potentialValue;
const mfem::Vector enthalpy =
hydrostatic_kernel_test_utils::make_constant_field(*f.enthalpyFes,
enthalpyValue);
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_constant_field(*f.enthalpyFes, enthalpyValue);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes,
potentialValue);
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, potentialValue);
const mfem::Vector displacementVariation =
gravity_prepared_test_utils::make_displacement(f, 0.67);
const mfem::Vector displacementVariation = gravity_prepared_test_utils::make_displacement(f, 0.67);
const MPI_Comm communicator = f.mesh->GetComm();
for (const double deformationScale : {0.0, 0.5, 1.0}) {
DYNAMIC_SECTION("Deformation scale = " << deformationScale) {
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, deformationScale);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, deformationScale);
mfem::Vector exactResidual;
mfem::Vector referenceResidual;
@@ -400,38 +392,35 @@ TEST_CASE(
mfem::Vector referenceGeometryAction;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, exactResidual);
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant,
exactResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant + 0.50, referenceResidual);
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant + 0.50,
referenceResidual
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, displacementVariation,
exactGeometryAction);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant,
displacementVariation, exactGeometryAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant + 0.50, displacementVariation,
referenceGeometryAction);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant + 0.50,
displacementVariation, referenceGeometryAction
);
const double exactResidualNorm =
gravity_prepared_test_utils::global_norm(exactResidual, communicator);
const double exactResidualNorm = gravity_prepared_test_utils::global_norm(exactResidual, communicator);
const double referenceResidualNorm =
gravity_prepared_test_utils::global_norm(referenceResidual,
communicator);
gravity_prepared_test_utils::global_norm(referenceResidual, communicator);
const double exactGeometryNorm = gravity_prepared_test_utils::global_norm(
exactGeometryAction, communicator);
const double exactGeometryNorm =
gravity_prepared_test_utils::global_norm(exactGeometryAction, communicator);
const double referenceGeometryNorm =
gravity_prepared_test_utils::global_norm(referenceGeometryAction,
communicator);
gravity_prepared_test_utils::global_norm(referenceGeometryAction, communicator);
REQUIRE(referenceResidualNorm > 1.0e-12);
@@ -444,59 +433,51 @@ TEST_CASE(
}
}
TEST_CASE("Hydrostatic Equilibrium Excludes Vacuum Elements",
tags::barotrope &tags::hydro &tags::kernels &tags::mapping
&tags::physics &tags::unit) {
TEST_CASE(
"Hydrostatic Equilibrium Excludes Vacuum Elements",
tags::barotrope &tags::hydro &tags::kernels &tags::mapping &tags::physics &tags::unit
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_zero_rotation();
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_zero_rotation();
const mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize());
mfem::Vector enthalpy(zeroEnthalpy);
enthalpy = 0.0;
const mfem::Vector vacuumPotential =
hydrostatic_kernel_test_utils::make_vacuum_supported_potential(f);
const mfem::Vector vacuumPotential = hydrostatic_kernel_test_utils::make_vacuum_supported_potential(f);
const mfem::Vector stellarPotential =
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes,
1.0);
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, 1.0);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
mfem::Vector residual;
mfem::Vector vacuumAction;
mfem::Vector stellarAction;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, vacuumPotential,
displacement, 0.0, residual);
f, *f.domainMapperStateless, rotation, enthalpy, vacuumPotential, displacement, 0.0, residual
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, vacuumPotential, displacement,
vacuumAction);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, vacuumPotential, displacement, vacuumAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, stellarPotential, displacement,
stellarAction);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, stellarPotential, displacement, stellarAction
);
const MPI_Comm communicator = f.mesh->GetComm();
const double residualNorm =
gravity_prepared_test_utils::global_norm(residual, communicator);
const double residualNorm = gravity_prepared_test_utils::global_norm(residual, communicator);
const double vacuumActionNorm =
gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
const double vacuumActionNorm = gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
const double stellarActionNorm =
gravity_prepared_test_utils::global_norm(stellarAction, communicator);
const double stellarActionNorm = gravity_prepared_test_utils::global_norm(stellarAction, communicator);
REQUIRE(stellarActionNorm > 1.0e-12);
@@ -505,35 +486,30 @@ TEST_CASE("Hydrostatic Equilibrium Excludes Vacuum Elements",
CHECK(vacuumActionNorm <= 5.0e-13 * stellarActionNorm);
}
TEST_CASE("Hydrostatic Jacobian Matches Blocks And Centered Differences",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian
&tags::kernels &tags::mapping &tags::physics) {
TEST_CASE(
"Hydrostatic Jacobian Matches Blocks And Centered Differences",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::kernels &tags::mapping &tags::physics
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_rotation();
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector enthalpyVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), 0.23);
gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.23);
const mfem::Vector potentialVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), 0.47);
gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.47);
const mfem::Vector displacementVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.displacementFes->GetTrueVSize(), 0.71);
gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.71);
constexpr double bernoulliConstant = 0.41;
constexpr double constantVariation = -0.37;
@@ -546,28 +522,26 @@ TEST_CASE("Hydrostatic Jacobian Matches Blocks And Centered Differences",
mfem::Vector completeAction;
mean_field::operators::kernels::apply_hydrostatic_equilibrium_enthalpy_action(
f, *f.domainMapperStateless, enthalpyVariation, displacement,
enthalpyAction);
f, *f.domainMapperStateless, enthalpyVariation, displacement, enthalpyAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, potentialVariation, displacement,
potentialAction);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, potentialVariation, displacement, potentialAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_constant_action(
f, *f.domainMapperStateless, constantVariation, displacement,
constantAction);
f, *f.domainMapperStateless, constantVariation, displacement, constantAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, displacementVariation,
displacementAction);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant,
displacementVariation, displacementAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement,
bernoulliConstant, enthalpyVariation, potentialVariation,
constantVariation, displacementVariation, completeAction);
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, enthalpyVariation,
potentialVariation, constantVariation, displacementVariation, completeAction
);
mfem::Vector blockAction(enthalpyAction);
blockAction += potentialAction;
@@ -576,23 +550,22 @@ TEST_CASE("Hydrostatic Jacobian Matches Blocks And Centered Differences",
const MPI_Comm communicator = f.mesh->GetComm();
const double blockError = gravity_prepared_test_utils::relative_error(
completeAction, blockAction, communicator);
const double blockError = gravity_prepared_test_utils::relative_error(completeAction, blockAction, communicator);
INFO("Hydrostatic block reconstruction error = " << blockError);
CHECK(blockError < 5.0e-13);
auto evaluate_residual = [&f,
&rotation](const mfem::Vector &trialEnthalpy,
const mfem::Vector &trialPotential,
const mfem::Vector &trialDisplacement,
const double trialConstant) {
auto evaluate_residual = [&f, &rotation](
const mfem::Vector &trialEnthalpy, const mfem::Vector &trialPotential,
const mfem::Vector &trialDisplacement, const double trialConstant
) {
mfem::Vector residual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, trialEnthalpy, trialPotential,
trialDisplacement, trialConstant, residual);
f, *f.domainMapperStateless, rotation, trialEnthalpy, trialPotential, trialDisplacement, trialConstant,
residual
);
return residual;
};
@@ -604,13 +577,10 @@ TEST_CASE("Hydrostatic Jacobian Matches Blocks And Centered Differences",
minusEnthalpy.Add(-epsilon, enthalpyVariation);
const mfem::Vector enthalpyDifference =
hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(plusEnthalpy, potential, displacement,
bernoulliConstant),
evaluate_residual(minusEnthalpy, potential, displacement,
bernoulliConstant),
epsilon);
const mfem::Vector enthalpyDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(plusEnthalpy, potential, displacement, bernoulliConstant),
evaluate_residual(minusEnthalpy, potential, displacement, bernoulliConstant), epsilon
);
mfem::Vector plusPotential(potential);
mfem::Vector minusPotential(potential);
@@ -619,21 +589,15 @@ TEST_CASE("Hydrostatic Jacobian Matches Blocks And Centered Differences",
minusPotential.Add(-epsilon, potentialVariation);
const mfem::Vector potentialDifference =
hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, plusPotential, displacement,
bernoulliConstant),
evaluate_residual(enthalpy, minusPotential, displacement,
bernoulliConstant),
epsilon);
const mfem::Vector potentialDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, plusPotential, displacement, bernoulliConstant),
evaluate_residual(enthalpy, minusPotential, displacement, bernoulliConstant), epsilon
);
const mfem::Vector constantDifference =
hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, potential, displacement,
bernoulliConstant + epsilon * constantVariation),
evaluate_residual(enthalpy, potential, displacement,
bernoulliConstant - epsilon * constantVariation),
epsilon);
const mfem::Vector constantDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, potential, displacement, bernoulliConstant + epsilon * constantVariation),
evaluate_residual(enthalpy, potential, displacement, bernoulliConstant - epsilon * constantVariation), epsilon
);
mfem::Vector plusDisplacement(displacement);
mfem::Vector minusDisplacement(displacement);
@@ -642,25 +606,22 @@ TEST_CASE("Hydrostatic Jacobian Matches Blocks And Centered Differences",
minusDisplacement.Add(-epsilon, displacementVariation);
const mfem::Vector displacementDifference =
hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, potential, plusDisplacement,
bernoulliConstant),
evaluate_residual(enthalpy, potential, minusDisplacement,
bernoulliConstant),
epsilon);
const mfem::Vector displacementDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, potential, plusDisplacement, bernoulliConstant),
evaluate_residual(enthalpy, potential, minusDisplacement, bernoulliConstant), epsilon
);
const double enthalpyError = gravity_prepared_test_utils::relative_error(
enthalpyAction, enthalpyDifference, communicator);
const double enthalpyError =
gravity_prepared_test_utils::relative_error(enthalpyAction, enthalpyDifference, communicator);
const double potentialError = gravity_prepared_test_utils::relative_error(
potentialAction, potentialDifference, communicator);
const double potentialError =
gravity_prepared_test_utils::relative_error(potentialAction, potentialDifference, communicator);
const double constantError = gravity_prepared_test_utils::relative_error(
constantAction, constantDifference, communicator);
const double constantError =
gravity_prepared_test_utils::relative_error(constantAction, constantDifference, communicator);
const double displacementError = gravity_prepared_test_utils::relative_error(
displacementAction, displacementDifference, communicator);
const double displacementError =
gravity_prepared_test_utils::relative_error(displacementAction, displacementDifference, communicator);
INFO("Hydrostatic enthalpy-block error = " << enthalpyError);
@@ -694,91 +655,86 @@ TEST_CASE("Hydrostatic Jacobian Matches Blocks And Centered Differences",
combinedMinusDisplacement.Add(-epsilon, displacementVariation);
const mfem::Vector combinedDifference =
hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(combinedPlusEnthalpy, combinedPlusPotential,
combinedPlusDisplacement,
bernoulliConstant + epsilon * constantVariation),
evaluate_residual(combinedMinusEnthalpy, combinedMinusPotential,
combinedMinusDisplacement,
bernoulliConstant - epsilon * constantVariation),
epsilon);
const mfem::Vector combinedDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(
combinedPlusEnthalpy, combinedPlusPotential, combinedPlusDisplacement,
bernoulliConstant + epsilon * constantVariation
),
evaluate_residual(
combinedMinusEnthalpy, combinedMinusPotential, combinedMinusDisplacement,
bernoulliConstant - epsilon * constantVariation
),
epsilon
);
const double blockNormSum =
gravity_prepared_test_utils::global_norm(enthalpyAction, communicator) +
const double blockNormSum = gravity_prepared_test_utils::global_norm(enthalpyAction, communicator) +
gravity_prepared_test_utils::global_norm(potentialAction, communicator) +
gravity_prepared_test_utils::global_norm(constantAction, communicator) +
gravity_prepared_test_utils::global_norm(displacementAction,
communicator);
gravity_prepared_test_utils::global_norm(displacementAction, communicator);
const double simultaneousError =
hydrostatic_kernel_test_utils::sum_normalized_error(
completeAction, combinedDifference, blockNormSum, communicator);
const double simultaneousError = hydrostatic_kernel_test_utils::sum_normalized_error(
completeAction, combinedDifference, blockNormSum, communicator
);
INFO("Hydrostatic simultaneous Jacobian error = " << simultaneousError);
CHECK(simultaneousError < 2.0e-7);
}
TEST_CASE("Hydrostatic Displacement Action Is Linear In Its Direction",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian
&tags::mapping &tags::physics &tags::unit &tags::kernels) {
TEST_CASE(
"Hydrostatic Displacement Action Is Linear In Its Direction",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::mapping &tags::physics &tags::unit
&tags::kernels
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_rotation();
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector firstDirection =
gravity_prepared_test_utils::make_deterministic_vector(
f.displacementFes->GetTrueVSize(), 0.31);
gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.31);
const mfem::Vector secondDirection =
gravity_prepared_test_utils::make_deterministic_vector(
f.displacementFes->GetTrueVSize(), 0.83);
gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.83);
constexpr double firstScale = 0.43;
constexpr double secondScale = -0.29;
constexpr double bernoulliConstant = 0.41;
const mfem::Vector combinedDirection =
gravity_prepared_test_utils::linear_combination(
firstDirection, firstScale, secondDirection, secondScale);
gravity_prepared_test_utils::linear_combination(firstDirection, firstScale, secondDirection, secondScale);
mfem::Vector firstAction;
mfem::Vector secondAction;
mfem::Vector combinedAction;
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, firstDirection, firstAction);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, firstDirection,
firstAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, secondDirection, secondAction);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, secondDirection,
secondAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, combinedDirection, combinedAction);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, combinedDirection,
combinedAction
);
const mfem::Vector expectedAction =
gravity_prepared_test_utils::linear_combination(
firstAction, firstScale, secondAction, secondScale);
gravity_prepared_test_utils::linear_combination(firstAction, firstScale, secondAction, secondScale);
const double linearityError = gravity_prepared_test_utils::relative_error(
combinedAction, expectedAction, f.mesh->GetComm());
const double linearityError =
gravity_prepared_test_utils::relative_error(combinedAction, expectedAction, f.mesh->GetComm());
INFO("Hydrostatic displacement-linearity error = " << linearityError);
@@ -787,8 +743,8 @@ TEST_CASE("Hydrostatic Displacement Action Is Linear In Its Direction",
TEST_CASE(
"Hydrostatic Residual Is Translationally Invariant On Deformed Geometry",
tags::barotrope &tags::hydro &tags::integration &tags::kernels
&tags::mapping &tags::physics &tags::residuals) {
tags::barotrope &tags::hydro &tags::integration &tags::kernels &tags::mapping &tags::physics &tags::residuals
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -814,33 +770,29 @@ TEST_CASE(
mfem::Vector translatedCenter(center);
translatedCenter += translation;
const mean_field::physics::RigidRotation baseRotation(angularVelocity,
center);
const mean_field::physics::RigidRotation baseRotation(angularVelocity, center);
const mean_field::physics::RigidRotation translatedRotation(angularVelocity,
translatedCenter);
const mean_field::physics::RigidRotation translatedRotation(angularVelocity, translatedCenter);
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f);
/*
* Use a nontrivially deformed base state so this checks rotation
* and mapped geometry simultaneously. The comparison state adds
* an exactly representable rigid translation to that deformation.
*/
const mfem::Vector baseDisplacement =
gravity_prepared_test_utils::make_displacement(f, 0.73);
const mfem::Vector baseDisplacement = gravity_prepared_test_utils::make_displacement(f, 0.73);
mfem::ParGridFunction translationField(f.displacementFes.get());
mfem::VectorFunctionCoefficient translationCoefficient(
f.mesh->Dimension(),
[&translation](const mfem::Vector &, mfem::Vector &value) {
f.mesh->Dimension(), [&translation](const mfem::Vector &, mfem::Vector &value) {
value.SetSize(translation.Size());
value = translation;
});
}
);
translationField.ProjectCoefficient(translationCoefficient);
@@ -858,47 +810,43 @@ TEST_CASE(
mfem::Vector untranslatedCenterResidual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, baseRotation, enthalpy, potential,
baseDisplacement, bernoulliConstant, baseResidual);
f, *f.domainMapperStateless, baseRotation, enthalpy, potential, baseDisplacement, bernoulliConstant,
baseResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, translatedRotation, enthalpy, potential,
translatedDisplacement, bernoulliConstant, translatedResidual);
f, *f.domainMapperStateless, translatedRotation, enthalpy, potential, translatedDisplacement, bernoulliConstant,
translatedResidual
);
/*
* Negative control: translate the geometry but leave the rotation
* center fixed. This must not agree with the covariant result.
*/
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, baseRotation, enthalpy, potential,
translatedDisplacement, bernoulliConstant, untranslatedCenterResidual);
f, *f.domainMapperStateless, baseRotation, enthalpy, potential, translatedDisplacement, bernoulliConstant,
untranslatedCenterResidual
);
const MPI_Comm communicator = f.mesh->GetComm();
const double baseResidualNorm =
gravity_prepared_test_utils::global_norm(baseResidual, communicator);
const double baseResidualNorm = gravity_prepared_test_utils::global_norm(baseResidual, communicator);
const double translatedResidualNorm =
gravity_prepared_test_utils::global_norm(translatedResidual,
communicator);
const double translatedResidualNorm = gravity_prepared_test_utils::global_norm(translatedResidual, communicator);
const double translationInvarianceError =
gravity_prepared_test_utils::relative_error(translatedResidual,
baseResidual, communicator);
gravity_prepared_test_utils::relative_error(translatedResidual, baseResidual, communicator);
const double fixedCenterDifference =
gravity_prepared_test_utils::relative_error(
untranslatedCenterResidual, translatedResidual, communicator);
gravity_prepared_test_utils::relative_error(untranslatedCenterResidual, translatedResidual, communicator);
INFO("Base deformed hydrostatic residual norm = " << baseResidualNorm);
INFO("Translated hydrostatic residual norm = " << translatedResidualNorm);
INFO("Mapped-rotation translation invariance error = "
<< translationInvarianceError);
INFO("Mapped-rotation translation invariance error = " << translationInvarianceError);
INFO("Relative change with untranslated rotation center = "
<< fixedCenterDifference);
INFO("Relative change with untranslated rotation center = " << fixedCenterDifference);
REQUIRE(baseResidualNorm > 1.0e-12);
REQUIRE(translatedResidualNorm > 1.0e-12);

View File

@@ -10,48 +10,43 @@ import mean_field;
import test_helpers;
namespace pressure_force_kernel_test_utils {
[[nodiscard]] mfem::Vector make_deterministic_vector(const int size,
const double phase) {
[[nodiscard]] mfem::Vector make_deterministic_vector(
const int size,
const double phase
) {
mfem::Vector vector(size);
for (int index = 0; index < size; ++index) {
const double position = static_cast<double>(index + 1);
vector(index) = 0.71 + 0.19 * std::sin(0.31 * position + phase) +
0.08 * std::cos(0.17 * position - 0.5 * phase);
vector(index) =
0.71 + 0.19 * std::sin(0.31 * position + phase) + 0.08 * std::cos(0.17 * position - 0.5 * phase);
}
return vector;
}
[[nodiscard]] mfem::Vector
make_zero_displacement(const mean_field::fem::FEM &f) {
[[nodiscard]] mfem::Vector make_zero_displacement(const mean_field::fem::FEM &f) {
mfem::Vector displacementTrue(f.displacementFes->GetTrueVSize());
displacementTrue = 0.0;
return displacementTrue;
}
[[nodiscard]] mfem::Vector
make_vacuum_only_enthalpy(const mean_field::fem::FEM &f) {
mfem::Vector enthalpyTrue =
make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.43);
[[nodiscard]] mfem::Vector make_vacuum_only_enthalpy(const mean_field::fem::FEM &f) {
mfem::Vector enthalpyTrue = make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.43);
using DomainSchema =
mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
const mean_field::field::FieldDofMap enthalpyMap =
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy,
DomainSchema>(*f.enthalpyFes);
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy, DomainSchema>(*f.enthalpyFes);
for (int reducedDof = 0; reducedDof < enthalpyMap.reduced_size();
++reducedDof) {
for (int reducedDof = 0; reducedDof < enthalpyMap.reduced_size(); ++reducedDof) {
enthalpyTrue(enthalpyMap.true_dof(reducedDof)) = 0.0;
}
return enthalpyTrue;
}
[[nodiscard]] mfem::Vector
make_positive_asymmetric_enthalpy(const mean_field::fem::FEM &f) {
[[nodiscard]] mfem::Vector make_positive_asymmetric_enthalpy(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
return 1.10 + 0.07 * position(0) - 0.04 * position(1) + 0.03 * position(2);
});
@@ -66,16 +61,16 @@ make_positive_asymmetric_enthalpy(const mean_field::fem::FEM &f) {
return enthalpyTrue;
}
[[nodiscard]] mfem::Vector
make_component_test_field(const mean_field::fem::FEM &f, const int component,
const int coordinate) {
[[nodiscard]] mfem::Vector make_component_test_field(
const mean_field::fem::FEM &f,
const int component,
const int coordinate
) {
const int dimension = f.mesh->Dimension();
MFEM_VERIFY(component >= 0 && component < dimension,
"The requested vector component is invalid.");
MFEM_VERIFY(component >= 0 && component < dimension, "The requested vector component is invalid.");
MFEM_VERIFY(coordinate >= -1 && coordinate < dimension,
"The requested coordinate is invalid.");
MFEM_VERIFY(coordinate >= -1 && coordinate < dimension, "The requested coordinate is invalid.");
/*
* coordinate == -1 gives the rigid translation e_component.
@@ -85,13 +80,13 @@ make_component_test_field(const mean_field::fem::FEM &f, const int component,
* w = x_coordinate e_component.
*/
mfem::VectorFunctionCoefficient coefficient(
dimension, [component, coordinate, dimension](
const mfem::Vector &position, mfem::Vector &value) {
dimension, [component, coordinate, dimension](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(dimension);
value = 0.0;
value(component) = coordinate < 0 ? 1.0 : position(coordinate);
});
}
);
mfem::ParGridFunction field(f.displacementFes.get());
@@ -103,11 +98,12 @@ make_component_test_field(const mean_field::fem::FEM &f, const int component,
return fieldTrue;
}
[[nodiscard]] double global_dot(const mfem::Vector &left,
[[nodiscard]] double global_dot(
const mfem::Vector &left,
const mfem::Vector &right,
MPI_Comm communicator) {
MFEM_VERIFY(left.Size() == right.Size(),
"The global dot-product vectors have different sizes.");
MPI_Comm communicator
) {
MFEM_VERIFY(left.Size() == right.Size(), "The global dot-product vectors have different sizes.");
const double localDot = left * right;
double globalDot = 0.0;
@@ -121,17 +117,22 @@ make_component_test_field(const mean_field::fem::FEM &f, const int component,
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domainMapper,
const mean_field::eos::Polytrope &barotrope,
const mfem::Vector &enthalpyTrue, const mfem::Vector &displacementTrue) {
MFEM_VERIFY(enthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(),
"The pressure-integral enthalpy vector has the wrong size.");
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue
) {
MFEM_VERIFY(
enthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(),
"The pressure-integral enthalpy vector has the wrong size."
);
MFEM_VERIFY(displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The pressure-integral displacement vector has the wrong size.");
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The pressure-integral displacement vector has the wrong size."
);
mfem::Vector enthalpyLocal(f.enthalpyFes->GetVSize());
const mfem::Operator *enthalpyProlongation =
f.enthalpyFes->GetProlongationMatrix();
const mfem::Operator *enthalpyProlongation = f.enthalpyFes->GetProlongationMatrix();
if (enthalpyProlongation != nullptr) {
enthalpyProlongation->Mult(enthalpyTrue, enthalpyLocal);
@@ -141,8 +142,7 @@ make_component_test_field(const mean_field::fem::FEM &f, const int component,
mfem::Vector displacementLocal(f.displacementFes->GetVSize());
const mfem::Operator *displacementProlongation =
f.displacementFes->GetProlongationMatrix();
const mfem::Operator *displacementProlongation = f.displacementFes->GetProlongationMatrix();
if (displacementProlongation != nullptr) {
displacementProlongation->Mult(displacementTrue, displacementLocal);
@@ -151,22 +151,19 @@ make_component_test_field(const mean_field::fem::FEM &f, const int component,
}
const double pressureExtraOrderValue =
barotrope.polytropic_index() *
static_cast<double>(mean_field::field::Enthalpy::Scalar::familyOrder);
barotrope.polytropic_index() * static_cast<double>(mean_field::field::Enthalpy::Scalar::familyOrder);
MFEM_VERIFY(std::isfinite(pressureExtraOrderValue) &&
pressureExtraOrderValue >= 0.0 &&
pressureExtraOrderValue <=
static_cast<double>(std::numeric_limits<int>::max()),
"The pressure-integral EOS order is invalid.");
MFEM_VERIFY(
std::isfinite(pressureExtraOrderValue) && pressureExtraOrderValue >= 0.0 &&
pressureExtraOrderValue <= static_cast<double>(std::numeric_limits<int>::max()),
"The pressure-integral EOS order is invalid."
);
const int pressureExtraOrder =
static_cast<int>(std::ceil(pressureExtraOrderValue));
const int pressureExtraOrder = static_cast<int>(std::ceil(pressureExtraOrderValue));
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
mean_field::mapping::DomainMapper::Workspace workspace(
f.mesh->Dimension());
mean_field::mapping::DomainMapper::Workspace workspace(f.mesh->Dimension());
mean_field::mapping::VolumeMappingContext mappingContext;
@@ -184,28 +181,24 @@ make_component_test_field(const mean_field::fem::FEM &f, const int component,
const int vacuumAttribute = field_dof_test_utils::vacuum_material_attribute;
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(transformation != nullptr,
"The pressure-integral reference received a null "
"element transformation.");
MFEM_VERIFY(
transformation != nullptr, "The pressure-integral reference received a null "
"element transformation."
);
if (transformation->Attribute == vacuumAttribute) {
continue;
}
const mfem::FiniteElement &enthalpyElement =
*f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &enthalpyElement = *f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *enthalpyDofTransformation =
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *enthalpyDofTransformation = f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
@@ -217,8 +210,7 @@ make_component_test_field(const mean_field::fem::FEM &f, const int component,
displacementLocal.GetSubVector(displacementDofs, elementDisplacement);
f.compactificationCoordinate->GetSubVector(compactificationDofs,
elementCompactification);
f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification);
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(elementEnthalpy);
@@ -229,70 +221,66 @@ make_component_test_field(const mean_field::fem::FEM &f, const int component,
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification);
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
const mean_field::mapping::ElementDisplacementData displacementData =
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement);
mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement);
const mean_field::mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification);
compactificationElement, elementCompactification
);
const mean_field::mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData};
.displacement = displacementData, .compactification = compactificationData
};
const mean_field::quadrature::Query query = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic,
transformation->OrderW(), std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
const mean_field::quadrature::Query query =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic, transformation->OrderW(),
std::array<int, 1>{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const mean_field::quadrature::MfemRule rule =
f.quadratureFactory->get(query, transformation->GetGeometryType());
MFEM_VERIFY(rule.integration_rule != nullptr,
"The pressure-integral quadrature rule is null.");
MFEM_VERIFY(rule.integration_rule != nullptr, "The pressure-integral quadrature rule is null.");
enthalpyShape.SetSize(enthalpyElement.GetDof());
for (int quadratureIndex = 0;
quadratureIndex < rule.integration_rule->GetNPoints();
++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint =
rule.integration_rule->IntPoint(quadratureIndex);
for (int quadratureIndex = 0; quadratureIndex < rule.integration_rule->GetNPoints(); ++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint = rule.integration_rule->IntPoint(quadratureIndex);
transformation->SetIntPoint(&integrationPoint);
const mean_field::mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(mappingData, *transformation,
integrationPoint, workspace,
mappingContext);
const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(mappingStatus == mean_field::mapping::MappingStatus::valid,
MFEM_VERIFY(
mappingStatus == mean_field::mapping::MappingStatus::valid,
"Stateless mapping failed in the "
"independent pressure integral. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex
<< ", status: " << static_cast<int>(mappingStatus));
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex << ", status: " << static_cast<int>(mappingStatus)
);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
const double enthalpyValue = elementEnthalpy * enthalpyShape;
const double pressureValue =
barotrope.pressure_from_enthalpy(enthalpyValue);
const double pressureValue = mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
barotrope, mean_field::eos::SpecificEnthalpyValue{enthalpyValue}
)
.value();
const double contribution =
pressureValue * mappingContext.quadrature.weight;
const double contribution = pressureValue * mappingContext.quadrature.weight;
MFEM_VERIFY(std::isfinite(pressureValue) && std::isfinite(contribution),
"The independent pressure integral "
"encountered a non-finite value.");
MFEM_VERIFY(
std::isfinite(pressureValue) && std::isfinite(contribution), "The independent pressure integral "
"encountered a non-finite value."
);
localPressureIntegral += contribution;
}
@@ -300,15 +288,16 @@ make_component_test_field(const mean_field::fem::FEM &f, const int component,
double globalPressureIntegral = 0.0;
MPI_Allreduce(&localPressureIntegral, &globalPressureIntegral, 1, MPI_DOUBLE,
MPI_SUM, f.mesh->GetComm());
MPI_Allreduce(&localPressureIntegral, &globalPressureIntegral, 1, MPI_DOUBLE, MPI_SUM, f.mesh->GetComm());
return globalPressureIntegral;
}
} // namespace pressure_force_kernel_test_utils
TEST_CASE("Pressure Force Residual Vanishes For Zero Enthalpy",
tags::barotrope &tags::pressure &tags::kernels &tags::integration) {
TEST_CASE(
"Pressure Force Residual Vanishes For Zero Enthalpy",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -320,25 +309,25 @@ TEST_CASE("Pressure Force Residual Vanishes For Zero Enthalpy",
mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize());
enthalpyTrue = 0.0;
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(f);
const mfem::Vector displacementTrue = pressure_force_kernel_test_utils::make_zero_displacement(f);
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
residualTrue);
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue
);
REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize());
const double residualNorm =
gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
CHECK(residualNorm == 0.0);
}
TEST_CASE("Pressure Force Residual Excludes Vacuum Enthalpy Exactly",
tags::barotrope &tags::pressure &tags::kernels &tags::integration) {
TEST_CASE(
"Pressure Force Residual Excludes Vacuum Enthalpy Exactly",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -347,11 +336,9 @@ TEST_CASE("Pressure Force Residual Excludes Vacuum Enthalpy Exactly",
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
const mfem::Vector enthalpyTrue =
pressure_force_kernel_test_utils::make_vacuum_only_enthalpy(f);
const mfem::Vector enthalpyTrue = pressure_force_kernel_test_utils::make_vacuum_only_enthalpy(f);
const double enthalpyNorm =
gravity_prepared_test_utils::global_norm(enthalpyTrue, f.mesh->GetComm());
const double enthalpyNorm = gravity_prepared_test_utils::global_norm(enthalpyTrue, f.mesh->GetComm());
/*
* Ensure this is a real exclusion test rather than another
@@ -359,25 +346,25 @@ TEST_CASE("Pressure Force Residual Excludes Vacuum Enthalpy Exactly",
*/
REQUIRE(enthalpyNorm > 0.0);
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(f);
const mfem::Vector displacementTrue = pressure_force_kernel_test_utils::make_zero_displacement(f);
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
residualTrue);
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue
);
REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize());
const double residualNorm =
gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
CHECK(residualNorm == 0.0);
}
TEST_CASE("Pressure Force Residual Is Nonzero For Positive Stellar Pressure",
tags::barotrope &tags::pressure &tags::kernels &tags::integration) {
TEST_CASE(
"Pressure Force Residual Is Nonzero For Positive Stellar Pressure",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -394,17 +381,15 @@ TEST_CASE("Pressure Force Residual Is Nonzero For Positive Stellar Pressure",
mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize());
enthalpyTrue = 1.0;
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(f);
const mfem::Vector displacementTrue = pressure_force_kernel_test_utils::make_zero_displacement(f);
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
residualTrue);
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue
);
const double residualNorm =
gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
INFO("Positive-pressure residual norm = " << residualNorm);
@@ -413,9 +398,10 @@ TEST_CASE("Pressure Force Residual Is Nonzero For Positive Stellar Pressure",
CHECK(residualNorm > 100.0 * std::numeric_limits<double>::epsilon());
}
TEST_CASE("Pressure Force Residual Does No Work Against Rigid Translations",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
&tags::accuracy) {
TEST_CASE(
"Pressure Force Residual Does No Work Against Rigid Translations",
tags::barotrope &tags::pressure &tags::kernels &tags::integration &tags::accuracy
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -426,20 +412,17 @@ TEST_CASE("Pressure Force Residual Does No Work Against Rigid Translations",
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
const mfem::Vector enthalpyTrue =
pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
const mfem::Vector enthalpyTrue = pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(f);
const mfem::Vector displacementTrue = pressure_force_kernel_test_utils::make_zero_displacement(f);
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
residualTrue);
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue
);
const double residualNorm =
gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
REQUIRE(residualNorm > 0.0);
@@ -447,17 +430,14 @@ TEST_CASE("Pressure Force Residual Does No Work Against Rigid Translations",
for (int component = 0; component < dimension; ++component) {
const mfem::Vector translationTrue =
pressure_force_kernel_test_utils::make_component_test_field(
f, component, -1);
pressure_force_kernel_test_utils::make_component_test_field(f, component, -1);
const double translationNorm = gravity_prepared_test_utils::global_norm(
translationTrue, f.mesh->GetComm());
const double translationNorm = gravity_prepared_test_utils::global_norm(translationTrue, f.mesh->GetComm());
const double translationWork = pressure_force_kernel_test_utils::global_dot(
translationTrue, residualTrue, f.mesh->GetComm());
const double translationWork =
pressure_force_kernel_test_utils::global_dot(translationTrue, residualTrue, f.mesh->GetComm());
const double dotProductScale =
std::fmax(residualNorm * translationNorm, 1.0);
const double dotProductScale = std::fmax(residualNorm * translationNorm, 1.0);
CAPTURE(component, translationWork, dotProductScale);
@@ -465,9 +445,10 @@ TEST_CASE("Pressure Force Residual Does No Work Against Rigid Translations",
}
}
TEST_CASE("Pressure Force Residual Matches Independent Pressure Integral",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
&tags::accuracy) {
TEST_CASE(
"Pressure Force Residual Matches Independent Pressure Integral",
tags::barotrope &tags::pressure &tags::kernels &tags::integration &tags::accuracy
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -478,17 +459,15 @@ TEST_CASE("Pressure Force Residual Matches Independent Pressure Integral",
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
const mfem::Vector enthalpyTrue =
pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
const mfem::Vector enthalpyTrue = pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(f);
const mfem::Vector displacementTrue = pressure_force_kernel_test_utils::make_zero_displacement(f);
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
residualTrue);
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue
);
const int dimension = f.mesh->Dimension();
@@ -499,19 +478,16 @@ TEST_CASE("Pressure Force Residual Matches Independent Pressure Integral",
for (int component = 0; component < dimension; ++component) {
for (int coordinate = 0; coordinate < dimension; ++coordinate) {
const mfem::Vector affineTestTrue =
pressure_force_kernel_test_utils::make_component_test_field(
f, component, coordinate);
pressure_force_kernel_test_utils::make_component_test_field(f, component, coordinate);
virtualWork(component, coordinate) =
pressure_force_kernel_test_utils::global_dot(
affineTestTrue, residualTrue, f.mesh->GetComm());
pressure_force_kernel_test_utils::global_dot(affineTestTrue, residualTrue, f.mesh->GetComm());
}
}
const double pressureIntegral =
pressure_force_kernel_test_utils::integrate_pressure(
f, *f.domainMapperStateless, barotrope, enthalpyTrue,
displacementTrue);
const double pressureIntegral = pressure_force_kernel_test_utils::integrate_pressure(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue
);
REQUIRE(std::isfinite(pressureIntegral));
@@ -545,28 +521,25 @@ TEST_CASE("Pressure Force Residual Matches Independent Pressure Integral",
for (int coordinate = 0; coordinate < dimension; ++coordinate) {
const double computedWork = virtualWork(component, coordinate);
const double expectedWork =
component == coordinate ? -pressureIntegral : 0.0;
const double expectedWork = component == coordinate ? -pressureIntegral : 0.0;
CAPTURE(component, coordinate, computedWork, expectedWork,
pressureIntegral, comparisonTolerance);
CAPTURE(component, coordinate, computedWork, expectedWork, pressureIntegral, comparisonTolerance);
CHECK(std::abs(computedWork - expectedWork) <= comparisonTolerance);
}
}
const double relativeMeanError =
std::abs(meanDiagonalWork + pressureIntegral) /
std::abs(pressureIntegral);
const double relativeMeanError = std::abs(meanDiagonalWork + pressureIntegral) / std::abs(pressureIntegral);
INFO("Relative mean diagonal error = " << relativeMeanError);
CHECK(relativeMeanError <= 1.0e-6);
}
TEST_CASE("Pressure Force Residual Matches Deformed Pressure Volume Variation",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
&tags::accuracy) {
TEST_CASE(
"Pressure Force Residual Matches Deformed Pressure Volume Variation",
tags::barotrope &tags::pressure &tags::kernels &tags::integration &tags::accuracy
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -580,16 +553,14 @@ TEST_CASE("Pressure Force Residual Matches Deformed Pressure Volume Variation",
* exercises a genuinely nonuniform pressure distribution.
*/
const mfem::Vector enthalpyTrue =
pressure_force_kernel_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), 0.37);
pressure_force_kernel_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.37);
/*
* make_displacement() contains anisotropic diagonal terms and
* quadratic cross terms. A scale of 0.67 therefore provides a
* nonzero, nonspherical, valid base geometry.
*/
const mfem::Vector baseDisplacementTrue =
gravity_prepared_test_utils::make_displacement(f, 0.67);
const mfem::Vector baseDisplacementTrue = gravity_prepared_test_utils::make_displacement(f, 0.67);
/*
* Differentiate along the same smooth deformation family. Thus
@@ -599,43 +570,38 @@ TEST_CASE("Pressure Force Residual Matches Deformed Pressure Volume Variation",
* This gives a controlled geometry path while still evaluating
* the derivative at a genuinely deformed base state.
*/
const mfem::Vector displacementVariationTrue =
gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector displacementVariationTrue = gravity_prepared_test_utils::make_displacement(f, 1.0);
const double baseDisplacementNorm = gravity_prepared_test_utils::global_norm(
baseDisplacementTrue, f.mesh->GetComm());
const double baseDisplacementNorm =
gravity_prepared_test_utils::global_norm(baseDisplacementTrue, f.mesh->GetComm());
const double variationNorm = gravity_prepared_test_utils::global_norm(
displacementVariationTrue, f.mesh->GetComm());
const double variationNorm = gravity_prepared_test_utils::global_norm(displacementVariationTrue, f.mesh->GetComm());
REQUIRE(baseDisplacementNorm >
100.0 * std::numeric_limits<double>::epsilon());
REQUIRE(baseDisplacementNorm > 100.0 * std::numeric_limits<double>::epsilon());
REQUIRE(variationNorm > 100.0 * std::numeric_limits<double>::epsilon());
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue,
baseDisplacementTrue, residualTrue);
f, *f.domainMapperStateless, barotrope, enthalpyTrue, baseDisplacementTrue, residualTrue
);
REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize());
const double residualWork = pressure_force_kernel_test_utils::global_dot(
displacementVariationTrue, residualTrue, f.mesh->GetComm());
const double residualWork =
pressure_force_kernel_test_utils::global_dot(displacementVariationTrue, residualTrue, f.mesh->GetComm());
REQUIRE(std::isfinite(residualWork));
REQUIRE(std::abs(residualWork) >
100.0 * std::numeric_limits<double>::epsilon());
REQUIRE(std::abs(residualWork) > 100.0 * std::numeric_limits<double>::epsilon());
/*
* The relatively broad initial sweep lets us see the expected
* centered-difference convergence before reaching the quadrature
* and representation plateau.
*/
constexpr std::array<double, 4> differenceSteps{1.0e-2, 5.0e-3, 2.5e-3,
1.25e-3};
constexpr std::array<double, 4> differenceSteps{1.0e-2, 5.0e-3, 2.5e-3, 1.25e-3};
double bestRelativeDiscrepancy = std::numeric_limits<double>::infinity();
@@ -648,21 +614,18 @@ TEST_CASE("Pressure Force Residual Matches Deformed Pressure Volume Variation",
displacementMinus.Add(-differenceStep, displacementVariationTrue);
const double pressureIntegralPlus =
pressure_force_kernel_test_utils::integrate_pressure(
f, *f.domainMapperStateless, barotrope, enthalpyTrue,
displacementPlus);
const double pressureIntegralPlus = pressure_force_kernel_test_utils::integrate_pressure(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementPlus
);
const double pressureIntegralMinus =
pressure_force_kernel_test_utils::integrate_pressure(
f, *f.domainMapperStateless, barotrope, enthalpyTrue,
displacementMinus);
const double pressureIntegralMinus = pressure_force_kernel_test_utils::integrate_pressure(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementMinus
);
REQUIRE(std::isfinite(pressureIntegralPlus));
REQUIRE(std::isfinite(pressureIntegralMinus));
const double pressureVolumeDerivative =
(pressureIntegralPlus - pressureIntegralMinus) / (2.0 * differenceStep);
const double pressureVolumeDerivative = (pressureIntegralPlus - pressureIntegralMinus) / (2.0 * differenceStep);
REQUIRE(std::isfinite(pressureVolumeDerivative));
@@ -674,8 +637,7 @@ TEST_CASE("Pressure Force Residual Matches Deformed Pressure Volume Variation",
REQUIRE(comparisonScale > 100.0 * std::numeric_limits<double>::epsilon());
const double absoluteDiscrepancy =
std::abs(residualWork + pressureVolumeDerivative);
const double absoluteDiscrepancy = std::abs(residualWork + pressureVolumeDerivative);
const double relativeDiscrepancy = absoluteDiscrepancy / comparisonScale;
@@ -689,8 +651,7 @@ TEST_CASE("Pressure Force Residual Matches Deformed Pressure Volume Variation",
INFO("Pressure-volume derivative = " << pressureVolumeDerivative);
INFO("Residual work plus derivative = " << residualWork +
pressureVolumeDerivative);
INFO("Residual work plus derivative = " << residualWork + pressureVolumeDerivative);
INFO("Relative discrepancy = " << relativeDiscrepancy);
@@ -702,8 +663,7 @@ TEST_CASE("Pressure Force Residual Matches Deformed Pressure Volume Variation",
CHECK(residualWork * pressureVolumeDerivative < 0.0);
}
INFO("Best pressure-volume relative discrepancy = "
<< bestRelativeDiscrepancy);
INFO("Best pressure-volume relative discrepancy = " << bestRelativeDiscrepancy);
/*
* This is intentionally a provisional but meaningful threshold.

View File

@@ -243,7 +243,11 @@ namespace prepared_barotropic_closure_test_utils {
) {
mfem::FunctionCoefficient coefficient([&equationOfState, condition](const mfem::Vector &position) {
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));
});
return project_scalar(*f.densityFes, coefficient);
@@ -785,7 +789,11 @@ namespace prepared_barotropic_closure_test_utils {
const mean_field::eos::Polytrope equationOfState(3.0, 1.5);
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 equilibriumDensity =

View File

@@ -10,33 +10,28 @@ using namespace mean_field;
using Catch::Matchers::WithinAbs;
namespace prepared_test = gravity_prepared_test_utils;
TEST_CASE("Prepared Mapped Hdiv Mass Matches Stateless Kernel",
tags::gravity_prepared) {
TEST_CASE(
"Prepared Mapped Hdiv Mass Matches Stateless Kernel",
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());
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 gravity_gradient_true =
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(),
0.21);
const mfem::Vector gravity_gradient =
prepared_operator.GetFluxMap().gather(gravity_gradient_true);
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.21);
const mfem::Vector gravity_gradient = prepared_operator.GetFluxMap().gather(gravity_gradient_true);
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
mfem::Vector identity_action;
mfem::Vector deformed_action;
for (const double deformation_scale : {0.0, 1.0}) {
const mfem::Vector displacement_true =
prepared_test::make_displacement(f, deformation_scale);
const mfem::Vector displacement =
prepared_operator.GetDisplacementMap().gather(displacement_true);
const mfem::Vector displacement_true = prepared_test::make_displacement(f, deformation_scale);
const mfem::Vector displacement = prepared_operator.GetDisplacementMap().gather(displacement_true);
prepared_operator.Prepare(displacement);
@@ -45,19 +40,15 @@ TEST_CASE("Prepared Mapped Hdiv Mass Matches Stateless Kernel",
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);
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);
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("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());
@@ -70,8 +61,7 @@ TEST_CASE("Prepared Mapped Hdiv Mass Matches Stateless Kernel",
}
}
const double geometry_change = prepared_test::relative_error(
deformed_action, identity_action, communicator);
const double geometry_change = prepared_test::relative_error(deformed_action, identity_action, communicator);
INFO("Relative action change under deformation = " << geometry_change);
@@ -79,30 +69,27 @@ TEST_CASE("Prepared Mapped Hdiv Mass Matches Stateless Kernel",
CHECK(geometry_change > 1.0e-5);
}
TEST_CASE("Prepared Mapped Hdiv Mass Preserves Operator Identities",
tags::gravity_prepared) {
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());
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.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));
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_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;
@@ -113,8 +100,7 @@ TEST_CASE("Prepared Mapped Hdiv Mass Preserves Operator Identities",
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 expected_combination = prepared_test::linear_combination(first_action, 1.7, second_action, -0.4);
mfem::Vector zero(first.Size());
zero = 0.0;
@@ -122,20 +108,14 @@ TEST_CASE("Prepared Mapped Hdiv Mass Preserves Operator Identities",
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();
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);
@@ -149,25 +129,23 @@ TEST_CASE("Prepared Mapped Hdiv Mass Preserves Operator Identities",
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_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_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) {
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);
operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless);
const mfem::Vector displacement =
prepared_operator.GetDisplacementMap().gather(
prepared_test::make_displacement(f, 1.0));
prepared_operator.GetDisplacementMap().gather(prepared_test::make_displacement(f, 1.0));
prepared_operator.Prepare(displacement);
mfem::Vector diagonal;
@@ -178,13 +156,11 @@ TEST_CASE("Prepared Mapped Hdiv Mass Diagonal Is Positive Across Both Domains",
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);
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))));
CHECK_THAT(diagonal(i), WithinAbs(gathered_true_diagonal(i), 1.0e-14 * std::abs(diagonal(i))));
}
}

View File

@@ -24,40 +24,52 @@ class EnthalpyJacobianOperator final : public mfem::Operator {
public:
EnthalpyJacobianOperator(
const int enthalpySize,
const mean_field::operators::PreparedHydrostaticEquilibriumOperator
&preparedOperator)
: mfem::Operator(enthalpySize), m_preparedOperator(preparedOperator) {}
const mean_field::operators::PreparedHydrostaticEquilibriumOperator &preparedOperator
)
: mfem::Operator(enthalpySize),
m_preparedOperator(preparedOperator) {
}
void Mult(const mfem::Vector &direction,
mfem::Vector &action) const override {
void Mult(
const mfem::Vector &direction,
mfem::Vector &action
) const override {
m_preparedOperator.ApplyEnthalpyJacobianAction(direction, action);
}
private:
const mean_field::operators::PreparedHydrostaticEquilibriumOperator
&m_preparedOperator;
const mean_field::operators::PreparedHydrostaticEquilibriumOperator &m_preparedOperator;
};
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies
make_dependencies() {
return {.discretization = {.identity = 701, .revision = 2},
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() {
return {
.discretization = {.identity = 701, .revision = 2},
.enthalpy = {.identity = 709, .revision = 3},
.gravityPotential = {.identity = 719, .revision = 5},
.displacement = {.identity = 727, .revision = 7},
.rotation = {.identity = 733, .revision = 11},
.bernoulliConstant = {.identity = 739, .revision = 13}};
.bernoulliConstant = {.identity = 739, .revision = 13}
};
}
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
make_state(const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential,
const mfem::Vector &displacement) {
return {.enthalpy = enthalpy,
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state(
const mfem::Vector &enthalpy,
const mfem::Vector &gravityPotential,
const mfem::Vector &displacement
) {
return {
.enthalpy = enthalpy,
.gravityPotential = gravityPotential,
.displacement = displacement,
.bernoulliConstant = bernoulliConstant};
.bernoulliConstant = bernoulliConstant
};
}
mfem::Vector make_vector(const std::array<double, 3> &values) {
mfem::Vector make_vector(
const std::array<
double,
3> &values
) {
mfem::Vector vector(3);
for (int component = 0; component < 3; ++component) {
@@ -67,22 +79,22 @@ mfem::Vector make_vector(const std::array<double, 3> &values) {
return vector;
}
mean_field::physics::RigidRotation
make_rotation(const AnalyticCase &analyticCase) {
mean_field::physics::RigidRotation make_rotation(const AnalyticCase &analyticCase) {
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 mfem::Vector &referencePosition,
const AnalyticCase &analyticCase,
mfem::Vector &physicalPosition) {
mfem::Vector &physicalPosition
) {
physicalPosition.SetSize(3);
for (int component = 0; component < 3; ++component) {
physicalPosition(component) =
analyticCase.deformationScale[static_cast<std::size_t>(component)] *
referencePosition(component);
analyticCase.deformationScale[static_cast<std::size_t>(component)] * referencePosition(component);
}
}
@@ -90,8 +102,7 @@ double exact_enthalpy_value(const mfem::Vector &referencePosition) {
double normalizedRadiusSquared = 0.0;
for (int component = 0; component < 3; ++component) {
const double normalizedCoordinate =
referencePosition(component) / mean_field::utils::RADIUS;
const double normalizedCoordinate = referencePosition(component) / mean_field::utils::RADIUS;
normalizedRadiusSquared += normalizedCoordinate * normalizedCoordinate;
}
@@ -99,10 +110,11 @@ double exact_enthalpy_value(const mfem::Vector &referencePosition) {
return enthalpyAmplitude * std::max(0.0, 1.0 - normalizedRadiusSquared);
}
double
exact_potential_value(const mfem::Vector &referencePosition,
double exact_potential_value(
const mfem::Vector &referencePosition,
const AnalyticCase &analyticCase,
const mean_field::physics::RigidRotation &rotation) {
const mean_field::physics::RigidRotation &rotation
) {
mfem::Vector physicalPosition;
map_to_physical(referencePosition, analyticCase, physicalPosition);
@@ -114,8 +126,7 @@ exact_potential_value(const mfem::Vector &referencePosition,
*
* analytically.
*/
return bernoulliConstant + rotation.potential(physicalPosition) -
exact_enthalpy_value(referencePosition);
return bernoulliConstant + rotation.potential(physicalPosition) - exact_enthalpy_value(referencePosition);
}
mfem::Array<int> make_stellar_element_marker(const mean_field::fem::FEM &f) {
@@ -124,17 +135,17 @@ mfem::Array<int> make_stellar_element_marker(const mean_field::fem::FEM &f) {
const int vacuumAttribute = field_dof_test_utils::vacuum_material_attribute;
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
stellarElementMarker[elementId] =
f.mesh->GetAttribute(elementId) != vacuumAttribute;
stellarElementMarker[elementId] = f.mesh->GetAttribute(elementId) != vacuumAttribute;
}
return stellarElementMarker;
}
} // namespace prepared_hydrostatic_analytic_solve_test_utils
TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
tags::barotrope_hydrostatic_prepared_analytic &tags::convergence
&tags::accuracy) {
TEST_CASE(
"Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
tags::barotrope_hydrostatic_prepared_analytic &tags::convergence &tags::accuracy
) {
using prepared_hydrostatic_analytic_solve_test_utils::AnalyticCase;
constexpr double deformationX = 1.08;
@@ -160,7 +171,8 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
{.name = "volume-preserving deformed rotating equilibrium",
.deformationScale = {deformationX, deformationY, deformationZ},
.angularVelocity = {0.17, -0.12, 0.43},
.rotationCenter = {0.031, -0.024, 0.018}}}};
.rotationCenter = {0.031, -0.024, 0.018}}}
};
auto args = test_utils::setup_args();
@@ -169,59 +181,51 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
const MPI_Comm communicator = f.mesh->GetComm();
const mean_field::field::FieldDofMap 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 mean_field::field::FieldDofMap 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 mean_field::field::FieldDofMap 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 mfem::Array<int> stellarElementMarker =
prepared_hydrostatic_analytic_solve_test_utils::
make_stellar_element_marker(f);
prepared_hydrostatic_analytic_solve_test_utils::make_stellar_element_marker(f);
for (const AnalyticCase &analyticCase : analyticCases) {
DYNAMIC_SECTION(analyticCase.name) {
const double deformationDeterminant = analyticCase.deformationScale[0] *
analyticCase.deformationScale[1] *
analyticCase.deformationScale[2];
const double deformationDeterminant =
analyticCase.deformationScale[0] * analyticCase.deformationScale[1] * analyticCase.deformationScale[2];
REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14);
const mean_field::physics::RigidRotation rotation =
prepared_hydrostatic_analytic_solve_test_utils::make_rotation(
analyticCase);
prepared_hydrostatic_analytic_solve_test_utils::make_rotation(analyticCase);
auto displacementFunction =
[&analyticCase](const mfem::Vector &referencePosition,
mfem::Vector &displacementValue) {
[&analyticCase](const mfem::Vector &referencePosition, mfem::Vector &displacementValue) {
mfem::Vector physicalPosition;
prepared_hydrostatic_analytic_solve_test_utils::map_to_physical(
referencePosition, analyticCase, physicalPosition);
referencePosition, analyticCase, physicalPosition
);
displacementValue.SetSize(3);
displacementValue = physicalPosition;
displacementValue -= referencePosition;
};
auto potentialFunction = [&analyticCase, &rotation](
const mfem::Vector &referencePosition) {
return prepared_hydrostatic_analytic_solve_test_utils::
exact_potential_value(referencePosition, analyticCase, rotation);
auto potentialFunction = [&analyticCase, &rotation](const mfem::Vector &referencePosition) {
return prepared_hydrostatic_analytic_solve_test_utils::exact_potential_value(
referencePosition, analyticCase, rotation
);
};
auto enthalpyFunction = [](const mfem::Vector &referencePosition) {
return prepared_hydrostatic_analytic_solve_test_utils::
exact_enthalpy_value(referencePosition);
return prepared_hydrostatic_analytic_solve_test_utils::exact_enthalpy_value(referencePosition);
};
mfem::VectorFunctionCoefficient displacementCoefficient(
f.mesh->Dimension(), displacementFunction);
mfem::VectorFunctionCoefficient displacementCoefficient(f.mesh->Dimension(), displacementFunction);
mfem::FunctionCoefficient potentialCoefficient(potentialFunction);
@@ -244,10 +248,8 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
displacementField.GetTrueDofs(displacementTrue);
potentialField.GetTrueDofs(gravityPotentialTrue);
const mfem::Vector displacement =
displacementMap.gather(displacementTrue);
const mfem::Vector gravityPotential =
gravityPotentialMap.gather(gravityPotentialTrue);
const mfem::Vector displacement = displacementMap.gather(displacementTrue);
const mfem::Vector gravityPotential = gravityPotentialMap.gather(gravityPotentialTrue);
/*
* This projection is not used as the solution. It gives
@@ -262,16 +264,15 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
zeroEnthalpyField = 0.0;
const double exactEnthalpyNorm = zeroEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
const double exactEnthalpyNorm =
zeroEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
const double projectionError = projectedEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
const double projectionError =
projectedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
REQUIRE(exactEnthalpyNorm > 0.0);
const double relativeProjectionError =
projectionError / exactEnthalpyNorm;
const double relativeProjectionError = projectionError / exactEnthalpyNorm;
/*
* Begin deliberately far from equilibrium.
@@ -280,16 +281,14 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
enthalpy = 0.0;
auto dependencies =
prepared_hydrostatic_analytic_solve_test_utils::make_dependencies();
auto dependencies = prepared_hydrostatic_analytic_solve_test_utils::make_dependencies();
mean_field::operators::PreparedHydrostaticEquilibriumOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
const auto initialReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(
enthalpy, gravityPotential, displacement),
dependencies, rotation);
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
dependencies, rotation
);
REQUIRE(initialReport.preparedResidual);
REQUIRE(initialReport.preparedAlgebraicJacobianBlocks);
@@ -298,9 +297,7 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
preparedOperator.BuildResidual(initialResidual);
const double initialResidualNorm =
gravity_prepared_test_utils::global_norm(initialResidual,
communicator);
const double initialResidualNorm = gravity_prepared_test_utils::global_norm(initialResidual, communicator);
REQUIRE(initialResidualNorm > 1.0e-12);
@@ -314,8 +311,9 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
* rotation, and displacement makes this a well-defined
* enthalpy solve.
*/
prepared_hydrostatic_analytic_solve_test_utils::EnthalpyJacobianOperator
enthalpyJacobian(enthalpyMap.reduced_size(), preparedOperator);
prepared_hydrostatic_analytic_solve_test_utils::EnthalpyJacobianOperator enthalpyJacobian(
enthalpyMap.reduced_size(), preparedOperator
);
mfem::Vector rightHandSide(initialResidual);
rightHandSide *= -1.0;
@@ -357,9 +355,9 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
++dependencies.enthalpy.revision;
const auto solvedReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(
enthalpy, gravityPotential, displacement),
dependencies, rotation);
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
dependencies, rotation
);
CHECK(solvedReport.contextReport.updatedEnthalpy);
@@ -373,9 +371,7 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
preparedOperator.BuildResidual(solvedResidual);
const double solvedResidualNorm =
gravity_prepared_test_utils::global_norm(solvedResidual,
communicator);
const double solvedResidualNorm = gravity_prepared_test_utils::global_norm(solvedResidual, communicator);
const double residualReduction = solvedResidualNorm / initialResidualNorm;
@@ -393,11 +389,10 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
enthalpyMap.scatter(enthalpy, enthalpyTrue);
solvedEnthalpyField.SetFromTrueDofs(enthalpyTrue);
const double solvedAnalyticError = solvedEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
const double solvedAnalyticError =
solvedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
const double relativeSolvedAnalyticError =
solvedAnalyticError / exactEnthalpyNorm;
const double relativeSolvedAnalyticError = solvedAnalyticError / exactEnthalpyNorm;
INFO("Deformation determinant = " << deformationDeterminant);
@@ -409,8 +404,7 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
INFO("Relative analytic projection floor = " << relativeProjectionError);
INFO("Relative solved analytic L2 error = "
<< relativeSolvedAnalyticError);
INFO("Relative solved analytic L2 error = " << relativeSolvedAnalyticError);
/*
* The discrete Bernoulli equation must be solved essentially
@@ -425,8 +419,7 @@ TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
* also contains potential-projection and mapped-space
* compatibility errors.
*/
CHECK(relativeSolvedAnalyticError <
std::max(5.0 * relativeProjectionError, 1.25e-4));
CHECK(relativeSolvedAnalyticError < std::max(5.0 * relativeProjectionError, 1.25e-4));
/*
* Record that the analytic error remains within one order of

View File

@@ -138,9 +138,7 @@ TEST_CASE(
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
const mfem::Vector enthalpy =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(
*f.enthalpyFes, 0.34
);
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 0.34);
const mfem::Vector gravityPotential =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>(
@@ -161,9 +159,7 @@ TEST_CASE(
);
const mfem::Vector enthalpyVariation =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(
*f.enthalpyFes, 1.07
);
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 1.07);
const mfem::Vector gravityPotentialVariation =
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);
const mfem::Vector enthalpy =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(
*f.enthalpyFes, 0.41
);
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 0.41);
const mfem::Vector gravityPotential =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>(
@@ -277,9 +271,7 @@ TEST_CASE(
CHECK(preparedOperator.GetEnthalpyMap().inactive_size() > 0);
const mfem::Vector enthalpyVariation =
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(
*f.enthalpyFes, 1.12
);
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 1.12);
const mfem::Vector gravityPotentialVariation =
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) {
const auto densityMap =
field_dof_test_utils::make_map<mean_field::field::Density>(*f.densityFes);
const auto densityMap = field_dof_test_utils::make_map<mean_field::field::Density>(*f.densityFes);
const auto displacementMap =
field_dof_test_utils::make_map<mean_field::field::Displacement>(*f.displacementFes);
const auto gravityFluxMap =
field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityFluxFes);
const auto gravityFluxMap = field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityFluxFes);
const auto gravityPotentialMap =
field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityPotentialFes);
const auto enthalpyMap =
field_dof_test_utils::make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
const auto enthalpyMap = field_dof_test_utils::make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
const std::array<int, CoupledForm::value_block_count> valueSizes{
densityMap.reduced_size(), displacementMap.reduced_size(), gravityFluxMap.reduced_size(),
@@ -345,8 +342,7 @@ TEST_CASE(
mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext);
massOperator.Prepare({.targetMass = 1.11}, dependencies);
const mfem::Vector reducedDisplacementDirection =
gravityContext.GetDisplacementMap().gather(displacementDirection);
const mfem::Vector reducedDisplacementDirection = gravityContext.GetDisplacementMap().gather(displacementDirection);
mfem::Vector analyticAction;
massOperator.ApplyDisplacementJacobianAction(reducedDisplacementDirection, analyticAction);
@@ -477,8 +473,7 @@ TEST_CASE(
massOperator.Prepare({.targetMass = 1.19}, dependencies);
const mfem::Vector reducedDensityDirection = gravityContext.GetDensityMap().gather(densityDirection);
const mfem::Vector reducedDisplacementDirection =
gravityContext.GetDisplacementMap().gather(displacementDirection);
const mfem::Vector reducedDisplacementDirection = gravityContext.GetDisplacementMap().gather(displacementDirection);
mfem::Vector densityAction;
mfem::Vector displacementAction;
@@ -486,9 +481,7 @@ TEST_CASE(
massOperator.ApplyDensityJacobianAction(reducedDensityDirection, densityAction);
massOperator.ApplyDisplacementJacobianAction(reducedDisplacementDirection, displacementAction);
massOperator.ApplyCompleteJacobianAction(
reducedDensityDirection, reducedDisplacementDirection, completeAction
);
massOperator.ApplyCompleteJacobianAction(reducedDensityDirection, reducedDisplacementDirection, completeAction);
CHECK(
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);
mean_field::operators::PreparedMassNormalizationJacobianOperator adapter(layout, massOperator);
CHECK(
layout.size(mass_normalization_test_utils::densityValue) ==
gravityContext.GetDensityMap().reduced_size()
);
CHECK(layout.size(mass_normalization_test_utils::densityValue) == gravityContext.GetDensityMap().reduced_size());
CHECK(
layout.size(mass_normalization_test_utils::displacementValue) ==
gravityContext.GetDisplacementMap().reduced_size()

View File

@@ -24,59 +24,76 @@ struct Maps final {
explicit Maps(const mean_field::fem::FEM &f)
: density(
mean_field::field::make_field_dof_map<mean_field::field::Density,
DomainSchema>(*f.densityFes)),
displacement(mean_field::field::make_field_dof_map<
mean_field::field::Displacement, DomainSchema>(
*f.displacementFes)),
mean_field::field::make_field_dof_map<
mean_field::field::Density,
DomainSchema>(*f.densityFes)
),
displacement(
mean_field::field::make_field_dof_map<
mean_field::field::Displacement,
DomainSchema>(*f.displacementFes)
),
gravityFlux(
mean_field::field::make_field_dof_map<mean_field::field::Gravity,
DomainSchema>(
*f.gravityFluxFes)),
mean_field::field::make_field_dof_map<
mean_field::field::Gravity,
DomainSchema>(*f.gravityFluxFes)
),
gravityPotential(
mean_field::field::make_field_dof_map<mean_field::field::Gravity,
DomainSchema>(
*f.gravityPotentialFes)),
enthalpy(mean_field::field::make_field_dof_map<
mean_field::field::Enthalpy, DomainSchema>(*f.enthalpyFes)) {}
mean_field::field::make_field_dof_map<
mean_field::field::Gravity,
DomainSchema>(*f.gravityPotentialFes)
),
enthalpy(
mean_field::field::make_field_dof_map<
mean_field::field::Enthalpy,
DomainSchema>(*f.enthalpyFes)
) {
}
};
[[nodiscard]]
mfem::Vector make_positive_enthalpy_true(const mean_field::fem::FEM &f,
const double phase) {
mfem::Vector make_positive_enthalpy_true(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::Vector enthalpy(f.enthalpyFes->GetTrueVSize());
for (int index = 0; index < enthalpy.Size(); ++index) {
const double position = static_cast<double>(index + 1);
enthalpy(index) = 0.93 + 0.09 * std::sin(0.23 * position + phase) +
0.04 * std::cos(0.17 * position - 0.5 * phase);
enthalpy(index) =
0.93 + 0.09 * std::sin(0.23 * position + phase) + 0.04 * std::cos(0.17 * position - 0.5 * phase);
}
return enthalpy;
}
[[nodiscard]]
mfem::Vector make_enthalpy_direction_true(const mean_field::fem::FEM &f,
const double phase) {
mfem::Vector make_enthalpy_direction_true(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::Vector direction(f.enthalpyFes->GetTrueVSize());
for (int index = 0; index < direction.Size(); ++index) {
const double position = static_cast<double>(index + 1);
direction(index) = 0.27 * std::sin(0.19 * position + phase) +
0.14 * std::cos(0.13 * position - 0.5 * phase);
direction(index) =
0.27 * std::sin(0.19 * position + phase) + 0.14 * std::cos(0.13 * position - 0.5 * phase);
}
return direction;
}
[[nodiscard]]
mfem::Vector make_displacement_direction_true(const mean_field::fem::FEM &f,
const double phase) {
MFEM_VERIFY(f.mesh->Dimension() == 3,
"The prepared pressure-force test requires a "
"three-dimensional mesh.");
mfem::Vector make_displacement_direction_true(
const mean_field::fem::FEM &f,
const double phase
) {
MFEM_VERIFY(
f.mesh->Dimension() == 3, "The prepared pressure-force test requires a "
"three-dimensional mesh."
);
mfem::ParGridFunction directionField(f.displacementFes.get());
@@ -90,15 +107,13 @@ mfem::Vector make_displacement_direction_true(const mean_field::fem::FEM &f,
value.SetSize(3);
value(0) =
0.019 * x + 0.011 * y * z - 0.006 * z * z + 0.004 * phase * y;
value(0) = 0.019 * x + 0.011 * y * z - 0.006 * z * z + 0.004 * phase * y;
value(1) =
-0.016 * y + 0.008 * x * z + 0.005 * x * x - 0.003 * phase * z;
value(1) = -0.016 * y + 0.008 * x * z + 0.005 * x * x - 0.003 * phase * z;
value(2) =
0.013 * z - 0.010 * x * y + 0.006 * y * y + 0.004 * phase * x;
});
value(2) = 0.013 * z - 0.010 * x * y + 0.006 * y * y + 0.004 * phase * x;
}
);
directionField.ProjectCoefficient(directionCoefficient);
@@ -110,95 +125,93 @@ mfem::Vector make_displacement_direction_true(const mean_field::fem::FEM &f,
}
[[nodiscard]]
double relative_difference(const mfem::Vector &left, const mfem::Vector &right,
const MPI_Comm communicator) {
MFEM_VERIFY(left.Size() == right.Size(),
"Cannot compare prepared pressure-force vectors with "
"different sizes.");
double relative_difference(
const mfem::Vector &left,
const mfem::Vector &right,
const MPI_Comm communicator
) {
MFEM_VERIFY(
left.Size() == right.Size(), "Cannot compare prepared pressure-force vectors with "
"different sizes."
);
mfem::Vector difference(left);
difference -= right;
const double scale =
std::max({gravity_prepared_test_utils::global_norm(left, communicator),
const double scale = std::max(
{gravity_prepared_test_utils::global_norm(left, communicator),
gravity_prepared_test_utils::global_norm(right, communicator),
100.0 * std::numeric_limits<double>::epsilon()});
100.0 * std::numeric_limits<double>::epsilon()}
);
return gravity_prepared_test_utils::global_norm(difference, communicator) /
scale;
return gravity_prepared_test_utils::global_norm(difference, communicator) / scale;
}
[[nodiscard]]
mean_field::operators::context::pressure_force::PressureForceDependencies
make_dependencies() {
return {.discretization = {.identity = 1201, .revision = 3},
mean_field::operators::context::pressure_force::PressureForceDependencies make_dependencies() {
return {
.discretization = {.identity = 1201, .revision = 3},
.enthalpy = {.identity = 1213, .revision = 5},
.displacement = {.identity = 1217, .revision = 7}};
.displacement = {.identity = 1217, .revision = 7}
};
}
constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::density_field.mass_term);
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term);
constexpr auto displacementValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto gravityGradientValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term);
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term);
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term);
constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto barotropicConstantValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field
.mass_normalization_term);
constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
constexpr auto gravityGradientResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term
);
constexpr auto gravityPotentialResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term
);
constexpr auto densityResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::density_field.mass_term);
mean_field::utils::blocks::get_residual_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term);
constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto enthalpyResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term);
constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto massResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field
.mass_normalization_term);
constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
[[nodiscard]]
mean_field::operators::BarotropicEquilibriumLayout
make_coupled_layout(const Maps &maps) {
mean_field::operators::BarotropicEquilibriumLayout make_coupled_layout(const Maps &maps) {
const std::array<int, CoupledForm::value_block_count> valueSizes{
maps.density.reduced_size(), maps.displacement.reduced_size(),
maps.gravityFlux.reduced_size(), maps.gravityPotential.reduced_size(),
maps.enthalpy.reduced_size(), 1};
maps.density.reduced_size(), maps.displacement.reduced_size(), maps.gravityFlux.reduced_size(),
maps.gravityPotential.reduced_size(), maps.enthalpy.reduced_size(), 1
};
const std::array<int, CoupledForm::residual_block_count> residualSizes{
maps.gravityFlux.reduced_size(), maps.gravityPotential.reduced_size(),
maps.density.reduced_size(), maps.displacement.reduced_size(),
maps.enthalpy.reduced_size(), 1};
maps.gravityFlux.reduced_size(), maps.gravityPotential.reduced_size(), maps.density.reduced_size(),
maps.displacement.reduced_size(), maps.enthalpy.reduced_size(), 1
};
return {valueSizes, residualSizes};
}
@@ -208,7 +221,8 @@ template <int index>
mfem::Vector copy_residual_block(
const mfem::Vector &action,
const mean_field::operators::BarotropicEquilibriumLayout &layout,
const mean_field::utils::blocks::residual_block<index> block) {
const mean_field::utils::blocks::residual_block<index> block
) {
mfem::Vector result(layout.size(block));
const int offset = layout.offset(block);
@@ -224,7 +238,8 @@ mfem::Vector copy_residual_block(
TEST_CASE(
"Prepared Pressure Force Uses FieldDof Supported Dimensions And Owns Its "
"Context",
tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::unit) {
tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::unit
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -235,8 +250,7 @@ TEST_CASE(
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
mean_field::operators::PreparedPressureForceOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState);
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
REQUIRE(maps.enthalpy.reduced_size() < maps.enthalpy.full_size());
@@ -244,17 +258,18 @@ TEST_CASE(
CHECK(preparedOperator.GetEnthalpySize() == maps.enthalpy.reduced_size());
CHECK(preparedOperator.GetDisplacementSize() ==
maps.displacement.reduced_size());
CHECK(preparedOperator.GetDisplacementSize() == maps.displacement.reduced_size());
CHECK(&preparedOperator.GetContext().GetPreparationStatistics() ==
&preparedOperator.GetContextPreparationStatistics());
CHECK(
&preparedOperator.GetContext().GetPreparationStatistics() == &preparedOperator.GetContextPreparationStatistics()
);
}
TEST_CASE("Prepared Pressure Force Jacobian Matches Full Stateless Columns "
TEST_CASE(
"Prepared Pressure Force Jacobian Matches Full Stateless Columns "
"Through FieldDof Restriction",
tags::barotrope &tags::pressure &tags::prepared &tags::field
&tags::integration &tags::accuracy) {
tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::integration &tags::accuracy
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -265,90 +280,87 @@ TEST_CASE("Prepared Pressure Force Jacobian Matches Full Stateless Columns "
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mfem::Vector enthalpy = maps.enthalpy.gather(
prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.47));
const mfem::Vector enthalpy =
maps.enthalpy.gather(prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.47));
const mfem::Vector displacement = maps.displacement.gather(
gravity_prepared_test_utils::make_displacement(f, 0.69));
const mfem::Vector displacement = maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.69));
const mfem::Vector enthalpyDirection = maps.enthalpy.gather(
prepared_pressure_force_test_utils::make_enthalpy_direction_true(f,
0.73));
const mfem::Vector enthalpyDirection =
maps.enthalpy.gather(prepared_pressure_force_test_utils::make_enthalpy_direction_true(f, 0.73));
const mfem::Vector displacementDirection = maps.displacement.gather(
prepared_pressure_force_test_utils::make_displacement_direction_true(
f, 0.83));
const mfem::Vector displacementDirection =
maps.displacement.gather(prepared_pressure_force_test_utils::make_displacement_direction_true(f, 0.83));
mean_field::operators::PreparedPressureForceOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState);
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
preparedOperator.Prepare(
{.enthalpy = enthalpy, .displacement = displacement},
prepared_pressure_force_test_utils::make_dependencies());
{.enthalpy = enthalpy, .displacement = displacement}, prepared_pressure_force_test_utils::make_dependencies()
);
const mfem::Vector enthalpyTrue = maps.enthalpy.scatter(enthalpy);
const mfem::Vector displacementTrue = maps.displacement.scatter(displacement);
const mfem::Vector enthalpyDirectionTrue =
maps.enthalpy.scatter(enthalpyDirection);
const mfem::Vector enthalpyDirectionTrue = maps.enthalpy.scatter(enthalpyDirection);
const mfem::Vector displacementDirectionTrue =
maps.displacement.scatter(displacementDirection);
const mfem::Vector displacementDirectionTrue = maps.displacement.scatter(displacementDirection);
mfem::Vector preparedEnthalpyAction;
mfem::Vector kernelEnthalpyActionTrue;
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyDirection,
preparedEnthalpyAction);
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyDirection, preparedEnthalpyAction);
mean_field::operators::kernels::apply_pressure_force_enthalpy_action(
f, *f.domainMapperStateless, equationOfState, enthalpyTrue,
enthalpyDirectionTrue, displacementTrue, kernelEnthalpyActionTrue);
f, *f.domainMapperStateless, equationOfState, enthalpyTrue, enthalpyDirectionTrue, displacementTrue,
kernelEnthalpyActionTrue
);
const mfem::Vector kernelEnthalpyAction =
maps.displacement.gather(kernelEnthalpyActionTrue);
const mfem::Vector kernelEnthalpyAction = maps.displacement.gather(kernelEnthalpyActionTrue);
CHECK(prepared_pressure_force_test_utils::relative_difference(
preparedEnthalpyAction, kernelEnthalpyAction, f.mesh->GetComm()) <
2.0e-12);
CHECK(
prepared_pressure_force_test_utils::relative_difference(
preparedEnthalpyAction, kernelEnthalpyAction, f.mesh->GetComm()
) < 2.0e-12
);
mfem::Vector preparedDisplacementAction;
mfem::Vector kernelDisplacementActionTrue;
preparedOperator.ApplyDisplacementJacobianAction(displacementDirection,
preparedDisplacementAction);
preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, preparedDisplacementAction);
mean_field::operators::kernels::apply_pressure_force_displacement_action(
f, *f.domainMapperStateless, equationOfState, enthalpyTrue,
displacementDirectionTrue, displacementTrue,
kernelDisplacementActionTrue);
f, *f.domainMapperStateless, equationOfState, enthalpyTrue, displacementDirectionTrue, displacementTrue,
kernelDisplacementActionTrue
);
const mfem::Vector kernelDisplacementAction =
maps.displacement.gather(kernelDisplacementActionTrue);
const mfem::Vector kernelDisplacementAction = maps.displacement.gather(kernelDisplacementActionTrue);
CHECK(prepared_pressure_force_test_utils::relative_difference(
preparedDisplacementAction, kernelDisplacementAction,
f.mesh->GetComm()) < 2.0e-12);
CHECK(
prepared_pressure_force_test_utils::relative_difference(
preparedDisplacementAction, kernelDisplacementAction, f.mesh->GetComm()
) < 2.0e-12
);
mfem::Vector fusedAction;
preparedOperator.ApplyCompleteJacobianAction(
enthalpyDirection, displacementDirection, fusedAction);
preparedOperator.ApplyCompleteJacobianAction(enthalpyDirection, displacementDirection, fusedAction);
mfem::Vector expectedFusedAction(kernelEnthalpyAction);
expectedFusedAction += kernelDisplacementAction;
CHECK(prepared_pressure_force_test_utils::relative_difference(
fusedAction, expectedFusedAction, f.mesh->GetComm()) < 2.0e-12);
CHECK(
prepared_pressure_force_test_utils::relative_difference(fusedAction, expectedFusedAction, f.mesh->GetComm()) <
2.0e-12
);
}
TEST_CASE(
"Prepared Pressure Force MFEM Adapter Routes Reduced Coupled FieldDof "
"Blocks",
tags::barotrope &tags::pressure &tags::prepared &tags::field
&tags::integration &tags::jacobian &tags::mfem_operators &tags::unit) {
tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::integration &tags::jacobian
&tags::mfem_operators &tags::unit
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -359,38 +371,31 @@ TEST_CASE(
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mfem::Vector enthalpy = maps.enthalpy.gather(
prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.53));
const mfem::Vector enthalpy =
maps.enthalpy.gather(prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.53));
const mfem::Vector displacement = maps.displacement.gather(
gravity_prepared_test_utils::make_displacement(f, 0.71));
const mfem::Vector displacement = maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.71));
const mfem::Vector enthalpyDirection = maps.enthalpy.gather(
prepared_pressure_force_test_utils::make_enthalpy_direction_true(f,
0.89));
const mfem::Vector enthalpyDirection =
maps.enthalpy.gather(prepared_pressure_force_test_utils::make_enthalpy_direction_true(f, 0.89));
const mfem::Vector displacementDirection = maps.displacement.gather(
prepared_pressure_force_test_utils::make_displacement_direction_true(
f, 0.97));
const mfem::Vector displacementDirection =
maps.displacement.gather(prepared_pressure_force_test_utils::make_displacement_direction_true(f, 0.97));
mean_field::operators::PreparedPressureForceOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState);
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
preparedOperator.Prepare(
{.enthalpy = enthalpy, .displacement = displacement},
prepared_pressure_force_test_utils::make_dependencies());
{.enthalpy = enthalpy, .displacement = displacement}, prepared_pressure_force_test_utils::make_dependencies()
);
const mean_field::operators::BarotropicEquilibriumLayout layout =
prepared_pressure_force_test_utils::make_coupled_layout(maps);
mean_field::operators::PreparedPressureForceJacobianOperator adapter(
layout, preparedOperator);
mean_field::operators::PreparedPressureForceJacobianOperator adapter(layout, preparedOperator);
CHECK(layout.size(prepared_pressure_force_test_utils::enthalpyValue) ==
maps.enthalpy.reduced_size());
CHECK(layout.size(prepared_pressure_force_test_utils::enthalpyValue) == maps.enthalpy.reduced_size());
CHECK(layout.size(prepared_pressure_force_test_utils::densityValue) ==
maps.density.reduced_size());
CHECK(layout.size(prepared_pressure_force_test_utils::densityValue) == maps.density.reduced_size());
mfem::BlockVector direction(layout.value_offsets());
@@ -401,66 +406,74 @@ TEST_CASE(
*/
direction.GetBlock(prepared_pressure_force_test_utils::densityValue) = 0.37;
direction.GetBlock(prepared_pressure_force_test_utils::gravityGradientValue) =
-0.41;
direction.GetBlock(prepared_pressure_force_test_utils::gravityGradientValue) = -0.41;
direction.GetBlock(
prepared_pressure_force_test_utils::gravityPotentialValue) = 0.59;
direction.GetBlock(prepared_pressure_force_test_utils::gravityPotentialValue) = 0.59;
direction.GetBlock(
prepared_pressure_force_test_utils::barotropicConstantValue) = -0.73;
direction.GetBlock(prepared_pressure_force_test_utils::barotropicConstantValue) = -0.73;
direction.GetBlock(prepared_pressure_force_test_utils::displacementValue) =
displacementDirection;
direction.GetBlock(prepared_pressure_force_test_utils::displacementValue) = displacementDirection;
direction.GetBlock(prepared_pressure_force_test_utils::enthalpyValue) =
enthalpyDirection;
direction.GetBlock(prepared_pressure_force_test_utils::enthalpyValue) = enthalpyDirection;
mfem::Vector expectedDisplacementAction;
preparedOperator.ApplyCompleteJacobianAction(
enthalpyDirection, displacementDirection, expectedDisplacementAction);
preparedOperator.ApplyCompleteJacobianAction(enthalpyDirection, displacementDirection, expectedDisplacementAction);
mfem::Vector action;
adapter.Mult(direction, action);
const mfem::Vector displacementResidualAction =
prepared_pressure_force_test_utils::copy_residual_block(
action, layout,
prepared_pressure_force_test_utils::displacementResidual);
const mfem::Vector displacementResidualAction = prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::displacementResidual
);
CHECK(prepared_pressure_force_test_utils::relative_difference(
displacementResidualAction, expectedDisplacementAction,
f.mesh->GetComm()) < 2.0e-14);
CHECK(prepared_pressure_force_test_utils::copy_residual_block(
action, layout,
prepared_pressure_force_test_utils::gravityGradientResidual)
.Norml2() == 0.0);
CHECK(prepared_pressure_force_test_utils::copy_residual_block(
action, layout,
prepared_pressure_force_test_utils::gravityPotentialResidual)
.Norml2() == 0.0);
CHECK(prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::densityResidual)
.Norml2() == 0.0);
CHECK(
prepared_pressure_force_test_utils::relative_difference(
displacementResidualAction, expectedDisplacementAction, f.mesh->GetComm()
) < 2.0e-14
);
CHECK(
prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::enthalpyResidual)
.Norml2() == 0.0);
action, layout, prepared_pressure_force_test_utils::gravityGradientResidual
)
.Norml2() == 0.0
);
CHECK(prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::massResidual)
.Norml2() == 0.0);
CHECK(
prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::gravityPotentialResidual
)
.Norml2() == 0.0
);
CHECK(
prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::densityResidual
)
.Norml2() == 0.0
);
CHECK(
prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::enthalpyResidual
)
.Norml2() == 0.0
);
CHECK(
prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::massResidual
)
.Norml2() == 0.0
);
}
TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
&tags::convergence &tags::h_refinement &tags::analytic_comparison
&tags::accuracy) {
TEST_CASE(
"Pressure Force Residual Converges To A Manufactured Analytic Force",
tags::barotrope &tags::pressure &tags::kernels &tags::integration &tags::convergence &tags::h_refinement
&tags::analytic_comparison &tags::accuracy
) {
constexpr int dimension = 3;
constexpr std::array<int, 2> refinementLevels{0, 1};
@@ -474,12 +487,10 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
std::array<double, refinementLevels.size()> relativeErrors{};
for (std::size_t levelIndex = 0; levelIndex < refinementLevels.size();
++levelIndex) {
for (std::size_t levelIndex = 0; levelIndex < refinementLevels.size(); ++levelIndex) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(
args.mesh_file, args, refinementLevels[levelIndex]);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, refinementLevels[levelIndex]);
REQUIRE(f.okay());
REQUIRE(f.mesh->Dimension() == dimension);
@@ -487,15 +498,12 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
const MPI_Comm communicator = f.mesh->GetComm();
constexpr double supportRadius =
supportRadiusFraction * mean_field::utils::RADIUS;
constexpr double supportRadius = supportRadiusFraction * mean_field::utils::RADIUS;
constexpr double supportRadiusSquared = supportRadius * supportRadius;
auto analyticEnthalpyFunction = [supportRadiusSquared](
const mfem::Vector &position) {
const double normalizedRadiusSquared =
(position * position) / supportRadiusSquared;
auto analyticEnthalpyFunction = [supportRadiusSquared](const mfem::Vector &position) {
const double normalizedRadiusSquared = (position * position) / supportRadiusSquared;
if (normalizedRadiusSquared >= 1.0) {
return 0.0;
@@ -503,18 +511,14 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
const double distanceToSupportBoundary = 1.0 - normalizedRadiusSquared;
return amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared /
distanceToSupportBoundary);
return amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared / distanceToSupportBoundary);
};
auto analyticPressureForceFunction = [supportRadiusSquared](
const mfem::Vector &position,
mfem::Vector &force) {
auto analyticPressureForceFunction = [supportRadiusSquared](const mfem::Vector &position, mfem::Vector &force) {
force.SetSize(dimension);
force = 0.0;
const double normalizedRadiusSquared =
(position * position) / supportRadiusSquared;
const double normalizedRadiusSquared = (position * position) / supportRadiusSquared;
if (normalizedRadiusSquared >= 1.0) {
return;
@@ -523,24 +527,20 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
const double distanceToSupportBoundary = 1.0 - normalizedRadiusSquared;
const double enthalpy =
amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared /
distanceToSupportBoundary);
amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared / distanceToSupportBoundary);
const double pressureGradientScale =
-2.0 * bumpSharpness * std::pow(enthalpy, 4.0) /
(supportRadiusSquared * distanceToSupportBoundary *
distanceToSupportBoundary);
(supportRadiusSquared * distanceToSupportBoundary * distanceToSupportBoundary);
for (int component = 0; component < dimension; ++component) {
force(component) = pressureGradientScale * position(component);
}
};
mfem::FunctionCoefficient analyticEnthalpyCoefficient(
analyticEnthalpyFunction);
mfem::FunctionCoefficient analyticEnthalpyCoefficient(analyticEnthalpyFunction);
mfem::VectorFunctionCoefficient analyticPressureForceCoefficient(
dimension, analyticPressureForceFunction);
mfem::VectorFunctionCoefficient analyticPressureForceCoefficient(dimension, analyticPressureForceFunction);
mfem::ParGridFunction discreteEnthalpyField(f.enthalpyFes.get());
@@ -558,8 +558,8 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
mfem::Vector discreteResidual;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, discreteEnthalpyTrue,
zeroDisplacement, discreteResidual);
f, *f.domainMapperStateless, barotrope, discreteEnthalpyTrue, zeroDisplacement, discreteResidual
);
REQUIRE(discreteResidual.Size() == f.displacementFes->GetTrueVSize());
@@ -569,8 +569,7 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
const int vacuumAttribute = field_dof_test_utils::vacuum_material_attribute;
for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size();
++attributeIndex) {
for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size(); ++attributeIndex) {
const int attribute = f.mesh->attributes[attributeIndex];
if (attribute != vacuumAttribute) {
@@ -578,34 +577,28 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
}
}
const mfem::Geometry::Type elementGeometry =
f.displacementFes->GetFE(0)->GetGeomType();
const mfem::Geometry::Type elementGeometry = f.displacementFes->GetFE(0)->GetGeomType();
for (int element = 1; element < f.mesh->GetNE(); ++element) {
REQUIRE(f.displacementFes->GetFE(element)->GetGeomType() ==
elementGeometry);
REQUIRE(f.displacementFes->GetFE(element)->GetGeomType() == elementGeometry);
}
const int referenceQuadratureOrder =
2 * f.displacementFes->GetMaxElementOrder() + 16;
const int referenceQuadratureOrder = 2 * f.displacementFes->GetMaxElementOrder() + 16;
const mfem::IntegrationRule &referenceQuadrature =
mfem::IntRules.Get(elementGeometry, referenceQuadratureOrder);
auto *analyticForceIntegrator =
new mfem::VectorDomainLFIntegrator(analyticPressureForceCoefficient);
auto *analyticForceIntegrator = new mfem::VectorDomainLFIntegrator(analyticPressureForceCoefficient);
analyticForceIntegrator->SetIntRule(&referenceQuadrature);
mfem::ParLinearForm analyticForceLoad(f.displacementFes.get());
analyticForceLoad.AddDomainIntegrator(analyticForceIntegrator,
stellarMarker);
analyticForceLoad.AddDomainIntegrator(analyticForceIntegrator, stellarMarker);
analyticForceLoad.Assemble();
std::unique_ptr<mfem::HypreParVector> analyticForceHypreVector(
analyticForceLoad.ParallelAssemble());
std::unique_ptr<mfem::HypreParVector> analyticForceHypreVector(analyticForceLoad.ParallelAssemble());
REQUIRE(analyticForceHypreVector != nullptr);
@@ -613,8 +606,7 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
REQUIRE(analyticForceTrue.Size() == discreteResidual.Size());
const double analyticForceNorm = gravity_prepared_test_utils::global_norm(
analyticForceTrue, communicator);
const double analyticForceNorm = gravity_prepared_test_utils::global_norm(analyticForceTrue, communicator);
REQUIRE(std::isfinite(analyticForceNorm));
REQUIRE(analyticForceNorm > 0.0);
@@ -631,8 +623,7 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
rieszForm.Assemble();
rieszForm.Finalize();
std::unique_ptr<mfem::HypreParMatrix> rieszMatrix(
rieszForm.ParallelAssemble());
std::unique_ptr<mfem::HypreParMatrix> rieszMatrix(rieszForm.ParallelAssemble());
REQUIRE(rieszMatrix != nullptr);
REQUIRE(rieszMatrix->Height() == discreteResidual.Size());
@@ -651,28 +642,28 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
rieszSolver.SetMaxIter(5000);
rieszSolver.SetPrintLevel(0);
auto calculateDualNorm = [&rieszSolver,
communicator](const mfem::Vector &functional) {
auto calculateDualNorm = [&rieszSolver, communicator](const mfem::Vector &functional) {
mfem::Vector rieszRepresentative(functional.Size());
rieszRepresentative = 0.0;
rieszSolver.Mult(functional, rieszRepresentative);
MFEM_VERIFY(rieszSolver.GetConverged(),
"The pressure-force convergence-test Riesz solve "
"did not converge.");
MFEM_VERIFY(
rieszSolver.GetConverged(), "The pressure-force convergence-test Riesz solve "
"did not converge."
);
const double dualNormSquared = gravity_prepared_test_utils::global_dot(
functional, rieszRepresentative, communicator);
const double dualNormSquared =
gravity_prepared_test_utils::global_dot(functional, rieszRepresentative, communicator);
MFEM_VERIFY(std::isfinite(dualNormSquared),
"The pressure-force dual norm is not finite.");
MFEM_VERIFY(std::isfinite(dualNormSquared), "The pressure-force dual norm is not finite.");
MFEM_VERIFY(dualNormSquared >=
-100.0 * std::numeric_limits<double>::epsilon(),
MFEM_VERIFY(
dualNormSquared >= -100.0 * std::numeric_limits<double>::epsilon(),
"The pressure-force Riesz operator produced a "
"negative dual norm.");
"negative dual norm."
);
return std::sqrt(std::max(dualNormSquared, 0.0));
};
@@ -700,12 +691,9 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
REQUIRE(relativeError > 0.0);
}
static_assert(
refinementLevels.size() == 2,
"This reduced convergence test expects exactly two refinement levels.");
static_assert(refinementLevels.size() == 2, "This reduced convergence test expects exactly two refinement levels.");
const double observedRate =
std::log(relativeErrors[0] / relativeErrors[1]) / std::log(2.0);
const double observedRate = std::log(relativeErrors[0] / relativeErrors[1]) / std::log(2.0);
INFO("Level 0 pressure-force relative dual error = " << relativeErrors[0]);

View File

@@ -14,94 +14,82 @@ namespace rotational_displacement_force_test_utils {
using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::density_field.mass_term);
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term);
constexpr auto displacementValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto gravityGradientValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term);
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term);
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term);
constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto barotropicConstantValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field
.mass_normalization_term);
constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
constexpr auto gravityGradientResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term
);
constexpr auto gravityPotentialResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term
);
constexpr auto densityResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::density_field.mass_term);
mean_field::utils::blocks::get_residual_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term);
constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto enthalpyResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term);
constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto massResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field
.mass_normalization_term);
constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
[[nodiscard]] mean_field::operators::RotationalDisplacementForceLayout
make_layout(const mean_field::fem::FEM &f) {
[[nodiscard]] mean_field::operators::RotationalDisplacementForceLayout make_layout(const mean_field::fem::FEM &f) {
using DomainSchema = gravity_prepared_test_utils::DomainSchema;
const auto densityMap =
gravity_prepared_test_utils::make_field_map<mean_field::field::Density>(
f);
const auto displacementMap = gravity_prepared_test_utils::make_field_map<
mean_field::field::Displacement>(f);
const auto densityMap = gravity_prepared_test_utils::make_field_map<mean_field::field::Density>(f);
const auto displacementMap = gravity_prepared_test_utils::make_field_map<mean_field::field::Displacement>(f);
const auto gravityFluxMap =
mean_field::field::make_field_dof_map<mean_field::field::Gravity,
DomainSchema>(*f.gravityFluxFes);
const auto gravityPotentialMap = mean_field::field::make_field_dof_map<
mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes);
mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityFluxFes);
const auto gravityPotentialMap =
mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes);
const auto enthalpyMap =
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy,
DomainSchema>(*f.enthalpyFes);
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy, DomainSchema>(*f.enthalpyFes);
const std::array<int, CoupledForm::value_block_count> valueSizes{
densityMap.reduced_size(), displacementMap.reduced_size(),
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(),
enthalpyMap.reduced_size(), 1};
densityMap.reduced_size(), displacementMap.reduced_size(), gravityFluxMap.reduced_size(),
gravityPotentialMap.reduced_size(), enthalpyMap.reduced_size(), 1
};
const std::array<int, CoupledForm::residual_block_count> residualSizes{
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(),
densityMap.reduced_size(), displacementMap.reduced_size(),
enthalpyMap.reduced_size(), 1};
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), densityMap.reduced_size(),
displacementMap.reduced_size(), enthalpyMap.reduced_size(), 1
};
return {valueSizes, residualSizes};
}
[[nodiscard]] mfem::Vector make_density(const mean_field::fem::FEM &f,
const double phase) {
[[nodiscard]] mfem::Vector make_density(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction densityField(f.densityFes.get());
mfem::FunctionCoefficient densityCoefficient(
[phase](const mfem::Vector &position) {
return 0.88 + 0.06 * std::sin(0.7 * position(0) + phase) +
0.04 * std::cos(0.6 * position(1) - phase) +
mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) {
return 0.88 + 0.06 * std::sin(0.7 * position(0) + phase) + 0.04 * std::cos(0.6 * position(1) - phase) +
0.025 * position(2) * position(2);
});
@@ -112,14 +100,15 @@ make_layout(const mean_field::fem::FEM &f) {
return densityTrue;
}
[[nodiscard]] mfem::Vector make_density_direction(const mean_field::fem::FEM &f,
const double phase) {
[[nodiscard]] mfem::Vector make_density_direction(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction densityField(f.densityFes.get());
mfem::FunctionCoefficient densityCoefficient(
[phase](const mfem::Vector &position) {
return 0.17 * std::sin(0.9 * position(0) + phase) -
0.12 * std::cos(0.8 * position(1) - phase) + 0.07 * position(2);
mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) {
return 0.17 * std::sin(0.9 * position(0) + phase) - 0.12 * std::cos(0.8 * position(1) - phase) +
0.07 * position(2);
});
densityField.ProjectCoefficient(densityCoefficient);
@@ -129,20 +118,16 @@ make_layout(const mean_field::fem::FEM &f) {
return densityTrue;
}
[[nodiscard]] mfem::Vector
make_displacement_direction(const mean_field::fem::FEM &f) {
mfem::Vector direction =
gravity_prepared_test_utils::make_displacement(f, 0.91);
[[nodiscard]] mfem::Vector make_displacement_direction(const mean_field::fem::FEM &f) {
mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, 0.91);
const mfem::Vector second =
gravity_prepared_test_utils::make_displacement(f, 0.27);
const mfem::Vector second = gravity_prepared_test_utils::make_displacement(f, 0.27);
direction -= second;
return direction;
}
[[nodiscard]] mean_field::physics::RigidRotation
make_rotation(const double scale = 1.0) {
[[nodiscard]] mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
mfem::Vector angularVelocity(3);
angularVelocity(0) = scale * 0.17;
angularVelocity(1) = scale * -0.09;
@@ -156,17 +141,17 @@ make_rotation(const double scale = 1.0) {
return mean_field::physics::RigidRotation(angularVelocity, center);
}
[[nodiscard]] mean_field::operators::context::rotational_displacement_force::
RotationalDisplacementForceDependencies
[[nodiscard]] mean_field::operators::context::rotational_displacement_force::RotationalDisplacementForceDependencies
make_dependencies() {
return {.discretization = {.identity = 211, .revision = 3},
return {
.discretization = {.identity = 211, .revision = 3},
.density = {.identity = 223, .revision = 5},
.displacement = {.identity = 227, .revision = 7},
.rotation = {.identity = 229, .revision = 11}};
.rotation = {.identity = 229, .revision = 11}
};
}
[[nodiscard]] mfem::Vector
make_vacuum_only_density(const mean_field::fem::FEM &f) {
[[nodiscard]] mfem::Vector make_vacuum_only_density(const mean_field::fem::FEM &f) {
mfem::ParGridFunction densityField(f.densityFes.get());
densityField = 0.0;
@@ -176,8 +161,7 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
int localVacuumElements = 0;
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
REQUIRE(transformation != nullptr);
@@ -195,8 +179,7 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
int globalVacuumElements = 0;
MPI_Allreduce(&localVacuumElements, &globalVacuumElements, 1, MPI_INT,
MPI_SUM, f.mesh->GetComm());
MPI_Allreduce(&localVacuumElements, &globalVacuumElements, 1, MPI_INT, MPI_SUM, f.mesh->GetComm());
REQUIRE(globalVacuumElements > 0);
@@ -205,20 +188,23 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
return densityTrue;
}
[[nodiscard]] double global_norm(const mfem::Vector &vector,
MPI_Comm communicator) {
[[nodiscard]] double global_norm(
const mfem::Vector &vector,
MPI_Comm communicator
) {
const double localSquaredNorm = vector * vector;
double globalSquaredNorm = 0.0;
MPI_Allreduce(&localSquaredNorm, &globalSquaredNorm, 1, MPI_DOUBLE, MPI_SUM,
communicator);
MPI_Allreduce(&localSquaredNorm, &globalSquaredNorm, 1, MPI_DOUBLE, MPI_SUM, communicator);
return std::sqrt(globalSquaredNorm);
}
[[nodiscard]] double global_dot(const mfem::Vector &left,
[[nodiscard]] double global_dot(
const mfem::Vector &left,
const mfem::Vector &right,
MPI_Comm communicator) {
MPI_Comm communicator
) {
REQUIRE(left.Size() == right.Size());
const double localDot = left * right;
@@ -229,25 +215,29 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
return globalDot;
}
[[nodiscard]] double relative_difference(const mfem::Vector &computed,
[[nodiscard]] double relative_difference(
const mfem::Vector &computed,
const mfem::Vector &reference,
MPI_Comm communicator) {
MPI_Comm communicator
) {
REQUIRE(computed.Size() == reference.Size());
mfem::Vector difference(computed);
difference -= reference;
return global_norm(difference, communicator) /
std::max(global_norm(reference, communicator),
std::numeric_limits<double>::epsilon());
std::max(global_norm(reference, communicator), std::numeric_limits<double>::epsilon());
}
[[nodiscard]] mfem::Vector centered_difference(
const mean_field::fem::FEM &f,
const mean_field::physics::RigidRotation &rotation,
const mfem::Vector &baseDensity, const mfem::Vector &densityDirection,
const mfem::Vector &baseDensity,
const mfem::Vector &densityDirection,
const mfem::Vector &baseDisplacement,
const mfem::Vector &displacementDirection, const double step) {
const mfem::Vector &displacementDirection,
const double step
) {
mfem::Vector plusDensity(baseDensity);
plusDensity.Add(step, densityDirection);
@@ -264,12 +254,12 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
mfem::Vector minusResidual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, plusDensity, plusDisplacement,
plusResidual);
f, *f.domainMapperStateless, rotation, plusDensity, plusDisplacement, plusResidual
);
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, minusDensity, minusDisplacement,
minusResidual);
f, *f.domainMapperStateless, rotation, minusDensity, minusDisplacement, minusResidual
);
plusResidual -= minusResidual;
plusResidual /= 2.0 * step;
@@ -280,7 +270,8 @@ template <int index>
[[nodiscard]] mfem::Vector copy_residual_block(
const mfem::Vector &action,
const mean_field::operators::RotationalDisplacementForceLayout &layout,
const mean_field::utils::blocks::residual_block<index> block) {
const mean_field::utils::blocks::residual_block<index> block
) {
mfem::Vector result(layout.size(block));
const int offset = layout.offset(block);
@@ -295,18 +286,17 @@ template <int index>
TEST_CASE(
"Rotational Displacement Force Query Includes Density Test And Linear "
"Position",
tags::rotation_prepared_unit) {
using DisplacementField =
mean_field::field::Field<mean_field::field::Displacement>;
tags::rotation_prepared_unit
) {
using DisplacementField = mean_field::field::Field<mean_field::field::Displacement>;
constexpr int geometryWeightOrder = 4;
constexpr mean_field::quadrature::Query query = DisplacementField::make_query<
mean_field::field::Displacement::Form::CentrifugalForce>(
mean_field::quadrature::QuadratureRole::discretization,
geometryWeightOrder, std::array<int, 1>{1},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
constexpr mean_field::quadrature::Query query =
DisplacementField::make_query<mean_field::field::Displacement::Form::CentrifugalForce>(
mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder, std::array<int, 1>{1},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
/* density: 2, displacement test: 3, position: 1, geometry: 4 */
constexpr int expectedBaseOrder = 2 + 3 + 1 + 4;
@@ -318,33 +308,33 @@ TEST_CASE(
STATIC_REQUIRE(*query.base_order == expectedBaseOrder);
}
TEST_CASE("Rotational Displacement Force Uses Negative Rotation-Potential "
TEST_CASE(
"Rotational Displacement Force Uses Negative Rotation-Potential "
"Gradient And Excludes Vacuum",
tags::rotation_kernel_accuracy) {
tags::rotation_kernel_accuracy
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const mfem::Vector density =
rotational_displacement_force_test_utils::make_density(f, 0.31);
const mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.31);
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0;
const mean_field::physics::RigidRotation rotation =
rotational_displacement_force_test_utils::make_rotation();
const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation();
mfem::Vector residual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, density, displacement, residual);
f, *f.domainMapperStateless, rotation, density, displacement, residual
);
mfem::ParGridFunction gradientTestField(f.displacementFes.get());
auto gradientFunction = [&rotation](const mfem::Vector &position,
mfem::Vector &value) {
auto gradientFunction = [&rotation](const mfem::Vector &position, mfem::Vector &value) {
rotation.potential_gradient(position, value);
};
@@ -356,73 +346,63 @@ TEST_CASE("Rotational Displacement Force Uses Negative Rotation-Potential "
gradientTestField.GetTrueDofs(gradientTestDirection);
const double signedWork =
rotational_displacement_force_test_utils::global_dot(
residual, gradientTestDirection, f.mesh->GetComm());
rotational_displacement_force_test_utils::global_dot(residual, gradientTestDirection, f.mesh->GetComm());
INFO("Rotation-force work against grad(Psi) = " << signedWork);
CHECK(signedWork < 0.0);
const mfem::Vector vacuumDensity =
rotational_displacement_force_test_utils::make_vacuum_only_density(f);
const mfem::Vector vacuumDensity = rotational_displacement_force_test_utils::make_vacuum_only_density(f);
mfem::Vector vacuumResidual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, vacuumDensity, displacement,
vacuumResidual);
f, *f.domainMapperStateless, rotation, vacuumDensity, displacement, vacuumResidual
);
CHECK(rotational_displacement_force_test_utils::global_norm(
vacuumResidual, f.mesh->GetComm()) == 0.0);
CHECK(rotational_displacement_force_test_utils::global_norm(vacuumResidual, f.mesh->GetComm()) == 0.0);
mfem::Vector zeroAngularVelocity(3);
mfem::Vector zeroCenter(3);
zeroAngularVelocity = 0.0;
zeroCenter = 0.0;
const mean_field::physics::RigidRotation zeroRotation(zeroAngularVelocity,
zeroCenter);
const mean_field::physics::RigidRotation zeroRotation(zeroAngularVelocity, zeroCenter);
mfem::Vector zeroRotationResidual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, zeroRotation, density, displacement,
zeroRotationResidual);
f, *f.domainMapperStateless, zeroRotation, density, displacement, zeroRotationResidual
);
CHECK(rotational_displacement_force_test_utils::global_norm(
zeroRotationResidual, f.mesh->GetComm()) == 0.0);
CHECK(rotational_displacement_force_test_utils::global_norm(zeroRotationResidual, f.mesh->GetComm()) == 0.0);
}
TEST_CASE("Prepared Rotational Displacement Force Reprepares Selectively",
tags::rotation_prepared) {
TEST_CASE(
"Prepared Rotational Displacement Force Reprepares Selectively",
tags::rotation_prepared
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
mfem::Vector densityTrue =
rotational_displacement_force_test_utils::make_density(f, 0.37);
mfem::Vector densityTrue = rotational_displacement_force_test_utils::make_density(f, 0.37);
const mfem::Vector displacementTrue =
gravity_prepared_test_utils::make_displacement(f, 0.53);
const mfem::Vector displacementTrue = gravity_prepared_test_utils::make_displacement(f, 0.53);
mean_field::physics::RigidRotation rotation =
rotational_displacement_force_test_utils::make_rotation(0.81);
mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.81);
auto dependencies =
rotational_displacement_force_test_utils::make_dependencies();
auto dependencies = rotational_displacement_force_test_utils::make_dependencies();
mean_field::operators::PreparedRotationalDisplacementForceOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless);
const auto &context = preparedOperator.GetContext();
mfem::Vector density = context.GetDensityMap().gather(densityTrue);
const mfem::Vector displacement =
context.GetDisplacementMap().gather(displacementTrue);
const mfem::Vector displacement = context.GetDisplacementMap().gather(displacementTrue);
const auto initialReport = preparedOperator.Prepare(
{.density = density, .displacement = displacement}, dependencies,
rotation);
const auto initialReport =
preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation);
REQUIRE(initialReport.DidAnyWork());
REQUIRE(initialReport.updatedRotation);
@@ -435,19 +415,18 @@ TEST_CASE("Prepared Rotational Displacement Force Reprepares Selectively",
preparedOperator.BuildResidual(preparedResidual);
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, densityTrue, displacementTrue,
kernelResidual);
f, *f.domainMapperStateless, rotation, densityTrue, displacementTrue, kernelResidual
);
const mfem::Vector kernelResidualReduced =
context.GetDisplacementMap().gather(kernelResidual);
const mfem::Vector kernelResidualReduced = context.GetDisplacementMap().gather(kernelResidual);
CHECK(rotational_displacement_force_test_utils::relative_difference(
preparedResidual, kernelResidualReduced, f.mesh->GetComm()) <
2.0e-12);
CHECK(
rotational_displacement_force_test_utils::relative_difference(
preparedResidual, kernelResidualReduced, f.mesh->GetComm()
) < 2.0e-12
);
CHECK_FALSE(preparedOperator
.Prepare({.density = density, .displacement = displacement},
dependencies, rotation)
CHECK_FALSE(preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation)
.DidAnyWork());
densityTrue = rotational_displacement_force_test_utils::make_density(f, 0.79);
@@ -455,9 +434,8 @@ TEST_CASE("Prepared Rotational Displacement Force Reprepares Selectively",
++dependencies.density.revision;
const auto densityReport = preparedOperator.Prepare(
{.density = density, .displacement = displacement}, dependencies,
rotation);
const auto densityReport =
preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation);
CHECK(densityReport.preparedResidual);
CHECK_FALSE(densityReport.updatedRotation);
@@ -466,9 +444,8 @@ TEST_CASE("Prepared Rotational Displacement Force Reprepares Selectively",
++dependencies.rotation.revision;
const auto rotationReport = preparedOperator.Prepare(
{.density = density, .displacement = displacement}, dependencies,
rotation);
const auto rotationReport =
preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation);
CHECK(rotationReport.updatedRotation);
CHECK(rotationReport.preparedResidual);
@@ -476,45 +453,40 @@ TEST_CASE("Prepared Rotational Displacement Force Reprepares Selectively",
CHECK(preparedOperator.GetResidualApplicationCount() == 1);
}
TEST_CASE("Rotational Displacement Force Jacobian Matches Both Columns And "
TEST_CASE(
"Rotational Displacement Force Jacobian Matches Both Columns And "
"Centered Differences",
tags::rotation_prepared_jacobian_accuracy) {
tags::rotation_prepared_jacobian_accuracy
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const mfem::Vector densityTrue =
rotational_displacement_force_test_utils::make_density(f, 0.43);
const mfem::Vector densityTrue = rotational_displacement_force_test_utils::make_density(f, 0.43);
const mfem::Vector densityDirectionTrue =
rotational_displacement_force_test_utils::make_density_direction(f, 0.59);
const mfem::Vector densityDirectionTrue = rotational_displacement_force_test_utils::make_density_direction(f, 0.59);
const mfem::Vector displacementTrue =
gravity_prepared_test_utils::make_displacement(f, 0.61);
const mfem::Vector displacementTrue = gravity_prepared_test_utils::make_displacement(f, 0.61);
const mfem::Vector displacementDirectionTrue =
rotational_displacement_force_test_utils::make_displacement_direction(f);
const mean_field::physics::RigidRotation rotation =
rotational_displacement_force_test_utils::make_rotation(0.93);
const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.93);
mean_field::operators::PreparedRotationalDisplacementForceOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless);
const auto &context = preparedOperator.GetContext();
const mfem::Vector density = context.GetDensityMap().gather(densityTrue);
const mfem::Vector densityDirection =
context.GetDensityMap().gather(densityDirectionTrue);
const mfem::Vector displacement =
context.GetDisplacementMap().gather(displacementTrue);
const mfem::Vector displacementDirection =
context.GetDisplacementMap().gather(displacementDirectionTrue);
const mfem::Vector densityDirection = context.GetDensityMap().gather(densityDirectionTrue);
const mfem::Vector displacement = context.GetDisplacementMap().gather(displacementTrue);
const mfem::Vector displacementDirection = context.GetDisplacementMap().gather(displacementDirectionTrue);
preparedOperator.Prepare(
{.density = density, .displacement = displacement},
rotational_displacement_force_test_utils::make_dependencies(), rotation);
rotational_displacement_force_test_utils::make_dependencies(), rotation
);
mfem::Vector densityAction;
mfem::Vector displacementAction;
@@ -522,17 +494,18 @@ TEST_CASE("Rotational Displacement Force Jacobian Matches Both Columns And "
preparedOperator.ApplyDensityJacobianAction(densityDirection, densityAction);
preparedOperator.ApplyDisplacementJacobianAction(displacementDirection,
displacementAction);
preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, displacementAction);
preparedOperator.ApplyCompleteJacobianAction(
densityDirection, displacementDirection, completeAction);
preparedOperator.ApplyCompleteJacobianAction(densityDirection, displacementDirection, completeAction);
mfem::Vector summedColumns(densityAction);
summedColumns += displacementAction;
CHECK(rotational_displacement_force_test_utils::relative_difference(
completeAction, summedColumns, f.mesh->GetComm()) < 2.0e-12);
CHECK(
rotational_displacement_force_test_utils::relative_difference(
completeAction, summedColumns, f.mesh->GetComm()
) < 2.0e-12
);
mfem::Vector zeroDensityTrue(densityDirectionTrue.Size());
mfem::Vector zeroDisplacementTrue(displacementDirectionTrue.Size());
@@ -541,39 +514,33 @@ TEST_CASE("Rotational Displacement Force Jacobian Matches Both Columns And "
constexpr double step = 1.0e-5;
const mfem::Vector densityDifferenceTrue =
rotational_displacement_force_test_utils::centered_difference(
f, rotation, densityTrue, densityDirectionTrue, displacementTrue,
zeroDisplacementTrue, step);
const mfem::Vector densityDifferenceTrue = rotational_displacement_force_test_utils::centered_difference(
f, rotation, densityTrue, densityDirectionTrue, displacementTrue, zeroDisplacementTrue, step
);
const mfem::Vector displacementDifferenceTrue =
rotational_displacement_force_test_utils::centered_difference(
f, rotation, densityTrue, zeroDensityTrue, displacementTrue,
displacementDirectionTrue, step);
const mfem::Vector displacementDifferenceTrue = rotational_displacement_force_test_utils::centered_difference(
f, rotation, densityTrue, zeroDensityTrue, displacementTrue, displacementDirectionTrue, step
);
const mfem::Vector completeDifferenceTrue =
rotational_displacement_force_test_utils::centered_difference(
f, rotation, densityTrue, densityDirectionTrue, displacementTrue,
displacementDirectionTrue, step);
const mfem::Vector completeDifferenceTrue = rotational_displacement_force_test_utils::centered_difference(
f, rotation, densityTrue, densityDirectionTrue, displacementTrue, displacementDirectionTrue, step
);
const mfem::Vector densityDifference =
context.GetDisplacementMap().gather(densityDifferenceTrue);
const mfem::Vector displacementDifference =
context.GetDisplacementMap().gather(displacementDifferenceTrue);
const mfem::Vector completeDifference =
context.GetDisplacementMap().gather(completeDifferenceTrue);
const mfem::Vector densityDifference = context.GetDisplacementMap().gather(densityDifferenceTrue);
const mfem::Vector displacementDifference = context.GetDisplacementMap().gather(displacementDifferenceTrue);
const mfem::Vector completeDifference = context.GetDisplacementMap().gather(completeDifferenceTrue);
const double densityError =
rotational_displacement_force_test_utils::relative_difference(
densityAction, densityDifference, f.mesh->GetComm());
const double densityError = rotational_displacement_force_test_utils::relative_difference(
densityAction, densityDifference, f.mesh->GetComm()
);
const double displacementError =
rotational_displacement_force_test_utils::relative_difference(
displacementAction, displacementDifference, f.mesh->GetComm());
const double displacementError = rotational_displacement_force_test_utils::relative_difference(
displacementAction, displacementDifference, f.mesh->GetComm()
);
const double completeError =
rotational_displacement_force_test_utils::relative_difference(
completeAction, completeDifference, f.mesh->GetComm());
const double completeError = rotational_displacement_force_test_utils::relative_difference(
completeAction, completeDifference, f.mesh->GetComm()
);
INFO("Density-column centered-difference error = " << densityError);
INFO("Displacement-column centered-difference error = " << displacementError);
@@ -584,109 +551,95 @@ TEST_CASE("Rotational Displacement Force Jacobian Matches Both Columns And "
CHECK(completeError < 4.0e-8);
}
TEST_CASE("Prepared Rotational Displacement Force MFEM Adapter Routes Only R-d",
tags::rotation_prepared_unit) {
TEST_CASE(
"Prepared Rotational Displacement Force MFEM Adapter Routes Only R-d",
tags::rotation_prepared_unit
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const mfem::Vector densityTrue =
rotational_displacement_force_test_utils::make_density(f, 0.47);
const mfem::Vector densityTrue = rotational_displacement_force_test_utils::make_density(f, 0.47);
const mfem::Vector densityDirectionTrue =
rotational_displacement_force_test_utils::make_density_direction(f, 0.63);
const mfem::Vector densityDirectionTrue = rotational_displacement_force_test_utils::make_density_direction(f, 0.63);
const mfem::Vector displacementTrue =
gravity_prepared_test_utils::make_displacement(f, 0.57);
const mfem::Vector displacementTrue = gravity_prepared_test_utils::make_displacement(f, 0.57);
const mfem::Vector displacementDirectionTrue =
rotational_displacement_force_test_utils::make_displacement_direction(f);
const mean_field::physics::RigidRotation rotation =
rotational_displacement_force_test_utils::make_rotation(0.87);
const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.87);
mean_field::operators::PreparedRotationalDisplacementForceOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless);
const auto &context = preparedOperator.GetContext();
const mfem::Vector density = context.GetDensityMap().gather(densityTrue);
const mfem::Vector densityDirection =
context.GetDensityMap().gather(densityDirectionTrue);
const mfem::Vector displacement =
context.GetDisplacementMap().gather(displacementTrue);
const mfem::Vector displacementDirection =
context.GetDisplacementMap().gather(displacementDirectionTrue);
const mfem::Vector densityDirection = context.GetDensityMap().gather(densityDirectionTrue);
const mfem::Vector displacement = context.GetDisplacementMap().gather(displacementTrue);
const mfem::Vector displacementDirection = context.GetDisplacementMap().gather(displacementDirectionTrue);
preparedOperator.Prepare(
{.density = density, .displacement = displacement},
rotational_displacement_force_test_utils::make_dependencies(), rotation);
rotational_displacement_force_test_utils::make_dependencies(), rotation
);
const auto layout = rotational_displacement_force_test_utils::make_layout(f);
mean_field::operators::PreparedRotationalDisplacementForceJacobianOperator
adapter(layout, preparedOperator);
mean_field::operators::PreparedRotationalDisplacementForceJacobianOperator adapter(layout, preparedOperator);
mfem::BlockVector direction(layout.value_offsets());
direction = 0.0;
direction.GetBlock(rotational_displacement_force_test_utils::densityValue) =
densityDirection;
direction.GetBlock(rotational_displacement_force_test_utils::densityValue) = densityDirection;
direction.GetBlock(
rotational_displacement_force_test_utils::displacementValue) =
displacementDirection;
direction.GetBlock(rotational_displacement_force_test_utils::displacementValue) = displacementDirection;
direction.GetBlock(
rotational_displacement_force_test_utils::gravityGradientValue) = 0.23;
direction.GetBlock(rotational_displacement_force_test_utils::gravityGradientValue) = 0.23;
direction.GetBlock(
rotational_displacement_force_test_utils::gravityPotentialValue) = -0.31;
direction.GetBlock(rotational_displacement_force_test_utils::gravityPotentialValue) = -0.31;
direction.GetBlock(rotational_displacement_force_test_utils::enthalpyValue) =
0.37;
direction.GetBlock(rotational_displacement_force_test_utils::enthalpyValue) = 0.37;
direction.GetBlock(
rotational_displacement_force_test_utils::barotropicConstantValue) =
-0.41;
direction.GetBlock(rotational_displacement_force_test_utils::barotropicConstantValue) = -0.41;
mfem::Vector action;
adapter.Mult(direction, action);
mfem::Vector expectedDisplacementAction;
preparedOperator.ApplyCompleteJacobianAction(
densityDirection, displacementDirection, expectedDisplacementAction);
preparedOperator.ApplyCompleteJacobianAction(densityDirection, displacementDirection, expectedDisplacementAction);
const mfem::Vector actualDisplacementAction =
rotational_displacement_force_test_utils::copy_residual_block(
action, layout,
rotational_displacement_force_test_utils::displacementResidual);
const mfem::Vector actualDisplacementAction = rotational_displacement_force_test_utils::copy_residual_block(
action, layout, rotational_displacement_force_test_utils::displacementResidual
);
CHECK(rotational_displacement_force_test_utils::relative_difference(
actualDisplacementAction, expectedDisplacementAction,
f.mesh->GetComm()) < 2.0e-12);
CHECK(
rotational_displacement_force_test_utils::relative_difference(
actualDisplacementAction, expectedDisplacementAction, f.mesh->GetComm()
) < 2.0e-12
);
const std::array<mfem::Vector, 5> zeroRows{
rotational_displacement_force_test_utils::copy_residual_block(
action, layout,
rotational_displacement_force_test_utils::gravityGradientResidual),
action, layout, rotational_displacement_force_test_utils::gravityGradientResidual
),
rotational_displacement_force_test_utils::copy_residual_block(
action, layout,
rotational_displacement_force_test_utils::gravityPotentialResidual),
action, layout, rotational_displacement_force_test_utils::gravityPotentialResidual
),
rotational_displacement_force_test_utils::copy_residual_block(
action, layout,
rotational_displacement_force_test_utils::densityResidual),
action, layout, rotational_displacement_force_test_utils::densityResidual
),
rotational_displacement_force_test_utils::copy_residual_block(
action, layout,
rotational_displacement_force_test_utils::enthalpyResidual),
action, layout, rotational_displacement_force_test_utils::enthalpyResidual
),
rotational_displacement_force_test_utils::copy_residual_block(
action, layout,
rotational_displacement_force_test_utils::massResidual)};
action, layout, rotational_displacement_force_test_utils::massResidual
)
};
for (const mfem::Vector &row : zeroRows) {
CHECK(rotational_displacement_force_test_utils::global_norm(
row, f.mesh->GetComm()) == 0.0);
CHECK(rotational_displacement_force_test_utils::global_norm(row, f.mesh->GetComm()) == 0.0);
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -13,22 +13,27 @@
import mean_field;
import test_helpers;
namespace eos = mean_field::eos;
namespace polytropic_eos_test_utils {
template <typename Function>
double centered_derivative(Function &&function, const double position,
const double step) {
double centered_derivative(
Function &&function,
const double position,
const double step
) {
return (function(position + step) - function(position - step)) / (2.0 * step);
}
template <typename Integrand>
double integrate_cube(const mfem::IntegrationRule &integrationRule,
Integrand &&integrand) {
double integrate_cube(
const mfem::IntegrationRule &integrationRule,
Integrand &&integrand
) {
double integral = 0.0;
for (int pointIndex = 0; pointIndex < integrationRule.GetNPoints();
++pointIndex) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(pointIndex);
for (int pointIndex = 0; pointIndex < integrationRule.GetNPoints(); ++pointIndex) {
const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(pointIndex);
integral += integrationPoint.weight * integrand(integrationPoint);
}
@@ -37,8 +42,10 @@ double integrate_cube(const mfem::IntegrationRule &integrationRule,
}
} // namespace polytropic_eos_test_utils
TEST_CASE("Polytropic EOS Satisfies Its Thermodynamic Identities",
tags::barotrope_eos_unit) {
TEST_CASE(
"Polytropic EOS Satisfies Its Thermodynamic Identities",
tags::barotrope_eos_unit
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 4> densities{1.0e-4, 0.02, 0.37, 2.4};
@@ -47,11 +54,9 @@ TEST_CASE("Polytropic EOS Satisfies Its Thermodynamic Identities",
for (const double polytropicIndex : polytropicIndices) {
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
const mean_field::eos::Polytrope barotrope(polytropicIndex,
polytropicConstant);
const mean_field::eos::Polytrope barotrope(polytropicIndex, polytropicConstant);
const double expectedEnthalpyScale =
(polytropicIndex + 1.0) * polytropicConstant;
const double expectedEnthalpyScale = (polytropicIndex + 1.0) * polytropicConstant;
CHECK(barotrope.polytropic_index() == polytropicIndex);
@@ -62,45 +67,41 @@ TEST_CASE("Polytropic EOS Satisfies Its Thermodynamic Identities",
for (const double density : densities) {
CAPTURE(polytropicIndex, polytropicConstant, density);
const double expectedPressure =
polytropicConstant * std::pow(density, 1.0 + 1.0 / polytropicIndex);
const double expectedPressure = polytropicConstant * std::pow(density, 1.0 + 1.0 / polytropicIndex);
const double expectedEnthalpy =
expectedEnthalpyScale * std::pow(density, 1.0 / polytropicIndex);
const double expectedEnthalpy = expectedEnthalpyScale * std::pow(density, 1.0 / polytropicIndex);
const double pressureFromDensity =
barotrope.pressure_from_density(density);
eos::evaluate<eos::quantity::Pressure>(barotrope, eos::DensityValue{density}).value();
const double enthalpyFromDensity =
barotrope.enthalpy_from_density(density);
eos::evaluate<eos::quantity::SpecificEnthalpy>(barotrope, eos::DensityValue{density}).value();
const double recoveredDensity =
barotrope.density_from_enthalpy(enthalpyFromDensity);
eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{enthalpyFromDensity})
.value();
const double pressureFromEnthalpy =
barotrope.pressure_from_enthalpy(enthalpyFromDensity);
eos::evaluate<eos::quantity::Pressure>(barotrope, eos::SpecificEnthalpyValue{enthalpyFromDensity})
.value();
CHECK_THAT(pressureFromDensity,
Catch::Matchers::WithinRel(expectedPressure, 2.0e-13));
CHECK_THAT(pressureFromDensity, Catch::Matchers::WithinRel(expectedPressure, 2.0e-13));
CHECK_THAT(enthalpyFromDensity,
Catch::Matchers::WithinRel(expectedEnthalpy, 2.0e-13));
CHECK_THAT(enthalpyFromDensity, Catch::Matchers::WithinRel(expectedEnthalpy, 2.0e-13));
CHECK_THAT(recoveredDensity,
Catch::Matchers::WithinRel(density, 5.0e-13));
CHECK_THAT(recoveredDensity, Catch::Matchers::WithinRel(density, 5.0e-13));
CHECK_THAT(pressureFromEnthalpy,
Catch::Matchers::WithinRel(expectedPressure, 5.0e-13));
CHECK_THAT(pressureFromEnthalpy, Catch::Matchers::WithinRel(expectedPressure, 5.0e-13));
/*
* Polytropic identity:
*
* P = rho h / (n + 1).
*/
CHECK_THAT(pressureFromEnthalpy,
Catch::Matchers::WithinRel(density * enthalpyFromDensity /
(polytropicIndex + 1.0),
5.0e-13));
CHECK_THAT(
pressureFromEnthalpy,
Catch::Matchers::WithinRel(density * enthalpyFromDensity / (polytropicIndex + 1.0), 5.0e-13)
);
/*
* Polytropic identity:
@@ -108,11 +109,14 @@ TEST_CASE("Polytropic EOS Satisfies Its Thermodynamic Identities",
* dP / dh = rho.
*
* The implementation should return the same
* value as density_from_enthalpy().
* value as the density from specific enthalpy relation.
*/
CHECK(
barotrope.pressure_derivative_from_enthalpy(enthalpyFromDensity) ==
barotrope.density_from_enthalpy(enthalpyFromDensity));
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{enthalpyFromDensity}
)
.value() == recoveredDensity)
);
/*
* Since
@@ -123,16 +127,22 @@ TEST_CASE("Polytropic EOS Satisfies Its Thermodynamic Identities",
*
* dP / d rho = h / n.
*/
CHECK_THAT(barotrope.pressure_derivative_from_density(density),
Catch::Matchers::WithinRel(
enthalpyFromDensity / polytropicIndex, 5.0e-13));
CHECK_THAT(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(
barotrope, eos::DensityValue{density}
)
.value()),
Catch::Matchers::WithinRel(enthalpyFromDensity / polytropicIndex, 5.0e-13)
);
}
}
}
}
TEST_CASE("Polytropic EOS Pressure Derivatives Match Centered Differences",
tags::barotrope_eos_jacobian) {
TEST_CASE(
"Polytropic EOS Pressure Derivatives Match Centered Differences",
tags::barotrope_eos_jacobian
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 3> positiveValues{0.2, 0.73, 1.8};
@@ -140,55 +150,62 @@ TEST_CASE("Polytropic EOS Pressure Derivatives Match Centered Differences",
constexpr double polytropicConstant = 0.61;
for (const double polytropicIndex : polytropicIndices) {
const mean_field::eos::Polytrope barotrope(polytropicIndex,
polytropicConstant);
const mean_field::eos::Polytrope barotrope(polytropicIndex, polytropicConstant);
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
for (const double enthalpy : positiveValues) {
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
const double numericalDerivative =
polytropic_eos_test_utils::centered_derivative(
const double numericalDerivative = polytropic_eos_test_utils::centered_derivative(
[&barotrope](const double perturbedEnthalpy) {
return barotrope.pressure_from_enthalpy(perturbedEnthalpy);
return eos::evaluate<eos::quantity::Pressure>(
barotrope, eos::SpecificEnthalpyValue{perturbedEnthalpy}
)
.value();
},
enthalpy, step);
enthalpy, step
);
const double analyticDerivative =
barotrope.pressure_derivative_from_enthalpy(enthalpy);
eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{enthalpy}
)
.value();
CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative,
analyticDerivative);
CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative, analyticDerivative);
CHECK_THAT(numericalDerivative,
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
}
for (const double density : positiveValues) {
const double step = 2.0e-6 * std::max(1.0, std::abs(density));
const double numericalDerivative =
polytropic_eos_test_utils::centered_derivative(
const double numericalDerivative = polytropic_eos_test_utils::centered_derivative(
[&barotrope](const double perturbedDensity) {
return barotrope.pressure_from_density(perturbedDensity);
return eos::evaluate<eos::quantity::Pressure>(barotrope, eos::DensityValue{perturbedDensity})
.value();
},
density, step);
density, step
);
const double analyticDerivative =
barotrope.pressure_derivative_from_density(density);
eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(
barotrope, eos::DensityValue{density}
)
.value();
CAPTURE(polytropicIndex, density, step, numericalDerivative,
analyticDerivative);
CAPTURE(polytropicIndex, density, step, numericalDerivative, analyticDerivative);
CHECK_THAT(numericalDerivative,
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
}
}
}
}
TEST_CASE("Polytropic EOS Density Derivative Matches Centered Differences",
tags::barotrope_eos_jacobian) {
TEST_CASE(
"Polytropic EOS Density Derivative Matches Centered Differences",
tags::barotrope_eos_jacobian
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 3> enthalpies{0.2, 0.73, 1.8};
@@ -196,72 +213,100 @@ TEST_CASE("Polytropic EOS Density Derivative Matches Centered Differences",
constexpr double polytropicConstant = 0.61;
for (const double polytropicIndex : polytropicIndices) {
const mean_field::eos::Polytrope barotrope(polytropicIndex,
polytropicConstant);
const mean_field::eos::Polytrope barotrope(polytropicIndex, polytropicConstant);
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
for (const double enthalpy : enthalpies) {
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
const double numericalDerivative =
polytropic_eos_test_utils::centered_derivative(
const double numericalDerivative = polytropic_eos_test_utils::centered_derivative(
[&barotrope](const double perturbedEnthalpy) {
return barotrope.density_from_enthalpy(perturbedEnthalpy);
return eos::evaluate<eos::quantity::Density>(
barotrope, eos::SpecificEnthalpyValue{perturbedEnthalpy}
)
.value();
},
enthalpy, step);
enthalpy, step
);
const double analyticDerivative =
barotrope.density_derivative_from_enthalpy(enthalpy);
eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{enthalpy}
)
.value();
CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative,
analyticDerivative);
CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative, analyticDerivative);
CHECK_THAT(numericalDerivative,
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
}
}
}
}
TEST_CASE("Polytropic EOS Defines Consistent Surface And Exterior Behavior",
tags::barotrope_eos_unit) {
TEST_CASE(
"Polytropic EOS Defines Consistent Surface And Exterior Behavior",
tags::barotrope_eos_unit
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr double polytropicConstant = 0.47;
constexpr double exteriorEnthalpy = -0.3;
for (const double polytropicIndex : polytropicIndices) {
const mean_field::eos::Polytrope barotrope(polytropicIndex,
polytropicConstant);
const mean_field::eos::Polytrope barotrope(polytropicIndex, polytropicConstant);
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
/*
* Exact surface values.
*/
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0);
CHECK(eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{0.0}).value() == 0.0);
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0);
CHECK(eos::evaluate<eos::quantity::Pressure>(barotrope, eos::SpecificEnthalpyValue{0.0}).value() == 0.0);
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0);
CHECK(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{0.0}
)
.value() == 0.0)
);
CHECK(barotrope.pressure_from_density(0.0) == 0.0);
CHECK(eos::evaluate<eos::quantity::Pressure>(barotrope, eos::DensityValue{0.0}).value() == 0.0);
CHECK(barotrope.enthalpy_from_density(0.0) == 0.0);
CHECK(eos::evaluate<eos::quantity::SpecificEnthalpy>(barotrope, eos::DensityValue{0.0}).value() == 0.0);
CHECK(barotrope.pressure_derivative_from_density(0.0) == 0.0);
CHECK(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(
barotrope, eos::DensityValue{0.0}
)
.value() == 0.0)
);
/*
* Positive-part extension into h < 0.
*/
CHECK(barotrope.density_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK(
eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{exteriorEnthalpy})
.value() == 0.0
);
CHECK(barotrope.pressure_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK(
eos::evaluate<eos::quantity::Pressure>(barotrope, eos::SpecificEnthalpyValue{exteriorEnthalpy})
.value() == 0.0
);
CHECK(barotrope.density_derivative_from_enthalpy(exteriorEnthalpy) ==
0.0);
CHECK(
(eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{exteriorEnthalpy}
)
.value() == 0.0)
);
CHECK(barotrope.pressure_derivative_from_enthalpy(exteriorEnthalpy) ==
0.0);
CHECK(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{exteriorEnthalpy}
)
.value() == 0.0)
);
/*
* At h = 0, rho(h) has a nonzero right
@@ -270,20 +315,23 @@ TEST_CASE("Polytropic EOS Defines Consistent Surface And Exterior Behavior",
const double expectedSurfaceDensityDerivative =
polytropicIndex == 1.0 ? 1.0 / barotrope.enthalpy_scale() : 0.0;
CHECK(barotrope.density_derivative_from_enthalpy(0.0) ==
expectedSurfaceDensityDerivative);
CHECK(
(eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{0.0}
)
.value() == expectedSurfaceDensityDerivative)
);
}
}
}
TEST_CASE("Polytropic EOS Rejects Invalid Physical Inputs",
tags::barotrope_eos_unit) {
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.999, 1.0),
std::invalid_argument);
TEST_CASE(
"Polytropic EOS Rejects Invalid Physical Inputs",
tags::barotrope_eos_unit
) {
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.999, 1.0), std::invalid_argument);
CHECK_THROWS_AS(
mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0),
std::invalid_argument);
CHECK_THROWS_AS(mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0), std::invalid_argument);
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument);
@@ -291,37 +339,55 @@ TEST_CASE("Polytropic EOS Rejects Invalid Physical Inputs",
const mean_field::eos::Polytrope barotrope(3.0, 0.75);
CHECK_THROWS_AS(barotrope.pressure_from_density(-0.1), std::domain_error);
CHECK_THROWS_AS(eos::evaluate<eos::quantity::Pressure>(barotrope, eos::DensityValue{-0.1}), std::domain_error);
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-0.1), std::domain_error);
CHECK_THROWS_AS(
eos::evaluate<eos::quantity::SpecificEnthalpy>(barotrope, eos::DensityValue{-0.1}), std::domain_error
);
CHECK_THROWS_AS(barotrope.pressure_derivative_from_density(-0.1),
std::domain_error);
CHECK_THROWS_AS(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(barotrope, eos::DensityValue{-0.1})),
std::domain_error
);
constexpr std::array<double, 3> nonfiniteValues{
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::quiet_NaN()};
std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::quiet_NaN()
};
for (const double nonfiniteValue : nonfiniteValues) {
CAPTURE(nonfiniteValue);
CHECK_THROWS_AS(barotrope.density_from_enthalpy(nonfiniteValue),
std::domain_error);
CHECK_THROWS_AS(
eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{nonfiniteValue}),
std::domain_error
);
CHECK_THROWS_AS(barotrope.pressure_from_enthalpy(nonfiniteValue),
std::domain_error);
CHECK_THROWS_AS(
eos::evaluate<eos::quantity::Pressure>(barotrope, eos::SpecificEnthalpyValue{nonfiniteValue}),
std::domain_error
);
CHECK_THROWS_AS(barotrope.density_derivative_from_enthalpy(nonfiniteValue),
std::domain_error);
CHECK_THROWS_AS(
(eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{nonfiniteValue}
)),
std::domain_error
);
CHECK_THROWS_AS(barotrope.pressure_derivative_from_enthalpy(nonfiniteValue),
std::domain_error);
CHECK_THROWS_AS(
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{nonfiniteValue}
)),
std::domain_error
);
}
}
TEST_CASE("Pressure Force And Pressure Integral Have Distinct Registered Forms",
tags::barotrope_pressure_quadrature_unit) {
TEST_CASE(
"Pressure Force And Pressure Integral Have Distinct Registered Forms",
tags::barotrope_pressure_quadrature_unit
) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
/*
@@ -336,38 +402,34 @@ TEST_CASE("Pressure Force And Pressure Integral Have Distinct Registered Forms",
*
* beyond the registered enthalpy operand.
*/
constexpr int enthalpyOrder =
mean_field::field::Enthalpy::Scalar::familyOrder;
constexpr int enthalpyOrder = mean_field::field::Enthalpy::Scalar::familyOrder;
constexpr int pressureExtraOrder = 3 * enthalpyOrder;
constexpr int geometryWeightOrder = 2;
constexpr mean_field::quadrature::Query pressureIntegralQuery =
EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic,
geometryWeightOrder, std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic, geometryWeightOrder,
std::array<int, 1>{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
constexpr mean_field::quadrature::Query pressureForceQuery =
EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization,
geometryWeightOrder, std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder,
std::array<int, 1>{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
STATIC_CHECK(mean_field::field::Enthalpy::Form::PressureIntegral::dynamicOrderCount == 1);
STATIC_CHECK(mean_field::field::Enthalpy::Form::PressureForce::dynamicOrderCount == 1);
STATIC_CHECK(
mean_field::field::Enthalpy::Form::PressureIntegral::dynamicOrderCount ==
1);
STATIC_CHECK(
mean_field::field::Enthalpy::Form::PressureForce::dynamicOrderCount == 1);
STATIC_CHECK(mean_field::field::Enthalpy::Form::PressureIntegral::policyKey !=
mean_field::field::Enthalpy::Form::PressureForce::policyKey);
mean_field::field::Enthalpy::Form::PressureIntegral::policyKey !=
mean_field::field::Enthalpy::Form::PressureForce::policyKey
);
REQUIRE(pressureIntegralQuery.base_order.has_value());
@@ -394,17 +456,13 @@ TEST_CASE("Pressure Force And Pressure Integral Have Distinct Registered Forms",
*/
CHECK(*pressureForceQuery.base_order == 16);
CHECK(pressureIntegralQuery.term ==
mean_field::quadrature::Term::pressure_integral);
CHECK(pressureIntegralQuery.term == mean_field::quadrature::Term::pressure_integral);
CHECK(pressureForceQuery.term ==
mean_field::quadrature::Term::pressure_force);
CHECK(pressureForceQuery.term == mean_field::quadrature::Term::pressure_force);
CHECK(pressureIntegralQuery.role ==
mean_field::quadrature::QuadratureRole::diagnostic);
CHECK(pressureIntegralQuery.role == mean_field::quadrature::QuadratureRole::diagnostic);
CHECK(pressureForceQuery.role ==
mean_field::quadrature::QuadratureRole::discretization);
CHECK(pressureForceQuery.role == mean_field::quadrature::QuadratureRole::discretization);
CHECK(pressureIntegralQuery.domain == mean_field::utils::DOMAINS::STELLAR);
@@ -415,19 +473,16 @@ TEST_CASE("Pressure Force And Pressure Integral Have Distinct Registered Forms",
* controls.
*/
mean_field::quadrature::RuleSet ruleSet =
mean_field::quadrature::make_rule_set(
mean_field::quadrature::Mode::production);
mean_field::quadrature::make_rule_set(mean_field::quadrature::Mode::production);
ruleSet.pressure_integral.boost = 3;
ruleSet.pressure_force.boost = 5;
const mean_field::quadrature::Policy policy(std::move(ruleSet));
const mean_field::quadrature::Resolution pressureIntegralResolution =
policy.resolve(pressureIntegralQuery);
const mean_field::quadrature::Resolution pressureIntegralResolution = policy.resolve(pressureIntegralQuery);
const mean_field::quadrature::Resolution pressureForceResolution =
policy.resolve(pressureForceQuery);
const mean_field::quadrature::Resolution pressureForceResolution = policy.resolve(pressureForceQuery);
CHECK(pressureIntegralResolution.base_order == 14);
@@ -442,12 +497,13 @@ TEST_CASE("Pressure Force And Pressure Integral Have Distinct Registered Forms",
CHECK(pressureForceResolution.order == 21);
}
TEST_CASE("Pressure Quadrature Exactly Integrates An N Three Polynomial",
tags::barotrope_pressure_quadrature_accuracy) {
TEST_CASE(
"Pressure Quadrature Exactly Integrates An N Three Polynomial",
tags::barotrope_pressure_quadrature_accuracy
) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
constexpr int enthalpyOrder =
mean_field::field::Enthalpy::Scalar::familyOrder;
constexpr int enthalpyOrder = mean_field::field::Enthalpy::Scalar::familyOrder;
constexpr int pressureExtraOrder = 3 * enthalpyOrder;
@@ -461,24 +517,20 @@ TEST_CASE("Pressure Quadrature Exactly Integrates An N Three Polynomial",
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
constexpr mean_field::quadrature::Query pressureIntegralQuery =
EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic, 0,
std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::affine);
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic, 0, std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::affine
);
constexpr mean_field::quadrature::Query pressureForceQuery =
EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization, 0,
std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::affine);
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization, 0, std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::affine
);
const mean_field::quadrature::RuleFactory ruleFactory{
mean_field::quadrature::Policy(mean_field::quadrature::make_rule_set(
mean_field::quadrature::Mode::production))};
mean_field::quadrature::Policy(mean_field::quadrature::make_rule_set(mean_field::quadrature::Mode::production))
};
const mean_field::quadrature::MfemRule pressureIntegralRule =
ruleFactory.get(pressureIntegralQuery, mfem::Geometry::CUBE);
@@ -496,17 +548,15 @@ TEST_CASE("Pressure Quadrature Exactly Integrates An N Three Polynomial",
*
* P = x^12 y^12 z^12 / 4.
*/
const double numericalPressureIntegral =
polytropic_eos_test_utils::integrate_cube(
*pressureIntegralRule.integration_rule,
[&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct =
integrationPoint.x * integrationPoint.y * integrationPoint.z;
const double numericalPressureIntegral = polytropic_eos_test_utils::integrate_cube(
*pressureIntegralRule.integration_rule, [&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct = integrationPoint.x * integrationPoint.y * integrationPoint.z;
const double enthalpy = std::pow(coordinateProduct, 3.0);
return barotrope.pressure_from_enthalpy(enthalpy);
});
return eos::evaluate<eos::quantity::Pressure>(barotrope, eos::SpecificEnthalpyValue{enthalpy}).value();
}
);
const double analyticPressureIntegral = 0.25 / std::pow(13.0, 3.0);
@@ -521,38 +571,33 @@ TEST_CASE("Pressure Quadrature Exactly Integrates An N Three Polynomial",
* -P div(w)
* = -x^14 y^14 z^14 / 4.
*/
const double numericalPressureForceIntegral =
polytropic_eos_test_utils::integrate_cube(
*pressureForceRule.integration_rule,
[&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct =
integrationPoint.x * integrationPoint.y * integrationPoint.z;
const double numericalPressureForceIntegral = polytropic_eos_test_utils::integrate_cube(
*pressureForceRule.integration_rule, [&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct = integrationPoint.x * integrationPoint.y * integrationPoint.z;
const double enthalpy = std::pow(coordinateProduct, 3.0);
const double pressure = barotrope.pressure_from_enthalpy(enthalpy);
const double pressure =
eos::evaluate<eos::quantity::Pressure>(barotrope, eos::SpecificEnthalpyValue{enthalpy}).value();
const double testDivergence =
integrationPoint.x * integrationPoint.x * integrationPoint.y *
const double testDivergence = integrationPoint.x * integrationPoint.x * integrationPoint.y *
integrationPoint.y * integrationPoint.z * integrationPoint.z;
return -pressure * testDivergence;
});
}
);
const double analyticPressureForceIntegral = -0.25 / std::pow(15.0, 3.0);
INFO("Pressure-integral quadrature order = "
<< pressureIntegralRule.resolution.order);
INFO("Pressure-integral quadrature order = " << pressureIntegralRule.resolution.order);
INFO("Pressure-force quadrature order = "
<< pressureForceRule.resolution.order);
INFO("Pressure-force quadrature order = " << pressureForceRule.resolution.order);
INFO("Numerical pressure integral = " << numericalPressureIntegral);
INFO("Analytic pressure integral = " << analyticPressureIntegral);
INFO(
"Numerical pressure-force integral = " << numericalPressureForceIntegral);
INFO("Numerical pressure-force integral = " << numericalPressureForceIntegral);
INFO("Analytic pressure-force integral = " << analyticPressureForceIntegral);
@@ -564,10 +609,7 @@ TEST_CASE("Pressure Quadrature Exactly Integrates An N Three Polynomial",
CHECK(pressureForceRule.resolution.order == 14);
CHECK_THAT(numericalPressureIntegral,
Catch::Matchers::WithinAbs(analyticPressureIntegral, 5.0e-14));
CHECK_THAT(numericalPressureIntegral, Catch::Matchers::WithinAbs(analyticPressureIntegral, 5.0e-14));
CHECK_THAT(
numericalPressureForceIntegral,
Catch::Matchers::WithinAbs(analyticPressureForceIntegral, 5.0e-14));
CHECK_THAT(numericalPressureForceIntegral, Catch::Matchers::WithinAbs(analyticPressureForceIntegral, 5.0e-14));
}

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

@@ -16,18 +16,25 @@ template <std::size_t N> struct Tag {
std::array<char, N> chars{};
// 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
constexpr operator const char *() const { return chars.data(); }
constexpr operator const char *() const {
return chars.data();
}
// ReSharper disable once CppNonExplicitConversionOperator
constexpr operator Catch::StringRef() const {
return Catch::StringRef(chars.data(), N - 1);
}
template <std::size_t M>
consteval Tag<N + M - 1> operator&(const Tag<M> &other) const {
template <std::size_t M> consteval Tag<N + M - 1> operator&(const Tag<M> &other) const {
std::array<char, N + M - 1> res{};
std::ranges::copy(chars.begin(), chars.end() - 1, res.begin());
std::ranges::copy(other.chars, res.begin() + (N - 1));
@@ -44,8 +51,13 @@ template <std::size_t N> consteval auto make_tag(const char (&str)[N]) {
return Tag<N + 2>{res};
}
template <std::size_t N, std::size_t M>
consteval auto sub_tag(const Tag<N> &parent, const char (&str)[M]) {
template <
std::size_t N,
std::size_t M>
consteval auto sub_tag(
const Tag<N> &parent,
const char (&str)[M]
) {
return parent & make_tag(str);
}
@@ -78,56 +90,53 @@ mean_field::utils::Args setup_args() {
export namespace gravity_prepared_test_utils {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
template <typename FieldT>
inline mean_field::field::FieldDofMap
make_field_map(const mean_field::fem::FEM &f) {
template <typename FieldT> inline mean_field::field::FieldDofMap make_field_map(const mean_field::fem::FEM &f) {
if constexpr (std::same_as<FieldT, mean_field::field::Density>) {
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(
*f.densityFes);
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(*f.densityFes);
} else if constexpr (std::same_as<FieldT, mean_field::field::Displacement>) {
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(
*f.displacementFes);
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(*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);
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) {
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) {
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);
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) {
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) {
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(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);
mfem::VectorFunctionCoefficient coefficient(f.mesh->Dimension(), displacement_function);
displacement.ProjectCoefficient(coefficient);
mfem::Vector displacement_true;
@@ -135,18 +144,19 @@ inline mfem::Vector make_displacement(const mean_field::fem::FEM &f,
return displacement_true;
}
inline mfem::Vector make_domain_supported_density(const mean_field::fem::FEM &f,
const bool stellar) {
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;
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);
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;
@@ -162,12 +172,13 @@ inline mfem::Vector make_domain_supported_density(const mean_field::fem::FEM &f,
return density_true;
}
inline mfem::Vector linear_combination(const mfem::Vector &first,
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.");
const double second_scale
) {
MFEM_VERIFY(first.Size() == second.Size(), "Cannot combine vectors with different sizes.");
mfem::Vector combination(first);
combination *= first_scale;
@@ -175,18 +186,22 @@ inline mfem::Vector linear_combination(const mfem::Vector &first,
return combination;
}
inline double global_norm(const mfem::Vector &vector, MPI_Comm communicator) {
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);
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.");
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;
@@ -194,24 +209,25 @@ inline double global_dot(const mfem::Vector &first, const mfem::Vector &second,
return global_dot;
}
inline double relative_error(const mfem::Vector &computed,
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.");
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());
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());
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
@@ -222,47 +238,46 @@ inline mean_field::mapping::DomainMapper make_domain_mapper() {
const mean_field::utils::Args args = test_utils::setup_args();
return mean_field::mapping::DomainMapper(
args.domain_mapper_options,
std::make_unique<const mean_field::mapping::compactification::
KelvinCompactification>(args.kelvin_options));
std::make_unique<const mean_field::mapping::compactification::KelvinCompactification>(args.kelvin_options)
);
}
inline constexpr int vacuum_material_attribute =
DomainSchema::template material_attribute<
mean_field::utils::domain::Vacuum>();
DomainSchema::template material_attribute<mean_field::utils::domain::Vacuum>();
template <typename FieldT>
inline mean_field::field::FieldDofMap
make_map(const mfem::ParFiniteElementSpace &finiteElementSpace) {
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(
finiteElementSpace);
inline mean_field::field::FieldDofMap make_map(const mfem::ParFiniteElementSpace &finiteElementSpace) {
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(finiteElementSpace);
}
template <typename FieldT>
inline mfem::Vector make_deterministic_supported_vector(
const mfem::ParFiniteElementSpace &finiteElementSpace, const double phase) {
const mean_field::field::FieldDofMap map =
make_map<FieldT>(finiteElementSpace);
const mfem::Vector full =
gravity_prepared_test_utils::make_deterministic_vector(map.full_size(),
phase);
const mfem::ParFiniteElementSpace &finiteElementSpace,
const double phase
) {
const mean_field::field::FieldDofMap map = make_map<FieldT>(finiteElementSpace);
const mfem::Vector full = gravity_prepared_test_utils::make_deterministic_vector(map.full_size(), phase);
return map.gather(full);
}
inline mfem::Vector make_supported_displacement(const mean_field::fem::FEM &f,
const double phase) {
const mean_field::field::FieldDofMap map =
make_map<mean_field::field::Displacement>(*f.displacementFes);
inline mfem::Vector make_supported_displacement(
const mean_field::fem::FEM &f,
const double 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(
const mean_field::fem::FEM &f,
const mean_field::physics::RigidRotation &rotation,
const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential,
const mfem::Vector &displacement, const double bernoulliConstant,
mfem::Vector &residual) {
const mean_field::field::FieldDofMap enthalpyMap =
make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
const mfem::Vector &enthalpy,
const mfem::Vector &gravityPotential,
const mfem::Vector &displacement,
const double bernoulliConstant,
mfem::Vector &residual
) {
const mean_field::field::FieldDofMap enthalpyMap = make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
const mean_field::field::FieldDofMap gravityPotentialMap =
make_map<mean_field::field::Gravity>(*f.gravityPotentialFes);
const mean_field::field::FieldDofMap displacementMap =
@@ -278,8 +293,9 @@ inline void apply_hydrostatic_reference(
displacementMap.scatter(displacement, displacementTrue);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpyTrue, gravityPotentialTrue,
displacementTrue, bernoulliConstant, residualTrue);
f, *f.domainMapperStateless, rotation, enthalpyTrue, gravityPotentialTrue, displacementTrue,
bernoulliConstant, residualTrue
);
residual.SetSize(enthalpyMap.reduced_size());
enthalpyMap.gather(residualTrue, residual);
@@ -315,21 +331,15 @@ inline constexpr auto hydro = sub_tag(physics, "hydro");
inline constexpr auto jacobian = sub_tag(integration & physics, "jacobian");
inline constexpr auto residuals = sub_tag(integration & physics, "residuals");
inline constexpr auto volume = sub_tag(mesh & geometry, "volume");
inline constexpr auto quadrature =
sub_tag(mesh & geometry & solver, "quadrature");
inline constexpr auto quadrature = sub_tag(mesh & geometry & solver, "quadrature");
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 =
sub_tag(mesh & convergence, "h_refinement");
inline constexpr auto p_refinement =
sub_tag(mesh & convergence, "p_refinement");
inline constexpr auto h_refinement = sub_tag(mesh & convergence, "h_refinement");
inline constexpr auto p_refinement = sub_tag(mesh & convergence, "p_refinement");
inline constexpr auto analytic_comparison =
sub_tag(solver & physics & residuals, "analytic_comparison");
inline constexpr auto self_consistency =
sub_tag(solver & physics, "self_consistency");
inline constexpr auto analytic_comparison = sub_tag(solver & physics & residuals, "analytic_comparison");
inline constexpr auto self_consistency = sub_tag(solver & physics, "self_consistency");
inline constexpr auto centrifugal = sub_tag(solver & physics, "centrifugal");
inline constexpr auto advection = sub_tag(solver & physics, "advection");
@@ -337,17 +347,13 @@ inline constexpr auto coriolis = sub_tag(solver & physics, "coriolis");
inline constexpr auto gravity = sub_tag(solver & physics, "gravity");
inline constexpr auto enthalpy = sub_tag(solver & physics, "enthalpy");
inline constexpr auto barotrope = sub_tag(physics, "barotrope");
inline constexpr auto mass_continuity =
sub_tag(solver & physics, "mass_continuity");
inline constexpr auto pressure_gradient =
sub_tag(solver & physics, "pressure_gradient");
inline constexpr auto mass_continuity = sub_tag(solver & physics, "mass_continuity");
inline constexpr auto pressure_gradient = sub_tag(solver & physics, "pressure_gradient");
inline constexpr auto viscosity = sub_tag(solver & physics, "viscosity");
inline constexpr auto compactification =
sub_tag(mesh & mapping, "compactification");
inline constexpr auto compactification = sub_tag(mesh & mapping, "compactification");
inline constexpr auto kelvin = sub_tag(compactification, "kelvin");
inline constexpr auto mapping_evaluator =
mapping & make_tag("grid_function_evaluator");
inline constexpr auto mapping_evaluator = mapping & make_tag("grid_function_evaluator");
inline constexpr auto mapping_evaluator_unit = mapping_evaluator & unit;
inline constexpr auto prepared = sub_tag(solver & physics, "prepared");
@@ -366,103 +372,110 @@ inline constexpr auto gravity_context = gravity & make_tag("context");
inline constexpr auto gravity_kernel = gravity & kernels;
inline constexpr auto gravity_accuracy = gravity & accuracy;
inline constexpr auto gravity_operator_unit = gravity_operator & unit;
inline constexpr auto gravity_operator_integration =
gravity_operator & integration;
inline constexpr auto gravity_operator_convergence =
gravity_operator & integration & make_tag("convergence");
inline constexpr auto gravity_analytic =
gravity & integration & make_tag("analytic_comparison");
inline constexpr auto gravity_consistency =
gravity & integration & make_tag("self_consistency");
inline constexpr auto gravity_prepared_jacobian =
gravity_prepared & integration & make_tag("jacobian");
inline constexpr auto gravity_operator_integration = gravity_operator & integration;
inline constexpr auto gravity_operator_convergence = gravity_operator & integration & make_tag("convergence");
inline constexpr auto gravity_analytic = gravity & integration & make_tag("analytic_comparison");
inline constexpr auto gravity_consistency = gravity & integration & make_tag("self_consistency");
inline constexpr auto gravity_prepared_jacobian = gravity_prepared & integration & make_tag("jacobian");
inline constexpr auto gravity_prepared_unit = gravity_prepared & unit;
inline constexpr auto gravity_prepared_jacobian_accuracy =
gravity_prepared_jacobian & accuracy;
inline constexpr auto gravity_prepared_jacobian_accuracy = gravity_prepared_jacobian & accuracy;
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_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_consistency_accuracy = gravity_consistency & accuracy;
inline constexpr auto gravity_integrator_unit = gravity & integrator & unit;
inline constexpr auto barotrope_prepared =
barotrope & solver & make_tag("prepared");
inline constexpr auto barotrope_prepared = barotrope & solver & make_tag("prepared");
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_pressure_quadrature =
barotrope & mesh & geometry & solver & make_tag("pressure") &
inline constexpr auto barotrope_eos_jacobian = barotrope_eos_unit & integration & make_tag("jacobian");
inline constexpr auto polytropic_eos_characterization =
barotrope & unit & make_tag("eos") & make_tag("characterization");
inline constexpr auto polytropic_eos_relation_contract =
barotrope & unit & make_tag("eos") & make_tag("relation_contract");
inline constexpr auto polytropic_eos_compatibility = barotrope & unit & make_tag("eos") & make_tag("compatibility");
inline constexpr auto equation_of_state = physics & make_tag("eos");
inline constexpr auto equation_of_state_type_system = equation_of_state & unit & make_tag("type_system");
inline constexpr auto equation_of_state_quantity_types = equation_of_state_type_system & make_tag("quantity_types");
inline constexpr auto equation_of_state_relation_contract =
equation_of_state_type_system & make_tag("relation_contract");
inline constexpr auto equation_of_state_runtime_view = equation_of_state & unit & make_tag("runtime_view");
inline constexpr auto equation_of_state_runtime_contract =
equation_of_state_runtime_view & make_tag("relation_contract");
inline constexpr auto equation_of_state_runtime_compatibility =
equation_of_state_runtime_view & make_tag("compatibility");
inline constexpr auto equation_of_state_consumer_contract =
equation_of_state & unit & make_tag("consumer_contract");
inline constexpr auto barotropic_closure_equation_of_state_contract =
equation_of_state_consumer_contract & make_tag("barotropic_closure");
inline constexpr auto pressure_force_equation_of_state_contract =
equation_of_state_consumer_contract & make_tag("pressure_force");
inline constexpr auto structure_seed_equation_of_state_contract =
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_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;
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
// so inherited [physics]/[solver] tags appear only once.
inline constexpr auto barotrope_hydrostatic =
barotrope & solver & make_tag("hydro");
inline constexpr auto barotrope_hydrostatic_context =
barotrope_hydrostatic & make_tag("context");
inline constexpr auto barotrope_hydrostatic_prepared =
barotrope_hydrostatic & make_tag("prepared");
inline constexpr auto barotrope_hydrostatic = barotrope & solver & make_tag("hydro");
inline constexpr auto barotrope_hydrostatic_context = barotrope_hydrostatic & make_tag("context");
inline constexpr auto barotrope_hydrostatic_prepared = barotrope_hydrostatic & make_tag("prepared");
inline constexpr auto barotrope_hydrostatic_prepared_residual =
barotrope_hydrostatic_prepared & integration & make_tag("residual");
inline constexpr auto barotrope_hydrostatic_prepared_jacobian =
barotrope_hydrostatic_prepared & integration & make_tag("jacobian");
inline constexpr auto barotrope_hydrostatic_prepared_analytic =
barotrope_hydrostatic_prepared & integration &
make_tag("analytic_comparison");
barotrope_hydrostatic_prepared & integration & make_tag("analytic_comparison");
inline constexpr auto barotrope_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_prepared =
barotrope_mass_normalization & make_tag("prepared");
inline constexpr auto barotrope_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_prepared = barotrope_mass_normalization & make_tag("prepared");
inline constexpr auto barotrope_mass_normalization_jacobian =
barotrope_mass_normalization_prepared & integration & make_tag("jacobian");
inline constexpr auto barotrope_mass_normalization_analytic =
barotrope_mass_normalization_prepared & integration &
make_tag("analytic_comparison");
barotrope_mass_normalization_prepared & integration & make_tag("analytic_comparison");
inline constexpr auto rotation_prepared = centrifugal & make_tag("prepared");
inline constexpr auto rotation_context = centrifugal & make_tag("context");
inline constexpr auto rotation_analytic =
centrifugal & integration & make_tag("analytic_comparison");
inline constexpr auto rotation_analytic = centrifugal & integration & make_tag("analytic_comparison");
inline constexpr auto rotation_context_unit = rotation_context & 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_accuracy =
rotation_prepared_jacobian & accuracy;
inline constexpr auto rotation_kernel_accuracy =
centrifugal & kernels & accuracy;
inline constexpr auto rotation_prepared_jacobian = rotation_prepared & integration & make_tag("jacobian");
inline constexpr auto rotation_prepared_jacobian_accuracy = rotation_prepared_jacobian & accuracy;
inline constexpr auto rotation_kernel_accuracy = centrifugal & kernels & accuracy;
inline constexpr auto rotation_integrator_unit = centrifugal & integrator & unit;
inline constexpr auto rotation_integrator_integration =
centrifugal & integrator & integration;
inline constexpr auto rotation_integrator_integration = centrifugal & integrator & integration;
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;
inline constexpr auto rotation_analytic_accuracy_geometry = rotation_analytic_accuracy & geometry;
} // namespace tags

View File

@@ -3,6 +3,7 @@
#include <catch2/catch_test_case_info.hpp>
#include <catch2/reporters/catch_reporter_registrars.hpp>
#include <catch2/reporters/catch_reporter_streaming_base.hpp>
#include <chrono>
#include <fstream>
#include <iomanip>
#include <iostream>
@@ -223,6 +224,7 @@ class CheckReporter : public Catch::StreamingReporterBase {
bool passed;
std::size_t assertionsPassed;
std::size_t assertionsFailed;
double durationSeconds;
std::vector<std::string> failureMessages;
std::vector<std::string> infoMessages;
};
@@ -231,6 +233,7 @@ class CheckReporter : public Catch::StreamingReporterBase {
std::vector<std::string> m_currentInfos;
std::unordered_set<unsigned int> m_currentInfoSequences;
std::vector<TestCaseData> m_testRunData;
std::chrono::time_point<std::chrono::steady_clock> m_testStartTime;
void captureInfoMessages(Catch::AssertionStats const &assertionStats) {
for (auto const &message : assertionStats.infoMessages) {
@@ -253,9 +256,8 @@ public:
}
static std::string getDescription() {
return "Console reporter with wrapping, tags, and collapsible HTML "
"export "
"with ANSI color rendering.";
return "Console reporter with wrapping, tags, live test progress, and collapsible HTML "
"export with ANSI color rendering.";
}
void testRunStarting(Catch::TestRunInfo const &_testRunInfo) override {
@@ -263,8 +265,20 @@ public:
std::cout << '\n';
std::cout << std::left << std::setw(85) << "Test Case Name"
<< "Status " << std::right << std::setw(8) << "Passed" << std::setw(8) << "Failed" << '\n';
std::cout << std::string(121, '-') << '\n';
<< "Status " << std::right << std::setw(8) << "Passed" << std::setw(8) << "Failed" << std::setw(12)
<< "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 {
@@ -300,14 +314,20 @@ public:
void testCaseEnded(Catch::TestCaseStats const &stats) override {
StreamingReporterBase::testCaseEnded(stats);
auto endTime = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed = endTime - m_testStartTime;
double duration_s = elapsed.count();
bool passed = stats.totals.assertions.allPassed();
std::string mark = passed ? "\033[32m✓\033[0m" : "\033[31m✗\033[0m";
std::string name = stats.testInfo->name;
auto wrappedName = wrapText(name, 83);
std::cout << std::left << std::setw(85) << wrappedName[0] << mark << " " << std::right << std::setw(8)
<< stats.totals.assertions.passed << std::setw(8) << stats.totals.assertions.failed << '\n';
// 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) {
std::cout << " \033[90m↳ \033[0m" // Dim indent arrow
@@ -327,12 +347,12 @@ public:
for (auto const &failure : m_currentFailures) {
std::cout << failure << '\n';
}
std::cout << std::string(121, '-') << '\n';
std::cout << std::string(133, '-') << '\n';
}
m_testRunData.push_back(
{name, tagsStr, passed, stats.totals.assertions.passed, stats.totals.assertions.failed, m_currentFailures,
m_currentInfos}
{name, tagsStr, passed, stats.totals.assertions.passed, stats.totals.assertions.failed, duration_s,
m_currentFailures, m_currentInfos}
);
m_currentFailures.clear();
@@ -343,7 +363,7 @@ public:
void testRunEnded(Catch::TestRunStats const &_testRunStats) override {
StreamingReporterBase::testRunEnded(_testRunStats);
std::cout << std::string(121, '=') << '\n';
std::cout << std::string(133, '=') << '\n';
auto const &tc = _testRunStats.totals.testCases;
auto const &as = _testRunStats.totals.assertions;
@@ -444,7 +464,9 @@ private:
html << " </div>\n";
html << " <div class='stats'>\n";
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";

File diff suppressed because it is too large Load Diff