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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -401,8 +401,14 @@ namespace mean_field::operators {
const double density = elementBaseDensity * densityShape; const double density = elementBaseDensity * densityShape;
const double enthalpy = elementBaseEnthalpy * enthalpyShape; const double enthalpy = elementBaseEnthalpy * enthalpyShape;
const double quadratureWeight = mappingContext.quadrature.weight; const double quadratureWeight = mappingContext.quadrature.weight;
const double eosDensity = m_equationOfState.density_from_enthalpy(enthalpy); const eos::SpecificEnthalpyValue specificEnthalpy{enthalpy};
const double enthalpyDerivative = m_equationOfState.density_derivative_from_enthalpy(enthalpy); const double eosDensity =
eos::evaluate<eos::quantity::Density>(m_equationOfState, specificEnthalpy).value();
const double enthalpyDerivative =
eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
m_equationOfState, specificEnthalpy
)
.value();
MFEM_VERIFY( MFEM_VERIFY(
std::isfinite(quadratureWeight) && quadratureWeight > 0.0 && std::isfinite(eosDensity) && std::isfinite(quadratureWeight) && quadratureWeight > 0.0 && std::isfinite(eosDensity) &&

View File

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

View File

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

View File

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

View File

@@ -619,9 +619,15 @@ namespace mean_field::operators {
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const double enthalpy = quadratureEnthalpy(quadraturePoint); const double enthalpy = quadratureEnthalpy(quadraturePoint);
const double pressure = m_equationOfState.pressure_from_enthalpy(enthalpy); const eos::SpecificEnthalpyValue specificEnthalpy{enthalpy};
const double pressure =
eos::evaluate<eos::quantity::Pressure>(m_equationOfState, specificEnthalpy).value();
const double pressureDerivative = m_equationOfState.pressure_derivative_from_enthalpy(enthalpy); const double pressureDerivative =
eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
m_equationOfState, specificEnthalpy
)
.value();
const double quadratureWeight = data.quadratureWeights(quadraturePoint); const double quadratureWeight = data.quadratureWeights(quadraturePoint);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -94,6 +94,65 @@ namespace field_dof_map_test_utils {
domain::RelationList<>>; domain::RelationList<>>;
} // namespace field_dof_map_test_utils } // namespace field_dof_map_test_utils
TEST_CASE(
"Field Boundary DOF Map Selects The Stellar Surface In Reduced Field Ordering",
tags::surface_boundary_dof_topology
) {
namespace domain = mean_field::utils::domain;
namespace field = mean_field::field;
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const field::FieldDofMap enthalpyMap =
field::make_field_dof_map<field::Enthalpy, field_dof_map_test_utils::Schema>(*f.enthalpyFes);
const field::FieldBoundaryDofMap stellarSurface =
field::make_field_boundary_dof_map<field::Enthalpy, domain::StellarSurface, field_dof_map_test_utils::Schema>(
*f.enthalpyFes, enthalpyMap
);
CHECK(stellarSurface.field_size() == enthalpyMap.reduced_size());
CHECK(field_dof_map_test_utils::global_sum(stellarSurface.size()) > 0);
CHECK(
field_dof_map_test_utils::global_sum(stellarSurface.size()) <
field_dof_map_test_utils::global_sum(enthalpyMap.reduced_size())
);
for (const int reducedDof : stellarSurface.reduced_dofs()) {
CAPTURE(reducedDof);
CHECK(stellarSurface.contains(reducedDof));
CHECK(enthalpyMap.contains_true_dof(enthalpyMap.true_dof(reducedDof)));
}
}
TEST_CASE(
"Field Point DOF Map Selects One Vector Vertex At The Computational Origin",
tags::translational_centering_topology
) {
namespace field = mean_field::field;
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const field::FieldDofMap displacementMap =
field::make_field_dof_map<field::Displacement, field_dof_map_test_utils::Schema>(*f.displacementFes);
mfem::Vector origin(f.mesh->SpaceDimension());
origin = 0.0;
const field::FieldPointDofMap centerRows =
field::make_field_point_dof_map<field::Displacement>(*f.displacementFes, displacementMap, origin, 1.0e-12);
CHECK(centerRows.field_size() == displacementMap.reduced_size());
CHECK(field_dof_map_test_utils::global_sum(centerRows.size()) == f.mesh->SpaceDimension());
for (const int reducedDof : centerRows.reduced_dofs()) {
CAPTURE(reducedDof);
CHECK(centerRows.contains(reducedDof));
CHECK(displacementMap.contains_true_dof(displacementMap.true_dof(reducedDof)));
}
}
TEST_CASE( TEST_CASE(
"Field DOF Map Preserves Canonical Bidirectional Indexing", "Field DOF Map Preserves Canonical Bidirectional Indexing",
tags::field_dof_unit tags::field_dof_unit
@@ -523,9 +582,7 @@ TEST_CASE(
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Displacement>); STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Displacement>);
STATIC_REQUIRE_FALSE( STATIC_REQUIRE_FALSE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::BarotropicConstant>);
field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::BarotropicConstant>
);
CHECK(true); CHECK(true);
} }
@@ -751,8 +808,7 @@ TEST_CASE(
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2); auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace = auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr); REQUIRE(finiteElementSpace != nullptr);
@@ -797,8 +853,7 @@ TEST_CASE(
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Enthalpy>::make_fec<field::Enthalpy::Scalar>(2); auto fec = field::Field<field::Enthalpy>::make_fec<field::Enthalpy::Scalar>(2);
auto finiteElementSpace = auto finiteElementSpace = field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr); REQUIRE(finiteElementSpace != nullptr);
@@ -850,8 +905,7 @@ TEST_CASE(
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2); auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace = auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr); REQUIRE(finiteElementSpace != nullptr);
@@ -901,8 +955,7 @@ TEST_CASE(
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Displacement>::make_fec<field::Displacement::Vector>(2); auto fec = field::Field<field::Displacement>::make_fec<field::Displacement::Vector>(2);
auto finiteElementSpace = auto finiteElementSpace = field::Field<field::Displacement>::make_fespace<field::Displacement::Vector>(mesh, *fec);
field::Field<field::Displacement>::make_fespace<field::Displacement::Vector>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr); REQUIRE(finiteElementSpace != nullptr);
@@ -940,12 +993,10 @@ TEST_CASE(
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2); auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace = auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
auto otherFec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2); auto otherFec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto otherFiniteElementSpace = auto otherFiniteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *otherFec);
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *otherFec);
REQUIRE(finiteElementSpace != nullptr); REQUIRE(finiteElementSpace != nullptr);
REQUIRE(otherFiniteElementSpace != nullptr); REQUIRE(otherFiniteElementSpace != nullptr);
@@ -959,8 +1010,7 @@ TEST_CASE(
const mfem::Array<int> empty; const mfem::Array<int> empty;
CHECK_THROWS_AS( CHECK_THROWS_AS(
(field::FieldDofGridFunctionAdapter( (field::FieldDofGridFunctionAdapter(
field::FieldDofMap(finiteElementSpace->GetTrueVSize() + 1, empty), field::FieldDofMap(finiteElementSpace->GetTrueVSize() + 1, empty), *finiteElementSpace
*finiteElementSpace
)), )),
std::invalid_argument std::invalid_argument
); );

View File

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

View File

@@ -129,8 +129,7 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual; element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator( integrators::GravityMomentumIntegrator integrator(
domain_mapper, displacement, compactification_coordinate, domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
integrators::GravityForceJacobianMode::field_coupled
); );
const int maximum_order = std::max( const int maximum_order = std::max(
@@ -300,9 +299,7 @@ TEST_CASE(
compactification_coordinate = 0.0; compactification_coordinate = 0.0;
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper(); mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
mapping::GridFunctionMappingEvaluator mapping_evaluator( mapping::GridFunctionMappingEvaluator mapping_evaluator(domain_mapper, displacement, compactification_coordinate);
domain_mapper, displacement, compactification_coordinate
);
auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); }; auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); };
@@ -381,8 +378,7 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual; element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator( integrators::GravityMomentumIntegrator integrator(
domain_mapper, displacement, compactification_coordinate, domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
integrators::GravityForceJacobianMode::field_coupled
); );
const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8); const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8);
@@ -473,9 +469,7 @@ TEST_CASE(
mfem::GridFunction compactification_coordinate(&compactification_fes); mfem::GridFunction compactification_coordinate(&compactification_fes);
compactification_coordinate = 0.0; compactification_coordinate = 0.0;
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper(); mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
mapping::GridFunctionMappingEvaluator mapping_evaluator( mapping::GridFunctionMappingEvaluator mapping_evaluator(domain_mapper, displacement, compactification_coordinate);
domain_mapper, displacement, compactification_coordinate
);
auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) { auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) {
gravity.SetSize(3); gravity.SetSize(3);
@@ -553,8 +547,7 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual; element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator( integrators::GravityMomentumIntegrator integrator(
domain_mapper, displacement, compactification_coordinate, domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
integrators::GravityForceJacobianMode::field_coupled
); );
const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8); const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8);

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,59 +24,76 @@ struct Maps final {
explicit Maps(const mean_field::fem::FEM &f) explicit Maps(const mean_field::fem::FEM &f)
: density( : density(
mean_field::field::make_field_dof_map<mean_field::field::Density, mean_field::field::make_field_dof_map<
DomainSchema>(*f.densityFes)), mean_field::field::Density,
displacement(mean_field::field::make_field_dof_map< DomainSchema>(*f.densityFes)
mean_field::field::Displacement, DomainSchema>( ),
*f.displacementFes)), displacement(
mean_field::field::make_field_dof_map<
mean_field::field::Displacement,
DomainSchema>(*f.displacementFes)
),
gravityFlux( gravityFlux(
mean_field::field::make_field_dof_map<mean_field::field::Gravity, mean_field::field::make_field_dof_map<
DomainSchema>( mean_field::field::Gravity,
*f.gravityFluxFes)), DomainSchema>(*f.gravityFluxFes)
),
gravityPotential( gravityPotential(
mean_field::field::make_field_dof_map<mean_field::field::Gravity, mean_field::field::make_field_dof_map<
DomainSchema>( mean_field::field::Gravity,
*f.gravityPotentialFes)), DomainSchema>(*f.gravityPotentialFes)
enthalpy(mean_field::field::make_field_dof_map< ),
mean_field::field::Enthalpy, DomainSchema>(*f.enthalpyFes)) {} enthalpy(
mean_field::field::make_field_dof_map<
mean_field::field::Enthalpy,
DomainSchema>(*f.enthalpyFes)
) {
}
}; };
[[nodiscard]] [[nodiscard]]
mfem::Vector make_positive_enthalpy_true(const mean_field::fem::FEM &f, mfem::Vector make_positive_enthalpy_true(
const double phase) { const mean_field::fem::FEM &f,
const double phase
) {
mfem::Vector enthalpy(f.enthalpyFes->GetTrueVSize()); mfem::Vector enthalpy(f.enthalpyFes->GetTrueVSize());
for (int index = 0; index < enthalpy.Size(); ++index) { for (int index = 0; index < enthalpy.Size(); ++index) {
const double position = static_cast<double>(index + 1); const double position = static_cast<double>(index + 1);
enthalpy(index) = 0.93 + 0.09 * std::sin(0.23 * position + phase) + enthalpy(index) =
0.04 * std::cos(0.17 * position - 0.5 * phase); 0.93 + 0.09 * std::sin(0.23 * position + phase) + 0.04 * std::cos(0.17 * position - 0.5 * phase);
} }
return enthalpy; return enthalpy;
} }
[[nodiscard]] [[nodiscard]]
mfem::Vector make_enthalpy_direction_true(const mean_field::fem::FEM &f, mfem::Vector make_enthalpy_direction_true(
const double phase) { const mean_field::fem::FEM &f,
const double phase
) {
mfem::Vector direction(f.enthalpyFes->GetTrueVSize()); mfem::Vector direction(f.enthalpyFes->GetTrueVSize());
for (int index = 0; index < direction.Size(); ++index) { for (int index = 0; index < direction.Size(); ++index) {
const double position = static_cast<double>(index + 1); const double position = static_cast<double>(index + 1);
direction(index) = 0.27 * std::sin(0.19 * position + phase) + direction(index) =
0.14 * std::cos(0.13 * position - 0.5 * phase); 0.27 * std::sin(0.19 * position + phase) + 0.14 * std::cos(0.13 * position - 0.5 * phase);
} }
return direction; return direction;
} }
[[nodiscard]] [[nodiscard]]
mfem::Vector make_displacement_direction_true(const mean_field::fem::FEM &f, mfem::Vector make_displacement_direction_true(
const double phase) { const mean_field::fem::FEM &f,
MFEM_VERIFY(f.mesh->Dimension() == 3, const double phase
"The prepared pressure-force test requires a " ) {
"three-dimensional mesh."); MFEM_VERIFY(
f.mesh->Dimension() == 3, "The prepared pressure-force test requires a "
"three-dimensional mesh."
);
mfem::ParGridFunction directionField(f.displacementFes.get()); 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.SetSize(3);
value(0) = value(0) = 0.019 * x + 0.011 * y * z - 0.006 * z * z + 0.004 * phase * y;
0.019 * x + 0.011 * y * z - 0.006 * z * z + 0.004 * phase * y;
value(1) = value(1) = -0.016 * y + 0.008 * x * z + 0.005 * x * x - 0.003 * phase * z;
-0.016 * y + 0.008 * x * z + 0.005 * x * x - 0.003 * phase * z;
value(2) = value(2) = 0.013 * z - 0.010 * x * y + 0.006 * y * y + 0.004 * phase * x;
0.013 * z - 0.010 * x * y + 0.006 * y * y + 0.004 * phase * x; }
}); );
directionField.ProjectCoefficient(directionCoefficient); directionField.ProjectCoefficient(directionCoefficient);
@@ -110,95 +125,93 @@ mfem::Vector make_displacement_direction_true(const mean_field::fem::FEM &f,
} }
[[nodiscard]] [[nodiscard]]
double relative_difference(const mfem::Vector &left, const mfem::Vector &right, double relative_difference(
const MPI_Comm communicator) { const mfem::Vector &left,
MFEM_VERIFY(left.Size() == right.Size(), const mfem::Vector &right,
"Cannot compare prepared pressure-force vectors with " const MPI_Comm communicator
"different sizes."); ) {
MFEM_VERIFY(
left.Size() == right.Size(), "Cannot compare prepared pressure-force vectors with "
"different sizes."
);
mfem::Vector difference(left); mfem::Vector difference(left);
difference -= right; difference -= right;
const double scale = const double scale = std::max(
std::max({gravity_prepared_test_utils::global_norm(left, communicator), {gravity_prepared_test_utils::global_norm(left, communicator),
gravity_prepared_test_utils::global_norm(right, 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) / return gravity_prepared_test_utils::global_norm(difference, communicator) / scale;
scale;
} }
[[nodiscard]] [[nodiscard]]
mean_field::operators::context::pressure_force::PressureForceDependencies mean_field::operators::context::pressure_force::PressureForceDependencies make_dependencies() {
make_dependencies() { return {
return {.discretization = {.identity = 1201, .revision = 3}, .discretization = {.identity = 1201, .revision = 3},
.enthalpy = {.identity = 1213, .revision = 5}, .enthalpy = {.identity = 1213, .revision = 5},
.displacement = {.identity = 1217, .revision = 7}}; .displacement = {.identity = 1217, .revision = 7}
};
} }
constexpr auto densityValue = constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementValue = constexpr auto displacementValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::displacement_field.geometry_term
mean_field::utils::blocks::displacement_field.geometry_term); );
constexpr auto gravityGradientValue = constexpr auto gravityGradientValue =
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.gradient_term);
mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue = constexpr auto gravityPotentialValue =
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.poisson_term);
mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue = constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::enthalpy_field.specific_term
mean_field::utils::blocks::enthalpy_field.specific_term); );
constexpr auto barotropicConstantValue = constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
mean_field::utils::blocks::barotropic_constant_field );
.mass_normalization_term);
constexpr auto gravityGradientResidual = constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::gravity_field.gradient_term
mean_field::utils::blocks::gravity_field.gradient_term); );
constexpr auto gravityPotentialResidual = constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::gravity_field.poisson_term
mean_field::utils::blocks::gravity_field.poisson_term); );
constexpr auto densityResidual = constexpr auto densityResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::get_residual_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementResidual = constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::displacement_field.geometry_term
mean_field::utils::blocks::displacement_field.geometry_term); );
constexpr auto enthalpyResidual = constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::enthalpy_field.specific_term
mean_field::utils::blocks::enthalpy_field.specific_term); );
constexpr auto massResidual = constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
mean_field::utils::blocks::barotropic_constant_field );
.mass_normalization_term);
[[nodiscard]] [[nodiscard]]
mean_field::operators::BarotropicEquilibriumLayout mean_field::operators::BarotropicEquilibriumLayout make_coupled_layout(const Maps &maps) {
make_coupled_layout(const Maps &maps) {
const std::array<int, CoupledForm::value_block_count> valueSizes{ const std::array<int, CoupledForm::value_block_count> valueSizes{
maps.density.reduced_size(), maps.displacement.reduced_size(), maps.density.reduced_size(), maps.displacement.reduced_size(), maps.gravityFlux.reduced_size(),
maps.gravityFlux.reduced_size(), maps.gravityPotential.reduced_size(), maps.gravityPotential.reduced_size(), maps.enthalpy.reduced_size(), 1
maps.enthalpy.reduced_size(), 1}; };
const std::array<int, CoupledForm::residual_block_count> residualSizes{ const std::array<int, CoupledForm::residual_block_count> residualSizes{
maps.gravityFlux.reduced_size(), maps.gravityPotential.reduced_size(), maps.gravityFlux.reduced_size(), maps.gravityPotential.reduced_size(), maps.density.reduced_size(),
maps.density.reduced_size(), maps.displacement.reduced_size(), maps.displacement.reduced_size(), maps.enthalpy.reduced_size(), 1
maps.enthalpy.reduced_size(), 1}; };
return {valueSizes, residualSizes}; return {valueSizes, residualSizes};
} }
@@ -208,7 +221,8 @@ template <int index>
mfem::Vector copy_residual_block( mfem::Vector copy_residual_block(
const mfem::Vector &action, const mfem::Vector &action,
const mean_field::operators::BarotropicEquilibriumLayout &layout, 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)); mfem::Vector result(layout.size(block));
const int offset = layout.offset(block); const int offset = layout.offset(block);
@@ -224,7 +238,8 @@ mfem::Vector copy_residual_block(
TEST_CASE( TEST_CASE(
"Prepared Pressure Force Uses FieldDof Supported Dimensions And Owns Its " "Prepared Pressure Force Uses FieldDof Supported Dimensions And Owns Its "
"Context", "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::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -235,8 +250,7 @@ TEST_CASE(
const mean_field::eos::Polytrope equationOfState(3.0, 0.25); const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
mean_field::operators::PreparedPressureForceOperator preparedOperator( mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
f, *f.domainMapperStateless, equationOfState);
REQUIRE(maps.enthalpy.reduced_size() < maps.enthalpy.full_size()); REQUIRE(maps.enthalpy.reduced_size() < maps.enthalpy.full_size());
@@ -244,17 +258,18 @@ TEST_CASE(
CHECK(preparedOperator.GetEnthalpySize() == maps.enthalpy.reduced_size()); CHECK(preparedOperator.GetEnthalpySize() == maps.enthalpy.reduced_size());
CHECK(preparedOperator.GetDisplacementSize() == CHECK(preparedOperator.GetDisplacementSize() == maps.displacement.reduced_size());
maps.displacement.reduced_size());
CHECK(&preparedOperator.GetContext().GetPreparationStatistics() == CHECK(
&preparedOperator.GetContextPreparationStatistics()); &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", "Through FieldDof Restriction",
tags::barotrope &tags::pressure &tags::prepared &tags::field tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::integration &tags::accuracy
&tags::integration &tags::accuracy) { ) {
mean_field::utils::Args args = test_utils::setup_args(); mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -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 mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mfem::Vector enthalpy = maps.enthalpy.gather( const mfem::Vector enthalpy =
prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.47)); maps.enthalpy.gather(prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.47));
const mfem::Vector displacement = maps.displacement.gather( const mfem::Vector displacement = maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.69));
gravity_prepared_test_utils::make_displacement(f, 0.69));
const mfem::Vector enthalpyDirection = maps.enthalpy.gather( const mfem::Vector enthalpyDirection =
prepared_pressure_force_test_utils::make_enthalpy_direction_true(f, maps.enthalpy.gather(prepared_pressure_force_test_utils::make_enthalpy_direction_true(f, 0.73));
0.73));
const mfem::Vector displacementDirection = maps.displacement.gather( const mfem::Vector displacementDirection =
prepared_pressure_force_test_utils::make_displacement_direction_true( maps.displacement.gather(prepared_pressure_force_test_utils::make_displacement_direction_true(f, 0.83));
f, 0.83));
mean_field::operators::PreparedPressureForceOperator preparedOperator( mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
f, *f.domainMapperStateless, equationOfState);
preparedOperator.Prepare( preparedOperator.Prepare(
{.enthalpy = enthalpy, .displacement = displacement}, {.enthalpy = enthalpy, .displacement = displacement}, prepared_pressure_force_test_utils::make_dependencies()
prepared_pressure_force_test_utils::make_dependencies()); );
const mfem::Vector enthalpyTrue = maps.enthalpy.scatter(enthalpy); const mfem::Vector enthalpyTrue = maps.enthalpy.scatter(enthalpy);
const mfem::Vector displacementTrue = maps.displacement.scatter(displacement); const mfem::Vector displacementTrue = maps.displacement.scatter(displacement);
const mfem::Vector enthalpyDirectionTrue = const mfem::Vector enthalpyDirectionTrue = maps.enthalpy.scatter(enthalpyDirection);
maps.enthalpy.scatter(enthalpyDirection);
const mfem::Vector displacementDirectionTrue = const mfem::Vector displacementDirectionTrue = maps.displacement.scatter(displacementDirection);
maps.displacement.scatter(displacementDirection);
mfem::Vector preparedEnthalpyAction; mfem::Vector preparedEnthalpyAction;
mfem::Vector kernelEnthalpyActionTrue; mfem::Vector kernelEnthalpyActionTrue;
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyDirection, preparedOperator.ApplyEnthalpyJacobianAction(enthalpyDirection, preparedEnthalpyAction);
preparedEnthalpyAction);
mean_field::operators::kernels::apply_pressure_force_enthalpy_action( mean_field::operators::kernels::apply_pressure_force_enthalpy_action(
f, *f.domainMapperStateless, equationOfState, enthalpyTrue, f, *f.domainMapperStateless, equationOfState, enthalpyTrue, enthalpyDirectionTrue, displacementTrue,
enthalpyDirectionTrue, displacementTrue, kernelEnthalpyActionTrue); kernelEnthalpyActionTrue
);
const mfem::Vector kernelEnthalpyAction = const mfem::Vector kernelEnthalpyAction = maps.displacement.gather(kernelEnthalpyActionTrue);
maps.displacement.gather(kernelEnthalpyActionTrue);
CHECK(prepared_pressure_force_test_utils::relative_difference( CHECK(
preparedEnthalpyAction, kernelEnthalpyAction, f.mesh->GetComm()) < prepared_pressure_force_test_utils::relative_difference(
2.0e-12); preparedEnthalpyAction, kernelEnthalpyAction, f.mesh->GetComm()
) < 2.0e-12
);
mfem::Vector preparedDisplacementAction; mfem::Vector preparedDisplacementAction;
mfem::Vector kernelDisplacementActionTrue; mfem::Vector kernelDisplacementActionTrue;
preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, preparedDisplacementAction);
preparedDisplacementAction);
mean_field::operators::kernels::apply_pressure_force_displacement_action( mean_field::operators::kernels::apply_pressure_force_displacement_action(
f, *f.domainMapperStateless, equationOfState, enthalpyTrue, f, *f.domainMapperStateless, equationOfState, enthalpyTrue, displacementDirectionTrue, displacementTrue,
displacementDirectionTrue, displacementTrue, kernelDisplacementActionTrue
kernelDisplacementActionTrue); );
const mfem::Vector kernelDisplacementAction = const mfem::Vector kernelDisplacementAction = maps.displacement.gather(kernelDisplacementActionTrue);
maps.displacement.gather(kernelDisplacementActionTrue);
CHECK(prepared_pressure_force_test_utils::relative_difference( CHECK(
preparedDisplacementAction, kernelDisplacementAction, prepared_pressure_force_test_utils::relative_difference(
f.mesh->GetComm()) < 2.0e-12); preparedDisplacementAction, kernelDisplacementAction, f.mesh->GetComm()
) < 2.0e-12
);
mfem::Vector fusedAction; mfem::Vector fusedAction;
preparedOperator.ApplyCompleteJacobianAction( preparedOperator.ApplyCompleteJacobianAction(enthalpyDirection, displacementDirection, fusedAction);
enthalpyDirection, displacementDirection, fusedAction);
mfem::Vector expectedFusedAction(kernelEnthalpyAction); mfem::Vector expectedFusedAction(kernelEnthalpyAction);
expectedFusedAction += kernelDisplacementAction; expectedFusedAction += kernelDisplacementAction;
CHECK(prepared_pressure_force_test_utils::relative_difference( CHECK(
fusedAction, expectedFusedAction, f.mesh->GetComm()) < 2.0e-12); prepared_pressure_force_test_utils::relative_difference(fusedAction, expectedFusedAction, f.mesh->GetComm()) <
2.0e-12
);
} }
TEST_CASE( TEST_CASE(
"Prepared Pressure Force MFEM Adapter Routes Reduced Coupled FieldDof " "Prepared Pressure Force MFEM Adapter Routes Reduced Coupled FieldDof "
"Blocks", "Blocks",
tags::barotrope &tags::pressure &tags::prepared &tags::field tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::integration &tags::jacobian
&tags::integration &tags::jacobian &tags::mfem_operators &tags::unit) { &tags::mfem_operators &tags::unit
) {
mean_field::utils::Args args = test_utils::setup_args(); mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -359,38 +371,31 @@ TEST_CASE(
const mean_field::eos::Polytrope equationOfState(3.0, 0.25); const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mfem::Vector enthalpy = maps.enthalpy.gather( const mfem::Vector enthalpy =
prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.53)); maps.enthalpy.gather(prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.53));
const mfem::Vector displacement = maps.displacement.gather( const mfem::Vector displacement = maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.71));
gravity_prepared_test_utils::make_displacement(f, 0.71));
const mfem::Vector enthalpyDirection = maps.enthalpy.gather( const mfem::Vector enthalpyDirection =
prepared_pressure_force_test_utils::make_enthalpy_direction_true(f, maps.enthalpy.gather(prepared_pressure_force_test_utils::make_enthalpy_direction_true(f, 0.89));
0.89));
const mfem::Vector displacementDirection = maps.displacement.gather( const mfem::Vector displacementDirection =
prepared_pressure_force_test_utils::make_displacement_direction_true( maps.displacement.gather(prepared_pressure_force_test_utils::make_displacement_direction_true(f, 0.97));
f, 0.97));
mean_field::operators::PreparedPressureForceOperator preparedOperator( mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
f, *f.domainMapperStateless, equationOfState);
preparedOperator.Prepare( preparedOperator.Prepare(
{.enthalpy = enthalpy, .displacement = displacement}, {.enthalpy = enthalpy, .displacement = displacement}, prepared_pressure_force_test_utils::make_dependencies()
prepared_pressure_force_test_utils::make_dependencies()); );
const mean_field::operators::BarotropicEquilibriumLayout layout = const mean_field::operators::BarotropicEquilibriumLayout layout =
prepared_pressure_force_test_utils::make_coupled_layout(maps); prepared_pressure_force_test_utils::make_coupled_layout(maps);
mean_field::operators::PreparedPressureForceJacobianOperator adapter( mean_field::operators::PreparedPressureForceJacobianOperator adapter(layout, preparedOperator);
layout, preparedOperator);
CHECK(layout.size(prepared_pressure_force_test_utils::enthalpyValue) == CHECK(layout.size(prepared_pressure_force_test_utils::enthalpyValue) == maps.enthalpy.reduced_size());
maps.enthalpy.reduced_size());
CHECK(layout.size(prepared_pressure_force_test_utils::densityValue) == CHECK(layout.size(prepared_pressure_force_test_utils::densityValue) == maps.density.reduced_size());
maps.density.reduced_size());
mfem::BlockVector direction(layout.value_offsets()); 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::densityValue) = 0.37;
direction.GetBlock(prepared_pressure_force_test_utils::gravityGradientValue) = direction.GetBlock(prepared_pressure_force_test_utils::gravityGradientValue) = -0.41;
-0.41;
direction.GetBlock( direction.GetBlock(prepared_pressure_force_test_utils::gravityPotentialValue) = 0.59;
prepared_pressure_force_test_utils::gravityPotentialValue) = 0.59;
direction.GetBlock( direction.GetBlock(prepared_pressure_force_test_utils::barotropicConstantValue) = -0.73;
prepared_pressure_force_test_utils::barotropicConstantValue) = -0.73;
direction.GetBlock(prepared_pressure_force_test_utils::displacementValue) = direction.GetBlock(prepared_pressure_force_test_utils::displacementValue) = displacementDirection;
displacementDirection;
direction.GetBlock(prepared_pressure_force_test_utils::enthalpyValue) = direction.GetBlock(prepared_pressure_force_test_utils::enthalpyValue) = enthalpyDirection;
enthalpyDirection;
mfem::Vector expectedDisplacementAction; mfem::Vector expectedDisplacementAction;
preparedOperator.ApplyCompleteJacobianAction( preparedOperator.ApplyCompleteJacobianAction(enthalpyDirection, displacementDirection, expectedDisplacementAction);
enthalpyDirection, displacementDirection, expectedDisplacementAction);
mfem::Vector action; mfem::Vector action;
adapter.Mult(direction, action); adapter.Mult(direction, action);
const mfem::Vector displacementResidualAction = const mfem::Vector displacementResidualAction = prepared_pressure_force_test_utils::copy_residual_block(
prepared_pressure_force_test_utils::copy_residual_block( action, layout, prepared_pressure_force_test_utils::displacementResidual
action, layout, );
prepared_pressure_force_test_utils::displacementResidual);
CHECK(prepared_pressure_force_test_utils::relative_difference( CHECK(
displacementResidualAction, expectedDisplacementAction, prepared_pressure_force_test_utils::relative_difference(
f.mesh->GetComm()) < 2.0e-14); 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( CHECK(
prepared_pressure_force_test_utils::copy_residual_block( prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::enthalpyResidual) action, layout, prepared_pressure_force_test_utils::gravityGradientResidual
.Norml2() == 0.0); )
.Norml2() == 0.0
);
CHECK(prepared_pressure_force_test_utils::copy_residual_block( CHECK(
action, layout, prepared_pressure_force_test_utils::massResidual) prepared_pressure_force_test_utils::copy_residual_block(
.Norml2() == 0.0); 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", TEST_CASE(
tags::barotrope &tags::pressure &tags::kernels &tags::integration "Pressure Force Residual Converges To A Manufactured Analytic Force",
&tags::convergence &tags::h_refinement &tags::analytic_comparison tags::barotrope &tags::pressure &tags::kernels &tags::integration &tags::convergence &tags::h_refinement
&tags::accuracy) { &tags::analytic_comparison &tags::accuracy
) {
constexpr int dimension = 3; constexpr int dimension = 3;
constexpr std::array<int, 2> refinementLevels{0, 1}; 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{}; std::array<double, refinementLevels.size()> relativeErrors{};
for (std::size_t levelIndex = 0; levelIndex < refinementLevels.size(); for (std::size_t levelIndex = 0; levelIndex < refinementLevels.size(); ++levelIndex) {
++levelIndex) {
mean_field::utils::Args args = test_utils::setup_args(); mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem( mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, refinementLevels[levelIndex]);
args.mesh_file, args, refinementLevels[levelIndex]);
REQUIRE(f.okay()); REQUIRE(f.okay());
REQUIRE(f.mesh->Dimension() == dimension); 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(); const MPI_Comm communicator = f.mesh->GetComm();
constexpr double supportRadius = constexpr double supportRadius = supportRadiusFraction * mean_field::utils::RADIUS;
supportRadiusFraction * mean_field::utils::RADIUS;
constexpr double supportRadiusSquared = supportRadius * supportRadius; constexpr double supportRadiusSquared = supportRadius * supportRadius;
auto analyticEnthalpyFunction = [supportRadiusSquared]( auto analyticEnthalpyFunction = [supportRadiusSquared](const mfem::Vector &position) {
const mfem::Vector &position) { const double normalizedRadiusSquared = (position * position) / supportRadiusSquared;
const double normalizedRadiusSquared =
(position * position) / supportRadiusSquared;
if (normalizedRadiusSquared >= 1.0) { if (normalizedRadiusSquared >= 1.0) {
return 0.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; const double distanceToSupportBoundary = 1.0 - normalizedRadiusSquared;
return amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared / return amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared / distanceToSupportBoundary);
distanceToSupportBoundary);
}; };
auto analyticPressureForceFunction = [supportRadiusSquared]( auto analyticPressureForceFunction = [supportRadiusSquared](const mfem::Vector &position, mfem::Vector &force) {
const mfem::Vector &position,
mfem::Vector &force) {
force.SetSize(dimension); force.SetSize(dimension);
force = 0.0; force = 0.0;
const double normalizedRadiusSquared = const double normalizedRadiusSquared = (position * position) / supportRadiusSquared;
(position * position) / supportRadiusSquared;
if (normalizedRadiusSquared >= 1.0) { if (normalizedRadiusSquared >= 1.0) {
return; return;
@@ -523,24 +527,20 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
const double distanceToSupportBoundary = 1.0 - normalizedRadiusSquared; const double distanceToSupportBoundary = 1.0 - normalizedRadiusSquared;
const double enthalpy = const double enthalpy =
amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared / amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared / distanceToSupportBoundary);
distanceToSupportBoundary);
const double pressureGradientScale = const double pressureGradientScale =
-2.0 * bumpSharpness * std::pow(enthalpy, 4.0) / -2.0 * bumpSharpness * std::pow(enthalpy, 4.0) /
(supportRadiusSquared * distanceToSupportBoundary * (supportRadiusSquared * distanceToSupportBoundary * distanceToSupportBoundary);
distanceToSupportBoundary);
for (int component = 0; component < dimension; ++component) { for (int component = 0; component < dimension; ++component) {
force(component) = pressureGradientScale * position(component); force(component) = pressureGradientScale * position(component);
} }
}; };
mfem::FunctionCoefficient analyticEnthalpyCoefficient( mfem::FunctionCoefficient analyticEnthalpyCoefficient(analyticEnthalpyFunction);
analyticEnthalpyFunction);
mfem::VectorFunctionCoefficient analyticPressureForceCoefficient( mfem::VectorFunctionCoefficient analyticPressureForceCoefficient(dimension, analyticPressureForceFunction);
dimension, analyticPressureForceFunction);
mfem::ParGridFunction discreteEnthalpyField(f.enthalpyFes.get()); mfem::ParGridFunction discreteEnthalpyField(f.enthalpyFes.get());
@@ -558,8 +558,8 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
mfem::Vector discreteResidual; mfem::Vector discreteResidual;
mean_field::operators::kernels::apply_pressure_force_residual( mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, discreteEnthalpyTrue, f, *f.domainMapperStateless, barotrope, discreteEnthalpyTrue, zeroDisplacement, discreteResidual
zeroDisplacement, discreteResidual); );
REQUIRE(discreteResidual.Size() == f.displacementFes->GetTrueVSize()); 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; const int vacuumAttribute = field_dof_test_utils::vacuum_material_attribute;
for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size(); for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size(); ++attributeIndex) {
++attributeIndex) {
const int attribute = f.mesh->attributes[attributeIndex]; const int attribute = f.mesh->attributes[attributeIndex];
if (attribute != vacuumAttribute) { if (attribute != vacuumAttribute) {
@@ -578,34 +577,28 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
} }
} }
const mfem::Geometry::Type elementGeometry = const mfem::Geometry::Type elementGeometry = f.displacementFes->GetFE(0)->GetGeomType();
f.displacementFes->GetFE(0)->GetGeomType();
for (int element = 1; element < f.mesh->GetNE(); ++element) { for (int element = 1; element < f.mesh->GetNE(); ++element) {
REQUIRE(f.displacementFes->GetFE(element)->GetGeomType() == REQUIRE(f.displacementFes->GetFE(element)->GetGeomType() == elementGeometry);
elementGeometry);
} }
const int referenceQuadratureOrder = const int referenceQuadratureOrder = 2 * f.displacementFes->GetMaxElementOrder() + 16;
2 * f.displacementFes->GetMaxElementOrder() + 16;
const mfem::IntegrationRule &referenceQuadrature = const mfem::IntegrationRule &referenceQuadrature =
mfem::IntRules.Get(elementGeometry, referenceQuadratureOrder); mfem::IntRules.Get(elementGeometry, referenceQuadratureOrder);
auto *analyticForceIntegrator = auto *analyticForceIntegrator = new mfem::VectorDomainLFIntegrator(analyticPressureForceCoefficient);
new mfem::VectorDomainLFIntegrator(analyticPressureForceCoefficient);
analyticForceIntegrator->SetIntRule(&referenceQuadrature); analyticForceIntegrator->SetIntRule(&referenceQuadrature);
mfem::ParLinearForm analyticForceLoad(f.displacementFes.get()); mfem::ParLinearForm analyticForceLoad(f.displacementFes.get());
analyticForceLoad.AddDomainIntegrator(analyticForceIntegrator, analyticForceLoad.AddDomainIntegrator(analyticForceIntegrator, stellarMarker);
stellarMarker);
analyticForceLoad.Assemble(); analyticForceLoad.Assemble();
std::unique_ptr<mfem::HypreParVector> analyticForceHypreVector( std::unique_ptr<mfem::HypreParVector> analyticForceHypreVector(analyticForceLoad.ParallelAssemble());
analyticForceLoad.ParallelAssemble());
REQUIRE(analyticForceHypreVector != nullptr); REQUIRE(analyticForceHypreVector != nullptr);
@@ -613,8 +606,7 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
REQUIRE(analyticForceTrue.Size() == discreteResidual.Size()); REQUIRE(analyticForceTrue.Size() == discreteResidual.Size());
const double analyticForceNorm = gravity_prepared_test_utils::global_norm( const double analyticForceNorm = gravity_prepared_test_utils::global_norm(analyticForceTrue, communicator);
analyticForceTrue, communicator);
REQUIRE(std::isfinite(analyticForceNorm)); REQUIRE(std::isfinite(analyticForceNorm));
REQUIRE(analyticForceNorm > 0.0); REQUIRE(analyticForceNorm > 0.0);
@@ -631,8 +623,7 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
rieszForm.Assemble(); rieszForm.Assemble();
rieszForm.Finalize(); rieszForm.Finalize();
std::unique_ptr<mfem::HypreParMatrix> rieszMatrix( std::unique_ptr<mfem::HypreParMatrix> rieszMatrix(rieszForm.ParallelAssemble());
rieszForm.ParallelAssemble());
REQUIRE(rieszMatrix != nullptr); REQUIRE(rieszMatrix != nullptr);
REQUIRE(rieszMatrix->Height() == discreteResidual.Size()); REQUIRE(rieszMatrix->Height() == discreteResidual.Size());
@@ -651,28 +642,28 @@ TEST_CASE("Pressure Force Residual Converges To A Manufactured Analytic Force",
rieszSolver.SetMaxIter(5000); rieszSolver.SetMaxIter(5000);
rieszSolver.SetPrintLevel(0); rieszSolver.SetPrintLevel(0);
auto calculateDualNorm = [&rieszSolver, auto calculateDualNorm = [&rieszSolver, communicator](const mfem::Vector &functional) {
communicator](const mfem::Vector &functional) {
mfem::Vector rieszRepresentative(functional.Size()); mfem::Vector rieszRepresentative(functional.Size());
rieszRepresentative = 0.0; rieszRepresentative = 0.0;
rieszSolver.Mult(functional, rieszRepresentative); rieszSolver.Mult(functional, rieszRepresentative);
MFEM_VERIFY(rieszSolver.GetConverged(), MFEM_VERIFY(
"The pressure-force convergence-test Riesz solve " rieszSolver.GetConverged(), "The pressure-force convergence-test Riesz solve "
"did not converge."); "did not converge."
);
const double dualNormSquared = gravity_prepared_test_utils::global_dot( const double dualNormSquared =
functional, rieszRepresentative, communicator); gravity_prepared_test_utils::global_dot(functional, rieszRepresentative, communicator);
MFEM_VERIFY(std::isfinite(dualNormSquared), MFEM_VERIFY(std::isfinite(dualNormSquared), "The pressure-force dual norm is not finite.");
"The pressure-force dual norm is not finite.");
MFEM_VERIFY(dualNormSquared >= MFEM_VERIFY(
-100.0 * std::numeric_limits<double>::epsilon(), dualNormSquared >= -100.0 * std::numeric_limits<double>::epsilon(),
"The pressure-force Riesz operator produced a " "The pressure-force Riesz operator produced a "
"negative dual norm."); "negative dual norm."
);
return std::sqrt(std::max(dualNormSquared, 0.0)); 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); REQUIRE(relativeError > 0.0);
} }
static_assert( static_assert(refinementLevels.size() == 2, "This reduced convergence test expects exactly two refinement levels.");
refinementLevels.size() == 2,
"This reduced convergence test expects exactly two refinement levels.");
const double observedRate = const double observedRate = std::log(relativeErrors[0] / relativeErrors[1]) / std::log(2.0);
std::log(relativeErrors[0] / relativeErrors[1]) / std::log(2.0);
INFO("Level 0 pressure-force relative dual error = " << relativeErrors[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; using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue = constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementValue = constexpr auto displacementValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::displacement_field.geometry_term
mean_field::utils::blocks::displacement_field.geometry_term); );
constexpr auto gravityGradientValue = constexpr auto gravityGradientValue =
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.gradient_term);
mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue = constexpr auto gravityPotentialValue =
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.poisson_term);
mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue = constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::enthalpy_field.specific_term
mean_field::utils::blocks::enthalpy_field.specific_term); );
constexpr auto barotropicConstantValue = constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::get_value_block<CoupledForm>( mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
mean_field::utils::blocks::barotropic_constant_field );
.mass_normalization_term);
constexpr auto gravityGradientResidual = constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::gravity_field.gradient_term
mean_field::utils::blocks::gravity_field.gradient_term); );
constexpr auto gravityPotentialResidual = constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::gravity_field.poisson_term
mean_field::utils::blocks::gravity_field.poisson_term); );
constexpr auto densityResidual = constexpr auto densityResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::get_residual_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementResidual = constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::displacement_field.geometry_term
mean_field::utils::blocks::displacement_field.geometry_term); );
constexpr auto enthalpyResidual = constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::enthalpy_field.specific_term
mean_field::utils::blocks::enthalpy_field.specific_term); );
constexpr auto massResidual = constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::get_residual_block<CoupledForm>( mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
mean_field::utils::blocks::barotropic_constant_field );
.mass_normalization_term);
[[nodiscard]] mean_field::operators::RotationalDisplacementForceLayout [[nodiscard]] mean_field::operators::RotationalDisplacementForceLayout make_layout(const mean_field::fem::FEM &f) {
make_layout(const mean_field::fem::FEM &f) {
using DomainSchema = gravity_prepared_test_utils::DomainSchema; using DomainSchema = gravity_prepared_test_utils::DomainSchema;
const auto densityMap = const auto densityMap = gravity_prepared_test_utils::make_field_map<mean_field::field::Density>(f);
gravity_prepared_test_utils::make_field_map<mean_field::field::Density>( const auto displacementMap = gravity_prepared_test_utils::make_field_map<mean_field::field::Displacement>(f);
f);
const auto displacementMap = gravity_prepared_test_utils::make_field_map<
mean_field::field::Displacement>(f);
const auto gravityFluxMap = const auto gravityFluxMap =
mean_field::field::make_field_dof_map<mean_field::field::Gravity, mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityFluxFes);
DomainSchema>(*f.gravityFluxFes); const auto gravityPotentialMap =
const auto gravityPotentialMap = mean_field::field::make_field_dof_map< mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes);
mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes);
const auto enthalpyMap = const auto enthalpyMap =
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy, mean_field::field::make_field_dof_map<mean_field::field::Enthalpy, DomainSchema>(*f.enthalpyFes);
DomainSchema>(*f.enthalpyFes);
const std::array<int, CoupledForm::value_block_count> valueSizes{ const std::array<int, CoupledForm::value_block_count> valueSizes{
densityMap.reduced_size(), displacementMap.reduced_size(), densityMap.reduced_size(), displacementMap.reduced_size(), gravityFluxMap.reduced_size(),
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), gravityPotentialMap.reduced_size(), enthalpyMap.reduced_size(), 1
enthalpyMap.reduced_size(), 1}; };
const std::array<int, CoupledForm::residual_block_count> residualSizes{ const std::array<int, CoupledForm::residual_block_count> residualSizes{
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), densityMap.reduced_size(),
densityMap.reduced_size(), displacementMap.reduced_size(), displacementMap.reduced_size(), enthalpyMap.reduced_size(), 1
enthalpyMap.reduced_size(), 1}; };
return {valueSizes, residualSizes}; return {valueSizes, residualSizes};
} }
[[nodiscard]] mfem::Vector make_density(const mean_field::fem::FEM &f, [[nodiscard]] mfem::Vector make_density(
const double phase) { const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction densityField(f.densityFes.get()); mfem::ParGridFunction densityField(f.densityFes.get());
mfem::FunctionCoefficient densityCoefficient( mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) {
[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) +
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); 0.025 * position(2) * position(2);
}); });
@@ -112,14 +100,15 @@ make_layout(const mean_field::fem::FEM &f) {
return densityTrue; return densityTrue;
} }
[[nodiscard]] mfem::Vector make_density_direction(const mean_field::fem::FEM &f, [[nodiscard]] mfem::Vector make_density_direction(
const double phase) { const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction densityField(f.densityFes.get()); mfem::ParGridFunction densityField(f.densityFes.get());
mfem::FunctionCoefficient densityCoefficient( mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) {
[phase](const mfem::Vector &position) { return 0.17 * std::sin(0.9 * position(0) + phase) - 0.12 * std::cos(0.8 * position(1) - phase) +
return 0.17 * std::sin(0.9 * position(0) + phase) - 0.07 * position(2);
0.12 * std::cos(0.8 * position(1) - phase) + 0.07 * position(2);
}); });
densityField.ProjectCoefficient(densityCoefficient); densityField.ProjectCoefficient(densityCoefficient);
@@ -129,20 +118,16 @@ make_layout(const mean_field::fem::FEM &f) {
return densityTrue; return densityTrue;
} }
[[nodiscard]] mfem::Vector [[nodiscard]] mfem::Vector make_displacement_direction(const mean_field::fem::FEM &f) {
make_displacement_direction(const mean_field::fem::FEM &f) { mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, 0.91);
mfem::Vector direction =
gravity_prepared_test_utils::make_displacement(f, 0.91);
const mfem::Vector second = const mfem::Vector second = gravity_prepared_test_utils::make_displacement(f, 0.27);
gravity_prepared_test_utils::make_displacement(f, 0.27);
direction -= second; direction -= second;
return direction; return direction;
} }
[[nodiscard]] mean_field::physics::RigidRotation [[nodiscard]] mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
make_rotation(const double scale = 1.0) {
mfem::Vector angularVelocity(3); mfem::Vector angularVelocity(3);
angularVelocity(0) = scale * 0.17; angularVelocity(0) = scale * 0.17;
angularVelocity(1) = scale * -0.09; angularVelocity(1) = scale * -0.09;
@@ -156,17 +141,17 @@ make_rotation(const double scale = 1.0) {
return mean_field::physics::RigidRotation(angularVelocity, center); return mean_field::physics::RigidRotation(angularVelocity, center);
} }
[[nodiscard]] mean_field::operators::context::rotational_displacement_force:: [[nodiscard]] mean_field::operators::context::rotational_displacement_force::RotationalDisplacementForceDependencies
RotationalDisplacementForceDependencies
make_dependencies() { make_dependencies() {
return {.discretization = {.identity = 211, .revision = 3}, return {
.discretization = {.identity = 211, .revision = 3},
.density = {.identity = 223, .revision = 5}, .density = {.identity = 223, .revision = 5},
.displacement = {.identity = 227, .revision = 7}, .displacement = {.identity = 227, .revision = 7},
.rotation = {.identity = 229, .revision = 11}}; .rotation = {.identity = 229, .revision = 11}
};
} }
[[nodiscard]] mfem::Vector [[nodiscard]] mfem::Vector make_vacuum_only_density(const mean_field::fem::FEM &f) {
make_vacuum_only_density(const mean_field::fem::FEM &f) {
mfem::ParGridFunction densityField(f.densityFes.get()); mfem::ParGridFunction densityField(f.densityFes.get());
densityField = 0.0; densityField = 0.0;
@@ -176,8 +161,7 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
int localVacuumElements = 0; int localVacuumElements = 0;
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation = mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
f.mesh->GetElementTransformation(elementId);
REQUIRE(transformation != nullptr); REQUIRE(transformation != nullptr);
@@ -195,8 +179,7 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
int globalVacuumElements = 0; int globalVacuumElements = 0;
MPI_Allreduce(&localVacuumElements, &globalVacuumElements, 1, MPI_INT, MPI_Allreduce(&localVacuumElements, &globalVacuumElements, 1, MPI_INT, MPI_SUM, f.mesh->GetComm());
MPI_SUM, f.mesh->GetComm());
REQUIRE(globalVacuumElements > 0); REQUIRE(globalVacuumElements > 0);
@@ -205,20 +188,23 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
return densityTrue; return densityTrue;
} }
[[nodiscard]] double global_norm(const mfem::Vector &vector, [[nodiscard]] double global_norm(
MPI_Comm communicator) { const mfem::Vector &vector,
MPI_Comm communicator
) {
const double localSquaredNorm = vector * vector; const double localSquaredNorm = vector * vector;
double globalSquaredNorm = 0.0; double globalSquaredNorm = 0.0;
MPI_Allreduce(&localSquaredNorm, &globalSquaredNorm, 1, MPI_DOUBLE, MPI_SUM, MPI_Allreduce(&localSquaredNorm, &globalSquaredNorm, 1, MPI_DOUBLE, MPI_SUM, communicator);
communicator);
return std::sqrt(globalSquaredNorm); return std::sqrt(globalSquaredNorm);
} }
[[nodiscard]] double global_dot(const mfem::Vector &left, [[nodiscard]] double global_dot(
const mfem::Vector &left,
const mfem::Vector &right, const mfem::Vector &right,
MPI_Comm communicator) { MPI_Comm communicator
) {
REQUIRE(left.Size() == right.Size()); REQUIRE(left.Size() == right.Size());
const double localDot = left * right; const double localDot = left * right;
@@ -229,25 +215,29 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
return globalDot; return globalDot;
} }
[[nodiscard]] double relative_difference(const mfem::Vector &computed, [[nodiscard]] double relative_difference(
const mfem::Vector &computed,
const mfem::Vector &reference, const mfem::Vector &reference,
MPI_Comm communicator) { MPI_Comm communicator
) {
REQUIRE(computed.Size() == reference.Size()); REQUIRE(computed.Size() == reference.Size());
mfem::Vector difference(computed); mfem::Vector difference(computed);
difference -= reference; difference -= reference;
return global_norm(difference, communicator) / return global_norm(difference, communicator) /
std::max(global_norm(reference, communicator), std::max(global_norm(reference, communicator), std::numeric_limits<double>::epsilon());
std::numeric_limits<double>::epsilon());
} }
[[nodiscard]] mfem::Vector centered_difference( [[nodiscard]] mfem::Vector centered_difference(
const mean_field::fem::FEM &f, const mean_field::fem::FEM &f,
const mean_field::physics::RigidRotation &rotation, const mean_field::physics::RigidRotation &rotation,
const mfem::Vector &baseDensity, const mfem::Vector &densityDirection, const mfem::Vector &baseDensity,
const mfem::Vector &densityDirection,
const mfem::Vector &baseDisplacement, const mfem::Vector &baseDisplacement,
const mfem::Vector &displacementDirection, const double step) { const mfem::Vector &displacementDirection,
const double step
) {
mfem::Vector plusDensity(baseDensity); mfem::Vector plusDensity(baseDensity);
plusDensity.Add(step, densityDirection); plusDensity.Add(step, densityDirection);
@@ -264,12 +254,12 @@ make_vacuum_only_density(const mean_field::fem::FEM &f) {
mfem::Vector minusResidual; mfem::Vector minusResidual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual( mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, plusDensity, plusDisplacement, f, *f.domainMapperStateless, rotation, plusDensity, plusDisplacement, plusResidual
plusResidual); );
mean_field::operators::kernels::apply_rotational_displacement_force_residual( mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, minusDensity, minusDisplacement, f, *f.domainMapperStateless, rotation, minusDensity, minusDisplacement, minusResidual
minusResidual); );
plusResidual -= minusResidual; plusResidual -= minusResidual;
plusResidual /= 2.0 * step; plusResidual /= 2.0 * step;
@@ -280,7 +270,8 @@ template <int index>
[[nodiscard]] mfem::Vector copy_residual_block( [[nodiscard]] mfem::Vector copy_residual_block(
const mfem::Vector &action, const mfem::Vector &action,
const mean_field::operators::RotationalDisplacementForceLayout &layout, 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)); mfem::Vector result(layout.size(block));
const int offset = layout.offset(block); const int offset = layout.offset(block);
@@ -295,18 +286,17 @@ template <int index>
TEST_CASE( TEST_CASE(
"Rotational Displacement Force Query Includes Density Test And Linear " "Rotational Displacement Force Query Includes Density Test And Linear "
"Position", "Position",
tags::rotation_prepared_unit) { tags::rotation_prepared_unit
using DisplacementField = ) {
mean_field::field::Field<mean_field::field::Displacement>; using DisplacementField = mean_field::field::Field<mean_field::field::Displacement>;
constexpr int geometryWeightOrder = 4; constexpr int geometryWeightOrder = 4;
constexpr mean_field::quadrature::Query query = DisplacementField::make_query< constexpr mean_field::quadrature::Query query =
mean_field::field::Displacement::Form::CentrifugalForce>( DisplacementField::make_query<mean_field::field::Displacement::Form::CentrifugalForce>(
mean_field::quadrature::QuadratureRole::discretization, mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder, std::array<int, 1>{1},
geometryWeightOrder, std::array<int, 1>{1}, mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
mean_field::utils::DOMAINS::STELLAR, );
mean_field::quadrature::MappingKind::general);
/* density: 2, displacement test: 3, position: 1, geometry: 4 */ /* density: 2, displacement test: 3, position: 1, geometry: 4 */
constexpr int expectedBaseOrder = 2 + 3 + 1 + 4; constexpr int expectedBaseOrder = 2 + 3 + 1 + 4;
@@ -318,33 +308,33 @@ TEST_CASE(
STATIC_REQUIRE(*query.base_order == expectedBaseOrder); 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", "Gradient And Excludes Vacuum",
tags::rotation_kernel_accuracy) { tags::rotation_kernel_accuracy
) {
mean_field::utils::Args args = test_utils::setup_args(); mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay()); REQUIRE(f.okay());
const mfem::Vector density = const mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.31);
rotational_displacement_force_test_utils::make_density(f, 0.31);
mfem::Vector displacement(f.displacementFes->GetTrueVSize()); mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0; displacement = 0.0;
const mean_field::physics::RigidRotation rotation = const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation();
rotational_displacement_force_test_utils::make_rotation();
mfem::Vector residual; mfem::Vector residual;
mean_field::operators::kernels::apply_rotational_displacement_force_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()); mfem::ParGridFunction gradientTestField(f.displacementFes.get());
auto gradientFunction = [&rotation](const mfem::Vector &position, auto gradientFunction = [&rotation](const mfem::Vector &position, mfem::Vector &value) {
mfem::Vector &value) {
rotation.potential_gradient(position, value); rotation.potential_gradient(position, value);
}; };
@@ -356,73 +346,63 @@ TEST_CASE("Rotational Displacement Force Uses Negative Rotation-Potential "
gradientTestField.GetTrueDofs(gradientTestDirection); gradientTestField.GetTrueDofs(gradientTestDirection);
const double signedWork = const double signedWork =
rotational_displacement_force_test_utils::global_dot( rotational_displacement_force_test_utils::global_dot(residual, gradientTestDirection, f.mesh->GetComm());
residual, gradientTestDirection, f.mesh->GetComm());
INFO("Rotation-force work against grad(Psi) = " << signedWork); INFO("Rotation-force work against grad(Psi) = " << signedWork);
CHECK(signedWork < 0.0); CHECK(signedWork < 0.0);
const mfem::Vector vacuumDensity = const mfem::Vector vacuumDensity = rotational_displacement_force_test_utils::make_vacuum_only_density(f);
rotational_displacement_force_test_utils::make_vacuum_only_density(f);
mfem::Vector vacuumResidual; mfem::Vector vacuumResidual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual( mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, vacuumDensity, displacement, f, *f.domainMapperStateless, rotation, vacuumDensity, displacement, vacuumResidual
vacuumResidual); );
CHECK(rotational_displacement_force_test_utils::global_norm( CHECK(rotational_displacement_force_test_utils::global_norm(vacuumResidual, f.mesh->GetComm()) == 0.0);
vacuumResidual, f.mesh->GetComm()) == 0.0);
mfem::Vector zeroAngularVelocity(3); mfem::Vector zeroAngularVelocity(3);
mfem::Vector zeroCenter(3); mfem::Vector zeroCenter(3);
zeroAngularVelocity = 0.0; zeroAngularVelocity = 0.0;
zeroCenter = 0.0; zeroCenter = 0.0;
const mean_field::physics::RigidRotation zeroRotation(zeroAngularVelocity, const mean_field::physics::RigidRotation zeroRotation(zeroAngularVelocity, zeroCenter);
zeroCenter);
mfem::Vector zeroRotationResidual; mfem::Vector zeroRotationResidual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual( mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, zeroRotation, density, displacement, f, *f.domainMapperStateless, zeroRotation, density, displacement, zeroRotationResidual
zeroRotationResidual); );
CHECK(rotational_displacement_force_test_utils::global_norm( CHECK(rotational_displacement_force_test_utils::global_norm(zeroRotationResidual, f.mesh->GetComm()) == 0.0);
zeroRotationResidual, f.mesh->GetComm()) == 0.0);
} }
TEST_CASE("Prepared Rotational Displacement Force Reprepares Selectively", TEST_CASE(
tags::rotation_prepared) { "Prepared Rotational Displacement Force Reprepares Selectively",
tags::rotation_prepared
) {
mean_field::utils::Args args = test_utils::setup_args(); mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay()); REQUIRE(f.okay());
mfem::Vector densityTrue = mfem::Vector densityTrue = rotational_displacement_force_test_utils::make_density(f, 0.37);
rotational_displacement_force_test_utils::make_density(f, 0.37);
const mfem::Vector displacementTrue = const mfem::Vector displacementTrue = gravity_prepared_test_utils::make_displacement(f, 0.53);
gravity_prepared_test_utils::make_displacement(f, 0.53);
mean_field::physics::RigidRotation rotation = mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.81);
rotational_displacement_force_test_utils::make_rotation(0.81);
auto dependencies = auto dependencies = rotational_displacement_force_test_utils::make_dependencies();
rotational_displacement_force_test_utils::make_dependencies();
mean_field::operators::PreparedRotationalDisplacementForceOperator mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless);
preparedOperator(f, *f.domainMapperStateless);
const auto &context = preparedOperator.GetContext(); const auto &context = preparedOperator.GetContext();
mfem::Vector density = context.GetDensityMap().gather(densityTrue); mfem::Vector density = context.GetDensityMap().gather(densityTrue);
const mfem::Vector displacement = const mfem::Vector displacement = context.GetDisplacementMap().gather(displacementTrue);
context.GetDisplacementMap().gather(displacementTrue);
const auto initialReport = preparedOperator.Prepare( const auto initialReport =
{.density = density, .displacement = displacement}, dependencies, preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation);
rotation);
REQUIRE(initialReport.DidAnyWork()); REQUIRE(initialReport.DidAnyWork());
REQUIRE(initialReport.updatedRotation); REQUIRE(initialReport.updatedRotation);
@@ -435,19 +415,18 @@ TEST_CASE("Prepared Rotational Displacement Force Reprepares Selectively",
preparedOperator.BuildResidual(preparedResidual); preparedOperator.BuildResidual(preparedResidual);
mean_field::operators::kernels::apply_rotational_displacement_force_residual( mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, densityTrue, displacementTrue, f, *f.domainMapperStateless, rotation, densityTrue, displacementTrue, kernelResidual
kernelResidual); );
const mfem::Vector kernelResidualReduced = const mfem::Vector kernelResidualReduced = context.GetDisplacementMap().gather(kernelResidual);
context.GetDisplacementMap().gather(kernelResidual);
CHECK(rotational_displacement_force_test_utils::relative_difference( CHECK(
preparedResidual, kernelResidualReduced, f.mesh->GetComm()) < rotational_displacement_force_test_utils::relative_difference(
2.0e-12); preparedResidual, kernelResidualReduced, f.mesh->GetComm()
) < 2.0e-12
);
CHECK_FALSE(preparedOperator CHECK_FALSE(preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation)
.Prepare({.density = density, .displacement = displacement},
dependencies, rotation)
.DidAnyWork()); .DidAnyWork());
densityTrue = rotational_displacement_force_test_utils::make_density(f, 0.79); 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; ++dependencies.density.revision;
const auto densityReport = preparedOperator.Prepare( const auto densityReport =
{.density = density, .displacement = displacement}, dependencies, preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation);
rotation);
CHECK(densityReport.preparedResidual); CHECK(densityReport.preparedResidual);
CHECK_FALSE(densityReport.updatedRotation); CHECK_FALSE(densityReport.updatedRotation);
@@ -466,9 +444,8 @@ TEST_CASE("Prepared Rotational Displacement Force Reprepares Selectively",
++dependencies.rotation.revision; ++dependencies.rotation.revision;
const auto rotationReport = preparedOperator.Prepare( const auto rotationReport =
{.density = density, .displacement = displacement}, dependencies, preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation);
rotation);
CHECK(rotationReport.updatedRotation); CHECK(rotationReport.updatedRotation);
CHECK(rotationReport.preparedResidual); CHECK(rotationReport.preparedResidual);
@@ -476,45 +453,40 @@ TEST_CASE("Prepared Rotational Displacement Force Reprepares Selectively",
CHECK(preparedOperator.GetResidualApplicationCount() == 1); 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", "Centered Differences",
tags::rotation_prepared_jacobian_accuracy) { tags::rotation_prepared_jacobian_accuracy
) {
mean_field::utils::Args args = test_utils::setup_args(); mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay()); REQUIRE(f.okay());
const mfem::Vector densityTrue = const mfem::Vector densityTrue = rotational_displacement_force_test_utils::make_density(f, 0.43);
rotational_displacement_force_test_utils::make_density(f, 0.43);
const mfem::Vector densityDirectionTrue = const mfem::Vector densityDirectionTrue = rotational_displacement_force_test_utils::make_density_direction(f, 0.59);
rotational_displacement_force_test_utils::make_density_direction(f, 0.59);
const mfem::Vector displacementTrue = const mfem::Vector displacementTrue = gravity_prepared_test_utils::make_displacement(f, 0.61);
gravity_prepared_test_utils::make_displacement(f, 0.61);
const mfem::Vector displacementDirectionTrue = const mfem::Vector displacementDirectionTrue =
rotational_displacement_force_test_utils::make_displacement_direction(f); rotational_displacement_force_test_utils::make_displacement_direction(f);
const mean_field::physics::RigidRotation rotation = const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.93);
rotational_displacement_force_test_utils::make_rotation(0.93);
mean_field::operators::PreparedRotationalDisplacementForceOperator mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless);
preparedOperator(f, *f.domainMapperStateless);
const auto &context = preparedOperator.GetContext(); const auto &context = preparedOperator.GetContext();
const mfem::Vector density = context.GetDensityMap().gather(densityTrue); const mfem::Vector density = context.GetDensityMap().gather(densityTrue);
const mfem::Vector densityDirection = const mfem::Vector densityDirection = context.GetDensityMap().gather(densityDirectionTrue);
context.GetDensityMap().gather(densityDirectionTrue); const mfem::Vector displacement = context.GetDisplacementMap().gather(displacementTrue);
const mfem::Vector displacement = const mfem::Vector displacementDirection = context.GetDisplacementMap().gather(displacementDirectionTrue);
context.GetDisplacementMap().gather(displacementTrue);
const mfem::Vector displacementDirection =
context.GetDisplacementMap().gather(displacementDirectionTrue);
preparedOperator.Prepare( preparedOperator.Prepare(
{.density = density, .displacement = displacement}, {.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 densityAction;
mfem::Vector displacementAction; mfem::Vector displacementAction;
@@ -522,17 +494,18 @@ TEST_CASE("Rotational Displacement Force Jacobian Matches Both Columns And "
preparedOperator.ApplyDensityJacobianAction(densityDirection, densityAction); preparedOperator.ApplyDensityJacobianAction(densityDirection, densityAction);
preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, displacementAction);
displacementAction);
preparedOperator.ApplyCompleteJacobianAction( preparedOperator.ApplyCompleteJacobianAction(densityDirection, displacementDirection, completeAction);
densityDirection, displacementDirection, completeAction);
mfem::Vector summedColumns(densityAction); mfem::Vector summedColumns(densityAction);
summedColumns += displacementAction; summedColumns += displacementAction;
CHECK(rotational_displacement_force_test_utils::relative_difference( CHECK(
completeAction, summedColumns, f.mesh->GetComm()) < 2.0e-12); rotational_displacement_force_test_utils::relative_difference(
completeAction, summedColumns, f.mesh->GetComm()
) < 2.0e-12
);
mfem::Vector zeroDensityTrue(densityDirectionTrue.Size()); mfem::Vector zeroDensityTrue(densityDirectionTrue.Size());
mfem::Vector zeroDisplacementTrue(displacementDirectionTrue.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; constexpr double step = 1.0e-5;
const mfem::Vector densityDifferenceTrue = const mfem::Vector densityDifferenceTrue = rotational_displacement_force_test_utils::centered_difference(
rotational_displacement_force_test_utils::centered_difference( f, rotation, densityTrue, densityDirectionTrue, displacementTrue, zeroDisplacementTrue, step
f, rotation, densityTrue, densityDirectionTrue, displacementTrue, );
zeroDisplacementTrue, step);
const mfem::Vector displacementDifferenceTrue = const mfem::Vector displacementDifferenceTrue = rotational_displacement_force_test_utils::centered_difference(
rotational_displacement_force_test_utils::centered_difference( f, rotation, densityTrue, zeroDensityTrue, displacementTrue, displacementDirectionTrue, step
f, rotation, densityTrue, zeroDensityTrue, displacementTrue, );
displacementDirectionTrue, step);
const mfem::Vector completeDifferenceTrue = const mfem::Vector completeDifferenceTrue = rotational_displacement_force_test_utils::centered_difference(
rotational_displacement_force_test_utils::centered_difference( f, rotation, densityTrue, densityDirectionTrue, displacementTrue, displacementDirectionTrue, step
f, rotation, densityTrue, densityDirectionTrue, displacementTrue, );
displacementDirectionTrue, step);
const mfem::Vector densityDifference = const mfem::Vector densityDifference = context.GetDisplacementMap().gather(densityDifferenceTrue);
context.GetDisplacementMap().gather(densityDifferenceTrue); const mfem::Vector displacementDifference = context.GetDisplacementMap().gather(displacementDifferenceTrue);
const mfem::Vector displacementDifference = const mfem::Vector completeDifference = context.GetDisplacementMap().gather(completeDifferenceTrue);
context.GetDisplacementMap().gather(displacementDifferenceTrue);
const mfem::Vector completeDifference =
context.GetDisplacementMap().gather(completeDifferenceTrue);
const double densityError = const double densityError = rotational_displacement_force_test_utils::relative_difference(
rotational_displacement_force_test_utils::relative_difference( densityAction, densityDifference, f.mesh->GetComm()
densityAction, densityDifference, f.mesh->GetComm()); );
const double displacementError = const double displacementError = rotational_displacement_force_test_utils::relative_difference(
rotational_displacement_force_test_utils::relative_difference( displacementAction, displacementDifference, f.mesh->GetComm()
displacementAction, displacementDifference, f.mesh->GetComm()); );
const double completeError = const double completeError = rotational_displacement_force_test_utils::relative_difference(
rotational_displacement_force_test_utils::relative_difference( completeAction, completeDifference, f.mesh->GetComm()
completeAction, completeDifference, f.mesh->GetComm()); );
INFO("Density-column centered-difference error = " << densityError); INFO("Density-column centered-difference error = " << densityError);
INFO("Displacement-column centered-difference error = " << displacementError); 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); CHECK(completeError < 4.0e-8);
} }
TEST_CASE("Prepared Rotational Displacement Force MFEM Adapter Routes Only R-d", TEST_CASE(
tags::rotation_prepared_unit) { "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::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay()); REQUIRE(f.okay());
const mfem::Vector densityTrue = const mfem::Vector densityTrue = rotational_displacement_force_test_utils::make_density(f, 0.47);
rotational_displacement_force_test_utils::make_density(f, 0.47);
const mfem::Vector densityDirectionTrue = const mfem::Vector densityDirectionTrue = rotational_displacement_force_test_utils::make_density_direction(f, 0.63);
rotational_displacement_force_test_utils::make_density_direction(f, 0.63);
const mfem::Vector displacementTrue = const mfem::Vector displacementTrue = gravity_prepared_test_utils::make_displacement(f, 0.57);
gravity_prepared_test_utils::make_displacement(f, 0.57);
const mfem::Vector displacementDirectionTrue = const mfem::Vector displacementDirectionTrue =
rotational_displacement_force_test_utils::make_displacement_direction(f); rotational_displacement_force_test_utils::make_displacement_direction(f);
const mean_field::physics::RigidRotation rotation = const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.87);
rotational_displacement_force_test_utils::make_rotation(0.87);
mean_field::operators::PreparedRotationalDisplacementForceOperator mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless);
preparedOperator(f, *f.domainMapperStateless);
const auto &context = preparedOperator.GetContext(); const auto &context = preparedOperator.GetContext();
const mfem::Vector density = context.GetDensityMap().gather(densityTrue); const mfem::Vector density = context.GetDensityMap().gather(densityTrue);
const mfem::Vector densityDirection = const mfem::Vector densityDirection = context.GetDensityMap().gather(densityDirectionTrue);
context.GetDensityMap().gather(densityDirectionTrue); const mfem::Vector displacement = context.GetDisplacementMap().gather(displacementTrue);
const mfem::Vector displacement = const mfem::Vector displacementDirection = context.GetDisplacementMap().gather(displacementDirectionTrue);
context.GetDisplacementMap().gather(displacementTrue);
const mfem::Vector displacementDirection =
context.GetDisplacementMap().gather(displacementDirectionTrue);
preparedOperator.Prepare( preparedOperator.Prepare(
{.density = density, .displacement = displacement}, {.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); const auto layout = rotational_displacement_force_test_utils::make_layout(f);
mean_field::operators::PreparedRotationalDisplacementForceJacobianOperator mean_field::operators::PreparedRotationalDisplacementForceJacobianOperator adapter(layout, preparedOperator);
adapter(layout, preparedOperator);
mfem::BlockVector direction(layout.value_offsets()); mfem::BlockVector direction(layout.value_offsets());
direction = 0.0; direction = 0.0;
direction.GetBlock(rotational_displacement_force_test_utils::densityValue) = direction.GetBlock(rotational_displacement_force_test_utils::densityValue) = densityDirection;
densityDirection;
direction.GetBlock( direction.GetBlock(rotational_displacement_force_test_utils::displacementValue) = displacementDirection;
rotational_displacement_force_test_utils::displacementValue) =
displacementDirection;
direction.GetBlock( direction.GetBlock(rotational_displacement_force_test_utils::gravityGradientValue) = 0.23;
rotational_displacement_force_test_utils::gravityGradientValue) = 0.23;
direction.GetBlock( direction.GetBlock(rotational_displacement_force_test_utils::gravityPotentialValue) = -0.31;
rotational_displacement_force_test_utils::gravityPotentialValue) = -0.31;
direction.GetBlock(rotational_displacement_force_test_utils::enthalpyValue) = direction.GetBlock(rotational_displacement_force_test_utils::enthalpyValue) = 0.37;
0.37;
direction.GetBlock( direction.GetBlock(rotational_displacement_force_test_utils::barotropicConstantValue) = -0.41;
rotational_displacement_force_test_utils::barotropicConstantValue) =
-0.41;
mfem::Vector action; mfem::Vector action;
adapter.Mult(direction, action); adapter.Mult(direction, action);
mfem::Vector expectedDisplacementAction; mfem::Vector expectedDisplacementAction;
preparedOperator.ApplyCompleteJacobianAction( preparedOperator.ApplyCompleteJacobianAction(densityDirection, displacementDirection, expectedDisplacementAction);
densityDirection, displacementDirection, expectedDisplacementAction);
const mfem::Vector actualDisplacementAction = const mfem::Vector actualDisplacementAction = rotational_displacement_force_test_utils::copy_residual_block(
rotational_displacement_force_test_utils::copy_residual_block( action, layout, rotational_displacement_force_test_utils::displacementResidual
action, layout, );
rotational_displacement_force_test_utils::displacementResidual);
CHECK(rotational_displacement_force_test_utils::relative_difference( CHECK(
actualDisplacementAction, expectedDisplacementAction, rotational_displacement_force_test_utils::relative_difference(
f.mesh->GetComm()) < 2.0e-12); actualDisplacementAction, expectedDisplacementAction, f.mesh->GetComm()
) < 2.0e-12
);
const std::array<mfem::Vector, 5> zeroRows{ const std::array<mfem::Vector, 5> zeroRows{
rotational_displacement_force_test_utils::copy_residual_block( rotational_displacement_force_test_utils::copy_residual_block(
action, layout, action, layout, rotational_displacement_force_test_utils::gravityGradientResidual
rotational_displacement_force_test_utils::gravityGradientResidual), ),
rotational_displacement_force_test_utils::copy_residual_block( rotational_displacement_force_test_utils::copy_residual_block(
action, layout, action, layout, rotational_displacement_force_test_utils::gravityPotentialResidual
rotational_displacement_force_test_utils::gravityPotentialResidual), ),
rotational_displacement_force_test_utils::copy_residual_block( rotational_displacement_force_test_utils::copy_residual_block(
action, layout, action, layout, rotational_displacement_force_test_utils::densityResidual
rotational_displacement_force_test_utils::densityResidual), ),
rotational_displacement_force_test_utils::copy_residual_block( rotational_displacement_force_test_utils::copy_residual_block(
action, layout, action, layout, rotational_displacement_force_test_utils::enthalpyResidual
rotational_displacement_force_test_utils::enthalpyResidual), ),
rotational_displacement_force_test_utils::copy_residual_block( rotational_displacement_force_test_utils::copy_residual_block(
action, layout, action, layout, rotational_displacement_force_test_utils::massResidual
rotational_displacement_force_test_utils::massResidual)}; )
};
for (const mfem::Vector &row : zeroRows) { for (const mfem::Vector &row : zeroRows) {
CHECK(rotational_displacement_force_test_utils::global_norm( CHECK(rotational_displacement_force_test_utils::global_norm(row, f.mesh->GetComm()) == 0.0);
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 mean_field;
import test_helpers; import test_helpers;
TEST_CASE("Polytropic EOS Satisfies Its Analytic Identities", TEST_CASE(
tags::barotrope_eos_unit) { "Polytropic EOS Satisfies Its Analytic Identities",
tags::barotrope_eos_unit
) {
using namespace mean_field::eos;
constexpr double polytropic_index = 3.0; constexpr double polytropic_index = 3.0;
constexpr double polytropic_constant = 1.5; constexpr double polytropic_constant = 1.5;
const mean_field::eos::Polytrope barotrope(polytropic_index, const Polytrope barotrope(polytropic_index, polytropic_constant);
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) { constexpr std::array<densityV, 5> densities{
const double pressure = barotrope.pressure_from_density(density); 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 = const enthalpyV enthalpy = evaluate<quantity::SpecificEnthalpy>(barotrope, density);
barotrope.density_from_enthalpy(enthalpy); const densityV reconstructed_density = evaluate<quantity::Density>(barotrope, enthalpy);
const double reconstructed_pressure = const pressureV reconstructed_pressure = evaluate<quantity::Pressure>(barotrope, enthalpy);
barotrope.pressure_from_enthalpy(enthalpy);
const double reconstructed_enthalpy = const enthalpyV reconstructed_enthalpy = evaluate<quantity::SpecificEnthalpy>(barotrope, pressure);
barotrope.enthalpy_from_pressure(pressure);
CHECK_THAT(reconstructed_density, CHECK_THAT(reconstructed_density.value(), Catch::Matchers::WithinRel(density.value(), 2.0e-14));
Catch::Matchers::WithinRel(density, 2.0e-14));
CHECK_THAT(reconstructed_pressure, CHECK_THAT(reconstructed_pressure.value(), Catch::Matchers::WithinRel(pressure.value(), 2.0e-14));
Catch::Matchers::WithinRel(pressure, 2.0e-14));
CHECK_THAT(reconstructed_enthalpy, CHECK_THAT(reconstructed_enthalpy.value(), Catch::Matchers::WithinRel(enthalpy.value(), 2.0e-14));
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( CHECK_THAT(
barotrope.pressure_derivative_from_density(density), pressure.value(),
Catch::Matchers::WithinRel(enthalpy / polytropic_index, 2.0e-14)); 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", TEST_CASE(
tags::barotrope_eos_jacobian) { "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 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) { CHECK(
const double step = 1.0e-6 * std::max(1.0, enthalpy); 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 = CHECK(
(barotrope.density_from_enthalpy(enthalpy + step) - (mean_field::eos::partialDerivative<
barotrope.density_from_enthalpy(enthalpy - step)) / mean_field::eos::quantity::Density, mean_field::eos::quantity::SpecificEnthalpy>(
(2.0 * step); barotrope, mean_field::eos::SpecificEnthalpyValue{-1.0}
)
.value() == 0.0)
);
const double pressure_difference = CHECK(
(barotrope.pressure_from_enthalpy(enthalpy + step) - (mean_field::eos::partialDerivative<
barotrope.pressure_from_enthalpy(enthalpy - step)) / mean_field::eos::quantity::Density, mean_field::eos::quantity::SpecificEnthalpy>(
(2.0 * step); barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
)
.value() == 0.0)
);
CHECK_THAT( CHECK(
density_difference, (mean_field::eos::partialDerivative<
Catch::Matchers::WithinRel( mean_field::eos::quantity::Pressure, mean_field::eos::quantity::SpecificEnthalpy>(
barotrope.density_derivative_from_enthalpy(enthalpy), 5.0e-10)); barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
)
CHECK_THAT( .value() == 0.0)
pressure_difference, );
Catch::Matchers::WithinRel(
barotrope.pressure_derivative_from_enthalpy(enthalpy), 5.0e-10));
}
} }
TEST_CASE("Polytropic EOS Has An Exact Zero Density Surface", TEST_CASE(
tags::barotrope_eos_unit) { "Polytropic EOS Rejects Invalid Material Parameters",
const mean_field::eos::Polytrope barotrope(3.0, 1.5); tags::barotrope_eos_unit
) {
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) {
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.5, 1.0), std::invalid_argument); CHECK_THROWS_AS(mean_field::eos::Polytrope(0.5, 1.0), std::invalid_argument);
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument); CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument);
CHECK_THROWS_AS( CHECK_THROWS_AS(mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0), std::invalid_argument);
mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0),
std::invalid_argument);
const mean_field::eos::Polytrope barotrope(3.0, 1.0); const mean_field::eos::Polytrope barotrope(3.0, 1.0);
CHECK_THROWS_AS(barotrope.pressure_from_density(-1.0), std::domain_error); CHECK_THROWS_AS(
mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(barotrope, mean_field::eos::DensityValue{-1.0}),
std::domain_error
);
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-1.0), std::domain_error); CHECK_THROWS_AS(
mean_field::eos::evaluate<mean_field::eos::quantity::SpecificEnthalpy>(
barotrope, mean_field::eos::DensityValue{-1.0}
),
std::domain_error
);
CHECK_THROWS_AS(barotrope.enthalpy_from_pressure(-1.0), std::domain_error); CHECK_THROWS_AS(
mean_field::eos::evaluate<mean_field::eos::quantity::SpecificEnthalpy>(
barotrope, mean_field::eos::PressureValue{-1.0}
),
std::domain_error
);
} }

View File

@@ -13,22 +13,27 @@
import mean_field; import mean_field;
import test_helpers; import test_helpers;
namespace eos = mean_field::eos;
namespace polytropic_eos_test_utils { namespace polytropic_eos_test_utils {
template <typename Function> template <typename Function>
double centered_derivative(Function &&function, const double position, double centered_derivative(
const double step) { Function &&function,
const double position,
const double step
) {
return (function(position + step) - function(position - step)) / (2.0 * step); return (function(position + step) - function(position - step)) / (2.0 * step);
} }
template <typename Integrand> template <typename Integrand>
double integrate_cube(const mfem::IntegrationRule &integrationRule, double integrate_cube(
Integrand &&integrand) { const mfem::IntegrationRule &integrationRule,
Integrand &&integrand
) {
double integral = 0.0; double integral = 0.0;
for (int pointIndex = 0; pointIndex < integrationRule.GetNPoints(); for (int pointIndex = 0; pointIndex < integrationRule.GetNPoints(); ++pointIndex) {
++pointIndex) { const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(pointIndex);
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(pointIndex);
integral += integrationPoint.weight * integrand(integrationPoint); integral += integrationPoint.weight * integrand(integrationPoint);
} }
@@ -37,8 +42,10 @@ double integrate_cube(const mfem::IntegrationRule &integrationRule,
} }
} // namespace polytropic_eos_test_utils } // namespace polytropic_eos_test_utils
TEST_CASE("Polytropic EOS Satisfies Its Thermodynamic Identities", TEST_CASE(
tags::barotrope_eos_unit) { "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, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 4> densities{1.0e-4, 0.02, 0.37, 2.4}; 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) { for (const double polytropicIndex : polytropicIndices) {
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) { DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
const mean_field::eos::Polytrope barotrope(polytropicIndex, const mean_field::eos::Polytrope barotrope(polytropicIndex, polytropicConstant);
polytropicConstant);
const double expectedEnthalpyScale = const double expectedEnthalpyScale = (polytropicIndex + 1.0) * polytropicConstant;
(polytropicIndex + 1.0) * polytropicConstant;
CHECK(barotrope.polytropic_index() == polytropicIndex); CHECK(barotrope.polytropic_index() == polytropicIndex);
@@ -62,45 +67,41 @@ TEST_CASE("Polytropic EOS Satisfies Its Thermodynamic Identities",
for (const double density : densities) { for (const double density : densities) {
CAPTURE(polytropicIndex, polytropicConstant, density); CAPTURE(polytropicIndex, polytropicConstant, density);
const double expectedPressure = const double expectedPressure = polytropicConstant * std::pow(density, 1.0 + 1.0 / polytropicIndex);
polytropicConstant * std::pow(density, 1.0 + 1.0 / polytropicIndex);
const double expectedEnthalpy = const double expectedEnthalpy = expectedEnthalpyScale * std::pow(density, 1.0 / polytropicIndex);
expectedEnthalpyScale * std::pow(density, 1.0 / polytropicIndex);
const double pressureFromDensity = const double pressureFromDensity =
barotrope.pressure_from_density(density); eos::evaluate<eos::quantity::Pressure>(barotrope, eos::DensityValue{density}).value();
const double enthalpyFromDensity = const double enthalpyFromDensity =
barotrope.enthalpy_from_density(density); eos::evaluate<eos::quantity::SpecificEnthalpy>(barotrope, eos::DensityValue{density}).value();
const double recoveredDensity = const double recoveredDensity =
barotrope.density_from_enthalpy(enthalpyFromDensity); eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{enthalpyFromDensity})
.value();
const double pressureFromEnthalpy = const double pressureFromEnthalpy =
barotrope.pressure_from_enthalpy(enthalpyFromDensity); eos::evaluate<eos::quantity::Pressure>(barotrope, eos::SpecificEnthalpyValue{enthalpyFromDensity})
.value();
CHECK_THAT(pressureFromDensity, CHECK_THAT(pressureFromDensity, Catch::Matchers::WithinRel(expectedPressure, 2.0e-13));
Catch::Matchers::WithinRel(expectedPressure, 2.0e-13));
CHECK_THAT(enthalpyFromDensity, CHECK_THAT(enthalpyFromDensity, Catch::Matchers::WithinRel(expectedEnthalpy, 2.0e-13));
Catch::Matchers::WithinRel(expectedEnthalpy, 2.0e-13));
CHECK_THAT(recoveredDensity, CHECK_THAT(recoveredDensity, Catch::Matchers::WithinRel(density, 5.0e-13));
Catch::Matchers::WithinRel(density, 5.0e-13));
CHECK_THAT(pressureFromEnthalpy, CHECK_THAT(pressureFromEnthalpy, Catch::Matchers::WithinRel(expectedPressure, 5.0e-13));
Catch::Matchers::WithinRel(expectedPressure, 5.0e-13));
/* /*
* Polytropic identity: * Polytropic identity:
* *
* P = rho h / (n + 1). * P = rho h / (n + 1).
*/ */
CHECK_THAT(pressureFromEnthalpy, CHECK_THAT(
Catch::Matchers::WithinRel(density * enthalpyFromDensity / pressureFromEnthalpy,
(polytropicIndex + 1.0), Catch::Matchers::WithinRel(density * enthalpyFromDensity / (polytropicIndex + 1.0), 5.0e-13)
5.0e-13)); );
/* /*
* Polytropic identity: * Polytropic identity:
@@ -108,11 +109,14 @@ TEST_CASE("Polytropic EOS Satisfies Its Thermodynamic Identities",
* dP / dh = rho. * dP / dh = rho.
* *
* The implementation should return the same * The implementation should return the same
* value as density_from_enthalpy(). * value as the density from specific enthalpy relation.
*/ */
CHECK( CHECK(
barotrope.pressure_derivative_from_enthalpy(enthalpyFromDensity) == (eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
barotrope.density_from_enthalpy(enthalpyFromDensity)); barotrope, eos::SpecificEnthalpyValue{enthalpyFromDensity}
)
.value() == recoveredDensity)
);
/* /*
* Since * Since
@@ -123,16 +127,22 @@ TEST_CASE("Polytropic EOS Satisfies Its Thermodynamic Identities",
* *
* dP / d rho = h / n. * dP / d rho = h / n.
*/ */
CHECK_THAT(barotrope.pressure_derivative_from_density(density), CHECK_THAT(
Catch::Matchers::WithinRel( (eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(
enthalpyFromDensity / polytropicIndex, 5.0e-13)); barotrope, eos::DensityValue{density}
)
.value()),
Catch::Matchers::WithinRel(enthalpyFromDensity / polytropicIndex, 5.0e-13)
);
} }
} }
} }
} }
TEST_CASE("Polytropic EOS Pressure Derivatives Match Centered Differences", TEST_CASE(
tags::barotrope_eos_jacobian) { "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> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 3> positiveValues{0.2, 0.73, 1.8}; 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; constexpr double polytropicConstant = 0.61;
for (const double polytropicIndex : polytropicIndices) { for (const double polytropicIndex : polytropicIndices) {
const mean_field::eos::Polytrope barotrope(polytropicIndex, const mean_field::eos::Polytrope barotrope(polytropicIndex, polytropicConstant);
polytropicConstant);
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) { DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
for (const double enthalpy : positiveValues) { for (const double enthalpy : positiveValues) {
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy)); const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
const double numericalDerivative = const double numericalDerivative = polytropic_eos_test_utils::centered_derivative(
polytropic_eos_test_utils::centered_derivative(
[&barotrope](const double perturbedEnthalpy) { [&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 = 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, CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative, analyticDerivative);
analyticDerivative);
CHECK_THAT(numericalDerivative, CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
} }
for (const double density : positiveValues) { for (const double density : positiveValues) {
const double step = 2.0e-6 * std::max(1.0, std::abs(density)); const double step = 2.0e-6 * std::max(1.0, std::abs(density));
const double numericalDerivative = const double numericalDerivative = polytropic_eos_test_utils::centered_derivative(
polytropic_eos_test_utils::centered_derivative(
[&barotrope](const double perturbedDensity) { [&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 = 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, CAPTURE(polytropicIndex, density, step, numericalDerivative, analyticDerivative);
analyticDerivative);
CHECK_THAT(numericalDerivative, CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
} }
} }
} }
} }
TEST_CASE("Polytropic EOS Density Derivative Matches Centered Differences", TEST_CASE(
tags::barotrope_eos_jacobian) { "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> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 3> enthalpies{0.2, 0.73, 1.8}; 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; constexpr double polytropicConstant = 0.61;
for (const double polytropicIndex : polytropicIndices) { for (const double polytropicIndex : polytropicIndices) {
const mean_field::eos::Polytrope barotrope(polytropicIndex, const mean_field::eos::Polytrope barotrope(polytropicIndex, polytropicConstant);
polytropicConstant);
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) { DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
for (const double enthalpy : enthalpies) { for (const double enthalpy : enthalpies) {
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy)); const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
const double numericalDerivative = const double numericalDerivative = polytropic_eos_test_utils::centered_derivative(
polytropic_eos_test_utils::centered_derivative(
[&barotrope](const double perturbedEnthalpy) { [&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 = 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, CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative, analyticDerivative);
analyticDerivative);
CHECK_THAT(numericalDerivative, CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
} }
} }
} }
} }
TEST_CASE("Polytropic EOS Defines Consistent Surface And Exterior Behavior", TEST_CASE(
tags::barotrope_eos_unit) { "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 std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr double polytropicConstant = 0.47; constexpr double polytropicConstant = 0.47;
constexpr double exteriorEnthalpy = -0.3; constexpr double exteriorEnthalpy = -0.3;
for (const double polytropicIndex : polytropicIndices) { for (const double polytropicIndex : polytropicIndices) {
const mean_field::eos::Polytrope barotrope(polytropicIndex, const mean_field::eos::Polytrope barotrope(polytropicIndex, polytropicConstant);
polytropicConstant);
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) { DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
/* /*
* Exact surface values. * 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. * 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) == CHECK(
0.0); (eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{exteriorEnthalpy}
)
.value() == 0.0)
);
CHECK(barotrope.pressure_derivative_from_enthalpy(exteriorEnthalpy) == CHECK(
0.0); (eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{exteriorEnthalpy}
)
.value() == 0.0)
);
/* /*
* At h = 0, rho(h) has a nonzero right * 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 = const double expectedSurfaceDensityDerivative =
polytropicIndex == 1.0 ? 1.0 / barotrope.enthalpy_scale() : 0.0; polytropicIndex == 1.0 ? 1.0 / barotrope.enthalpy_scale() : 0.0;
CHECK(barotrope.density_derivative_from_enthalpy(0.0) == CHECK(
expectedSurfaceDensityDerivative); (eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{0.0}
)
.value() == expectedSurfaceDensityDerivative)
);
} }
} }
} }
TEST_CASE("Polytropic EOS Rejects Invalid Physical Inputs", TEST_CASE(
tags::barotrope_eos_unit) { "Polytropic EOS Rejects Invalid Physical Inputs",
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.999, 1.0), tags::barotrope_eos_unit
std::invalid_argument); ) {
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.999, 1.0), std::invalid_argument);
CHECK_THROWS_AS( CHECK_THROWS_AS(mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0), std::invalid_argument);
mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0),
std::invalid_argument);
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument); CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument);
@@ -291,37 +339,55 @@ TEST_CASE("Polytropic EOS Rejects Invalid Physical Inputs",
const mean_field::eos::Polytrope barotrope(3.0, 0.75); 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), CHECK_THROWS_AS(
std::domain_error); (eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(barotrope, eos::DensityValue{-0.1})),
std::domain_error
);
constexpr std::array<double, 3> nonfiniteValues{ constexpr std::array<double, 3> nonfiniteValues{
std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(), std::numeric_limits<double>::quiet_NaN()
std::numeric_limits<double>::quiet_NaN()}; };
for (const double nonfiniteValue : nonfiniteValues) { for (const double nonfiniteValue : nonfiniteValues) {
CAPTURE(nonfiniteValue); CAPTURE(nonfiniteValue);
CHECK_THROWS_AS(barotrope.density_from_enthalpy(nonfiniteValue), CHECK_THROWS_AS(
std::domain_error); eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{nonfiniteValue}),
std::domain_error
);
CHECK_THROWS_AS(barotrope.pressure_from_enthalpy(nonfiniteValue), CHECK_THROWS_AS(
std::domain_error); eos::evaluate<eos::quantity::Pressure>(barotrope, eos::SpecificEnthalpyValue{nonfiniteValue}),
std::domain_error
);
CHECK_THROWS_AS(barotrope.density_derivative_from_enthalpy(nonfiniteValue), CHECK_THROWS_AS(
std::domain_error); (eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{nonfiniteValue}
)),
std::domain_error
);
CHECK_THROWS_AS(barotrope.pressure_derivative_from_enthalpy(nonfiniteValue), CHECK_THROWS_AS(
std::domain_error); (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", TEST_CASE(
tags::barotrope_pressure_quadrature_unit) { "Pressure Force And Pressure Integral Have Distinct Registered Forms",
tags::barotrope_pressure_quadrature_unit
) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>; 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. * beyond the registered enthalpy operand.
*/ */
constexpr int enthalpyOrder = constexpr int enthalpyOrder = mean_field::field::Enthalpy::Scalar::familyOrder;
mean_field::field::Enthalpy::Scalar::familyOrder;
constexpr int pressureExtraOrder = 3 * enthalpyOrder; constexpr int pressureExtraOrder = 3 * enthalpyOrder;
constexpr int geometryWeightOrder = 2; constexpr int geometryWeightOrder = 2;
constexpr mean_field::quadrature::Query pressureIntegralQuery = constexpr mean_field::quadrature::Query pressureIntegralQuery =
EnthalpyField::make_query< EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::field::Enthalpy::Form::PressureIntegral>( mean_field::quadrature::QuadratureRole::diagnostic, geometryWeightOrder,
mean_field::quadrature::QuadratureRole::diagnostic, std::array<int, 1>{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR,
geometryWeightOrder, std::array<int, 1>{pressureExtraOrder}, mean_field::quadrature::MappingKind::general
mean_field::utils::DOMAINS::STELLAR, );
mean_field::quadrature::MappingKind::general);
constexpr mean_field::quadrature::Query pressureForceQuery = constexpr mean_field::quadrature::Query pressureForceQuery =
EnthalpyField::make_query< EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::field::Enthalpy::Form::PressureForce>( mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder,
mean_field::quadrature::QuadratureRole::discretization, std::array<int, 1>{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR,
geometryWeightOrder, std::array<int, 1>{pressureExtraOrder}, mean_field::quadrature::MappingKind::general
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( STATIC_CHECK(
mean_field::field::Enthalpy::Form::PressureIntegral::dynamicOrderCount == mean_field::field::Enthalpy::Form::PressureIntegral::policyKey !=
1); mean_field::field::Enthalpy::Form::PressureForce::policyKey
);
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);
REQUIRE(pressureIntegralQuery.base_order.has_value()); 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(*pressureForceQuery.base_order == 16);
CHECK(pressureIntegralQuery.term == CHECK(pressureIntegralQuery.term == mean_field::quadrature::Term::pressure_integral);
mean_field::quadrature::Term::pressure_integral);
CHECK(pressureForceQuery.term == CHECK(pressureForceQuery.term == mean_field::quadrature::Term::pressure_force);
mean_field::quadrature::Term::pressure_force);
CHECK(pressureIntegralQuery.role == CHECK(pressureIntegralQuery.role == mean_field::quadrature::QuadratureRole::diagnostic);
mean_field::quadrature::QuadratureRole::diagnostic);
CHECK(pressureForceQuery.role == CHECK(pressureForceQuery.role == mean_field::quadrature::QuadratureRole::discretization);
mean_field::quadrature::QuadratureRole::discretization);
CHECK(pressureIntegralQuery.domain == mean_field::utils::DOMAINS::STELLAR); CHECK(pressureIntegralQuery.domain == mean_field::utils::DOMAINS::STELLAR);
@@ -415,19 +473,16 @@ TEST_CASE("Pressure Force And Pressure Integral Have Distinct Registered Forms",
* controls. * controls.
*/ */
mean_field::quadrature::RuleSet ruleSet = mean_field::quadrature::RuleSet ruleSet =
mean_field::quadrature::make_rule_set( mean_field::quadrature::make_rule_set(mean_field::quadrature::Mode::production);
mean_field::quadrature::Mode::production);
ruleSet.pressure_integral.boost = 3; ruleSet.pressure_integral.boost = 3;
ruleSet.pressure_force.boost = 5; ruleSet.pressure_force.boost = 5;
const mean_field::quadrature::Policy policy(std::move(ruleSet)); const mean_field::quadrature::Policy policy(std::move(ruleSet));
const mean_field::quadrature::Resolution pressureIntegralResolution = const mean_field::quadrature::Resolution pressureIntegralResolution = policy.resolve(pressureIntegralQuery);
policy.resolve(pressureIntegralQuery);
const mean_field::quadrature::Resolution pressureForceResolution = const mean_field::quadrature::Resolution pressureForceResolution = policy.resolve(pressureForceQuery);
policy.resolve(pressureForceQuery);
CHECK(pressureIntegralResolution.base_order == 14); CHECK(pressureIntegralResolution.base_order == 14);
@@ -442,12 +497,13 @@ TEST_CASE("Pressure Force And Pressure Integral Have Distinct Registered Forms",
CHECK(pressureForceResolution.order == 21); CHECK(pressureForceResolution.order == 21);
} }
TEST_CASE("Pressure Quadrature Exactly Integrates An N Three Polynomial", TEST_CASE(
tags::barotrope_pressure_quadrature_accuracy) { "Pressure Quadrature Exactly Integrates An N Three Polynomial",
tags::barotrope_pressure_quadrature_accuracy
) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>; using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
constexpr int enthalpyOrder = constexpr int enthalpyOrder = mean_field::field::Enthalpy::Scalar::familyOrder;
mean_field::field::Enthalpy::Scalar::familyOrder;
constexpr int pressureExtraOrder = 3 * enthalpyOrder; 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); const mean_field::eos::Polytrope barotrope(3.0, 0.25);
constexpr mean_field::quadrature::Query pressureIntegralQuery = constexpr mean_field::quadrature::Query pressureIntegralQuery =
EnthalpyField::make_query< EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::field::Enthalpy::Form::PressureIntegral>( mean_field::quadrature::QuadratureRole::diagnostic, 0, std::array<int, 1>{pressureExtraOrder},
mean_field::quadrature::QuadratureRole::diagnostic, 0, mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::affine
std::array<int, 1>{pressureExtraOrder}, );
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::affine);
constexpr mean_field::quadrature::Query pressureForceQuery = constexpr mean_field::quadrature::Query pressureForceQuery =
EnthalpyField::make_query< EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::field::Enthalpy::Form::PressureForce>( mean_field::quadrature::QuadratureRole::discretization, 0, std::array<int, 1>{pressureExtraOrder},
mean_field::quadrature::QuadratureRole::discretization, 0, mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::affine
std::array<int, 1>{pressureExtraOrder}, );
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::affine);
const mean_field::quadrature::RuleFactory ruleFactory{ const mean_field::quadrature::RuleFactory ruleFactory{
mean_field::quadrature::Policy(mean_field::quadrature::make_rule_set( mean_field::quadrature::Policy(mean_field::quadrature::make_rule_set(mean_field::quadrature::Mode::production))
mean_field::quadrature::Mode::production))}; };
const mean_field::quadrature::MfemRule pressureIntegralRule = const mean_field::quadrature::MfemRule pressureIntegralRule =
ruleFactory.get(pressureIntegralQuery, mfem::Geometry::CUBE); 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. * P = x^12 y^12 z^12 / 4.
*/ */
const double numericalPressureIntegral = const double numericalPressureIntegral = polytropic_eos_test_utils::integrate_cube(
polytropic_eos_test_utils::integrate_cube( *pressureIntegralRule.integration_rule, [&barotrope](const mfem::IntegrationPoint &integrationPoint) {
*pressureIntegralRule.integration_rule, const double coordinateProduct = integrationPoint.x * integrationPoint.y * integrationPoint.z;
[&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct =
integrationPoint.x * integrationPoint.y * integrationPoint.z;
const double enthalpy = std::pow(coordinateProduct, 3.0); 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); 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) * -P div(w)
* = -x^14 y^14 z^14 / 4. * = -x^14 y^14 z^14 / 4.
*/ */
const double numericalPressureForceIntegral = const double numericalPressureForceIntegral = polytropic_eos_test_utils::integrate_cube(
polytropic_eos_test_utils::integrate_cube( *pressureForceRule.integration_rule, [&barotrope](const mfem::IntegrationPoint &integrationPoint) {
*pressureForceRule.integration_rule, const double coordinateProduct = integrationPoint.x * integrationPoint.y * integrationPoint.z;
[&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct =
integrationPoint.x * integrationPoint.y * integrationPoint.z;
const double enthalpy = std::pow(coordinateProduct, 3.0); 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 = const double testDivergence = integrationPoint.x * integrationPoint.x * integrationPoint.y *
integrationPoint.x * integrationPoint.x * integrationPoint.y *
integrationPoint.y * integrationPoint.z * integrationPoint.z; integrationPoint.y * integrationPoint.z * integrationPoint.z;
return -pressure * testDivergence; return -pressure * testDivergence;
}); }
);
const double analyticPressureForceIntegral = -0.25 / std::pow(15.0, 3.0); const double analyticPressureForceIntegral = -0.25 / std::pow(15.0, 3.0);
INFO("Pressure-integral quadrature order = " INFO("Pressure-integral quadrature order = " << pressureIntegralRule.resolution.order);
<< pressureIntegralRule.resolution.order);
INFO("Pressure-force quadrature order = " INFO("Pressure-force quadrature order = " << pressureForceRule.resolution.order);
<< pressureForceRule.resolution.order);
INFO("Numerical pressure integral = " << numericalPressureIntegral); INFO("Numerical pressure integral = " << numericalPressureIntegral);
INFO("Analytic pressure integral = " << analyticPressureIntegral); INFO("Analytic pressure integral = " << analyticPressureIntegral);
INFO( INFO("Numerical pressure-force integral = " << numericalPressureForceIntegral);
"Numerical pressure-force integral = " << numericalPressureForceIntegral);
INFO("Analytic pressure-force integral = " << analyticPressureForceIntegral); 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(pressureForceRule.resolution.order == 14);
CHECK_THAT(numericalPressureIntegral, CHECK_THAT(numericalPressureIntegral, Catch::Matchers::WithinAbs(analyticPressureIntegral, 5.0e-14));
Catch::Matchers::WithinAbs(analyticPressureIntegral, 5.0e-14));
CHECK_THAT( CHECK_THAT(numericalPressureForceIntegral, Catch::Matchers::WithinAbs(analyticPressureForceIntegral, 5.0e-14));
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{}; std::array<char, N> chars{};
// ReSharper disable once CppNonExplicitConvertingConstructor // ReSharper disable once CppNonExplicitConvertingConstructor
consteval Tag(std::array<char, N> arr) : chars(arr) {} consteval Tag(
std::array<
char,
N> arr
)
: chars(arr) {
}
// ReSharper disable once CppNonExplicitConversionOperator // ReSharper disable once CppNonExplicitConversionOperator
constexpr operator const char *() const { return chars.data(); } constexpr operator const char *() const {
return chars.data();
}
// ReSharper disable once CppNonExplicitConversionOperator // ReSharper disable once CppNonExplicitConversionOperator
constexpr operator Catch::StringRef() const { constexpr operator Catch::StringRef() const {
return Catch::StringRef(chars.data(), N - 1); return Catch::StringRef(chars.data(), N - 1);
} }
template <std::size_t M> template <std::size_t M> consteval Tag<N + M - 1> operator&(const Tag<M> &other) const {
consteval Tag<N + M - 1> operator&(const Tag<M> &other) const {
std::array<char, N + M - 1> res{}; std::array<char, N + M - 1> res{};
std::ranges::copy(chars.begin(), chars.end() - 1, res.begin()); std::ranges::copy(chars.begin(), chars.end() - 1, res.begin());
std::ranges::copy(other.chars, res.begin() + (N - 1)); std::ranges::copy(other.chars, res.begin() + (N - 1));
@@ -44,8 +51,13 @@ template <std::size_t N> consteval auto make_tag(const char (&str)[N]) {
return Tag<N + 2>{res}; return Tag<N + 2>{res};
} }
template <std::size_t N, std::size_t M> template <
consteval auto sub_tag(const Tag<N> &parent, const char (&str)[M]) { std::size_t N,
std::size_t M>
consteval auto sub_tag(
const Tag<N> &parent,
const char (&str)[M]
) {
return parent & make_tag(str); return parent & make_tag(str);
} }
@@ -78,56 +90,53 @@ mean_field::utils::Args setup_args() {
export namespace gravity_prepared_test_utils { export namespace gravity_prepared_test_utils {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
template <typename FieldT> template <typename FieldT> inline mean_field::field::FieldDofMap make_field_map(const mean_field::fem::FEM &f) {
inline mean_field::field::FieldDofMap
make_field_map(const mean_field::fem::FEM &f) {
if constexpr (std::same_as<FieldT, mean_field::field::Density>) { if constexpr (std::same_as<FieldT, mean_field::field::Density>) {
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>( return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(*f.densityFes);
*f.densityFes);
} else if constexpr (std::same_as<FieldT, mean_field::field::Displacement>) { } else if constexpr (std::same_as<FieldT, mean_field::field::Displacement>) {
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>( return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(*f.displacementFes);
*f.displacementFes);
} else { } else {
static_assert(std::same_as<FieldT, mean_field::field::Gravity>); static_assert(std::same_as<FieldT, mean_field::field::Gravity>);
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>( return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(*f.gravityFluxFes);
*f.gravityFluxFes);
} }
} }
template <typename FieldT> template <typename FieldT>
inline mfem::Vector gather_field(const mean_field::fem::FEM &f, inline mfem::Vector gather_field(
const mfem::Vector &true_vector) { const mean_field::fem::FEM &f,
const mfem::Vector &true_vector
) {
return make_field_map<FieldT>(f).gather(true_vector); return make_field_map<FieldT>(f).gather(true_vector);
} }
inline mfem::Vector make_deterministic_vector(const int size, inline mfem::Vector make_deterministic_vector(
const double phase = 0.0) { const int size,
const double phase = 0.0
) {
mfem::Vector vector(size); mfem::Vector vector(size);
for (int i = 0; i < size; ++i) { for (int i = 0; i < size; ++i) {
const double index = static_cast<double>(i + 1); const double index = static_cast<double>(i + 1);
vector(i) = std::sin(0.37 * index + phase) + vector(i) = std::sin(0.37 * index + phase) + 0.31 * std::cos(0.19 * index - 0.5 * phase);
0.31 * std::cos(0.19 * index - 0.5 * phase);
} }
return vector; return vector;
} }
inline mfem::Vector make_displacement(const mean_field::fem::FEM &f, inline mfem::Vector make_displacement(
const double scale) { const mean_field::fem::FEM &f,
const double scale
) {
mfem::ParGridFunction displacement(f.displacementFes.get()); mfem::ParGridFunction displacement(f.displacementFes.get());
auto displacement_function = [scale](const mfem::Vector &position, auto displacement_function = [scale](const mfem::Vector &position, mfem::Vector &value) {
mfem::Vector &value) {
value.SetSize(3); value.SetSize(3);
value(0) = scale * (0.04 * position(0) + 0.01 * position(1) * position(2)); value(0) = scale * (0.04 * position(0) + 0.01 * position(1) * position(2));
value(1) = value(1) = scale * (-0.03 * position(1) + 0.008 * position(0) * position(2));
scale * (-0.03 * position(1) + 0.008 * position(0) * position(2));
value(2) = scale * (0.02 * position(2) - 0.006 * position(0) * position(1)); value(2) = scale * (0.02 * position(2) - 0.006 * position(0) * position(1));
}; };
mfem::VectorFunctionCoefficient coefficient(f.mesh->Dimension(), mfem::VectorFunctionCoefficient coefficient(f.mesh->Dimension(), displacement_function);
displacement_function);
displacement.ProjectCoefficient(coefficient); displacement.ProjectCoefficient(coefficient);
mfem::Vector displacement_true; mfem::Vector displacement_true;
@@ -135,18 +144,19 @@ inline mfem::Vector make_displacement(const mean_field::fem::FEM &f,
return displacement_true; return displacement_true;
} }
inline mfem::Vector make_domain_supported_density(const mean_field::fem::FEM &f, inline mfem::Vector make_domain_supported_density(
const bool stellar) { const mean_field::fem::FEM &f,
const bool stellar
) {
mfem::Vector attribute_values(f.mesh->attributes.Max()); mfem::Vector attribute_values(f.mesh->attributes.Max());
attribute_values = 0.0; attribute_values = 0.0;
using DomainSchema = using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
for (int i = 0; i < f.mesh->attributes.Size(); ++i) { for (int i = 0; i < f.mesh->attributes.Size(); ++i) {
const int attribute = f.mesh->attributes[i]; const int attribute = f.mesh->attributes[i];
const bool is_stellar = DomainSchema::template attribute_belongs_to< const bool is_stellar =
mean_field::utils::domain::Stellar>(attribute); DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Stellar>(attribute);
if (is_stellar == stellar) { if (is_stellar == stellar) {
attribute_values(attribute - 1) = 1.0; 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; 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 double first_scale,
const mfem::Vector &second, const mfem::Vector &second,
const double second_scale) { const double second_scale
MFEM_VERIFY(first.Size() == second.Size(), ) {
"Cannot combine vectors with different sizes."); MFEM_VERIFY(first.Size() == second.Size(), "Cannot combine vectors with different sizes.");
mfem::Vector combination(first); mfem::Vector combination(first);
combination *= first_scale; combination *= first_scale;
@@ -175,18 +186,22 @@ inline mfem::Vector linear_combination(const mfem::Vector &first,
return combination; 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; const double local_norm_squared = vector * vector;
double global_norm_squared = 0.0; double global_norm_squared = 0.0;
MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator);
MPI_SUM, communicator);
return std::sqrt(global_norm_squared); return std::sqrt(global_norm_squared);
} }
inline double global_dot(const mfem::Vector &first, const mfem::Vector &second, inline double global_dot(
MPI_Comm communicator) { const mfem::Vector &first,
MFEM_VERIFY(first.Size() == second.Size(), const mfem::Vector &second,
"Cannot take the dot product of vectors with different sizes."); 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; const double local_dot = first * second;
double global_dot = 0.0; double global_dot = 0.0;
@@ -194,24 +209,25 @@ inline double global_dot(const mfem::Vector &first, const mfem::Vector &second,
return global_dot; return global_dot;
} }
inline double relative_error(const mfem::Vector &computed, inline double relative_error(
const mfem::Vector &computed,
const mfem::Vector &reference, const mfem::Vector &reference,
MPI_Comm communicator) { MPI_Comm communicator
MFEM_VERIFY(computed.Size() == reference.Size(), ) {
"Cannot compare vectors with different sizes."); MFEM_VERIFY(computed.Size() == reference.Size(), "Cannot compare vectors with different sizes.");
mfem::Vector difference(computed); mfem::Vector difference(computed);
difference -= reference; difference -= reference;
return global_norm(difference, communicator) / return global_norm(difference, communicator) /
std::max(global_norm(reference, communicator), std::max(global_norm(reference, communicator), std::numeric_limits<double>::epsilon());
std::numeric_limits<double>::epsilon());
} }
inline double relative_scalar_error(const double computed, inline double relative_scalar_error(
const double reference) { const double computed,
return std::abs(computed - reference) / const double reference
std::max(std::abs(reference), std::numeric_limits<double>::epsilon()); ) {
return std::abs(computed - reference) / std::max(std::abs(reference), std::numeric_limits<double>::epsilon());
} }
} // namespace gravity_prepared_test_utils } // namespace gravity_prepared_test_utils
@@ -222,47 +238,46 @@ inline mean_field::mapping::DomainMapper make_domain_mapper() {
const mean_field::utils::Args args = test_utils::setup_args(); const mean_field::utils::Args args = test_utils::setup_args();
return mean_field::mapping::DomainMapper( return mean_field::mapping::DomainMapper(
args.domain_mapper_options, args.domain_mapper_options,
std::make_unique<const mean_field::mapping::compactification:: std::make_unique<const mean_field::mapping::compactification::KelvinCompactification>(args.kelvin_options)
KelvinCompactification>(args.kelvin_options)); );
} }
inline constexpr int vacuum_material_attribute = inline constexpr int vacuum_material_attribute =
DomainSchema::template material_attribute< DomainSchema::template material_attribute<mean_field::utils::domain::Vacuum>();
mean_field::utils::domain::Vacuum>();
template <typename FieldT> template <typename FieldT>
inline mean_field::field::FieldDofMap inline mean_field::field::FieldDofMap make_map(const mfem::ParFiniteElementSpace &finiteElementSpace) {
make_map(const mfem::ParFiniteElementSpace &finiteElementSpace) { return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(finiteElementSpace);
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(
finiteElementSpace);
} }
template <typename FieldT> template <typename FieldT>
inline mfem::Vector make_deterministic_supported_vector( inline mfem::Vector make_deterministic_supported_vector(
const mfem::ParFiniteElementSpace &finiteElementSpace, const double phase) { const mfem::ParFiniteElementSpace &finiteElementSpace,
const mean_field::field::FieldDofMap map = const double phase
make_map<FieldT>(finiteElementSpace); ) {
const mfem::Vector full = const mean_field::field::FieldDofMap map = make_map<FieldT>(finiteElementSpace);
gravity_prepared_test_utils::make_deterministic_vector(map.full_size(), const mfem::Vector full = gravity_prepared_test_utils::make_deterministic_vector(map.full_size(), phase);
phase);
return map.gather(full); return map.gather(full);
} }
inline mfem::Vector make_supported_displacement(const mean_field::fem::FEM &f, inline mfem::Vector make_supported_displacement(
const double phase) { const mean_field::fem::FEM &f,
const mean_field::field::FieldDofMap map = const double phase
make_map<mean_field::field::Displacement>(*f.displacementFes); ) {
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)); return map.gather(gravity_prepared_test_utils::make_displacement(f, phase));
} }
inline void apply_hydrostatic_reference( inline void apply_hydrostatic_reference(
const mean_field::fem::FEM &f, const mean_field::fem::FEM &f,
const mean_field::physics::RigidRotation &rotation, const mean_field::physics::RigidRotation &rotation,
const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential, const mfem::Vector &enthalpy,
const mfem::Vector &displacement, const double bernoulliConstant, const mfem::Vector &gravityPotential,
mfem::Vector &residual) { const mfem::Vector &displacement,
const mean_field::field::FieldDofMap enthalpyMap = const double bernoulliConstant,
make_map<mean_field::field::Enthalpy>(*f.enthalpyFes); mfem::Vector &residual
) {
const mean_field::field::FieldDofMap enthalpyMap = make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
const mean_field::field::FieldDofMap gravityPotentialMap = const mean_field::field::FieldDofMap gravityPotentialMap =
make_map<mean_field::field::Gravity>(*f.gravityPotentialFes); make_map<mean_field::field::Gravity>(*f.gravityPotentialFes);
const mean_field::field::FieldDofMap displacementMap = const mean_field::field::FieldDofMap displacementMap =
@@ -278,8 +293,9 @@ inline void apply_hydrostatic_reference(
displacementMap.scatter(displacement, displacementTrue); displacementMap.scatter(displacement, displacementTrue);
mean_field::operators::kernels::apply_hydrostatic_equilibrium( mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpyTrue, gravityPotentialTrue, f, *f.domainMapperStateless, rotation, enthalpyTrue, gravityPotentialTrue, displacementTrue,
displacementTrue, bernoulliConstant, residualTrue); bernoulliConstant, residualTrue
);
residual.SetSize(enthalpyMap.reduced_size()); residual.SetSize(enthalpyMap.reduced_size());
enthalpyMap.gather(residualTrue, residual); enthalpyMap.gather(residualTrue, residual);
@@ -315,21 +331,15 @@ inline constexpr auto hydro = sub_tag(physics, "hydro");
inline constexpr auto jacobian = sub_tag(integration & physics, "jacobian"); inline constexpr auto jacobian = sub_tag(integration & physics, "jacobian");
inline constexpr auto residuals = sub_tag(integration & physics, "residuals"); inline constexpr auto residuals = sub_tag(integration & physics, "residuals");
inline constexpr auto volume = sub_tag(mesh & geometry, "volume"); inline constexpr auto volume = sub_tag(mesh & geometry, "volume");
inline constexpr auto quadrature = inline constexpr auto quadrature = sub_tag(mesh & geometry & solver, "quadrature");
sub_tag(mesh & geometry & solver, "quadrature");
inline constexpr auto convergence = sub_tag(solver, "convergence"); inline constexpr auto convergence = sub_tag(solver, "convergence");
inline constexpr auto transformations = inline constexpr auto transformations = sub_tag(mesh & geometry, "transformations");
sub_tag(mesh & geometry, "transformations");
inline constexpr auto h_refinement = inline constexpr auto h_refinement = sub_tag(mesh & convergence, "h_refinement");
sub_tag(mesh & convergence, "h_refinement"); inline constexpr auto p_refinement = sub_tag(mesh & convergence, "p_refinement");
inline constexpr auto p_refinement =
sub_tag(mesh & convergence, "p_refinement");
inline constexpr auto analytic_comparison = inline constexpr auto analytic_comparison = sub_tag(solver & physics & residuals, "analytic_comparison");
sub_tag(solver & physics & residuals, "analytic_comparison"); inline constexpr auto self_consistency = sub_tag(solver & physics, "self_consistency");
inline constexpr auto self_consistency =
sub_tag(solver & physics, "self_consistency");
inline constexpr auto centrifugal = sub_tag(solver & physics, "centrifugal"); inline constexpr auto centrifugal = sub_tag(solver & physics, "centrifugal");
inline constexpr auto advection = sub_tag(solver & physics, "advection"); inline constexpr auto advection = sub_tag(solver & physics, "advection");
@@ -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 gravity = sub_tag(solver & physics, "gravity");
inline constexpr auto enthalpy = sub_tag(solver & physics, "enthalpy"); inline constexpr auto enthalpy = sub_tag(solver & physics, "enthalpy");
inline constexpr auto barotrope = sub_tag(physics, "barotrope"); inline constexpr auto barotrope = sub_tag(physics, "barotrope");
inline constexpr auto mass_continuity = inline constexpr auto mass_continuity = sub_tag(solver & physics, "mass_continuity");
sub_tag(solver & physics, "mass_continuity"); inline constexpr auto pressure_gradient = sub_tag(solver & physics, "pressure_gradient");
inline constexpr auto pressure_gradient =
sub_tag(solver & physics, "pressure_gradient");
inline constexpr auto viscosity = sub_tag(solver & physics, "viscosity"); inline constexpr auto viscosity = sub_tag(solver & physics, "viscosity");
inline constexpr auto compactification = inline constexpr auto compactification = sub_tag(mesh & mapping, "compactification");
sub_tag(mesh & mapping, "compactification");
inline constexpr auto kelvin = sub_tag(compactification, "kelvin"); inline constexpr auto kelvin = sub_tag(compactification, "kelvin");
inline constexpr auto mapping_evaluator = inline constexpr auto mapping_evaluator = mapping & make_tag("grid_function_evaluator");
mapping & make_tag("grid_function_evaluator");
inline constexpr auto mapping_evaluator_unit = mapping_evaluator & unit; inline constexpr auto mapping_evaluator_unit = mapping_evaluator & unit;
inline constexpr auto prepared = sub_tag(solver & physics, "prepared"); inline constexpr auto prepared = sub_tag(solver & physics, "prepared");
@@ -366,103 +372,110 @@ inline constexpr auto gravity_context = gravity & make_tag("context");
inline constexpr auto gravity_kernel = gravity & kernels; inline constexpr auto gravity_kernel = gravity & kernels;
inline constexpr auto gravity_accuracy = gravity & accuracy; inline constexpr auto gravity_accuracy = gravity & accuracy;
inline constexpr auto gravity_operator_unit = gravity_operator & unit; inline constexpr auto gravity_operator_unit = gravity_operator & unit;
inline constexpr auto gravity_operator_integration = inline constexpr auto gravity_operator_integration = gravity_operator & integration;
gravity_operator & integration; inline constexpr auto gravity_operator_convergence = gravity_operator & integration & make_tag("convergence");
inline constexpr auto gravity_operator_convergence = inline constexpr auto gravity_analytic = gravity & integration & make_tag("analytic_comparison");
gravity_operator & integration & make_tag("convergence"); inline constexpr auto gravity_consistency = gravity & integration & make_tag("self_consistency");
inline constexpr auto gravity_analytic = inline constexpr auto gravity_prepared_jacobian = gravity_prepared & integration & make_tag("jacobian");
gravity & integration & make_tag("analytic_comparison");
inline constexpr auto gravity_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_unit = gravity_prepared & unit;
inline constexpr auto gravity_prepared_jacobian_accuracy = inline constexpr auto gravity_prepared_jacobian_accuracy = gravity_prepared_jacobian & accuracy;
gravity_prepared_jacobian & accuracy;
inline constexpr auto gravity_kernel_accuracy = gravity_kernel & accuracy; inline constexpr auto gravity_kernel_accuracy = gravity_kernel & accuracy;
inline constexpr auto gravity_kernel_integration = gravity_kernel & integration; inline constexpr auto gravity_kernel_integration = gravity_kernel & integration;
inline constexpr auto gravity_kernel_convergence = inline constexpr auto gravity_kernel_convergence = gravity_kernel & integration & make_tag("convergence");
gravity_kernel & integration & make_tag("convergence");
inline constexpr auto gravity_analytic_accuracy = gravity_analytic & accuracy; inline constexpr auto gravity_analytic_accuracy = gravity_analytic & accuracy;
inline constexpr auto gravity_consistency_accuracy = inline constexpr auto gravity_consistency_accuracy = gravity_consistency & accuracy;
gravity_consistency & accuracy;
inline constexpr auto gravity_integrator_unit = gravity & integrator & unit; inline constexpr auto gravity_integrator_unit = gravity & integrator & unit;
inline constexpr auto barotrope_prepared = inline constexpr auto barotrope_prepared = barotrope & solver & make_tag("prepared");
barotrope & solver & make_tag("prepared");
inline constexpr auto barotrope_eos_unit = barotrope & unit & make_tag("eos"); inline constexpr auto barotrope_eos_unit = barotrope & unit & make_tag("eos");
inline constexpr auto barotrope_eos_jacobian = inline constexpr auto barotrope_eos_jacobian = barotrope_eos_unit & integration & make_tag("jacobian");
barotrope_eos_unit & integration & make_tag("jacobian"); inline constexpr auto polytropic_eos_characterization =
inline constexpr auto barotrope_pressure_quadrature = barotrope & unit & make_tag("eos") & make_tag("characterization");
barotrope & mesh & geometry & solver & make_tag("pressure") & 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"); make_tag("pressure_gradient") & make_tag("quadrature");
inline constexpr auto barotrope_pressure_quadrature_unit = inline constexpr auto barotrope_pressure_quadrature_unit = barotrope_pressure_quadrature & unit;
barotrope_pressure_quadrature & unit; inline constexpr auto barotrope_pressure_quadrature_accuracy = barotrope_pressure_quadrature & accuracy;
inline constexpr auto barotrope_pressure_quadrature_accuracy = inline constexpr auto barotrope_prepared_jacobian = barotrope_prepared & integration & make_tag("jacobian");
barotrope_pressure_quadrature & accuracy; inline constexpr auto barotrope_context = barotrope & solver & make_tag("context");
inline constexpr auto barotrope_prepared_jacobian = inline constexpr auto barotrope_context_integration = barotrope_context & integration;
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 = inline constexpr auto barotrope_prepared_analytic =
barotrope_prepared & integration & make_tag("analytic_comparison"); barotrope_prepared & integration & make_tag("analytic_comparison");
inline constexpr auto barotrope_prepared_jacobian_accuracy = inline constexpr auto barotrope_prepared_jacobian_accuracy = 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_geometry = inline constexpr auto barotrope_prepared_jacobian_unit = barotrope_prepared_jacobian & unit;
barotrope_prepared_jacobian & geometry;
inline constexpr auto barotrope_prepared_jacobian_unit =
barotrope_prepared_jacobian & unit;
// Canonical hydrostatic-suite tags. The leaf tags are composed directly // Canonical hydrostatic-suite tags. The leaf tags are composed directly
// so inherited [physics]/[solver] tags appear only once. // so inherited [physics]/[solver] tags appear only once.
inline constexpr auto barotrope_hydrostatic = inline constexpr auto barotrope_hydrostatic = barotrope & solver & make_tag("hydro");
barotrope & solver & make_tag("hydro"); inline constexpr auto barotrope_hydrostatic_context = barotrope_hydrostatic & make_tag("context");
inline constexpr auto barotrope_hydrostatic_context = inline constexpr auto barotrope_hydrostatic_prepared = barotrope_hydrostatic & make_tag("prepared");
barotrope_hydrostatic & make_tag("context");
inline constexpr auto barotrope_hydrostatic_prepared =
barotrope_hydrostatic & make_tag("prepared");
inline constexpr auto barotrope_hydrostatic_prepared_residual = inline constexpr auto barotrope_hydrostatic_prepared_residual =
barotrope_hydrostatic_prepared & integration & make_tag("residual"); barotrope_hydrostatic_prepared & integration & make_tag("residual");
inline constexpr auto barotrope_hydrostatic_prepared_jacobian = inline constexpr auto barotrope_hydrostatic_prepared_jacobian =
barotrope_hydrostatic_prepared & integration & make_tag("jacobian"); barotrope_hydrostatic_prepared & integration & make_tag("jacobian");
inline constexpr auto barotrope_hydrostatic_prepared_analytic = inline constexpr auto barotrope_hydrostatic_prepared_analytic =
barotrope_hydrostatic_prepared & integration & barotrope_hydrostatic_prepared & integration & make_tag("analytic_comparison");
make_tag("analytic_comparison");
inline constexpr auto barotrope_mass_normalization = inline constexpr auto barotrope_mass_normalization = barotrope & solver & make_tag("mass_normalization");
barotrope & solver & make_tag("mass_normalization"); inline constexpr auto barotrope_mass_normalization_context = barotrope_mass_normalization & make_tag("context");
inline constexpr auto barotrope_mass_normalization_context = inline constexpr auto barotrope_mass_normalization_prepared = barotrope_mass_normalization & make_tag("prepared");
barotrope_mass_normalization & make_tag("context");
inline constexpr auto barotrope_mass_normalization_prepared =
barotrope_mass_normalization & make_tag("prepared");
inline constexpr auto barotrope_mass_normalization_jacobian = inline constexpr auto barotrope_mass_normalization_jacobian =
barotrope_mass_normalization_prepared & integration & make_tag("jacobian"); barotrope_mass_normalization_prepared & integration & make_tag("jacobian");
inline constexpr auto barotrope_mass_normalization_analytic = inline constexpr auto barotrope_mass_normalization_analytic =
barotrope_mass_normalization_prepared & integration & barotrope_mass_normalization_prepared & integration & make_tag("analytic_comparison");
make_tag("analytic_comparison");
inline constexpr auto rotation_prepared = centrifugal & make_tag("prepared"); inline constexpr auto rotation_prepared = centrifugal & make_tag("prepared");
inline constexpr auto rotation_context = centrifugal & make_tag("context"); inline constexpr auto rotation_context = centrifugal & make_tag("context");
inline constexpr auto rotation_analytic = inline constexpr auto rotation_analytic = centrifugal & integration & make_tag("analytic_comparison");
centrifugal & integration & make_tag("analytic_comparison");
inline constexpr auto rotation_context_unit = rotation_context & unit; inline constexpr auto rotation_context_unit = rotation_context & unit;
inline constexpr auto rotation_prepared_unit = rotation_prepared & unit; inline constexpr auto rotation_prepared_unit = rotation_prepared & unit;
inline constexpr auto rotation_prepared_jacobian = inline constexpr auto rotation_prepared_jacobian = rotation_prepared & integration & make_tag("jacobian");
rotation_prepared & integration & make_tag("jacobian"); inline constexpr auto rotation_prepared_jacobian_accuracy = rotation_prepared_jacobian & accuracy;
inline constexpr auto rotation_prepared_jacobian_accuracy = inline constexpr auto rotation_kernel_accuracy = centrifugal & kernels & 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_unit = centrifugal & integrator & unit;
inline constexpr auto rotation_integrator_integration = inline constexpr auto rotation_integrator_integration = centrifugal & integrator & integration;
centrifugal & integrator & integration;
inline constexpr auto rotation_integrator_convergence = inline constexpr auto rotation_integrator_convergence =
rotation_integrator_integration & convergence & h_refinement; rotation_integrator_integration & convergence & h_refinement;
inline constexpr auto rotation_analytic_unit = rotation_analytic & unit; inline constexpr auto rotation_analytic_unit = rotation_analytic & unit;
inline constexpr auto rotation_analytic_accuracy = rotation_analytic & accuracy; inline constexpr auto rotation_analytic_accuracy = rotation_analytic & accuracy;
inline constexpr auto rotation_analytic_accuracy_geometry = inline constexpr auto rotation_analytic_accuracy_geometry = rotation_analytic_accuracy & geometry;
rotation_analytic_accuracy & geometry;
} // namespace tags } // namespace tags

View File

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

File diff suppressed because it is too large Load Diff