feat(surface): major work on implementing surface constraints in a presciption agnostic manner
This commit is contained in:
@@ -94,6 +94,65 @@ namespace field_dof_map_test_utils {
|
||||
domain::RelationList<>>;
|
||||
} // namespace field_dof_map_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Field Boundary DOF Map Selects The Stellar Surface In Reduced Field Ordering",
|
||||
tags::surface_boundary_dof_topology
|
||||
) {
|
||||
namespace domain = mean_field::utils::domain;
|
||||
namespace field = mean_field::field;
|
||||
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(f.okay());
|
||||
|
||||
const field::FieldDofMap enthalpyMap =
|
||||
field::make_field_dof_map<field::Enthalpy, field_dof_map_test_utils::Schema>(*f.enthalpyFes);
|
||||
const field::FieldBoundaryDofMap stellarSurface =
|
||||
field::make_field_boundary_dof_map<field::Enthalpy, domain::StellarSurface, field_dof_map_test_utils::Schema>(
|
||||
*f.enthalpyFes, enthalpyMap
|
||||
);
|
||||
CHECK(stellarSurface.field_size() == enthalpyMap.reduced_size());
|
||||
CHECK(field_dof_map_test_utils::global_sum(stellarSurface.size()) > 0);
|
||||
CHECK(
|
||||
field_dof_map_test_utils::global_sum(stellarSurface.size()) <
|
||||
field_dof_map_test_utils::global_sum(enthalpyMap.reduced_size())
|
||||
);
|
||||
|
||||
for (const int reducedDof : stellarSurface.reduced_dofs()) {
|
||||
CAPTURE(reducedDof);
|
||||
CHECK(stellarSurface.contains(reducedDof));
|
||||
CHECK(enthalpyMap.contains_true_dof(enthalpyMap.true_dof(reducedDof)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Field Point DOF Map Selects One Vector Vertex At The Computational Origin",
|
||||
tags::translational_centering_topology
|
||||
) {
|
||||
namespace field = mean_field::field;
|
||||
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(f.okay());
|
||||
|
||||
const field::FieldDofMap displacementMap =
|
||||
field::make_field_dof_map<field::Displacement, field_dof_map_test_utils::Schema>(*f.displacementFes);
|
||||
mfem::Vector origin(f.mesh->SpaceDimension());
|
||||
origin = 0.0;
|
||||
|
||||
const field::FieldPointDofMap centerRows =
|
||||
field::make_field_point_dof_map<field::Displacement>(*f.displacementFes, displacementMap, origin, 1.0e-12);
|
||||
|
||||
CHECK(centerRows.field_size() == displacementMap.reduced_size());
|
||||
CHECK(field_dof_map_test_utils::global_sum(centerRows.size()) == f.mesh->SpaceDimension());
|
||||
|
||||
for (const int reducedDof : centerRows.reduced_dofs()) {
|
||||
CAPTURE(reducedDof);
|
||||
CHECK(centerRows.contains(reducedDof));
|
||||
CHECK(displacementMap.contains_true_dof(displacementMap.true_dof(reducedDof)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Field DOF Map Preserves Canonical Bidirectional Indexing",
|
||||
tags::field_dof_unit
|
||||
@@ -523,9 +582,7 @@ TEST_CASE(
|
||||
|
||||
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Displacement>);
|
||||
|
||||
STATIC_REQUIRE_FALSE(
|
||||
field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::BarotropicConstant>
|
||||
);
|
||||
STATIC_REQUIRE_FALSE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::BarotropicConstant>);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
@@ -750,9 +807,8 @@ TEST_CASE(
|
||||
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
|
||||
|
||||
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
|
||||
auto finiteElementSpace =
|
||||
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
|
||||
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
|
||||
auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
|
||||
|
||||
REQUIRE(finiteElementSpace != nullptr);
|
||||
|
||||
@@ -796,9 +852,8 @@ TEST_CASE(
|
||||
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
|
||||
|
||||
auto fec = field::Field<field::Enthalpy>::make_fec<field::Enthalpy::Scalar>(2);
|
||||
auto finiteElementSpace =
|
||||
field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
|
||||
auto fec = field::Field<field::Enthalpy>::make_fec<field::Enthalpy::Scalar>(2);
|
||||
auto finiteElementSpace = field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
|
||||
|
||||
REQUIRE(finiteElementSpace != nullptr);
|
||||
|
||||
@@ -849,9 +904,8 @@ TEST_CASE(
|
||||
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
|
||||
|
||||
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
|
||||
auto finiteElementSpace =
|
||||
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
|
||||
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
|
||||
auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
|
||||
|
||||
REQUIRE(finiteElementSpace != nullptr);
|
||||
|
||||
@@ -900,9 +954,8 @@ TEST_CASE(
|
||||
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
|
||||
|
||||
auto fec = field::Field<field::Displacement>::make_fec<field::Displacement::Vector>(2);
|
||||
auto finiteElementSpace =
|
||||
field::Field<field::Displacement>::make_fespace<field::Displacement::Vector>(mesh, *fec);
|
||||
auto fec = field::Field<field::Displacement>::make_fec<field::Displacement::Vector>(2);
|
||||
auto finiteElementSpace = field::Field<field::Displacement>::make_fespace<field::Displacement::Vector>(mesh, *fec);
|
||||
|
||||
REQUIRE(finiteElementSpace != nullptr);
|
||||
|
||||
@@ -939,13 +992,11 @@ TEST_CASE(
|
||||
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
|
||||
|
||||
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
|
||||
auto finiteElementSpace =
|
||||
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
|
||||
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
|
||||
auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
|
||||
|
||||
auto otherFec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
|
||||
auto otherFiniteElementSpace =
|
||||
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *otherFec);
|
||||
auto otherFec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
|
||||
auto otherFiniteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *otherFec);
|
||||
|
||||
REQUIRE(finiteElementSpace != nullptr);
|
||||
REQUIRE(otherFiniteElementSpace != nullptr);
|
||||
@@ -959,8 +1010,7 @@ TEST_CASE(
|
||||
const mfem::Array<int> empty;
|
||||
CHECK_THROWS_AS(
|
||||
(field::FieldDofGridFunctionAdapter(
|
||||
field::FieldDofMap(finiteElementSpace->GetTrueVSize() + 1, empty),
|
||||
*finiteElementSpace
|
||||
field::FieldDofMap(finiteElementSpace->GetTrueVSize() + 1, empty), *finiteElementSpace
|
||||
)),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
@@ -11,7 +11,10 @@ using namespace mean_field;
|
||||
namespace {
|
||||
struct SerialMappingData {
|
||||
explicit SerialMappingData(mfem::Mesh &mesh)
|
||||
: compactification_fes(&mesh, &compactification_fec),
|
||||
: compactification_fes(
|
||||
&mesh,
|
||||
&compactification_fec
|
||||
),
|
||||
compactification_coordinate(&compactification_fes),
|
||||
mapper(field_dof_test_utils::make_domain_mapper()) {
|
||||
compactification_coordinate = 0.0;
|
||||
@@ -83,7 +86,7 @@ TEST_CASE(
|
||||
quadrature::RuleFactory quadrature_factory(std::move(policy));
|
||||
|
||||
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
|
||||
const int position_order = displacement_element->GetOrder();
|
||||
const int position_order = displacement_element->GetOrder();
|
||||
|
||||
quadrature_factory.configure_centrifugal(
|
||||
integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation,
|
||||
@@ -183,7 +186,7 @@ TEST_CASE(
|
||||
quadrature::RuleFactory quadrature_factory(std::move(policy));
|
||||
|
||||
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
|
||||
const int position_order = displacement_element->GetOrder();
|
||||
const int position_order = displacement_element->GetOrder();
|
||||
|
||||
quadrature_factory.configure_centrifugal(
|
||||
integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation,
|
||||
@@ -344,7 +347,7 @@ TEST_CASE(
|
||||
quadrature::RuleFactory quadrature_factory(std::move(policy));
|
||||
|
||||
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
|
||||
const int position_order = displacement_element->GetOrder();
|
||||
const int position_order = displacement_element->GetOrder();
|
||||
|
||||
quadrature_factory.configure_centrifugal(
|
||||
integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation,
|
||||
@@ -606,8 +609,8 @@ TEST_CASE(
|
||||
mapping_evaluator.GetQuadratureContext(*transformation, integration_point);
|
||||
const double signed_map_determinant = context.detJ;
|
||||
|
||||
local_minimum_map_determinant = std::min(local_minimum_map_determinant, signed_map_determinant);
|
||||
local_maximum_map_determinant = std::max(local_maximum_map_determinant, signed_map_determinant);
|
||||
local_minimum_map_determinant = std::min(local_minimum_map_determinant, signed_map_determinant);
|
||||
local_maximum_map_determinant = std::max(local_maximum_map_determinant, signed_map_determinant);
|
||||
|
||||
mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
|
||||
velocity_element->CalcShape(integration_point, velocity_shape);
|
||||
@@ -808,7 +811,7 @@ TEST_CASE(
|
||||
mapping_evaluator.GetQuadratureContext(*transformation, integration_point);
|
||||
const double signed_map_determinant = context.detJ;
|
||||
|
||||
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
|
||||
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
|
||||
|
||||
mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
|
||||
velocity_element->CalcShape(integration_point, velocity_shape);
|
||||
@@ -999,7 +1002,7 @@ TEST_CASE(
|
||||
mapping_evaluator.GetQuadratureContext(*transformation, integration_point);
|
||||
const double signed_map_determinant = context.detJ;
|
||||
|
||||
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
|
||||
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
|
||||
|
||||
mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
|
||||
velocity_element->CalcShape(integration_point, velocity_shape);
|
||||
|
||||
@@ -43,8 +43,8 @@ TEST_CASE(
|
||||
mfem::GridFunction displacement(&displacement_fes);
|
||||
displacement = 0.0;
|
||||
mfem::GridFunction compactification_coordinate(&compactification_fes);
|
||||
compactification_coordinate = 0.0;
|
||||
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
|
||||
compactification_coordinate = 0.0;
|
||||
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
|
||||
|
||||
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
|
||||
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
|
||||
@@ -129,8 +129,7 @@ TEST_CASE(
|
||||
element_residual[displacement_block] = &displacement_residual;
|
||||
|
||||
integrators::GravityMomentumIntegrator integrator(
|
||||
domain_mapper, displacement, compactification_coordinate,
|
||||
integrators::GravityForceJacobianMode::field_coupled
|
||||
domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
|
||||
);
|
||||
|
||||
const int maximum_order = std::max(
|
||||
@@ -297,12 +296,10 @@ TEST_CASE(
|
||||
mfem::GridFunction displacement(&displacement_fes);
|
||||
displacement = 0.0;
|
||||
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::GridFunctionMappingEvaluator mapping_evaluator(
|
||||
domain_mapper, displacement, compactification_coordinate
|
||||
);
|
||||
mapping::GridFunctionMappingEvaluator mapping_evaluator(domain_mapper, displacement, compactification_coordinate);
|
||||
|
||||
auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); };
|
||||
|
||||
@@ -381,8 +378,7 @@ TEST_CASE(
|
||||
element_residual[displacement_block] = &displacement_residual;
|
||||
|
||||
integrators::GravityMomentumIntegrator integrator(
|
||||
domain_mapper, displacement, compactification_coordinate,
|
||||
integrators::GravityForceJacobianMode::field_coupled
|
||||
domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
|
||||
);
|
||||
|
||||
const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8);
|
||||
@@ -471,11 +467,9 @@ TEST_CASE(
|
||||
mfem::GridFunction displacement(&displacement_fes);
|
||||
displacement = 0.0;
|
||||
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::GridFunctionMappingEvaluator mapping_evaluator(
|
||||
domain_mapper, displacement, compactification_coordinate
|
||||
);
|
||||
mapping::GridFunctionMappingEvaluator mapping_evaluator(domain_mapper, displacement, compactification_coordinate);
|
||||
|
||||
auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) {
|
||||
gravity.SetSize(3);
|
||||
@@ -553,8 +547,7 @@ TEST_CASE(
|
||||
element_residual[displacement_block] = &displacement_residual;
|
||||
|
||||
integrators::GravityMomentumIntegrator integrator(
|
||||
domain_mapper, displacement, compactification_coordinate,
|
||||
integrators::GravityForceJacobianMode::field_coupled
|
||||
domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled
|
||||
);
|
||||
|
||||
const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
@@ -12,17 +14,11 @@ import test_helpers;
|
||||
namespace {
|
||||
struct StellarModelExtensionTracker final {
|
||||
int structureValidationCount{0};
|
||||
int surfaceValidationCount{0};
|
||||
int surfaceResolutionCount{0};
|
||||
|
||||
const mean_field::eos::EquationOfState *structureEquationOfState{nullptr};
|
||||
|
||||
const mean_field::eos::EquationOfState *surfaceValidationEquationOfState{nullptr};
|
||||
|
||||
const mean_field::eos::EquationOfState *surfaceResolutionEquationOfState{nullptr};
|
||||
const mean_field::eos::Polytrope *structureEquationOfState{nullptr};
|
||||
};
|
||||
|
||||
class StellarModelTestStructure final : public mean_field::models::structure::StructureBase {
|
||||
class StellarModelTestStructure final {
|
||||
public:
|
||||
explicit StellarModelTestStructure(std::shared_ptr<StellarModelExtensionTracker> tracker)
|
||||
: m_tracker(std::move(tracker)),
|
||||
@@ -32,17 +28,17 @@ namespace {
|
||||
) {
|
||||
}
|
||||
|
||||
[[nodiscard]] const mean_field::eos::EquationOfState &equationOfState() const noexcept override {
|
||||
[[nodiscard]] const mean_field::eos::Polytrope &equationOfState() const noexcept {
|
||||
m_tracker->structureEquationOfState = &m_equationOfState;
|
||||
return m_equationOfState;
|
||||
}
|
||||
|
||||
[[nodiscard]] double targetMass() const noexcept override {
|
||||
[[nodiscard]] double targetMass() const noexcept {
|
||||
return 2.5;
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::models::structure::StructureSeed
|
||||
makeInitialSeed(const mean_field::models::structure::StructureSeedRequest &request) const override {
|
||||
makeInitialSeed(const mean_field::models::structure::StructureSeedRequest &request) const {
|
||||
mean_field::models::structure::StructureSeed seed;
|
||||
|
||||
seed.radius.SetSize(2);
|
||||
@@ -65,7 +61,7 @@ namespace {
|
||||
return seed;
|
||||
}
|
||||
|
||||
void validate() const override {
|
||||
void validate() const {
|
||||
++m_tracker->structureValidationCount;
|
||||
}
|
||||
|
||||
@@ -74,59 +70,85 @@ namespace {
|
||||
mean_field::eos::Polytrope m_equationOfState;
|
||||
};
|
||||
|
||||
class StellarModelTestSurface final : public mean_field::surface::SurfaceBase {
|
||||
class StructureWithoutSeed final {
|
||||
public:
|
||||
explicit StellarModelTestSurface(std::shared_ptr<StellarModelExtensionTracker> tracker)
|
||||
: m_tracker(std::move(tracker)) {
|
||||
}
|
||||
[[nodiscard]] const mean_field::eos::Polytrope &equationOfState() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
mean_field::surface::ResolvedSurfaceCondition
|
||||
resolve(const mean_field::eos::EquationOfState &equationOfState) const override {
|
||||
++m_tracker->surfaceResolutionCount;
|
||||
[[nodiscard]] double targetMass() const noexcept;
|
||||
|
||||
m_tracker->surfaceResolutionEquationOfState = &equationOfState;
|
||||
|
||||
return mean_field::surface::ResolvedSurfaceCondition{0.375};
|
||||
}
|
||||
|
||||
void validate(const mean_field::eos::EquationOfState &equationOfState) const override {
|
||||
++m_tracker->surfaceValidationCount;
|
||||
|
||||
m_tracker->surfaceValidationEquationOfState = &equationOfState;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<StellarModelExtensionTracker> m_tracker;
|
||||
void validate() const;
|
||||
};
|
||||
|
||||
class SurfaceWithoutPhysicalQuantity final { };
|
||||
|
||||
struct ModelSurfaceState final {
|
||||
double specificEnthalpy;
|
||||
|
||||
[[nodiscard]] mean_field::eos::SpecificEnthalpyValue
|
||||
value(mean_field::eos::quantity::SpecificEnthalpy) const noexcept {
|
||||
return mean_field::eos::SpecificEnthalpyValue{specificEnthalpy};
|
||||
}
|
||||
};
|
||||
|
||||
using PolytropicStellarModel = mean_field::models::StellarModel<mean_field::models::structure::PolytropicStructure>;
|
||||
|
||||
using ExtensionStellarModel = mean_field::models::StellarModel<StellarModelTestStructure>;
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Model Owns Structure And Surface Prescriptions",
|
||||
tags::barotrope &tags::unit &tags::model
|
||||
tags::stellar_model_type_contract
|
||||
) {
|
||||
STATIC_REQUIRE_FALSE(std::is_copy_constructible_v<mean_field::models::StellarModel>);
|
||||
STATIC_CHECK(mean_field::models::StructurePrescription<mean_field::models::structure::PolytropicStructure>);
|
||||
STATIC_CHECK(mean_field::models::StructurePrescription<StellarModelTestStructure>);
|
||||
STATIC_CHECK_FALSE(mean_field::models::StructurePrescription<StructureWithoutSeed>);
|
||||
|
||||
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<mean_field::models::StellarModel>);
|
||||
STATIC_CHECK(
|
||||
mean_field::models::SurfacePrescription<
|
||||
mean_field::surface::ConstantPressureSurface, mean_field::eos::Polytrope>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::models::SurfacePrescription<SurfaceWithoutPhysicalQuantity, mean_field::eos::Polytrope>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(std::is_nothrow_move_constructible_v<mean_field::models::StellarModel>);
|
||||
STATIC_CHECK_FALSE(std::derived_from<StellarModelTestStructure, mean_field::models::structure::StructureBase>);
|
||||
STATIC_CHECK_FALSE(
|
||||
std::derived_from<
|
||||
mean_field::models::structure::PolytropicStructure, mean_field::models::structure::StructureBase>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
decltype(std::declval<const mean_field::models::structure::StructureBase &>().equationOfState()),
|
||||
mean_field::eos::EquationOfStateView>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(std::is_nothrow_move_assignable_v<mean_field::models::StellarModel>);
|
||||
STATIC_REQUIRE_FALSE(std::is_copy_constructible_v<PolytropicStellarModel>);
|
||||
|
||||
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<PolytropicStellarModel>);
|
||||
|
||||
STATIC_REQUIRE(std::is_nothrow_move_constructible_v<PolytropicStellarModel>);
|
||||
|
||||
STATIC_REQUIRE(std::is_nothrow_move_assignable_v<PolytropicStellarModel>);
|
||||
|
||||
mean_field::models::StellarModel model{
|
||||
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
|
||||
mean_field::surface::Isobaric{0.0}
|
||||
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
|
||||
};
|
||||
|
||||
CHECK(model.targetMass() == 1.0);
|
||||
CHECK(model.resolvedSurfaceCondition().targetEnthalpy == 0.0);
|
||||
|
||||
CHECK(
|
||||
dynamic_cast<const mean_field::models::structure::PolytropicStructure *>(&model.structurePrescription()) !=
|
||||
nullptr
|
||||
STATIC_CHECK(std::same_as<decltype(model), PolytropicStellarModel>);
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
decltype(model.structurePrescription()), const mean_field::models::structure::PolytropicStructure &>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
std::same_as<decltype(model.surfacePrescription()), const mean_field::surface::ConstantPressureSurface &>
|
||||
);
|
||||
STATIC_CHECK(std::same_as<decltype(model.equationOfState()), const mean_field::eos::Polytrope &>);
|
||||
|
||||
CHECK(dynamic_cast<const mean_field::surface::Isobaric *>(&model.surfacePrescription()) != nullptr);
|
||||
CHECK(model.targetMass() == 1.0);
|
||||
CHECK(model.compiledSurfaceConstraint().targetPressure() == mean_field::eos::PressureValue{0.0});
|
||||
CHECK(&model.equationOfState() == &model.structurePrescription().equationOfState());
|
||||
CHECK(model.surfacePrescription().targetPressure() == mean_field::eos::PressureValue{0.0});
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
@@ -135,7 +157,7 @@ TEST_CASE(
|
||||
) {
|
||||
mean_field::models::StellarModel model{
|
||||
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
|
||||
mean_field::surface::Isobaric{}
|
||||
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
|
||||
};
|
||||
|
||||
const mean_field::models::structure::StructureSeed seed =
|
||||
@@ -153,45 +175,49 @@ TEST_CASE(
|
||||
|
||||
TEST_CASE(
|
||||
"Moving A Stellar Model Preserves Stable Prescription Addresses",
|
||||
tags::barotrope &tags::unit &tags::model
|
||||
tags::barotrope &tags::unit &tags::model &tags::surface_constraint_lifetime
|
||||
) {
|
||||
mean_field::models::StellarModel originalModel{
|
||||
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
|
||||
mean_field::surface::Isobaric{}
|
||||
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
|
||||
};
|
||||
|
||||
const mean_field::models::structure::StructureBase *structureAddress = &originalModel.structurePrescription();
|
||||
const mean_field::models::structure::PolytropicStructure *structureAddress = &originalModel.structurePrescription();
|
||||
|
||||
const mean_field::surface::SurfaceBase *surfaceAddress = &originalModel.surfacePrescription();
|
||||
const mean_field::surface::ConstantPressureSurface *surfaceAddress = &originalModel.surfacePrescription();
|
||||
|
||||
const mean_field::eos::EquationOfState *equationOfStateAddress = &originalModel.equationOfState();
|
||||
const mean_field::eos::Polytrope *equationOfStateAddress = &originalModel.equationOfState();
|
||||
|
||||
const auto *compiledSurfaceConstraintAddress = &originalModel.compiledSurfaceConstraint();
|
||||
|
||||
mean_field::models::StellarModel movedModel{std::move(originalModel)};
|
||||
|
||||
CHECK(&movedModel.structurePrescription() == structureAddress);
|
||||
CHECK(&movedModel.surfacePrescription() == surfaceAddress);
|
||||
CHECK(&movedModel.equationOfState() == equationOfStateAddress);
|
||||
CHECK(&movedModel.compiledSurfaceConstraint() == compiledSurfaceConstraintAddress);
|
||||
CHECK(movedModel.targetMass() == 1.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Model Resolves A Positive Isobaric Surface",
|
||||
"Stellar Model Compiles A Positive Constant Pressure Surface",
|
||||
tags::barotrope &tags::unit &tags::model
|
||||
) {
|
||||
constexpr double targetPressure = 0.03125;
|
||||
|
||||
mean_field::models::StellarModel model{
|
||||
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
|
||||
mean_field::surface::Isobaric{targetPressure}
|
||||
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{targetPressure}}
|
||||
};
|
||||
|
||||
const double targetEnthalpy = model.resolvedSurfaceCondition().targetEnthalpy;
|
||||
const double requiredSpecificEnthalpy = mean_field::eos::evaluate<mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
model.equationOfState(), mean_field::eos::PressureValue{targetPressure}
|
||||
)
|
||||
.value();
|
||||
|
||||
CHECK(targetEnthalpy > 0.0);
|
||||
CHECK(
|
||||
std::abs(model.equationOfState().pressure_from_enthalpy(targetEnthalpy) - targetPressure) <
|
||||
64.0 * std::numeric_limits<double>::epsilon()
|
||||
);
|
||||
CHECK(requiredSpecificEnthalpy > 0.0);
|
||||
CHECK(model.compiledSurfaceConstraint().targetPressure() == mean_field::eos::PressureValue{targetPressure});
|
||||
CHECK(model.compiledSurfaceConstraint().residual(ModelSurfaceState{requiredSpecificEnthalpy}) == 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
@@ -200,27 +226,22 @@ TEST_CASE(
|
||||
) {
|
||||
const auto tracker = std::make_shared<StellarModelExtensionTracker>();
|
||||
|
||||
mean_field::models::StellarModel model{StellarModelTestStructure{tracker}, StellarModelTestSurface{tracker}};
|
||||
mean_field::models::StellarModel model{
|
||||
StellarModelTestStructure{tracker},
|
||||
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.375}}
|
||||
};
|
||||
|
||||
STATIC_CHECK(std::same_as<decltype(model), ExtensionStellarModel>);
|
||||
|
||||
REQUIRE(tracker->structureValidationCount == 1);
|
||||
REQUIRE(tracker->surfaceValidationCount == 1);
|
||||
REQUIRE(tracker->surfaceResolutionCount == 1);
|
||||
|
||||
CHECK(dynamic_cast<const StellarModelTestStructure *>(&model.structurePrescription()) != nullptr);
|
||||
|
||||
CHECK(dynamic_cast<const StellarModelTestSurface *>(&model.surfacePrescription()) != nullptr);
|
||||
|
||||
const mean_field::eos::EquationOfState *ownedEquationOfState = &model.equationOfState();
|
||||
const mean_field::eos::Polytrope *ownedEquationOfState = &model.equationOfState();
|
||||
|
||||
CHECK(tracker->structureEquationOfState == ownedEquationOfState);
|
||||
|
||||
CHECK(tracker->surfaceValidationEquationOfState == ownedEquationOfState);
|
||||
|
||||
CHECK(tracker->surfaceResolutionEquationOfState == ownedEquationOfState);
|
||||
|
||||
CHECK(model.targetMass() == 2.5);
|
||||
|
||||
CHECK(model.resolvedSurfaceCondition().targetEnthalpy == 0.375);
|
||||
CHECK(model.compiledSurfaceConstraint().targetPressure() == mean_field::eos::PressureValue{0.375});
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
@@ -229,23 +250,25 @@ TEST_CASE(
|
||||
) {
|
||||
mean_field::models::StellarModel sourceModel{
|
||||
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.25},
|
||||
mean_field::surface::Isobaric{0.0}
|
||||
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
|
||||
};
|
||||
|
||||
mean_field::models::StellarModel destinationModel{
|
||||
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{2.0, 0.5}, 4.0},
|
||||
mean_field::surface::Isobaric{0.02}
|
||||
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.02}}
|
||||
};
|
||||
|
||||
const mean_field::models::structure::StructureBase *sourceStructureAddress = &sourceModel.structurePrescription();
|
||||
const mean_field::models::structure::PolytropicStructure *sourceStructureAddress =
|
||||
&sourceModel.structurePrescription();
|
||||
|
||||
const mean_field::surface::SurfaceBase *sourceSurfaceAddress = &sourceModel.surfacePrescription();
|
||||
const mean_field::surface::ConstantPressureSurface *sourceSurfaceAddress = &sourceModel.surfacePrescription();
|
||||
|
||||
const mean_field::eos::EquationOfState *sourceEquationOfStateAddress = &sourceModel.equationOfState();
|
||||
const mean_field::eos::Polytrope *sourceEquationOfStateAddress = &sourceModel.equationOfState();
|
||||
|
||||
const double sourceTargetEnthalpy = sourceModel.resolvedSurfaceCondition().targetEnthalpy;
|
||||
const mean_field::eos::PressureValue sourceTargetPressure =
|
||||
sourceModel.compiledSurfaceConstraint().targetPressure();
|
||||
|
||||
destinationModel = std::move(sourceModel);
|
||||
destinationModel = std::move(sourceModel);
|
||||
|
||||
CHECK(&destinationModel.structurePrescription() == sourceStructureAddress);
|
||||
|
||||
@@ -255,5 +278,83 @@ TEST_CASE(
|
||||
|
||||
CHECK(destinationModel.targetMass() == 1.25);
|
||||
|
||||
CHECK(destinationModel.resolvedSurfaceCondition().targetEnthalpy == sourceTargetEnthalpy);
|
||||
}
|
||||
CHECK(destinationModel.compiledSurfaceConstraint().targetPressure() == sourceTargetPressure);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Model View Supports Heterogeneous Typed Models",
|
||||
tags::stellar_model_runtime_view
|
||||
) {
|
||||
const auto tracker = std::make_shared<StellarModelExtensionTracker>();
|
||||
|
||||
const mean_field::models::StellarModel polytropicModel{
|
||||
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
|
||||
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
|
||||
};
|
||||
|
||||
const mean_field::models::StellarModel extensionModel{
|
||||
StellarModelTestStructure{tracker},
|
||||
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.375}}
|
||||
};
|
||||
|
||||
STATIC_CHECK(std::is_trivially_copyable_v<mean_field::models::StellarModelView>);
|
||||
STATIC_CHECK_FALSE(std::constructible_from<mean_field::models::StellarModelView, PolytropicStellarModel &&>);
|
||||
|
||||
const std::array views{
|
||||
mean_field::models::StellarModelView{polytropicModel}, mean_field::models::StellarModelView{extensionModel}
|
||||
};
|
||||
|
||||
CHECK(views[0].targetMass() == 1.0);
|
||||
CHECK(views[1].targetMass() == 2.5);
|
||||
CHECK(views[1].surfaceCondition().targetPressure == 0.375);
|
||||
REQUIRE(views[1].surfaceDependencies().stateFields.size() == 1);
|
||||
CHECK(
|
||||
views[1].surfaceDependencies().residualRowField ==
|
||||
mean_field::surface::surfaceFieldId<mean_field::field::Enthalpy>
|
||||
);
|
||||
|
||||
const auto pressure =
|
||||
views[0].equationOfState().tryEvaluate<mean_field::eos::quantity::Pressure>(mean_field::eos::DensityValue{0.7});
|
||||
|
||||
REQUIRE(pressure.has_value());
|
||||
CHECK(
|
||||
pressure->value() == mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
|
||||
polytropicModel.equationOfState(), mean_field::eos::DensityValue{0.7}
|
||||
)
|
||||
.value()
|
||||
);
|
||||
|
||||
const mean_field::models::structure::StructureSeed seed =
|
||||
views[1].makeInitialSeed({.centralDensity = 1.75, .radialSampleCount = 2});
|
||||
|
||||
CHECK(seed.centralDensity == 1.75);
|
||||
CHECK(seed.radius.Size() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Model View Retains Stable Pointees When Its Owner Moves",
|
||||
tags::stellar_model_runtime_view
|
||||
) {
|
||||
mean_field::models::StellarModel originalModel{
|
||||
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
|
||||
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
|
||||
};
|
||||
|
||||
const mean_field::models::StellarModelView view{originalModel};
|
||||
PolytropicStellarModel movedModel{std::move(originalModel)};
|
||||
|
||||
const auto pressure =
|
||||
view.equationOfState().tryEvaluate<mean_field::eos::quantity::Pressure>(mean_field::eos::DensityValue{0.7});
|
||||
const mean_field::models::structure::StructureSeed seed =
|
||||
view.makeInitialSeed({.centralDensity = 1.0, .radialSampleCount = 8});
|
||||
|
||||
REQUIRE(pressure.has_value());
|
||||
CHECK(view.targetMass() == movedModel.targetMass());
|
||||
CHECK(
|
||||
pressure->value() == mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
|
||||
movedModel.equationOfState(), mean_field::eos::DensityValue{0.7}
|
||||
)
|
||||
.value()
|
||||
);
|
||||
CHECK(seed.radius.Size() == 8);
|
||||
}
|
||||
|
||||
@@ -164,9 +164,7 @@ TEST_CASE(
|
||||
CHECK_FALSE(gravityPotentialReport.updatedDisplacement);
|
||||
CHECK_FALSE(gravityPotentialReport.updatedBernoulliConstant);
|
||||
|
||||
CHECK(
|
||||
context.GetBaseGravityPotentialTrue()(context.GetGravityPotentialMap().true_dof(0)) == gravityPotential(0)
|
||||
);
|
||||
CHECK(context.GetBaseGravityPotentialTrue()(context.GetGravityPotentialMap().true_dof(0)) == gravityPotential(0));
|
||||
|
||||
++dependencies.bernoulliConstant.revision;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,386 +11,370 @@ import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace gravity_displacement_force_analytic_test_utils {
|
||||
struct AffineCase {
|
||||
const char *name;
|
||||
std::array<double, 3> scales;
|
||||
};
|
||||
struct AffineCase {
|
||||
const char *name;
|
||||
std::array<double, 3> scales;
|
||||
};
|
||||
|
||||
[[nodiscard]] double analytic_sphere_volume(const double radius) {
|
||||
return (4.0 / 3.0) * std::numbers::pi * radius * radius * radius;
|
||||
}
|
||||
[[nodiscard]] double analytic_sphere_volume(const double radius) {
|
||||
return (4.0 / 3.0) * std::numbers::pi * radius * radius * radius;
|
||||
}
|
||||
|
||||
[[nodiscard]] double determinant(const std::array<double, 3> &scales) {
|
||||
return scales[0] * scales[1] * scales[2];
|
||||
}
|
||||
[[nodiscard]] double determinant(
|
||||
const std::array<
|
||||
double,
|
||||
3> &scales
|
||||
) {
|
||||
return scales[0] * scales[1] * scales[2];
|
||||
}
|
||||
|
||||
[[nodiscard]] double relative_scalar_error(const double computed,
|
||||
const double expected) {
|
||||
return std::abs(computed - expected) / std::max(std::abs(expected), 1.0e-30);
|
||||
}
|
||||
[[nodiscard]] double relative_scalar_error(
|
||||
const double computed,
|
||||
const double expected
|
||||
) {
|
||||
return std::abs(computed - expected) / std::max(std::abs(expected), 1.0e-30);
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector make_constant_density(const mean_field::fem::FEM &f,
|
||||
const double densityValue) {
|
||||
mfem::ParGridFunction densityField(f.densityFes.get());
|
||||
mfem::ConstantCoefficient densityCoefficient(densityValue);
|
||||
densityField.ProjectCoefficient(densityCoefficient);
|
||||
[[nodiscard]] mfem::Vector make_constant_density(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double densityValue
|
||||
) {
|
||||
mfem::ParGridFunction densityField(f.densityFes.get());
|
||||
mfem::ConstantCoefficient densityCoefficient(densityValue);
|
||||
densityField.ProjectCoefficient(densityCoefficient);
|
||||
|
||||
mfem::Vector densityTrue;
|
||||
densityField.GetTrueDofs(densityTrue);
|
||||
return densityTrue;
|
||||
}
|
||||
mfem::Vector densityTrue;
|
||||
densityField.GetTrueDofs(densityTrue);
|
||||
return densityTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector
|
||||
make_reference_gravity(const mean_field::fem::FEM &f,
|
||||
const std::array<double, 3> &referenceGravity) {
|
||||
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
|
||||
[[nodiscard]] mfem::Vector make_reference_gravity(
|
||||
const mean_field::fem::FEM &f,
|
||||
const std::array<
|
||||
double,
|
||||
3> &referenceGravity
|
||||
) {
|
||||
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
|
||||
|
||||
mfem::VectorFunctionCoefficient gravityCoefficient(
|
||||
f.mesh->Dimension(),
|
||||
[referenceGravity](const mfem::Vector &, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
mfem::VectorFunctionCoefficient gravityCoefficient(
|
||||
f.mesh->Dimension(), [referenceGravity](const mfem::Vector &, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
value(component) =
|
||||
referenceGravity[static_cast<std::size_t>(component)];
|
||||
}
|
||||
});
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
value(component) = referenceGravity[static_cast<std::size_t>(component)];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
gravityField.ProjectCoefficient(gravityCoefficient);
|
||||
gravityField.ProjectCoefficient(gravityCoefficient);
|
||||
|
||||
mfem::Vector gravityTrue;
|
||||
gravityField.GetTrueDofs(gravityTrue);
|
||||
return gravityTrue;
|
||||
}
|
||||
mfem::Vector gravityTrue;
|
||||
gravityField.GetTrueDofs(gravityTrue);
|
||||
return gravityTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector make_radial_gravity(const mean_field::fem::FEM &f,
|
||||
const double radialCoefficient) {
|
||||
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
|
||||
[[nodiscard]] mfem::Vector make_radial_gravity(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double radialCoefficient
|
||||
) {
|
||||
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
|
||||
|
||||
mfem::VectorFunctionCoefficient gravityCoefficient(
|
||||
f.mesh->Dimension(),
|
||||
[radialCoefficient](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(position.Size());
|
||||
mfem::VectorFunctionCoefficient gravityCoefficient(
|
||||
f.mesh->Dimension(), [radialCoefficient](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(position.Size());
|
||||
|
||||
for (int component = 0; component < position.Size(); ++component) {
|
||||
value(component) = radialCoefficient * position(component);
|
||||
}
|
||||
});
|
||||
for (int component = 0; component < position.Size(); ++component) {
|
||||
value(component) = radialCoefficient * position(component);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
gravityField.ProjectCoefficient(gravityCoefficient);
|
||||
gravityField.ProjectCoefficient(gravityCoefficient);
|
||||
|
||||
mfem::Vector gravityTrue;
|
||||
gravityField.GetTrueDofs(gravityTrue);
|
||||
return gravityTrue;
|
||||
}
|
||||
mfem::Vector gravityTrue;
|
||||
gravityField.GetTrueDofs(gravityTrue);
|
||||
return gravityTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector
|
||||
make_affine_displacement(const mean_field::fem::FEM &f,
|
||||
const std::array<double, 3> &scales) {
|
||||
mfem::ParGridFunction displacementField(f.displacementFes.get());
|
||||
[[nodiscard]] mfem::Vector make_affine_displacement(
|
||||
const mean_field::fem::FEM &f,
|
||||
const std::array<
|
||||
double,
|
||||
3> &scales
|
||||
) {
|
||||
mfem::ParGridFunction displacementField(f.displacementFes.get());
|
||||
|
||||
mfem::VectorFunctionCoefficient displacementCoefficient(
|
||||
f.mesh->Dimension(),
|
||||
[scales](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(position.Size());
|
||||
mfem::VectorFunctionCoefficient displacementCoefficient(
|
||||
f.mesh->Dimension(), [scales](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(position.Size());
|
||||
|
||||
for (int component = 0; component < position.Size(); ++component) {
|
||||
value(component) =
|
||||
(scales[static_cast<std::size_t>(component)] - 1.0) *
|
||||
position(component);
|
||||
}
|
||||
});
|
||||
for (int component = 0; component < position.Size(); ++component) {
|
||||
value(component) = (scales[static_cast<std::size_t>(component)] - 1.0) * position(component);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
displacementField.ProjectCoefficient(displacementCoefficient);
|
||||
displacementField.ProjectCoefficient(displacementCoefficient);
|
||||
|
||||
mfem::Vector displacementTrue;
|
||||
displacementField.GetTrueDofs(displacementTrue);
|
||||
return displacementTrue;
|
||||
}
|
||||
mfem::Vector displacementTrue;
|
||||
displacementField.GetTrueDofs(displacementTrue);
|
||||
return displacementTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector
|
||||
make_constant_test_direction(const mean_field::fem::FEM &f,
|
||||
const int selectedComponent) {
|
||||
mfem::ParGridFunction testField(f.displacementFes.get());
|
||||
[[nodiscard]] mfem::Vector make_constant_test_direction(
|
||||
const mean_field::fem::FEM &f,
|
||||
const int selectedComponent
|
||||
) {
|
||||
mfem::ParGridFunction testField(f.displacementFes.get());
|
||||
|
||||
mfem::VectorFunctionCoefficient testCoefficient(
|
||||
f.mesh->Dimension(),
|
||||
[selectedComponent](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(position.Size());
|
||||
value = 0.0;
|
||||
value(selectedComponent) = 1.0;
|
||||
});
|
||||
mfem::VectorFunctionCoefficient testCoefficient(
|
||||
f.mesh->Dimension(), [selectedComponent](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(position.Size());
|
||||
value = 0.0;
|
||||
value(selectedComponent) = 1.0;
|
||||
}
|
||||
);
|
||||
|
||||
testField.ProjectCoefficient(testCoefficient);
|
||||
testField.ProjectCoefficient(testCoefficient);
|
||||
|
||||
mfem::Vector testTrue;
|
||||
testField.GetTrueDofs(testTrue);
|
||||
return testTrue;
|
||||
}
|
||||
mfem::Vector testTrue;
|
||||
testField.GetTrueDofs(testTrue);
|
||||
return testTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector
|
||||
make_dilation_test_direction(const mean_field::fem::FEM &f) {
|
||||
mfem::ParGridFunction testField(f.displacementFes.get());
|
||||
[[nodiscard]] mfem::Vector make_dilation_test_direction(const mean_field::fem::FEM &f) {
|
||||
mfem::ParGridFunction testField(f.displacementFes.get());
|
||||
|
||||
mfem::VectorFunctionCoefficient testCoefficient(
|
||||
f.mesh->Dimension(), [](const mfem::Vector &position,
|
||||
mfem::Vector &value) { value = position; });
|
||||
mfem::VectorFunctionCoefficient testCoefficient(
|
||||
f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) { value = position; }
|
||||
);
|
||||
|
||||
testField.ProjectCoefficient(testCoefficient);
|
||||
testField.ProjectCoefficient(testCoefficient);
|
||||
|
||||
mfem::Vector testTrue;
|
||||
testField.GetTrueDofs(testTrue);
|
||||
return testTrue;
|
||||
}
|
||||
mfem::Vector testTrue;
|
||||
testField.GetTrueDofs(testTrue);
|
||||
return testTrue;
|
||||
}
|
||||
|
||||
void set_mass_normalized_density(mean_field::fem::FEM &f,
|
||||
const double targetMass,
|
||||
mfem::ParGridFunction &densityField) {
|
||||
const mfem::Vector stellarDensityTrue =
|
||||
gravity_prepared_test_utils::make_domain_supported_density(f, true);
|
||||
void set_mass_normalized_density(
|
||||
mean_field::fem::FEM &f,
|
||||
const double targetMass,
|
||||
mfem::ParGridFunction &densityField
|
||||
) {
|
||||
const mfem::Vector stellarDensityTrue = gravity_prepared_test_utils::make_domain_supported_density(f, true);
|
||||
|
||||
densityField.SetFromTrueDofs(stellarDensityTrue);
|
||||
densityField.SetFromTrueDofs(stellarDensityTrue);
|
||||
|
||||
const double unnormalizedMass =
|
||||
mean_field::analysis::domain_integrate_grid_function(
|
||||
f, densityField, mean_field::utils::DOMAINS::STELLAR);
|
||||
const double unnormalizedMass =
|
||||
mean_field::analysis::domain_integrate_grid_function(f, densityField, mean_field::utils::DOMAINS::STELLAR);
|
||||
|
||||
MFEM_VERIFY(unnormalizedMass > 0.0,
|
||||
"The analytic gravity-force test obtained non-positive mass.");
|
||||
MFEM_VERIFY(unnormalizedMass > 0.0, "The analytic gravity-force test obtained non-positive mass.");
|
||||
|
||||
densityField *= targetMass / unnormalizedMass;
|
||||
}
|
||||
densityField *= targetMass / unnormalizedMass;
|
||||
}
|
||||
} // namespace gravity_displacement_force_analytic_test_utils
|
||||
|
||||
TEST_CASE("Gravity Displacement Force Matches Analytic Affine Resultants",
|
||||
tags::gravity &tags::accuracy &tags::analytic_comparison
|
||||
&tags::integration) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
TEST_CASE(
|
||||
"Gravity Displacement Force Matches Analytic Affine Resultants",
|
||||
tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
REQUIRE(f.okay());
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
constexpr double densityValue = 1.37;
|
||||
constexpr double densityValue = 1.37;
|
||||
|
||||
constexpr std::array<double, 3> physicalGravity{0.31, -0.47, 0.22};
|
||||
constexpr std::array<double, 3> physicalGravity{0.31, -0.47, 0.22};
|
||||
|
||||
constexpr std::array<
|
||||
gravity_displacement_force_analytic_test_utils::AffineCase, 3>
|
||||
affineCases{{{.name = "identity geometry", .scales = {1.0, 1.0, 1.0}},
|
||||
{.name = "volume-preserving affine geometry",
|
||||
.scales = {1.14, 0.93, 1.0 / (1.14 * 0.93)}},
|
||||
{.name = "volume-changing affine geometry",
|
||||
.scales = {1.11, 0.96, 1.07}}}};
|
||||
constexpr std::array<gravity_displacement_force_analytic_test_utils::AffineCase, 3> affineCases{
|
||||
{{.name = "identity geometry", .scales = {1.0, 1.0, 1.0}},
|
||||
{.name = "volume-preserving affine geometry", .scales = {1.14, 0.93, 1.0 / (1.14 * 0.93)}},
|
||||
{.name = "volume-changing affine geometry", .scales = {1.11, 0.96, 1.07}}}
|
||||
};
|
||||
|
||||
const mfem::Vector density =
|
||||
gravity_displacement_force_analytic_test_utils::make_constant_density(
|
||||
f, densityValue);
|
||||
const mfem::Vector density = gravity_displacement_force_analytic_test_utils::make_constant_density(f, densityValue);
|
||||
|
||||
const double referenceVolume =
|
||||
gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(
|
||||
mean_field::utils::RADIUS);
|
||||
const double referenceVolume =
|
||||
gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(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
|
||||
&affineCase : affineCases) {
|
||||
DYNAMIC_SECTION(affineCase.name) {
|
||||
const double mapDeterminant =
|
||||
gravity_displacement_force_analytic_test_utils::determinant(
|
||||
affineCase.scales);
|
||||
for (const gravity_displacement_force_analytic_test_utils::AffineCase &affineCase : affineCases) {
|
||||
DYNAMIC_SECTION(affineCase.name) {
|
||||
const double mapDeterminant =
|
||||
gravity_displacement_force_analytic_test_utils::determinant(affineCase.scales);
|
||||
|
||||
REQUIRE(mapDeterminant > 0.0);
|
||||
REQUIRE(mapDeterminant > 0.0);
|
||||
|
||||
std::array<double, 3> referenceGravity{};
|
||||
std::array<double, 3> referenceGravity{};
|
||||
|
||||
/*
|
||||
* For x = A X, the H(div) Piola relation is
|
||||
*
|
||||
* g_phys = A g_ref / det(A).
|
||||
*
|
||||
* Prescribe the RT pullback that represents the requested
|
||||
* constant physical gravity field exactly.
|
||||
*/
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
referenceGravity[static_cast<std::size_t>(component)] =
|
||||
mapDeterminant *
|
||||
physicalGravity[static_cast<std::size_t>(component)] /
|
||||
affineCase.scales[static_cast<std::size_t>(component)];
|
||||
}
|
||||
/*
|
||||
* For x = A X, the H(div) Piola relation is
|
||||
*
|
||||
* g_phys = A g_ref / det(A).
|
||||
*
|
||||
* Prescribe the RT pullback that represents the requested
|
||||
* constant physical gravity field exactly.
|
||||
*/
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
referenceGravity[static_cast<std::size_t>(component)] =
|
||||
mapDeterminant * physicalGravity[static_cast<std::size_t>(component)] /
|
||||
affineCase.scales[static_cast<std::size_t>(component)];
|
||||
}
|
||||
|
||||
const mfem::Vector gravityGradient =
|
||||
gravity_displacement_force_analytic_test_utils::
|
||||
make_reference_gravity(f, referenceGravity);
|
||||
const mfem::Vector gravityGradient =
|
||||
gravity_displacement_force_analytic_test_utils::make_reference_gravity(f, referenceGravity);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_displacement_force_analytic_test_utils::
|
||||
make_affine_displacement(f, affineCase.scales);
|
||||
const mfem::Vector displacement =
|
||||
gravity_displacement_force_analytic_test_utils::make_affine_displacement(f, affineCase.scales);
|
||||
|
||||
mfem::Vector residual;
|
||||
mfem::Vector residual;
|
||||
|
||||
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
|
||||
f, *f.domainMapperStateless, density, gravityGradient, displacement,
|
||||
residual);
|
||||
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
|
||||
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
|
||||
);
|
||||
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
const mfem::Vector testDirection =
|
||||
gravity_displacement_force_analytic_test_utils::
|
||||
make_constant_test_direction(f, component);
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
const mfem::Vector testDirection =
|
||||
gravity_displacement_force_analytic_test_utils::make_constant_test_direction(f, component);
|
||||
|
||||
const double computedResultant =
|
||||
gravity_prepared_test_utils::global_dot(residual, testDirection,
|
||||
f.mesh->GetComm());
|
||||
const double computedResultant =
|
||||
gravity_prepared_test_utils::global_dot(residual, testDirection, f.mesh->GetComm());
|
||||
|
||||
const double expectedResultant =
|
||||
densityValue *
|
||||
physicalGravity[static_cast<std::size_t>(component)] *
|
||||
mapDeterminant * referenceVolume;
|
||||
const double expectedResultant = densityValue * physicalGravity[static_cast<std::size_t>(component)] *
|
||||
mapDeterminant * referenceVolume;
|
||||
|
||||
const double relativeError =
|
||||
gravity_displacement_force_analytic_test_utils::
|
||||
relative_scalar_error(computedResultant, expectedResultant);
|
||||
const double relativeError = gravity_displacement_force_analytic_test_utils::relative_scalar_error(
|
||||
computedResultant, expectedResultant
|
||||
);
|
||||
|
||||
CAPTURE(component);
|
||||
INFO("Map determinant = " << mapDeterminant);
|
||||
INFO("Computed resultant = " << computedResultant);
|
||||
INFO("Analytic resultant = " << expectedResultant);
|
||||
INFO("Relative resultant error = " << relativeError);
|
||||
CAPTURE(component);
|
||||
INFO("Map determinant = " << mapDeterminant);
|
||||
INFO("Computed resultant = " << computedResultant);
|
||||
INFO("Analytic resultant = " << expectedResultant);
|
||||
INFO("Relative resultant error = " << relativeError);
|
||||
|
||||
CHECK(relativeError < relativeTolerance);
|
||||
}
|
||||
CHECK(relativeError < relativeTolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Displacement Force Reproduces Analytic Homogeneous Sphere Work",
|
||||
tags::gravity &tags::accuracy &tags::analytic_comparison
|
||||
&tags::integration) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
REQUIRE(f.okay());
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
const double radius = mean_field::utils::RADIUS;
|
||||
const double mass = mean_field::utils::MASS;
|
||||
const double volume =
|
||||
gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(
|
||||
radius);
|
||||
const double radius = mean_field::utils::RADIUS;
|
||||
const double mass = mean_field::utils::MASS;
|
||||
const double volume = gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(radius);
|
||||
|
||||
const double densityValue = mass / volume;
|
||||
const double radialGravityCoefficient =
|
||||
mean_field::utils::G * mass / (radius * radius * radius);
|
||||
const double densityValue = mass / volume;
|
||||
const double radialGravityCoefficient = mean_field::utils::G * mass / (radius * radius * radius);
|
||||
|
||||
const mfem::Vector density =
|
||||
gravity_displacement_force_analytic_test_utils::make_constant_density(
|
||||
f, densityValue);
|
||||
const mfem::Vector density = gravity_displacement_force_analytic_test_utils::make_constant_density(f, densityValue);
|
||||
|
||||
const mfem::Vector gravityGradient =
|
||||
gravity_displacement_force_analytic_test_utils::make_radial_gravity(
|
||||
f, radialGravityCoefficient);
|
||||
const mfem::Vector gravityGradient =
|
||||
gravity_displacement_force_analytic_test_utils::make_radial_gravity(f, radialGravityCoefficient);
|
||||
|
||||
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
|
||||
displacement = 0.0;
|
||||
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
|
||||
displacement = 0.0;
|
||||
|
||||
mfem::Vector residual;
|
||||
mfem::Vector residual;
|
||||
|
||||
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
|
||||
f, *f.domainMapperStateless, density, gravityGradient, displacement,
|
||||
residual);
|
||||
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
|
||||
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
|
||||
);
|
||||
|
||||
const mfem::Vector dilationDirection =
|
||||
gravity_displacement_force_analytic_test_utils::
|
||||
make_dilation_test_direction(f);
|
||||
const mfem::Vector dilationDirection =
|
||||
gravity_displacement_force_analytic_test_utils::make_dilation_test_direction(f);
|
||||
|
||||
const double computedWork = gravity_prepared_test_utils::global_dot(
|
||||
residual, dilationDirection, f.mesh->GetComm());
|
||||
const double computedWork = gravity_prepared_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm());
|
||||
|
||||
const double analyticWork =
|
||||
(3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
|
||||
const double analyticWork = (3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
|
||||
|
||||
const double relativeError =
|
||||
gravity_displacement_force_analytic_test_utils::relative_scalar_error(
|
||||
computedWork, analyticWork);
|
||||
const double relativeError =
|
||||
gravity_displacement_force_analytic_test_utils::relative_scalar_error(computedWork, analyticWork);
|
||||
|
||||
INFO("Computed positive gravity work = " << computedWork);
|
||||
INFO("Analytic positive gravity work = " << analyticWork);
|
||||
INFO("Computed gravitational virial = " << -computedWork);
|
||||
INFO("Analytic binding energy = " << -analyticWork);
|
||||
INFO("Relative analytic work error = " << relativeError);
|
||||
INFO("Computed positive gravity work = " << computedWork);
|
||||
INFO("Analytic positive gravity work = " << analyticWork);
|
||||
INFO("Computed gravitational virial = " << -computedWork);
|
||||
INFO("Analytic binding energy = " << -analyticWork);
|
||||
INFO("Relative analytic work error = " << relativeError);
|
||||
|
||||
REQUIRE(computedWork > 0.0);
|
||||
CHECK(relativeError < 1.0e-5);
|
||||
REQUIRE(computedWork > 0.0);
|
||||
CHECK(relativeError < 1.0e-5);
|
||||
}
|
||||
|
||||
TEST_CASE("Solved Homogeneous Sphere Gravity Force Matches Analytic Virial",
|
||||
tags::gravity &tags::accuracy &tags::analytic_comparison
|
||||
&tags::integration &tags::initialization) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
args.p.rtol = 1.0e-13;
|
||||
args.p.max_iters = std::max(args.p.max_iters, 1000);
|
||||
TEST_CASE(
|
||||
"Solved Homogeneous Sphere Gravity Force Matches Analytic Virial",
|
||||
tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration &tags::initialization
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
args.p.rtol = 1.0e-13;
|
||||
args.p.max_iters = std::max(args.p.max_iters, 1000);
|
||||
|
||||
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.domainMapperStateless != nullptr);
|
||||
REQUIRE(f.okay());
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
mfem::ParGridFunction displacementField(f.displacementFes.get());
|
||||
displacementField = 0.0;
|
||||
mfem::ParGridFunction displacementField(f.displacementFes.get());
|
||||
displacementField = 0.0;
|
||||
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
*f.displacement = 0.0;
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
*f.displacement = 0.0;
|
||||
|
||||
const double radius = mean_field::utils::RADIUS;
|
||||
const double mass = mean_field::utils::MASS;
|
||||
const double radius = mean_field::utils::RADIUS;
|
||||
const double mass = mean_field::utils::MASS;
|
||||
|
||||
mfem::ParGridFunction densityField(f.densityFes.get());
|
||||
mfem::ParGridFunction densityField(f.densityFes.get());
|
||||
|
||||
gravity_displacement_force_analytic_test_utils::set_mass_normalized_density(
|
||||
f, mass, densityField);
|
||||
gravity_displacement_force_analytic_test_utils::set_mass_normalized_density(f, mass, densityField);
|
||||
|
||||
const mean_field::physics::GravitySolution gravitySolution =
|
||||
mean_field::physics::solve_gravity_field(f, args, densityField,
|
||||
displacementField);
|
||||
const mean_field::physics::GravitySolution gravitySolution =
|
||||
mean_field::physics::solve_gravity_field(f, args, densityField, displacementField);
|
||||
|
||||
mfem::Vector densityTrue;
|
||||
mfem::Vector gravityGradientTrue;
|
||||
mfem::Vector displacementTrue;
|
||||
mfem::Vector densityTrue;
|
||||
mfem::Vector gravityGradientTrue;
|
||||
mfem::Vector displacementTrue;
|
||||
|
||||
densityField.GetTrueDofs(densityTrue);
|
||||
gravitySolution.gradPhi.GetTrueDofs(gravityGradientTrue);
|
||||
displacementField.GetTrueDofs(displacementTrue);
|
||||
densityField.GetTrueDofs(densityTrue);
|
||||
gravitySolution.gradPhi.GetTrueDofs(gravityGradientTrue);
|
||||
displacementField.GetTrueDofs(displacementTrue);
|
||||
|
||||
mfem::Vector residual;
|
||||
mfem::Vector residual;
|
||||
|
||||
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
|
||||
f, *f.domainMapperStateless, densityTrue, gravityGradientTrue,
|
||||
displacementTrue, residual);
|
||||
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
|
||||
f, *f.domainMapperStateless, densityTrue, gravityGradientTrue, displacementTrue, residual
|
||||
);
|
||||
|
||||
const mfem::Vector dilationDirection =
|
||||
gravity_displacement_force_analytic_test_utils::
|
||||
make_dilation_test_direction(f);
|
||||
const mfem::Vector dilationDirection =
|
||||
gravity_displacement_force_analytic_test_utils::make_dilation_test_direction(f);
|
||||
|
||||
const double computedWork = gravity_prepared_test_utils::global_dot(
|
||||
residual, dilationDirection, f.mesh->GetComm());
|
||||
const double computedWork = gravity_prepared_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm());
|
||||
|
||||
const double analyticWork =
|
||||
(3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
|
||||
const double analyticWork = (3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
|
||||
|
||||
const double relativeError =
|
||||
gravity_displacement_force_analytic_test_utils::relative_scalar_error(
|
||||
computedWork, analyticWork);
|
||||
const double relativeError =
|
||||
gravity_displacement_force_analytic_test_utils::relative_scalar_error(computedWork, analyticWork);
|
||||
|
||||
INFO("Solved-field positive gravity work = " << computedWork);
|
||||
INFO("Analytic positive gravity work = " << analyticWork);
|
||||
INFO("Solved-field gravitational virial = " << -computedWork);
|
||||
INFO("Analytic homogeneous-sphere binding energy = " << -analyticWork);
|
||||
INFO("Relative solved-field virial error = " << relativeError);
|
||||
INFO("Solved-field positive gravity work = " << computedWork);
|
||||
INFO("Analytic positive gravity work = " << analyticWork);
|
||||
INFO("Solved-field gravitational virial = " << -computedWork);
|
||||
INFO("Analytic homogeneous-sphere binding energy = " << -analyticWork);
|
||||
INFO("Relative solved-field virial error = " << relativeError);
|
||||
|
||||
REQUIRE(computedWork > 0.0);
|
||||
CHECK(relativeError < 1.0e-5);
|
||||
REQUIRE(computedWork > 0.0);
|
||||
CHECK(relativeError < 1.0e-5);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -72,7 +72,10 @@ TEST_CASE(
|
||||
|
||||
constexpr double enthalpyValue = 0.8;
|
||||
|
||||
const double densityValue = barotrope.density_from_enthalpy(enthalpyValue);
|
||||
const double densityValue = mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{enthalpyValue}
|
||||
)
|
||||
.value();
|
||||
|
||||
const mfem::Vector enthalpy = project_constant(*f.enthalpyFes, enthalpyValue);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -242,8 +242,12 @@ namespace prepared_barotropic_closure_test_utils {
|
||||
const ClosureCondition &condition
|
||||
) {
|
||||
mfem::FunctionCoefficient coefficient([&equationOfState, condition](const mfem::Vector &position) {
|
||||
const double enthalpy = evaluate_enthalpy(position, condition);
|
||||
return condition.densityFactor * equationOfState.density_from_enthalpy(enthalpy) + condition.densityOffset +
|
||||
const double enthalpy = evaluate_enthalpy(position, condition);
|
||||
const double equationOfStateDensity = mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
|
||||
equationOfState, mean_field::eos::SpecificEnthalpyValue{enthalpy}
|
||||
)
|
||||
.value();
|
||||
return condition.densityFactor * equationOfStateDensity + condition.densityOffset +
|
||||
condition.densityGradient * (0.40 * position(0) + 0.25 * position(1) - 0.15 * position(2));
|
||||
});
|
||||
return project_scalar(*f.densityFes, coefficient);
|
||||
@@ -784,8 +788,12 @@ namespace prepared_barotropic_closure_test_utils {
|
||||
const Maps maps(f);
|
||||
const mean_field::eos::Polytrope equationOfState(3.0, 1.5);
|
||||
|
||||
constexpr double enthalpyValue = 1.20;
|
||||
const double equilibriumDensityValue = equationOfState.density_from_enthalpy(enthalpyValue);
|
||||
constexpr double enthalpyValue = 1.20;
|
||||
const double equilibriumDensityValue =
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
|
||||
equationOfState, mean_field::eos::SpecificEnthalpyValue{enthalpyValue}
|
||||
)
|
||||
.value();
|
||||
|
||||
const mfem::Vector enthalpy = reduce(maps.enthalpy, make_constant_field(*f.enthalpyFes, enthalpyValue));
|
||||
const mfem::Vector equilibriumDensity =
|
||||
|
||||
@@ -10,181 +10,157 @@ using namespace mean_field;
|
||||
using Catch::Matchers::WithinAbs;
|
||||
namespace prepared_test = gravity_prepared_test_utils;
|
||||
|
||||
TEST_CASE("Prepared Mapped Hdiv Mass Matches Stateless Kernel",
|
||||
tags::gravity_prepared) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
TEST_CASE(
|
||||
"Prepared Mapped Hdiv Mass Matches Stateless Kernel",
|
||||
tags::gravity_prepared
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
operators::PreparedMappedHDivMassOperator prepared_operator(
|
||||
f, *f.domainMapperStateless);
|
||||
REQUIRE(prepared_operator.Width() ==
|
||||
prepared_operator.GetFluxMap().reduced_size());
|
||||
REQUIRE(prepared_operator.Height() ==
|
||||
prepared_operator.GetFluxMap().reduced_size());
|
||||
operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless);
|
||||
REQUIRE(prepared_operator.Width() == prepared_operator.GetFluxMap().reduced_size());
|
||||
REQUIRE(prepared_operator.Height() == prepared_operator.GetFluxMap().reduced_size());
|
||||
|
||||
const mfem::Vector gravity_gradient_true =
|
||||
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(),
|
||||
0.21);
|
||||
const mfem::Vector gravity_gradient =
|
||||
prepared_operator.GetFluxMap().gather(gravity_gradient_true);
|
||||
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
||||
const mfem::Vector gravity_gradient_true =
|
||||
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.21);
|
||||
const mfem::Vector gravity_gradient = prepared_operator.GetFluxMap().gather(gravity_gradient_true);
|
||||
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
||||
|
||||
mfem::Vector identity_action;
|
||||
mfem::Vector deformed_action;
|
||||
mfem::Vector identity_action;
|
||||
mfem::Vector deformed_action;
|
||||
|
||||
for (const double deformation_scale : {0.0, 1.0}) {
|
||||
const mfem::Vector displacement_true =
|
||||
prepared_test::make_displacement(f, deformation_scale);
|
||||
for (const double deformation_scale : {0.0, 1.0}) {
|
||||
const mfem::Vector displacement_true = prepared_test::make_displacement(f, deformation_scale);
|
||||
const mfem::Vector displacement = prepared_operator.GetDisplacementMap().gather(displacement_true);
|
||||
|
||||
prepared_operator.Prepare(displacement);
|
||||
|
||||
mfem::Vector prepared_action;
|
||||
|
||||
prepared_operator.Mult(gravity_gradient, prepared_action);
|
||||
mfem::Vector reference_action_true;
|
||||
operators::kernels::apply_mapped_hdiv_mass(
|
||||
f, *f.domainMapperStateless, gravity_gradient_true, displacement_true, reference_action_true
|
||||
);
|
||||
const mfem::Vector reference_action = prepared_operator.GetFluxMap().gather(reference_action_true);
|
||||
|
||||
const double relative_error = prepared_test::relative_error(prepared_action, reference_action, communicator);
|
||||
|
||||
INFO("Deformation scale = " << deformation_scale);
|
||||
INFO("Prepared action norm = " << prepared_test::global_norm(prepared_action, communicator));
|
||||
INFO("Reference action norm = " << prepared_test::global_norm(reference_action, communicator));
|
||||
INFO("Relative prepared-operator error = " << relative_error);
|
||||
|
||||
REQUIRE(prepared_operator.IsPrepared());
|
||||
CHECK_THAT(relative_error, WithinAbs(0.0, 2.0e-11));
|
||||
|
||||
if (deformation_scale == 0.0) {
|
||||
identity_action = prepared_action;
|
||||
} else {
|
||||
deformed_action = prepared_action;
|
||||
}
|
||||
}
|
||||
|
||||
const double geometry_change = prepared_test::relative_error(deformed_action, identity_action, communicator);
|
||||
|
||||
INFO("Relative action change under deformation = " << geometry_change);
|
||||
|
||||
CHECK(prepared_operator.GetPreparationCount() == 2);
|
||||
CHECK(geometry_change > 1.0e-5);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Mapped Hdiv Mass Preserves Operator Identities",
|
||||
tags::gravity_prepared
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless);
|
||||
REQUIRE(prepared_operator.Width() == prepared_operator.GetFluxMap().reduced_size());
|
||||
REQUIRE(prepared_operator.Height() == prepared_operator.GetFluxMap().reduced_size());
|
||||
const mfem::Vector displacement =
|
||||
prepared_operator.GetDisplacementMap().gather(displacement_true);
|
||||
|
||||
prepared_operator.GetDisplacementMap().gather(prepared_test::make_displacement(f, 1.0));
|
||||
prepared_operator.Prepare(displacement);
|
||||
|
||||
mfem::Vector prepared_action;
|
||||
const mfem::Vector first = prepared_operator.GetFluxMap().gather(
|
||||
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.17)
|
||||
);
|
||||
const mfem::Vector second = prepared_operator.GetFluxMap().gather(
|
||||
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.83)
|
||||
);
|
||||
const mfem::Vector combination = prepared_test::linear_combination(first, 1.7, second, -0.4);
|
||||
|
||||
prepared_operator.Mult(gravity_gradient, prepared_action);
|
||||
mfem::Vector reference_action_true;
|
||||
operators::kernels::apply_mapped_hdiv_mass(
|
||||
f, *f.domainMapperStateless, gravity_gradient_true, displacement_true,
|
||||
reference_action_true);
|
||||
const mfem::Vector reference_action =
|
||||
prepared_operator.GetFluxMap().gather(reference_action_true);
|
||||
mfem::Vector first_action;
|
||||
mfem::Vector second_action;
|
||||
mfem::Vector combination_action;
|
||||
mfem::Vector zero_action;
|
||||
|
||||
const double relative_error = prepared_test::relative_error(
|
||||
prepared_action, reference_action, communicator);
|
||||
prepared_operator.Mult(first, first_action);
|
||||
prepared_operator.Mult(second, second_action);
|
||||
prepared_operator.Mult(combination, combination_action);
|
||||
|
||||
INFO("Deformation scale = " << deformation_scale);
|
||||
INFO("Prepared action norm = "
|
||||
<< prepared_test::global_norm(prepared_action, communicator));
|
||||
INFO("Reference action norm = "
|
||||
<< prepared_test::global_norm(reference_action, communicator));
|
||||
INFO("Relative prepared-operator error = " << relative_error);
|
||||
mfem::Vector expected_combination = prepared_test::linear_combination(first_action, 1.7, second_action, -0.4);
|
||||
|
||||
REQUIRE(prepared_operator.IsPrepared());
|
||||
CHECK_THAT(relative_error, WithinAbs(0.0, 2.0e-11));
|
||||
mfem::Vector zero(first.Size());
|
||||
zero = 0.0;
|
||||
prepared_operator.Mult(zero, zero_action);
|
||||
|
||||
if (deformation_scale == 0.0) {
|
||||
identity_action = prepared_action;
|
||||
} else {
|
||||
deformed_action = prepared_action;
|
||||
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
||||
|
||||
const double first_second_product = prepared_test::global_dot(first, second_action, communicator);
|
||||
const double second_first_product = prepared_test::global_dot(second, first_action, communicator);
|
||||
const double symmetry_error = prepared_test::relative_scalar_error(first_second_product, second_first_product);
|
||||
const double linearity_error =
|
||||
prepared_test::relative_error(combination_action, expected_combination, communicator);
|
||||
const double first_energy = prepared_test::global_dot(first, first_action, communicator);
|
||||
const double second_energy = prepared_test::global_dot(second, second_action, communicator);
|
||||
const std::uint64_t preparation_count = prepared_operator.GetPreparationCount();
|
||||
|
||||
mfem::Vector repeated_action;
|
||||
prepared_operator.Mult(first, repeated_action);
|
||||
|
||||
INFO("u^T M v = " << first_second_product);
|
||||
INFO("v^T M u = " << second_first_product);
|
||||
INFO("Relative symmetry error = " << symmetry_error);
|
||||
INFO("Relative linearity error = " << linearity_error);
|
||||
INFO("u^T M u = " << first_energy);
|
||||
INFO("v^T M v = " << second_energy);
|
||||
|
||||
CHECK_THAT(symmetry_error, WithinAbs(0.0, 2.0e-12));
|
||||
CHECK_THAT(linearity_error, WithinAbs(0.0, 2.0e-12));
|
||||
CHECK_THAT(prepared_test::global_norm(zero_action, communicator), WithinAbs(0.0, 1.0e-14));
|
||||
CHECK(first_energy > 0.0);
|
||||
CHECK(second_energy > 0.0);
|
||||
CHECK(prepared_test::relative_error(repeated_action, first_action, communicator) < 2.0e-14);
|
||||
CHECK(prepared_operator.GetPreparationCount() == preparation_count);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Mapped Hdiv Mass Diagonal Is Positive Across Both Domains",
|
||||
tags::gravity_prepared
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless);
|
||||
const mfem::Vector displacement =
|
||||
prepared_operator.GetDisplacementMap().gather(prepared_test::make_displacement(f, 1.0));
|
||||
prepared_operator.Prepare(displacement);
|
||||
|
||||
mfem::Vector diagonal;
|
||||
mfem::Vector true_diagonal;
|
||||
prepared_operator.AssembleDiagonal(diagonal);
|
||||
prepared_operator.AssembleTrueDiagonal(true_diagonal);
|
||||
|
||||
REQUIRE(diagonal.Size() == prepared_operator.Height());
|
||||
REQUIRE(true_diagonal.Size() == prepared_operator.GetFluxMap().full_size());
|
||||
|
||||
const mfem::Vector gathered_true_diagonal = prepared_operator.GetFluxMap().gather(true_diagonal);
|
||||
|
||||
for (int i = 0; i < diagonal.Size(); ++i) {
|
||||
REQUIRE(std::isfinite(diagonal(i)));
|
||||
CHECK(diagonal(i) > 0.0);
|
||||
CHECK_THAT(diagonal(i), WithinAbs(gathered_true_diagonal(i), 1.0e-14 * std::abs(diagonal(i))));
|
||||
}
|
||||
}
|
||||
|
||||
const double geometry_change = prepared_test::relative_error(
|
||||
deformed_action, identity_action, communicator);
|
||||
|
||||
INFO("Relative action change under deformation = " << geometry_change);
|
||||
|
||||
CHECK(prepared_operator.GetPreparationCount() == 2);
|
||||
CHECK(geometry_change > 1.0e-5);
|
||||
}
|
||||
|
||||
TEST_CASE("Prepared Mapped Hdiv Mass Preserves Operator Identities",
|
||||
tags::gravity_prepared) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
operators::PreparedMappedHDivMassOperator prepared_operator(
|
||||
f, *f.domainMapperStateless);
|
||||
REQUIRE(prepared_operator.Width() ==
|
||||
prepared_operator.GetFluxMap().reduced_size());
|
||||
REQUIRE(prepared_operator.Height() ==
|
||||
prepared_operator.GetFluxMap().reduced_size());
|
||||
const mfem::Vector displacement =
|
||||
prepared_operator.GetDisplacementMap().gather(
|
||||
prepared_test::make_displacement(f, 1.0));
|
||||
prepared_operator.Prepare(displacement);
|
||||
|
||||
const mfem::Vector first = prepared_operator.GetFluxMap().gather(
|
||||
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(),
|
||||
0.17));
|
||||
const mfem::Vector second = prepared_operator.GetFluxMap().gather(
|
||||
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(),
|
||||
0.83));
|
||||
const mfem::Vector combination =
|
||||
prepared_test::linear_combination(first, 1.7, second, -0.4);
|
||||
|
||||
mfem::Vector first_action;
|
||||
mfem::Vector second_action;
|
||||
mfem::Vector combination_action;
|
||||
mfem::Vector zero_action;
|
||||
|
||||
prepared_operator.Mult(first, first_action);
|
||||
prepared_operator.Mult(second, second_action);
|
||||
prepared_operator.Mult(combination, combination_action);
|
||||
|
||||
mfem::Vector expected_combination =
|
||||
prepared_test::linear_combination(first_action, 1.7, second_action, -0.4);
|
||||
|
||||
mfem::Vector zero(first.Size());
|
||||
zero = 0.0;
|
||||
prepared_operator.Mult(zero, zero_action);
|
||||
|
||||
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
||||
|
||||
const double first_second_product =
|
||||
prepared_test::global_dot(first, second_action, communicator);
|
||||
const double second_first_product =
|
||||
prepared_test::global_dot(second, first_action, communicator);
|
||||
const double symmetry_error = prepared_test::relative_scalar_error(
|
||||
first_second_product, second_first_product);
|
||||
const double linearity_error = prepared_test::relative_error(
|
||||
combination_action, expected_combination, communicator);
|
||||
const double first_energy =
|
||||
prepared_test::global_dot(first, first_action, communicator);
|
||||
const double second_energy =
|
||||
prepared_test::global_dot(second, second_action, communicator);
|
||||
const std::uint64_t preparation_count =
|
||||
prepared_operator.GetPreparationCount();
|
||||
|
||||
mfem::Vector repeated_action;
|
||||
prepared_operator.Mult(first, repeated_action);
|
||||
|
||||
INFO("u^T M v = " << first_second_product);
|
||||
INFO("v^T M u = " << second_first_product);
|
||||
INFO("Relative symmetry error = " << symmetry_error);
|
||||
INFO("Relative linearity error = " << linearity_error);
|
||||
INFO("u^T M u = " << first_energy);
|
||||
INFO("v^T M v = " << second_energy);
|
||||
|
||||
CHECK_THAT(symmetry_error, WithinAbs(0.0, 2.0e-12));
|
||||
CHECK_THAT(linearity_error, WithinAbs(0.0, 2.0e-12));
|
||||
CHECK_THAT(prepared_test::global_norm(zero_action, communicator),
|
||||
WithinAbs(0.0, 1.0e-14));
|
||||
CHECK(first_energy > 0.0);
|
||||
CHECK(second_energy > 0.0);
|
||||
CHECK(prepared_test::relative_error(repeated_action, first_action,
|
||||
communicator) < 2.0e-14);
|
||||
CHECK(prepared_operator.GetPreparationCount() == preparation_count);
|
||||
}
|
||||
|
||||
TEST_CASE("Prepared Mapped Hdiv Mass Diagonal Is Positive Across Both Domains",
|
||||
tags::gravity_prepared) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
operators::PreparedMappedHDivMassOperator prepared_operator(
|
||||
f, *f.domainMapperStateless);
|
||||
const mfem::Vector displacement =
|
||||
prepared_operator.GetDisplacementMap().gather(
|
||||
prepared_test::make_displacement(f, 1.0));
|
||||
prepared_operator.Prepare(displacement);
|
||||
|
||||
mfem::Vector diagonal;
|
||||
mfem::Vector true_diagonal;
|
||||
prepared_operator.AssembleDiagonal(diagonal);
|
||||
prepared_operator.AssembleTrueDiagonal(true_diagonal);
|
||||
|
||||
REQUIRE(diagonal.Size() == prepared_operator.Height());
|
||||
REQUIRE(true_diagonal.Size() == prepared_operator.GetFluxMap().full_size());
|
||||
|
||||
const mfem::Vector gathered_true_diagonal =
|
||||
prepared_operator.GetFluxMap().gather(true_diagonal);
|
||||
|
||||
for (int i = 0; i < diagonal.Size(); ++i) {
|
||||
REQUIRE(std::isfinite(diagonal(i)));
|
||||
CHECK(diagonal(i) > 0.0);
|
||||
CHECK_THAT(diagonal(i), WithinAbs(gathered_true_diagonal(i),
|
||||
1.0e-14 * std::abs(diagonal(i))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,430 +9,423 @@ import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace prepared_hydrostatic_analytic_solve_test_utils {
|
||||
constexpr double bernoulliConstant = 0.83;
|
||||
constexpr double enthalpyAmplitude = 0.61;
|
||||
constexpr double bernoulliConstant = 0.83;
|
||||
constexpr double enthalpyAmplitude = 0.61;
|
||||
|
||||
struct AnalyticCase {
|
||||
const char *name;
|
||||
struct AnalyticCase {
|
||||
const char *name;
|
||||
|
||||
std::array<double, 3> deformationScale;
|
||||
std::array<double, 3> angularVelocity;
|
||||
std::array<double, 3> rotationCenter;
|
||||
};
|
||||
std::array<double, 3> deformationScale;
|
||||
std::array<double, 3> angularVelocity;
|
||||
std::array<double, 3> rotationCenter;
|
||||
};
|
||||
|
||||
class EnthalpyJacobianOperator final : public mfem::Operator {
|
||||
public:
|
||||
EnthalpyJacobianOperator(
|
||||
const int enthalpySize,
|
||||
const mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
&preparedOperator)
|
||||
: mfem::Operator(enthalpySize), m_preparedOperator(preparedOperator) {}
|
||||
class EnthalpyJacobianOperator final : public mfem::Operator {
|
||||
public:
|
||||
EnthalpyJacobianOperator(
|
||||
const int enthalpySize,
|
||||
const mean_field::operators::PreparedHydrostaticEquilibriumOperator &preparedOperator
|
||||
)
|
||||
: mfem::Operator(enthalpySize),
|
||||
m_preparedOperator(preparedOperator) {
|
||||
}
|
||||
|
||||
void Mult(const mfem::Vector &direction,
|
||||
mfem::Vector &action) const override {
|
||||
m_preparedOperator.ApplyEnthalpyJacobianAction(direction, action);
|
||||
}
|
||||
void Mult(
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
m_preparedOperator.ApplyEnthalpyJacobianAction(direction, action);
|
||||
}
|
||||
|
||||
private:
|
||||
const mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
&m_preparedOperator;
|
||||
};
|
||||
private:
|
||||
const mean_field::operators::PreparedHydrostaticEquilibriumOperator &m_preparedOperator;
|
||||
};
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies
|
||||
make_dependencies() {
|
||||
return {.discretization = {.identity = 701, .revision = 2},
|
||||
.enthalpy = {.identity = 709, .revision = 3},
|
||||
.gravityPotential = {.identity = 719, .revision = 5},
|
||||
.displacement = {.identity = 727, .revision = 7},
|
||||
.rotation = {.identity = 733, .revision = 11},
|
||||
.bernoulliConstant = {.identity = 739, .revision = 13}};
|
||||
}
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 701, .revision = 2},
|
||||
.enthalpy = {.identity = 709, .revision = 3},
|
||||
.gravityPotential = {.identity = 719, .revision = 5},
|
||||
.displacement = {.identity = 727, .revision = 7},
|
||||
.rotation = {.identity = 733, .revision = 11},
|
||||
.bernoulliConstant = {.identity = 739, .revision = 13}
|
||||
};
|
||||
}
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
|
||||
make_state(const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &displacement) {
|
||||
return {.enthalpy = enthalpy,
|
||||
.gravityPotential = gravityPotential,
|
||||
.displacement = displacement,
|
||||
.bernoulliConstant = bernoulliConstant};
|
||||
}
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state(
|
||||
const mfem::Vector &enthalpy,
|
||||
const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &displacement
|
||||
) {
|
||||
return {
|
||||
.enthalpy = enthalpy,
|
||||
.gravityPotential = gravityPotential,
|
||||
.displacement = displacement,
|
||||
.bernoulliConstant = bernoulliConstant
|
||||
};
|
||||
}
|
||||
|
||||
mfem::Vector make_vector(const std::array<double, 3> &values) {
|
||||
mfem::Vector vector(3);
|
||||
mfem::Vector make_vector(
|
||||
const std::array<
|
||||
double,
|
||||
3> &values
|
||||
) {
|
||||
mfem::Vector vector(3);
|
||||
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
vector(component) = values[static_cast<std::size_t>(component)];
|
||||
}
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
vector(component) = values[static_cast<std::size_t>(component)];
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
|
||||
mean_field::physics::RigidRotation
|
||||
make_rotation(const AnalyticCase &analyticCase) {
|
||||
return mean_field::physics::RigidRotation(
|
||||
make_vector(analyticCase.angularVelocity),
|
||||
make_vector(analyticCase.rotationCenter));
|
||||
}
|
||||
mean_field::physics::RigidRotation make_rotation(const AnalyticCase &analyticCase) {
|
||||
return mean_field::physics::RigidRotation(
|
||||
make_vector(analyticCase.angularVelocity), make_vector(analyticCase.rotationCenter)
|
||||
);
|
||||
}
|
||||
|
||||
void map_to_physical(const mfem::Vector &referencePosition,
|
||||
const AnalyticCase &analyticCase,
|
||||
mfem::Vector &physicalPosition) {
|
||||
physicalPosition.SetSize(3);
|
||||
void map_to_physical(
|
||||
const mfem::Vector &referencePosition,
|
||||
const AnalyticCase &analyticCase,
|
||||
mfem::Vector &physicalPosition
|
||||
) {
|
||||
physicalPosition.SetSize(3);
|
||||
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
physicalPosition(component) =
|
||||
analyticCase.deformationScale[static_cast<std::size_t>(component)] *
|
||||
referencePosition(component);
|
||||
}
|
||||
}
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
physicalPosition(component) =
|
||||
analyticCase.deformationScale[static_cast<std::size_t>(component)] * referencePosition(component);
|
||||
}
|
||||
}
|
||||
|
||||
double exact_enthalpy_value(const mfem::Vector &referencePosition) {
|
||||
double normalizedRadiusSquared = 0.0;
|
||||
double exact_enthalpy_value(const mfem::Vector &referencePosition) {
|
||||
double normalizedRadiusSquared = 0.0;
|
||||
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
const double normalizedCoordinate =
|
||||
referencePosition(component) / mean_field::utils::RADIUS;
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
const double normalizedCoordinate = referencePosition(component) / mean_field::utils::RADIUS;
|
||||
|
||||
normalizedRadiusSquared += normalizedCoordinate * normalizedCoordinate;
|
||||
}
|
||||
normalizedRadiusSquared += normalizedCoordinate * normalizedCoordinate;
|
||||
}
|
||||
|
||||
return enthalpyAmplitude * std::max(0.0, 1.0 - normalizedRadiusSquared);
|
||||
}
|
||||
return enthalpyAmplitude * std::max(0.0, 1.0 - normalizedRadiusSquared);
|
||||
}
|
||||
|
||||
double
|
||||
exact_potential_value(const mfem::Vector &referencePosition,
|
||||
const AnalyticCase &analyticCase,
|
||||
const mean_field::physics::RigidRotation &rotation) {
|
||||
mfem::Vector physicalPosition;
|
||||
double exact_potential_value(
|
||||
const mfem::Vector &referencePosition,
|
||||
const AnalyticCase &analyticCase,
|
||||
const mean_field::physics::RigidRotation &rotation
|
||||
) {
|
||||
mfem::Vector physicalPosition;
|
||||
|
||||
map_to_physical(referencePosition, analyticCase, physicalPosition);
|
||||
map_to_physical(referencePosition, analyticCase, physicalPosition);
|
||||
|
||||
/*
|
||||
* Construct Phi so that
|
||||
*
|
||||
* h + Phi - Psi_rotation - C = 0
|
||||
*
|
||||
* analytically.
|
||||
*/
|
||||
return bernoulliConstant + rotation.potential(physicalPosition) -
|
||||
exact_enthalpy_value(referencePosition);
|
||||
}
|
||||
/*
|
||||
* Construct Phi so that
|
||||
*
|
||||
* h + Phi - Psi_rotation - C = 0
|
||||
*
|
||||
* analytically.
|
||||
*/
|
||||
return bernoulliConstant + rotation.potential(physicalPosition) - exact_enthalpy_value(referencePosition);
|
||||
}
|
||||
|
||||
mfem::Array<int> make_stellar_element_marker(const mean_field::fem::FEM &f) {
|
||||
mfem::Array<int> stellarElementMarker(f.mesh->GetNE());
|
||||
mfem::Array<int> make_stellar_element_marker(const mean_field::fem::FEM &f) {
|
||||
mfem::Array<int> stellarElementMarker(f.mesh->GetNE());
|
||||
|
||||
const int vacuumAttribute = field_dof_test_utils::vacuum_material_attribute;
|
||||
const int vacuumAttribute = field_dof_test_utils::vacuum_material_attribute;
|
||||
|
||||
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
||||
stellarElementMarker[elementId] =
|
||||
f.mesh->GetAttribute(elementId) != vacuumAttribute;
|
||||
}
|
||||
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
||||
stellarElementMarker[elementId] = f.mesh->GetAttribute(elementId) != vacuumAttribute;
|
||||
}
|
||||
|
||||
return stellarElementMarker;
|
||||
}
|
||||
return stellarElementMarker;
|
||||
}
|
||||
} // namespace prepared_hydrostatic_analytic_solve_test_utils
|
||||
|
||||
TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
|
||||
tags::barotrope_hydrostatic_prepared_analytic &tags::convergence
|
||||
&tags::accuracy) {
|
||||
using prepared_hydrostatic_analytic_solve_test_utils::AnalyticCase;
|
||||
|
||||
constexpr double deformationX = 1.08;
|
||||
constexpr double deformationY = 0.96;
|
||||
|
||||
/*
|
||||
* The third scale makes the affine deformation
|
||||
* volume-preserving:
|
||||
*
|
||||
* det(F) = sx * sy * sz = 1.
|
||||
*/
|
||||
constexpr double deformationZ = 1.0 / (deformationX * deformationY);
|
||||
|
||||
const std::array<AnalyticCase, 3> analyticCases{
|
||||
{{.name = "spherical nonrotating equilibrium",
|
||||
.deformationScale = {1.0, 1.0, 1.0},
|
||||
.angularVelocity = {0.0, 0.0, 0.0},
|
||||
.rotationCenter = {0.0, 0.0, 0.0}},
|
||||
{.name = "spherical rotating equilibrium",
|
||||
.deformationScale = {1.0, 1.0, 1.0},
|
||||
.angularVelocity = {0.13, -0.09, 0.31},
|
||||
.rotationCenter = {0.04, -0.03, 0.02}},
|
||||
{.name = "volume-preserving deformed rotating equilibrium",
|
||||
.deformationScale = {deformationX, deformationY, deformationZ},
|
||||
.angularVelocity = {0.17, -0.12, 0.43},
|
||||
.rotationCenter = {0.031, -0.024, 0.018}}}};
|
||||
TEST_CASE(
|
||||
"Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
|
||||
tags::barotrope_hydrostatic_prepared_analytic &tags::convergence &tags::accuracy
|
||||
) {
|
||||
using prepared_hydrostatic_analytic_solve_test_utils::AnalyticCase;
|
||||
|
||||
auto args = test_utils::setup_args();
|
||||
constexpr double deformationX = 1.08;
|
||||
constexpr double deformationY = 0.96;
|
||||
|
||||
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
/*
|
||||
* The third scale makes the affine deformation
|
||||
* volume-preserving:
|
||||
*
|
||||
* det(F) = sx * sy * sz = 1.
|
||||
*/
|
||||
constexpr double deformationZ = 1.0 / (deformationX * deformationY);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
const std::array<AnalyticCase, 3> analyticCases{
|
||||
{{.name = "spherical nonrotating equilibrium",
|
||||
.deformationScale = {1.0, 1.0, 1.0},
|
||||
.angularVelocity = {0.0, 0.0, 0.0},
|
||||
.rotationCenter = {0.0, 0.0, 0.0}},
|
||||
{.name = "spherical rotating equilibrium",
|
||||
.deformationScale = {1.0, 1.0, 1.0},
|
||||
.angularVelocity = {0.13, -0.09, 0.31},
|
||||
.rotationCenter = {0.04, -0.03, 0.02}},
|
||||
{.name = "volume-preserving deformed rotating equilibrium",
|
||||
.deformationScale = {deformationX, deformationY, deformationZ},
|
||||
.angularVelocity = {0.17, -0.12, 0.43},
|
||||
.rotationCenter = {0.031, -0.024, 0.018}}}
|
||||
};
|
||||
|
||||
const mean_field::field::FieldDofMap enthalpyMap =
|
||||
field_dof_test_utils::make_map<mean_field::field::Enthalpy>(
|
||||
*f.enthalpyFes);
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
const mean_field::field::FieldDofMap gravityPotentialMap =
|
||||
field_dof_test_utils::make_map<mean_field::field::Gravity>(
|
||||
*f.gravityPotentialFes);
|
||||
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::field::FieldDofMap displacementMap =
|
||||
field_dof_test_utils::make_map<mean_field::field::Displacement>(
|
||||
*f.displacementFes);
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const mfem::Array<int> stellarElementMarker =
|
||||
prepared_hydrostatic_analytic_solve_test_utils::
|
||||
make_stellar_element_marker(f);
|
||||
const mean_field::field::FieldDofMap enthalpyMap =
|
||||
field_dof_test_utils::make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
|
||||
|
||||
for (const AnalyticCase &analyticCase : analyticCases) {
|
||||
DYNAMIC_SECTION(analyticCase.name) {
|
||||
const double deformationDeterminant = analyticCase.deformationScale[0] *
|
||||
analyticCase.deformationScale[1] *
|
||||
analyticCase.deformationScale[2];
|
||||
const mean_field::field::FieldDofMap gravityPotentialMap =
|
||||
field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityPotentialFes);
|
||||
|
||||
REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14);
|
||||
const mean_field::field::FieldDofMap displacementMap =
|
||||
field_dof_test_utils::make_map<mean_field::field::Displacement>(*f.displacementFes);
|
||||
|
||||
const mean_field::physics::RigidRotation rotation =
|
||||
prepared_hydrostatic_analytic_solve_test_utils::make_rotation(
|
||||
analyticCase);
|
||||
const mfem::Array<int> stellarElementMarker =
|
||||
prepared_hydrostatic_analytic_solve_test_utils::make_stellar_element_marker(f);
|
||||
|
||||
auto displacementFunction =
|
||||
[&analyticCase](const mfem::Vector &referencePosition,
|
||||
mfem::Vector &displacementValue) {
|
||||
mfem::Vector physicalPosition;
|
||||
for (const AnalyticCase &analyticCase : analyticCases) {
|
||||
DYNAMIC_SECTION(analyticCase.name) {
|
||||
const double deformationDeterminant =
|
||||
analyticCase.deformationScale[0] * analyticCase.deformationScale[1] * analyticCase.deformationScale[2];
|
||||
|
||||
prepared_hydrostatic_analytic_solve_test_utils::map_to_physical(
|
||||
referencePosition, analyticCase, physicalPosition);
|
||||
REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14);
|
||||
|
||||
displacementValue.SetSize(3);
|
||||
displacementValue = physicalPosition;
|
||||
displacementValue -= referencePosition;
|
||||
};
|
||||
const mean_field::physics::RigidRotation rotation =
|
||||
prepared_hydrostatic_analytic_solve_test_utils::make_rotation(analyticCase);
|
||||
|
||||
auto potentialFunction = [&analyticCase, &rotation](
|
||||
const mfem::Vector &referencePosition) {
|
||||
return prepared_hydrostatic_analytic_solve_test_utils::
|
||||
exact_potential_value(referencePosition, analyticCase, rotation);
|
||||
};
|
||||
auto displacementFunction =
|
||||
[&analyticCase](const mfem::Vector &referencePosition, mfem::Vector &displacementValue) {
|
||||
mfem::Vector physicalPosition;
|
||||
|
||||
auto enthalpyFunction = [](const mfem::Vector &referencePosition) {
|
||||
return prepared_hydrostatic_analytic_solve_test_utils::
|
||||
exact_enthalpy_value(referencePosition);
|
||||
};
|
||||
prepared_hydrostatic_analytic_solve_test_utils::map_to_physical(
|
||||
referencePosition, analyticCase, physicalPosition
|
||||
);
|
||||
|
||||
mfem::VectorFunctionCoefficient displacementCoefficient(
|
||||
f.mesh->Dimension(), displacementFunction);
|
||||
displacementValue.SetSize(3);
|
||||
displacementValue = physicalPosition;
|
||||
displacementValue -= referencePosition;
|
||||
};
|
||||
|
||||
mfem::FunctionCoefficient potentialCoefficient(potentialFunction);
|
||||
auto potentialFunction = [&analyticCase, &rotation](const mfem::Vector &referencePosition) {
|
||||
return prepared_hydrostatic_analytic_solve_test_utils::exact_potential_value(
|
||||
referencePosition, analyticCase, rotation
|
||||
);
|
||||
};
|
||||
|
||||
mfem::FunctionCoefficient exactEnthalpyCoefficient(enthalpyFunction);
|
||||
auto enthalpyFunction = [](const mfem::Vector &referencePosition) {
|
||||
return prepared_hydrostatic_analytic_solve_test_utils::exact_enthalpy_value(referencePosition);
|
||||
};
|
||||
|
||||
/*
|
||||
* Project the prescribed geometry and potential.
|
||||
*/
|
||||
mfem::ParGridFunction displacementField(f.displacementFes.get());
|
||||
mfem::VectorFunctionCoefficient displacementCoefficient(f.mesh->Dimension(), displacementFunction);
|
||||
|
||||
mfem::ParGridFunction potentialField(f.gravityPotentialFes.get());
|
||||
mfem::FunctionCoefficient potentialCoefficient(potentialFunction);
|
||||
|
||||
displacementField.ProjectCoefficient(displacementCoefficient);
|
||||
mfem::FunctionCoefficient exactEnthalpyCoefficient(enthalpyFunction);
|
||||
|
||||
potentialField.ProjectCoefficient(potentialCoefficient);
|
||||
/*
|
||||
* Project the prescribed geometry and potential.
|
||||
*/
|
||||
mfem::ParGridFunction displacementField(f.displacementFes.get());
|
||||
|
||||
mfem::Vector displacementTrue;
|
||||
mfem::Vector gravityPotentialTrue;
|
||||
mfem::ParGridFunction potentialField(f.gravityPotentialFes.get());
|
||||
|
||||
displacementField.GetTrueDofs(displacementTrue);
|
||||
potentialField.GetTrueDofs(gravityPotentialTrue);
|
||||
displacementField.ProjectCoefficient(displacementCoefficient);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
displacementMap.gather(displacementTrue);
|
||||
const mfem::Vector gravityPotential =
|
||||
gravityPotentialMap.gather(gravityPotentialTrue);
|
||||
potentialField.ProjectCoefficient(potentialCoefficient);
|
||||
|
||||
/*
|
||||
* This projection is not used as the solution. It gives
|
||||
* the best directly available representation baseline
|
||||
* against which the solved field can be compared.
|
||||
*/
|
||||
mfem::ParGridFunction projectedEnthalpyField(f.enthalpyFes.get());
|
||||
mfem::Vector displacementTrue;
|
||||
mfem::Vector gravityPotentialTrue;
|
||||
|
||||
projectedEnthalpyField.ProjectCoefficient(exactEnthalpyCoefficient);
|
||||
displacementField.GetTrueDofs(displacementTrue);
|
||||
potentialField.GetTrueDofs(gravityPotentialTrue);
|
||||
|
||||
mfem::ParGridFunction zeroEnthalpyField(f.enthalpyFes.get());
|
||||
const mfem::Vector displacement = displacementMap.gather(displacementTrue);
|
||||
const mfem::Vector gravityPotential = gravityPotentialMap.gather(gravityPotentialTrue);
|
||||
|
||||
zeroEnthalpyField = 0.0;
|
||||
/*
|
||||
* This projection is not used as the solution. It gives
|
||||
* the best directly available representation baseline
|
||||
* against which the solved field can be compared.
|
||||
*/
|
||||
mfem::ParGridFunction projectedEnthalpyField(f.enthalpyFes.get());
|
||||
|
||||
const double exactEnthalpyNorm = zeroEnthalpyField.ComputeL2Error(
|
||||
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
|
||||
projectedEnthalpyField.ProjectCoefficient(exactEnthalpyCoefficient);
|
||||
|
||||
const double projectionError = projectedEnthalpyField.ComputeL2Error(
|
||||
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
|
||||
mfem::ParGridFunction zeroEnthalpyField(f.enthalpyFes.get());
|
||||
|
||||
REQUIRE(exactEnthalpyNorm > 0.0);
|
||||
zeroEnthalpyField = 0.0;
|
||||
|
||||
const double relativeProjectionError =
|
||||
projectionError / exactEnthalpyNorm;
|
||||
const double exactEnthalpyNorm =
|
||||
zeroEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
|
||||
|
||||
/*
|
||||
* Begin deliberately far from equilibrium.
|
||||
*/
|
||||
mfem::Vector enthalpy(enthalpyMap.reduced_size());
|
||||
const double projectionError =
|
||||
projectedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
|
||||
|
||||
enthalpy = 0.0;
|
||||
REQUIRE(exactEnthalpyNorm > 0.0);
|
||||
|
||||
auto dependencies =
|
||||
prepared_hydrostatic_analytic_solve_test_utils::make_dependencies();
|
||||
const double relativeProjectionError = projectionError / exactEnthalpyNorm;
|
||||
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
preparedOperator(f, *f.domainMapperStateless);
|
||||
/*
|
||||
* Begin deliberately far from equilibrium.
|
||||
*/
|
||||
mfem::Vector enthalpy(enthalpyMap.reduced_size());
|
||||
|
||||
const auto initialReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_analytic_solve_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement),
|
||||
dependencies, rotation);
|
||||
enthalpy = 0.0;
|
||||
|
||||
REQUIRE(initialReport.preparedResidual);
|
||||
REQUIRE(initialReport.preparedAlgebraicJacobianBlocks);
|
||||
auto dependencies = prepared_hydrostatic_analytic_solve_test_utils::make_dependencies();
|
||||
|
||||
mfem::Vector initialResidual;
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
preparedOperator.BuildResidual(initialResidual);
|
||||
const auto initialReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
const double initialResidualNorm =
|
||||
gravity_prepared_test_utils::global_norm(initialResidual,
|
||||
communicator);
|
||||
REQUIRE(initialReport.preparedResidual);
|
||||
REQUIRE(initialReport.preparedAlgebraicJacobianBlocks);
|
||||
|
||||
REQUIRE(initialResidualNorm > 1.0e-12);
|
||||
mfem::Vector initialResidual;
|
||||
|
||||
/*
|
||||
* One discrete Newton step:
|
||||
*
|
||||
* M_h delta_h = -R_h.
|
||||
*
|
||||
* The full four-block Bernoulli Jacobian is rectangular
|
||||
* and underdetermined in isolation. Freezing Phi, C,
|
||||
* rotation, and displacement makes this a well-defined
|
||||
* enthalpy solve.
|
||||
*/
|
||||
prepared_hydrostatic_analytic_solve_test_utils::EnthalpyJacobianOperator
|
||||
enthalpyJacobian(enthalpyMap.reduced_size(), preparedOperator);
|
||||
preparedOperator.BuildResidual(initialResidual);
|
||||
|
||||
mfem::Vector rightHandSide(initialResidual);
|
||||
rightHandSide *= -1.0;
|
||||
const double initialResidualNorm = gravity_prepared_test_utils::global_norm(initialResidual, communicator);
|
||||
|
||||
mfem::Vector enthalpyCorrection(enthalpyMap.reduced_size());
|
||||
REQUIRE(initialResidualNorm > 1.0e-12);
|
||||
|
||||
enthalpyCorrection = 0.0;
|
||||
/*
|
||||
* One discrete Newton step:
|
||||
*
|
||||
* M_h delta_h = -R_h.
|
||||
*
|
||||
* The full four-block Bernoulli Jacobian is rectangular
|
||||
* and underdetermined in isolation. Freezing Phi, C,
|
||||
* rotation, and displacement makes this a well-defined
|
||||
* enthalpy solve.
|
||||
*/
|
||||
prepared_hydrostatic_analytic_solve_test_utils::EnthalpyJacobianOperator enthalpyJacobian(
|
||||
enthalpyMap.reduced_size(), preparedOperator
|
||||
);
|
||||
|
||||
/*
|
||||
* The reduced operator contains only stellar-supported
|
||||
* enthalpy DOFs and is positive definite. MINRES remains
|
||||
* appropriate for this symmetric system.
|
||||
*/
|
||||
mfem::MINRESSolver linearSolver(communicator);
|
||||
mfem::Vector rightHandSide(initialResidual);
|
||||
rightHandSide *= -1.0;
|
||||
|
||||
linearSolver.SetOperator(enthalpyJacobian);
|
||||
mfem::Vector enthalpyCorrection(enthalpyMap.reduced_size());
|
||||
|
||||
linearSolver.SetRelTol(1.0e-13);
|
||||
linearSolver.SetAbsTol(1.0e-14);
|
||||
linearSolver.SetMaxIter(2000);
|
||||
linearSolver.SetPrintLevel(0);
|
||||
enthalpyCorrection = 0.0;
|
||||
|
||||
linearSolver.Mult(rightHandSide, enthalpyCorrection);
|
||||
/*
|
||||
* The reduced operator contains only stellar-supported
|
||||
* enthalpy DOFs and is positive definite. MINRES remains
|
||||
* appropriate for this symmetric system.
|
||||
*/
|
||||
mfem::MINRESSolver linearSolver(communicator);
|
||||
|
||||
INFO("Linear solver converged = " << linearSolver.GetConverged());
|
||||
linearSolver.SetOperator(enthalpyJacobian);
|
||||
|
||||
INFO("Linear solver iterations = " << linearSolver.GetNumIterations());
|
||||
linearSolver.SetRelTol(1.0e-13);
|
||||
linearSolver.SetAbsTol(1.0e-14);
|
||||
linearSolver.SetMaxIter(2000);
|
||||
linearSolver.SetPrintLevel(0);
|
||||
|
||||
INFO("Linear solver final norm = " << linearSolver.GetFinalNorm());
|
||||
linearSolver.Mult(rightHandSide, enthalpyCorrection);
|
||||
|
||||
REQUIRE(linearSolver.GetConverged());
|
||||
INFO("Linear solver converged = " << linearSolver.GetConverged());
|
||||
|
||||
enthalpy += enthalpyCorrection;
|
||||
INFO("Linear solver iterations = " << linearSolver.GetNumIterations());
|
||||
|
||||
/*
|
||||
* Only the enthalpy state changed. Geometry, rotation,
|
||||
* and algebraic Jacobian data must remain reusable.
|
||||
*/
|
||||
++dependencies.enthalpy.revision;
|
||||
INFO("Linear solver final norm = " << linearSolver.GetFinalNorm());
|
||||
|
||||
const auto solvedReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_analytic_solve_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement),
|
||||
dependencies, rotation);
|
||||
REQUIRE(linearSolver.GetConverged());
|
||||
|
||||
CHECK(solvedReport.contextReport.updatedEnthalpy);
|
||||
enthalpy += enthalpyCorrection;
|
||||
|
||||
CHECK(solvedReport.contextReport.preparedBaseState);
|
||||
/*
|
||||
* Only the enthalpy state changed. Geometry, rotation,
|
||||
* and algebraic Jacobian data must remain reusable.
|
||||
*/
|
||||
++dependencies.enthalpy.revision;
|
||||
|
||||
CHECK_FALSE(solvedReport.contextReport.preparedGeometryState);
|
||||
const auto solvedReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
CHECK_FALSE(solvedReport.preparedAlgebraicJacobianBlocks);
|
||||
CHECK(solvedReport.contextReport.updatedEnthalpy);
|
||||
|
||||
mfem::Vector solvedResidual;
|
||||
CHECK(solvedReport.contextReport.preparedBaseState);
|
||||
|
||||
preparedOperator.BuildResidual(solvedResidual);
|
||||
CHECK_FALSE(solvedReport.contextReport.preparedGeometryState);
|
||||
|
||||
const double solvedResidualNorm =
|
||||
gravity_prepared_test_utils::global_norm(solvedResidual,
|
||||
communicator);
|
||||
CHECK_FALSE(solvedReport.preparedAlgebraicJacobianBlocks);
|
||||
|
||||
const double residualReduction = solvedResidualNorm / initialResidualNorm;
|
||||
mfem::Vector solvedResidual;
|
||||
|
||||
/*
|
||||
* Compare the solved field with the continuum analytic
|
||||
* enthalpy over stellar elements only.
|
||||
*
|
||||
* All three mappings have determinant one, so this
|
||||
* normalized L2 error is also unchanged by the physical
|
||||
* volume transformation.
|
||||
*/
|
||||
mfem::ParGridFunction solvedEnthalpyField(f.enthalpyFes.get());
|
||||
preparedOperator.BuildResidual(solvedResidual);
|
||||
|
||||
mfem::Vector enthalpyTrue(enthalpyMap.full_size());
|
||||
enthalpyMap.scatter(enthalpy, enthalpyTrue);
|
||||
solvedEnthalpyField.SetFromTrueDofs(enthalpyTrue);
|
||||
const double solvedResidualNorm = gravity_prepared_test_utils::global_norm(solvedResidual, communicator);
|
||||
|
||||
const double solvedAnalyticError = solvedEnthalpyField.ComputeL2Error(
|
||||
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
|
||||
const double residualReduction = solvedResidualNorm / initialResidualNorm;
|
||||
|
||||
const double relativeSolvedAnalyticError =
|
||||
solvedAnalyticError / exactEnthalpyNorm;
|
||||
/*
|
||||
* Compare the solved field with the continuum analytic
|
||||
* enthalpy over stellar elements only.
|
||||
*
|
||||
* All three mappings have determinant one, so this
|
||||
* normalized L2 error is also unchanged by the physical
|
||||
* volume transformation.
|
||||
*/
|
||||
mfem::ParGridFunction solvedEnthalpyField(f.enthalpyFes.get());
|
||||
|
||||
INFO("Deformation determinant = " << deformationDeterminant);
|
||||
mfem::Vector enthalpyTrue(enthalpyMap.full_size());
|
||||
enthalpyMap.scatter(enthalpy, enthalpyTrue);
|
||||
solvedEnthalpyField.SetFromTrueDofs(enthalpyTrue);
|
||||
|
||||
INFO("Initial weak residual norm = " << initialResidualNorm);
|
||||
const double solvedAnalyticError =
|
||||
solvedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
|
||||
|
||||
INFO("Solved weak residual norm = " << solvedResidualNorm);
|
||||
const double relativeSolvedAnalyticError = solvedAnalyticError / exactEnthalpyNorm;
|
||||
|
||||
INFO("Weak residual reduction = " << residualReduction);
|
||||
INFO("Deformation determinant = " << deformationDeterminant);
|
||||
|
||||
INFO("Relative analytic projection floor = " << relativeProjectionError);
|
||||
INFO("Initial weak residual norm = " << initialResidualNorm);
|
||||
|
||||
INFO("Relative solved analytic L2 error = "
|
||||
<< relativeSolvedAnalyticError);
|
||||
INFO("Solved weak residual norm = " << solvedResidualNorm);
|
||||
|
||||
/*
|
||||
* The discrete Bernoulli equation must be solved essentially
|
||||
* to the linear-solver floor.
|
||||
*/
|
||||
CHECK(residualReduction < 1.0e-10);
|
||||
INFO("Weak residual reduction = " << residualReduction);
|
||||
|
||||
/*
|
||||
* The directly projected analytic enthalpy provides a lower
|
||||
* representation bound, but it is not the expected solution
|
||||
* of the cross-space discrete Bernoulli equation. The latter
|
||||
* also contains potential-projection and mapped-space
|
||||
* compatibility errors.
|
||||
*/
|
||||
CHECK(relativeSolvedAnalyticError <
|
||||
std::max(5.0 * relativeProjectionError, 1.25e-4));
|
||||
INFO("Relative analytic projection floor = " << relativeProjectionError);
|
||||
|
||||
/*
|
||||
* Record that the analytic error remains within one order of
|
||||
* magnitude of the direct enthalpy projection floor.
|
||||
*/
|
||||
CHECK(relativeSolvedAnalyticError / relativeProjectionError < 5.0);
|
||||
INFO("Relative solved analytic L2 error = " << relativeSolvedAnalyticError);
|
||||
|
||||
/*
|
||||
* The discrete Bernoulli equation must be solved essentially
|
||||
* to the linear-solver floor.
|
||||
*/
|
||||
CHECK(residualReduction < 1.0e-10);
|
||||
|
||||
/*
|
||||
* The directly projected analytic enthalpy provides a lower
|
||||
* representation bound, but it is not the expected solution
|
||||
* of the cross-space discrete Bernoulli equation. The latter
|
||||
* also contains potential-projection and mapped-space
|
||||
* compatibility errors.
|
||||
*/
|
||||
CHECK(relativeSolvedAnalyticError < std::max(5.0 * relativeProjectionError, 1.25e-4));
|
||||
|
||||
/*
|
||||
* Record that the analytic error remains within one order of
|
||||
* magnitude of the direct enthalpy projection floor.
|
||||
*/
|
||||
CHECK(relativeSolvedAnalyticError / relativeProjectionError < 5.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,9 +138,7 @@ TEST_CASE(
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(
|
||||
*f.enthalpyFes, 0.34
|
||||
);
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 0.34);
|
||||
|
||||
const mfem::Vector gravityPotential =
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>(
|
||||
@@ -161,9 +159,7 @@ TEST_CASE(
|
||||
);
|
||||
|
||||
const mfem::Vector enthalpyVariation =
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(
|
||||
*f.enthalpyFes, 1.07
|
||||
);
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 1.07);
|
||||
|
||||
const mfem::Vector gravityPotentialVariation =
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>(
|
||||
@@ -236,9 +232,7 @@ TEST_CASE(
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(
|
||||
*f.enthalpyFes, 0.41
|
||||
);
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 0.41);
|
||||
|
||||
const mfem::Vector gravityPotential =
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>(
|
||||
@@ -277,9 +271,7 @@ TEST_CASE(
|
||||
CHECK(preparedOperator.GetEnthalpyMap().inactive_size() > 0);
|
||||
|
||||
const mfem::Vector enthalpyVariation =
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(
|
||||
*f.enthalpyFes, 1.12
|
||||
);
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Enthalpy>(*f.enthalpyFes, 1.12);
|
||||
|
||||
const mfem::Vector gravityPotentialVariation =
|
||||
field_dof_test_utils::make_deterministic_supported_vector<mean_field::field::Gravity>(
|
||||
|
||||
@@ -26,16 +26,13 @@ namespace mass_normalization_test_utils {
|
||||
);
|
||||
|
||||
[[nodiscard]] mean_field::operators::MassNormalizationLayout make_layout(const mean_field::fem::FEM &f) {
|
||||
const auto densityMap =
|
||||
field_dof_test_utils::make_map<mean_field::field::Density>(*f.densityFes);
|
||||
const auto densityMap = field_dof_test_utils::make_map<mean_field::field::Density>(*f.densityFes);
|
||||
const auto displacementMap =
|
||||
field_dof_test_utils::make_map<mean_field::field::Displacement>(*f.displacementFes);
|
||||
const auto gravityFluxMap =
|
||||
field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityFluxFes);
|
||||
const auto gravityFluxMap = field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityFluxFes);
|
||||
const auto gravityPotentialMap =
|
||||
field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityPotentialFes);
|
||||
const auto enthalpyMap =
|
||||
field_dof_test_utils::make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
|
||||
const auto enthalpyMap = field_dof_test_utils::make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
|
||||
|
||||
const std::array<int, CoupledForm::value_block_count> valueSizes{
|
||||
densityMap.reduced_size(), displacementMap.reduced_size(), gravityFluxMap.reduced_size(),
|
||||
@@ -43,7 +40,7 @@ namespace mass_normalization_test_utils {
|
||||
};
|
||||
|
||||
const std::array<int, CoupledForm::residual_block_count> residualSizes{
|
||||
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), densityMap.reduced_size(),
|
||||
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), densityMap.reduced_size(),
|
||||
displacementMap.reduced_size(), enthalpyMap.reduced_size(), 1
|
||||
};
|
||||
|
||||
@@ -345,8 +342,7 @@ TEST_CASE(
|
||||
mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext);
|
||||
massOperator.Prepare({.targetMass = 1.11}, dependencies);
|
||||
|
||||
const mfem::Vector reducedDisplacementDirection =
|
||||
gravityContext.GetDisplacementMap().gather(displacementDirection);
|
||||
const mfem::Vector reducedDisplacementDirection = gravityContext.GetDisplacementMap().gather(displacementDirection);
|
||||
|
||||
mfem::Vector analyticAction;
|
||||
massOperator.ApplyDisplacementJacobianAction(reducedDisplacementDirection, analyticAction);
|
||||
@@ -476,9 +472,8 @@ TEST_CASE(
|
||||
mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext);
|
||||
massOperator.Prepare({.targetMass = 1.19}, dependencies);
|
||||
|
||||
const mfem::Vector reducedDensityDirection = gravityContext.GetDensityMap().gather(densityDirection);
|
||||
const mfem::Vector reducedDisplacementDirection =
|
||||
gravityContext.GetDisplacementMap().gather(displacementDirection);
|
||||
const mfem::Vector reducedDensityDirection = gravityContext.GetDensityMap().gather(densityDirection);
|
||||
const mfem::Vector reducedDisplacementDirection = gravityContext.GetDisplacementMap().gather(displacementDirection);
|
||||
|
||||
mfem::Vector densityAction;
|
||||
mfem::Vector displacementAction;
|
||||
@@ -486,9 +481,7 @@ TEST_CASE(
|
||||
|
||||
massOperator.ApplyDensityJacobianAction(reducedDensityDirection, densityAction);
|
||||
massOperator.ApplyDisplacementJacobianAction(reducedDisplacementDirection, displacementAction);
|
||||
massOperator.ApplyCompleteJacobianAction(
|
||||
reducedDensityDirection, reducedDisplacementDirection, completeAction
|
||||
);
|
||||
massOperator.ApplyCompleteJacobianAction(reducedDensityDirection, reducedDisplacementDirection, completeAction);
|
||||
|
||||
CHECK(
|
||||
mass_normalization_test_utils::relative_error(completeAction(0), densityAction(0) + displacementAction(0)) <
|
||||
@@ -498,10 +491,7 @@ TEST_CASE(
|
||||
const auto layout = mass_normalization_test_utils::make_layout(f);
|
||||
mean_field::operators::PreparedMassNormalizationJacobianOperator adapter(layout, massOperator);
|
||||
|
||||
CHECK(
|
||||
layout.size(mass_normalization_test_utils::densityValue) ==
|
||||
gravityContext.GetDensityMap().reduced_size()
|
||||
);
|
||||
CHECK(layout.size(mass_normalization_test_utils::densityValue) == gravityContext.GetDensityMap().reduced_size());
|
||||
CHECK(
|
||||
layout.size(mass_normalization_test_utils::displacementValue) ==
|
||||
gravityContext.GetDisplacementMap().reduced_size()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,115 +8,202 @@
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
TEST_CASE("Polytropic EOS Satisfies Its Analytic Identities",
|
||||
tags::barotrope_eos_unit) {
|
||||
constexpr double polytropic_index = 3.0;
|
||||
constexpr double polytropic_constant = 1.5;
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Satisfies Its Analytic Identities",
|
||||
tags::barotrope_eos_unit
|
||||
) {
|
||||
using namespace mean_field::eos;
|
||||
|
||||
const mean_field::eos::Polytrope barotrope(polytropic_index,
|
||||
polytropic_constant);
|
||||
constexpr double polytropic_index = 3.0;
|
||||
constexpr double polytropic_constant = 1.5;
|
||||
|
||||
const std::array<double, 5> densities{1.0e-6, 1.0e-3, 0.1, 0.7, 2.0};
|
||||
const Polytrope barotrope(polytropic_index, polytropic_constant);
|
||||
|
||||
for (const double density : densities) {
|
||||
const double pressure = barotrope.pressure_from_density(density);
|
||||
using densityV = DensityValue;
|
||||
using pressureV = PressureValue;
|
||||
using enthalpyV = SpecificEnthalpyValue;
|
||||
|
||||
const double enthalpy = barotrope.enthalpy_from_density(density);
|
||||
constexpr std::array<densityV, 5> densities{
|
||||
densityV{1.0e-6}, densityV{1.0e-3}, densityV{0.1}, densityV{0.7}, densityV{2.0}
|
||||
};
|
||||
|
||||
const double reconstructed_density =
|
||||
barotrope.density_from_enthalpy(enthalpy);
|
||||
for (const densityV density : densities) {
|
||||
const pressureV pressure = evaluate<quantity::Pressure>(barotrope, density);
|
||||
|
||||
const double reconstructed_pressure =
|
||||
barotrope.pressure_from_enthalpy(enthalpy);
|
||||
const enthalpyV enthalpy = evaluate<quantity::SpecificEnthalpy>(barotrope, density);
|
||||
const densityV reconstructed_density = evaluate<quantity::Density>(barotrope, enthalpy);
|
||||
|
||||
const double reconstructed_enthalpy =
|
||||
barotrope.enthalpy_from_pressure(pressure);
|
||||
const pressureV reconstructed_pressure = evaluate<quantity::Pressure>(barotrope, enthalpy);
|
||||
|
||||
CHECK_THAT(reconstructed_density,
|
||||
Catch::Matchers::WithinRel(density, 2.0e-14));
|
||||
const enthalpyV reconstructed_enthalpy = evaluate<quantity::SpecificEnthalpy>(barotrope, pressure);
|
||||
|
||||
CHECK_THAT(reconstructed_pressure,
|
||||
Catch::Matchers::WithinRel(pressure, 2.0e-14));
|
||||
CHECK_THAT(reconstructed_density.value(), Catch::Matchers::WithinRel(density.value(), 2.0e-14));
|
||||
|
||||
CHECK_THAT(reconstructed_enthalpy,
|
||||
Catch::Matchers::WithinRel(enthalpy, 2.0e-14));
|
||||
CHECK_THAT(reconstructed_pressure.value(), Catch::Matchers::WithinRel(pressure.value(), 2.0e-14));
|
||||
|
||||
CHECK_THAT(pressure,
|
||||
Catch::Matchers::WithinRel(
|
||||
density * enthalpy / (polytropic_index + 1.0), 2.0e-14));
|
||||
CHECK_THAT(reconstructed_enthalpy.value(), Catch::Matchers::WithinRel(enthalpy.value(), 2.0e-14));
|
||||
|
||||
CHECK_THAT(barotrope.pressure_derivative_from_enthalpy(enthalpy),
|
||||
Catch::Matchers::WithinRel(density, 2.0e-14));
|
||||
CHECK_THAT(
|
||||
pressure.value(),
|
||||
Catch::Matchers::WithinRel(density.value() * enthalpy.value() / (polytropic_index + 1.0), 2.0e-14)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
barotrope.pressure_derivative_from_density(density),
|
||||
Catch::Matchers::WithinRel(enthalpy / polytropic_index, 2.0e-14));
|
||||
}
|
||||
CHECK_THAT(
|
||||
(mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Pressure, mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{enthalpy}
|
||||
)
|
||||
.value()),
|
||||
Catch::Matchers::WithinRel(density.value(), 2.0e-14)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
(mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Pressure, mean_field::eos::quantity::Density>(
|
||||
barotrope, mean_field::eos::DensityValue{density}
|
||||
)
|
||||
.value()),
|
||||
Catch::Matchers::WithinRel(enthalpy.value() / polytropic_index, 2.0e-14)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Polytropic EOS Derivatives Match Centered Differences",
|
||||
tags::barotrope_eos_jacobian) {
|
||||
const mean_field::eos::Polytrope barotrope(3.0, 1.5);
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Derivatives Match Centered Differences",
|
||||
tags::barotrope_eos_jacobian
|
||||
) {
|
||||
using namespace mean_field::eos;
|
||||
|
||||
const std::array<double, 4> enthalpies{0.05, 0.2, 0.7, 1.4};
|
||||
const Polytrope barotrope(3.0, 1.5);
|
||||
|
||||
for (const double enthalpy : enthalpies) {
|
||||
const double step = 1.0e-6 * std::max(1.0, enthalpy);
|
||||
using densityV = DensityValue;
|
||||
using pressureV = PressureValue;
|
||||
using enthalpyV = SpecificEnthalpyValue;
|
||||
|
||||
const double density_difference =
|
||||
(barotrope.density_from_enthalpy(enthalpy + step) -
|
||||
barotrope.density_from_enthalpy(enthalpy - step)) /
|
||||
(2.0 * step);
|
||||
constexpr std::array<enthalpyV, 4> enthalpies{enthalpyV{0.05}, enthalpyV{0.2}, enthalpyV{0.7}, enthalpyV{1.4}};
|
||||
|
||||
const double pressure_difference =
|
||||
(barotrope.pressure_from_enthalpy(enthalpy + step) -
|
||||
barotrope.pressure_from_enthalpy(enthalpy - step)) /
|
||||
(2.0 * step);
|
||||
for (const enthalpyV enthalpy : enthalpies) {
|
||||
const enthalpyV step = enthalpyV{1.0e-6} * std::max(1.0, enthalpy.value());
|
||||
|
||||
CHECK_THAT(
|
||||
density_difference,
|
||||
Catch::Matchers::WithinRel(
|
||||
barotrope.density_derivative_from_enthalpy(enthalpy), 5.0e-10));
|
||||
const densityV density_difference = (evaluate<quantity::Density>(barotrope, enthalpy + step) -
|
||||
evaluate<quantity::Density>(barotrope, enthalpy - step)) /
|
||||
(2.0 * step.value());
|
||||
|
||||
CHECK_THAT(
|
||||
pressure_difference,
|
||||
Catch::Matchers::WithinRel(
|
||||
barotrope.pressure_derivative_from_enthalpy(enthalpy), 5.0e-10));
|
||||
}
|
||||
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);
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Has An Exact Zero Density Surface",
|
||||
tags::barotrope_eos_unit
|
||||
) {
|
||||
const mean_field::eos::Polytrope barotrope(3.0, 1.5);
|
||||
|
||||
CHECK(barotrope.density_from_enthalpy(-1.0) == 0.0);
|
||||
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0);
|
||||
CHECK(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{-1.0}
|
||||
)
|
||||
.value() == 0.0
|
||||
);
|
||||
CHECK(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
|
||||
)
|
||||
.value() == 0.0
|
||||
);
|
||||
|
||||
CHECK(barotrope.pressure_from_enthalpy(-1.0) == 0.0);
|
||||
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0);
|
||||
CHECK(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{-1.0}
|
||||
)
|
||||
.value() == 0.0
|
||||
);
|
||||
CHECK(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
|
||||
)
|
||||
.value() == 0.0
|
||||
);
|
||||
|
||||
CHECK(barotrope.density_derivative_from_enthalpy(-1.0) == 0.0);
|
||||
CHECK(
|
||||
(mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Density, mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{-1.0}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
|
||||
CHECK(barotrope.density_derivative_from_enthalpy(0.0) == 0.0);
|
||||
CHECK(
|
||||
(mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Density, mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
|
||||
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0);
|
||||
CHECK(
|
||||
(mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Pressure, mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE("Polytropic EOS Rejects Invalid Material Parameters",
|
||||
tags::barotrope_eos_unit) {
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.5, 1.0), std::invalid_argument);
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Rejects Invalid Material Parameters",
|
||||
tags::barotrope_eos_unit
|
||||
) {
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.5, 1.0), std::invalid_argument);
|
||||
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument);
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0),
|
||||
std::invalid_argument);
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0), std::invalid_argument);
|
||||
|
||||
const mean_field::eos::Polytrope barotrope(3.0, 1.0);
|
||||
const mean_field::eos::Polytrope barotrope(3.0, 1.0);
|
||||
|
||||
CHECK_THROWS_AS(barotrope.pressure_from_density(-1.0), std::domain_error);
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(barotrope, mean_field::eos::DensityValue{-1.0}),
|
||||
std::domain_error
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-1.0), std::domain_error);
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::DensityValue{-1.0}
|
||||
),
|
||||
std::domain_error
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(barotrope.enthalpy_from_pressure(-1.0), std::domain_error);
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::PressureValue{-1.0}
|
||||
),
|
||||
std::domain_error
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
125
tests/physics/equation_of_state_consumer_contracts.cpp
Normal file
125
tests/physics/equation_of_state_consumer_contracts.cpp
Normal 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>);
|
||||
}
|
||||
322
tests/physics/equation_of_state_runtime_view.cpp
Normal file
322
tests/physics/equation_of_state_runtime_view.cpp
Normal 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());
|
||||
}
|
||||
228
tests/physics/equation_of_state_type_system.cpp
Normal file
228
tests/physics/equation_of_state_type_system.cpp
Normal 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
156
tests/physics/polytropic_eos_characterization.cpp
Normal file
156
tests/physics/polytropic_eos_characterization.cpp
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
200
tests/physics/polytropic_eos_relations.cpp
Normal file
200
tests/physics/polytropic_eos_relations.cpp
Normal 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)
|
||||
);
|
||||
}
|
||||
329
tests/surface/constant_surface_compilation.cpp
Normal file
329
tests/surface/constant_surface_compilation.cpp
Normal 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));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -13,456 +13,469 @@ export module test_helpers;
|
||||
import mean_field;
|
||||
|
||||
template <std::size_t N> struct Tag {
|
||||
std::array<char, N> chars{};
|
||||
std::array<char, N> chars{};
|
||||
|
||||
// ReSharper disable once CppNonExplicitConvertingConstructor
|
||||
consteval Tag(std::array<char, N> arr) : chars(arr) {}
|
||||
// ReSharper disable once CppNonExplicitConvertingConstructor
|
||||
consteval Tag(
|
||||
std::array<
|
||||
char,
|
||||
N> arr
|
||||
)
|
||||
: chars(arr) {
|
||||
}
|
||||
|
||||
// ReSharper disable once CppNonExplicitConversionOperator
|
||||
constexpr operator const char *() const { return chars.data(); }
|
||||
// ReSharper disable once CppNonExplicitConversionOperator
|
||||
constexpr operator const char *() const {
|
||||
return chars.data();
|
||||
}
|
||||
|
||||
// ReSharper disable once CppNonExplicitConversionOperator
|
||||
constexpr operator Catch::StringRef() const {
|
||||
return Catch::StringRef(chars.data(), N - 1);
|
||||
}
|
||||
// ReSharper disable once CppNonExplicitConversionOperator
|
||||
constexpr operator Catch::StringRef() const {
|
||||
return Catch::StringRef(chars.data(), N - 1);
|
||||
}
|
||||
|
||||
template <std::size_t M>
|
||||
consteval Tag<N + M - 1> operator&(const Tag<M> &other) const {
|
||||
std::array<char, N + M - 1> res{};
|
||||
std::ranges::copy(chars.begin(), chars.end() - 1, res.begin());
|
||||
std::ranges::copy(other.chars, res.begin() + (N - 1));
|
||||
return {res};
|
||||
}
|
||||
template <std::size_t M> consteval Tag<N + M - 1> operator&(const Tag<M> &other) const {
|
||||
std::array<char, N + M - 1> res{};
|
||||
std::ranges::copy(chars.begin(), chars.end() - 1, res.begin());
|
||||
std::ranges::copy(other.chars, res.begin() + (N - 1));
|
||||
return {res};
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t N> consteval auto make_tag(const char (&str)[N]) {
|
||||
std::array<char, N + 2> res{};
|
||||
res[0] = '[';
|
||||
std::ranges::copy(str, str + N - 1, res.begin() + 1);
|
||||
res[N] = ']';
|
||||
res[N + 1] = '\0';
|
||||
return Tag<N + 2>{res};
|
||||
std::array<char, N + 2> res{};
|
||||
res[0] = '[';
|
||||
std::ranges::copy(str, str + N - 1, res.begin() + 1);
|
||||
res[N] = ']';
|
||||
res[N + 1] = '\0';
|
||||
return Tag<N + 2>{res};
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval auto sub_tag(const Tag<N> &parent, const char (&str)[M]) {
|
||||
return parent & make_tag(str);
|
||||
template <
|
||||
std::size_t N,
|
||||
std::size_t M>
|
||||
consteval auto sub_tag(
|
||||
const Tag<N> &parent,
|
||||
const char (&str)[M]
|
||||
) {
|
||||
return parent & make_tag(str);
|
||||
}
|
||||
|
||||
namespace test_utils::detail {
|
||||
std::optional<mean_field::utils::Args> configured_args;
|
||||
std::optional<mean_field::utils::Args> configured_args;
|
||||
|
||||
mean_field::utils::Args make_default_args() {
|
||||
mean_field::utils::Args args;
|
||||
args.mesh_file = "sandbox.smesh";
|
||||
args.p.rtol = 1.0e-12;
|
||||
args.p.atol = 1.0e-12;
|
||||
return args;
|
||||
}
|
||||
mean_field::utils::Args make_default_args() {
|
||||
mean_field::utils::Args args;
|
||||
args.mesh_file = "sandbox.smesh";
|
||||
args.p.rtol = 1.0e-12;
|
||||
args.p.atol = 1.0e-12;
|
||||
return args;
|
||||
}
|
||||
} // namespace test_utils::detail
|
||||
|
||||
export namespace test_utils {
|
||||
void set_args(mean_field::utils::Args args) {
|
||||
detail::configured_args = std::move(args);
|
||||
}
|
||||
void set_args(mean_field::utils::Args args) {
|
||||
detail::configured_args = std::move(args);
|
||||
}
|
||||
|
||||
mean_field::utils::Args setup_args() {
|
||||
if (detail::configured_args.has_value()) {
|
||||
return *detail::configured_args;
|
||||
}
|
||||
mean_field::utils::Args setup_args() {
|
||||
if (detail::configured_args.has_value()) {
|
||||
return *detail::configured_args;
|
||||
}
|
||||
|
||||
return detail::make_default_args();
|
||||
}
|
||||
return detail::make_default_args();
|
||||
}
|
||||
} // namespace 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>
|
||||
inline mean_field::field::FieldDofMap
|
||||
make_field_map(const mean_field::fem::FEM &f) {
|
||||
if constexpr (std::same_as<FieldT, mean_field::field::Density>) {
|
||||
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(
|
||||
*f.densityFes);
|
||||
} else if constexpr (std::same_as<FieldT, mean_field::field::Displacement>) {
|
||||
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(
|
||||
*f.displacementFes);
|
||||
} else {
|
||||
static_assert(std::same_as<FieldT, mean_field::field::Gravity>);
|
||||
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(
|
||||
*f.gravityFluxFes);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FieldT>
|
||||
inline mfem::Vector gather_field(const mean_field::fem::FEM &f,
|
||||
const mfem::Vector &true_vector) {
|
||||
return make_field_map<FieldT>(f).gather(true_vector);
|
||||
}
|
||||
|
||||
inline mfem::Vector make_deterministic_vector(const int size,
|
||||
const double phase = 0.0) {
|
||||
mfem::Vector vector(size);
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
const double index = static_cast<double>(i + 1);
|
||||
vector(i) = std::sin(0.37 * index + phase) +
|
||||
0.31 * std::cos(0.19 * index - 0.5 * phase);
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
inline mfem::Vector make_displacement(const mean_field::fem::FEM &f,
|
||||
const double scale) {
|
||||
mfem::ParGridFunction displacement(f.displacementFes.get());
|
||||
|
||||
auto displacement_function = [scale](const mfem::Vector &position,
|
||||
mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value(0) = scale * (0.04 * position(0) + 0.01 * position(1) * position(2));
|
||||
value(1) =
|
||||
scale * (-0.03 * position(1) + 0.008 * position(0) * position(2));
|
||||
value(2) = scale * (0.02 * position(2) - 0.006 * position(0) * position(1));
|
||||
};
|
||||
|
||||
mfem::VectorFunctionCoefficient coefficient(f.mesh->Dimension(),
|
||||
displacement_function);
|
||||
displacement.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector displacement_true;
|
||||
displacement.GetTrueDofs(displacement_true);
|
||||
return displacement_true;
|
||||
}
|
||||
|
||||
inline mfem::Vector make_domain_supported_density(const mean_field::fem::FEM &f,
|
||||
const bool stellar) {
|
||||
mfem::Vector attribute_values(f.mesh->attributes.Max());
|
||||
attribute_values = 0.0;
|
||||
|
||||
using DomainSchema =
|
||||
mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
for (int i = 0; i < f.mesh->attributes.Size(); ++i) {
|
||||
const int attribute = f.mesh->attributes[i];
|
||||
const bool is_stellar = DomainSchema::template attribute_belongs_to<
|
||||
mean_field::utils::domain::Stellar>(attribute);
|
||||
|
||||
if (is_stellar == stellar) {
|
||||
attribute_values(attribute - 1) = 1.0;
|
||||
template <typename FieldT> inline mean_field::field::FieldDofMap make_field_map(const mean_field::fem::FEM &f) {
|
||||
if constexpr (std::same_as<FieldT, mean_field::field::Density>) {
|
||||
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(*f.densityFes);
|
||||
} else if constexpr (std::same_as<FieldT, mean_field::field::Displacement>) {
|
||||
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(*f.displacementFes);
|
||||
} else {
|
||||
static_assert(std::same_as<FieldT, mean_field::field::Gravity>);
|
||||
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(*f.gravityFluxFes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mfem::PWConstCoefficient coefficient(attribute_values);
|
||||
mfem::ParGridFunction density(f.densityFes.get());
|
||||
density.ProjectCoefficient(coefficient);
|
||||
template <typename FieldT>
|
||||
inline mfem::Vector gather_field(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mfem::Vector &true_vector
|
||||
) {
|
||||
return make_field_map<FieldT>(f).gather(true_vector);
|
||||
}
|
||||
|
||||
mfem::Vector density_true;
|
||||
density.GetTrueDofs(density_true);
|
||||
return density_true;
|
||||
}
|
||||
inline mfem::Vector make_deterministic_vector(
|
||||
const int size,
|
||||
const double phase = 0.0
|
||||
) {
|
||||
mfem::Vector vector(size);
|
||||
|
||||
inline mfem::Vector linear_combination(const mfem::Vector &first,
|
||||
const double first_scale,
|
||||
const mfem::Vector &second,
|
||||
const double second_scale) {
|
||||
MFEM_VERIFY(first.Size() == second.Size(),
|
||||
"Cannot combine vectors with different sizes.");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
const double index = static_cast<double>(i + 1);
|
||||
vector(i) = std::sin(0.37 * index + phase) + 0.31 * std::cos(0.19 * index - 0.5 * phase);
|
||||
}
|
||||
|
||||
mfem::Vector combination(first);
|
||||
combination *= first_scale;
|
||||
combination.Add(second_scale, second);
|
||||
return combination;
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
|
||||
inline double global_norm(const mfem::Vector &vector, MPI_Comm communicator) {
|
||||
const double local_norm_squared = vector * vector;
|
||||
double global_norm_squared = 0.0;
|
||||
MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE,
|
||||
MPI_SUM, communicator);
|
||||
return std::sqrt(global_norm_squared);
|
||||
}
|
||||
inline mfem::Vector make_displacement(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double scale
|
||||
) {
|
||||
mfem::ParGridFunction displacement(f.displacementFes.get());
|
||||
|
||||
inline double global_dot(const mfem::Vector &first, const mfem::Vector &second,
|
||||
MPI_Comm communicator) {
|
||||
MFEM_VERIFY(first.Size() == second.Size(),
|
||||
"Cannot take the dot product of vectors with different sizes.");
|
||||
auto displacement_function = [scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value(0) = scale * (0.04 * position(0) + 0.01 * position(1) * position(2));
|
||||
value(1) = scale * (-0.03 * position(1) + 0.008 * position(0) * position(2));
|
||||
value(2) = scale * (0.02 * position(2) - 0.006 * position(0) * position(1));
|
||||
};
|
||||
|
||||
const double local_dot = first * second;
|
||||
double global_dot = 0.0;
|
||||
MPI_Allreduce(&local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
||||
return global_dot;
|
||||
}
|
||||
mfem::VectorFunctionCoefficient coefficient(f.mesh->Dimension(), displacement_function);
|
||||
displacement.ProjectCoefficient(coefficient);
|
||||
|
||||
inline double relative_error(const mfem::Vector &computed,
|
||||
const mfem::Vector &reference,
|
||||
MPI_Comm communicator) {
|
||||
MFEM_VERIFY(computed.Size() == reference.Size(),
|
||||
"Cannot compare vectors with different sizes.");
|
||||
mfem::Vector displacement_true;
|
||||
displacement.GetTrueDofs(displacement_true);
|
||||
return displacement_true;
|
||||
}
|
||||
|
||||
mfem::Vector difference(computed);
|
||||
difference -= reference;
|
||||
inline mfem::Vector make_domain_supported_density(
|
||||
const mean_field::fem::FEM &f,
|
||||
const bool stellar
|
||||
) {
|
||||
mfem::Vector attribute_values(f.mesh->attributes.Max());
|
||||
attribute_values = 0.0;
|
||||
|
||||
return global_norm(difference, communicator) /
|
||||
std::max(global_norm(reference, communicator),
|
||||
std::numeric_limits<double>::epsilon());
|
||||
}
|
||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
inline double relative_scalar_error(const double computed,
|
||||
const double reference) {
|
||||
return std::abs(computed - reference) /
|
||||
std::max(std::abs(reference), std::numeric_limits<double>::epsilon());
|
||||
}
|
||||
for (int i = 0; i < f.mesh->attributes.Size(); ++i) {
|
||||
const int attribute = f.mesh->attributes[i];
|
||||
const bool is_stellar =
|
||||
DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Stellar>(attribute);
|
||||
|
||||
if (is_stellar == stellar) {
|
||||
attribute_values(attribute - 1) = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
mfem::PWConstCoefficient coefficient(attribute_values);
|
||||
mfem::ParGridFunction density(f.densityFes.get());
|
||||
density.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector density_true;
|
||||
density.GetTrueDofs(density_true);
|
||||
return density_true;
|
||||
}
|
||||
|
||||
inline mfem::Vector linear_combination(
|
||||
const mfem::Vector &first,
|
||||
const double first_scale,
|
||||
const mfem::Vector &second,
|
||||
const double second_scale
|
||||
) {
|
||||
MFEM_VERIFY(first.Size() == second.Size(), "Cannot combine vectors with different sizes.");
|
||||
|
||||
mfem::Vector combination(first);
|
||||
combination *= first_scale;
|
||||
combination.Add(second_scale, second);
|
||||
return combination;
|
||||
}
|
||||
|
||||
inline double global_norm(
|
||||
const mfem::Vector &vector,
|
||||
MPI_Comm communicator
|
||||
) {
|
||||
const double local_norm_squared = vector * vector;
|
||||
double global_norm_squared = 0.0;
|
||||
MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
||||
return std::sqrt(global_norm_squared);
|
||||
}
|
||||
|
||||
inline double global_dot(
|
||||
const mfem::Vector &first,
|
||||
const mfem::Vector &second,
|
||||
MPI_Comm communicator
|
||||
) {
|
||||
MFEM_VERIFY(first.Size() == second.Size(), "Cannot take the dot product of vectors with different sizes.");
|
||||
|
||||
const double local_dot = first * second;
|
||||
double global_dot = 0.0;
|
||||
MPI_Allreduce(&local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
||||
return global_dot;
|
||||
}
|
||||
|
||||
inline double relative_error(
|
||||
const mfem::Vector &computed,
|
||||
const mfem::Vector &reference,
|
||||
MPI_Comm communicator
|
||||
) {
|
||||
MFEM_VERIFY(computed.Size() == reference.Size(), "Cannot compare vectors with different sizes.");
|
||||
|
||||
mfem::Vector difference(computed);
|
||||
difference -= reference;
|
||||
|
||||
return global_norm(difference, communicator) /
|
||||
std::max(global_norm(reference, communicator), std::numeric_limits<double>::epsilon());
|
||||
}
|
||||
|
||||
inline double relative_scalar_error(
|
||||
const double computed,
|
||||
const double reference
|
||||
) {
|
||||
return std::abs(computed - reference) / std::max(std::abs(reference), std::numeric_limits<double>::epsilon());
|
||||
}
|
||||
} // namespace gravity_prepared_test_utils
|
||||
|
||||
export namespace field_dof_test_utils {
|
||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
inline mean_field::mapping::DomainMapper make_domain_mapper() {
|
||||
const mean_field::utils::Args args = test_utils::setup_args();
|
||||
return mean_field::mapping::DomainMapper(
|
||||
args.domain_mapper_options,
|
||||
std::make_unique<const mean_field::mapping::compactification::
|
||||
KelvinCompactification>(args.kelvin_options));
|
||||
}
|
||||
inline mean_field::mapping::DomainMapper make_domain_mapper() {
|
||||
const mean_field::utils::Args args = test_utils::setup_args();
|
||||
return mean_field::mapping::DomainMapper(
|
||||
args.domain_mapper_options,
|
||||
std::make_unique<const mean_field::mapping::compactification::KelvinCompactification>(args.kelvin_options)
|
||||
);
|
||||
}
|
||||
|
||||
inline constexpr int vacuum_material_attribute =
|
||||
DomainSchema::template material_attribute<
|
||||
mean_field::utils::domain::Vacuum>();
|
||||
inline constexpr int vacuum_material_attribute =
|
||||
DomainSchema::template material_attribute<mean_field::utils::domain::Vacuum>();
|
||||
|
||||
template <typename FieldT>
|
||||
inline mean_field::field::FieldDofMap
|
||||
make_map(const mfem::ParFiniteElementSpace &finiteElementSpace) {
|
||||
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(
|
||||
finiteElementSpace);
|
||||
}
|
||||
template <typename FieldT>
|
||||
inline mean_field::field::FieldDofMap make_map(const mfem::ParFiniteElementSpace &finiteElementSpace) {
|
||||
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(finiteElementSpace);
|
||||
}
|
||||
|
||||
template <typename FieldT>
|
||||
inline mfem::Vector make_deterministic_supported_vector(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace, const double phase) {
|
||||
const mean_field::field::FieldDofMap map =
|
||||
make_map<FieldT>(finiteElementSpace);
|
||||
const mfem::Vector full =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(map.full_size(),
|
||||
phase);
|
||||
return map.gather(full);
|
||||
}
|
||||
template <typename FieldT>
|
||||
inline mfem::Vector make_deterministic_supported_vector(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const double phase
|
||||
) {
|
||||
const mean_field::field::FieldDofMap map = make_map<FieldT>(finiteElementSpace);
|
||||
const mfem::Vector full = gravity_prepared_test_utils::make_deterministic_vector(map.full_size(), phase);
|
||||
return map.gather(full);
|
||||
}
|
||||
|
||||
inline mfem::Vector make_supported_displacement(const mean_field::fem::FEM &f,
|
||||
const double phase) {
|
||||
const mean_field::field::FieldDofMap map =
|
||||
make_map<mean_field::field::Displacement>(*f.displacementFes);
|
||||
return map.gather(gravity_prepared_test_utils::make_displacement(f, phase));
|
||||
}
|
||||
inline mfem::Vector make_supported_displacement(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double phase
|
||||
) {
|
||||
const mean_field::field::FieldDofMap map = make_map<mean_field::field::Displacement>(*f.displacementFes);
|
||||
return map.gather(gravity_prepared_test_utils::make_displacement(f, phase));
|
||||
}
|
||||
|
||||
inline void apply_hydrostatic_reference(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::physics::RigidRotation &rotation,
|
||||
const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &displacement, const double bernoulliConstant,
|
||||
mfem::Vector &residual) {
|
||||
const mean_field::field::FieldDofMap enthalpyMap =
|
||||
make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
|
||||
const mean_field::field::FieldDofMap gravityPotentialMap =
|
||||
make_map<mean_field::field::Gravity>(*f.gravityPotentialFes);
|
||||
const mean_field::field::FieldDofMap displacementMap =
|
||||
make_map<mean_field::field::Displacement>(*f.displacementFes);
|
||||
inline void apply_hydrostatic_reference(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::physics::RigidRotation &rotation,
|
||||
const mfem::Vector &enthalpy,
|
||||
const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &displacement,
|
||||
const double bernoulliConstant,
|
||||
mfem::Vector &residual
|
||||
) {
|
||||
const mean_field::field::FieldDofMap enthalpyMap = make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
|
||||
const mean_field::field::FieldDofMap gravityPotentialMap =
|
||||
make_map<mean_field::field::Gravity>(*f.gravityPotentialFes);
|
||||
const mean_field::field::FieldDofMap displacementMap =
|
||||
make_map<mean_field::field::Displacement>(*f.displacementFes);
|
||||
|
||||
mfem::Vector enthalpyTrue(enthalpyMap.full_size());
|
||||
mfem::Vector gravityPotentialTrue(gravityPotentialMap.full_size());
|
||||
mfem::Vector displacementTrue(displacementMap.full_size());
|
||||
mfem::Vector residualTrue;
|
||||
mfem::Vector enthalpyTrue(enthalpyMap.full_size());
|
||||
mfem::Vector gravityPotentialTrue(gravityPotentialMap.full_size());
|
||||
mfem::Vector displacementTrue(displacementMap.full_size());
|
||||
mfem::Vector residualTrue;
|
||||
|
||||
enthalpyMap.scatter(enthalpy, enthalpyTrue);
|
||||
gravityPotentialMap.scatter(gravityPotential, gravityPotentialTrue);
|
||||
displacementMap.scatter(displacement, displacementTrue);
|
||||
enthalpyMap.scatter(enthalpy, enthalpyTrue);
|
||||
gravityPotentialMap.scatter(gravityPotential, gravityPotentialTrue);
|
||||
displacementMap.scatter(displacement, displacementTrue);
|
||||
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, rotation, enthalpyTrue, gravityPotentialTrue,
|
||||
displacementTrue, bernoulliConstant, residualTrue);
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, rotation, enthalpyTrue, gravityPotentialTrue, displacementTrue,
|
||||
bernoulliConstant, residualTrue
|
||||
);
|
||||
|
||||
residual.SetSize(enthalpyMap.reduced_size());
|
||||
enthalpyMap.gather(residualTrue, residual);
|
||||
}
|
||||
residual.SetSize(enthalpyMap.reduced_size());
|
||||
enthalpyMap.gather(residualTrue, residual);
|
||||
}
|
||||
} // namespace field_dof_test_utils
|
||||
|
||||
export namespace tags {
|
||||
inline constexpr auto geometry = make_tag("geometry");
|
||||
inline constexpr auto physics = make_tag("physics");
|
||||
inline constexpr auto unit = make_tag("unit");
|
||||
inline constexpr auto mesh = make_tag("mesh");
|
||||
inline constexpr auto integration = make_tag("integration");
|
||||
inline constexpr auto solver = make_tag("solver");
|
||||
inline constexpr auto integrator = make_tag("integrator");
|
||||
inline constexpr auto mapping = make_tag("mapping");
|
||||
inline constexpr auto utils = make_tag("utils");
|
||||
inline constexpr auto mfem_operators = make_tag("operators");
|
||||
inline constexpr auto initialization = make_tag("initialization");
|
||||
inline constexpr auto accuracy = make_tag("accuracy");
|
||||
inline constexpr auto closure = make_tag("closure");
|
||||
inline constexpr auto kernels = make_tag("kernels");
|
||||
inline constexpr auto surface = make_tag("surface");
|
||||
inline constexpr auto model = make_tag("model");
|
||||
inline constexpr auto geometry = make_tag("geometry");
|
||||
inline constexpr auto physics = make_tag("physics");
|
||||
inline constexpr auto unit = make_tag("unit");
|
||||
inline constexpr auto mesh = make_tag("mesh");
|
||||
inline constexpr auto integration = make_tag("integration");
|
||||
inline constexpr auto solver = make_tag("solver");
|
||||
inline constexpr auto integrator = make_tag("integrator");
|
||||
inline constexpr auto mapping = make_tag("mapping");
|
||||
inline constexpr auto utils = make_tag("utils");
|
||||
inline constexpr auto mfem_operators = make_tag("operators");
|
||||
inline constexpr auto initialization = make_tag("initialization");
|
||||
inline constexpr auto accuracy = make_tag("accuracy");
|
||||
inline constexpr auto closure = make_tag("closure");
|
||||
inline constexpr auto kernels = make_tag("kernels");
|
||||
inline constexpr auto surface = make_tag("surface");
|
||||
inline constexpr auto model = make_tag("model");
|
||||
|
||||
inline constexpr auto field = sub_tag(mesh & physics, "field");
|
||||
inline constexpr auto field_dof = field & make_tag("dof");
|
||||
inline constexpr auto field_dof_unit = field_dof & unit;
|
||||
inline constexpr auto field_dof_integration = field_dof & integration;
|
||||
inline constexpr auto field = sub_tag(mesh & physics, "field");
|
||||
inline constexpr auto field_dof = field & make_tag("dof");
|
||||
inline constexpr auto field_dof_unit = field_dof & unit;
|
||||
inline constexpr auto field_dof_integration = field_dof & integration;
|
||||
|
||||
inline constexpr auto pressure = sub_tag(physics, "pressure");
|
||||
inline constexpr auto pressure = sub_tag(physics, "pressure");
|
||||
|
||||
inline constexpr auto hydro = sub_tag(physics, "hydro");
|
||||
inline constexpr auto jacobian = sub_tag(integration & physics, "jacobian");
|
||||
inline constexpr auto residuals = sub_tag(integration & physics, "residuals");
|
||||
inline constexpr auto volume = sub_tag(mesh & geometry, "volume");
|
||||
inline constexpr auto quadrature =
|
||||
sub_tag(mesh & geometry & solver, "quadrature");
|
||||
inline constexpr auto convergence = sub_tag(solver, "convergence");
|
||||
inline constexpr auto transformations =
|
||||
sub_tag(mesh & geometry, "transformations");
|
||||
inline constexpr auto hydro = sub_tag(physics, "hydro");
|
||||
inline constexpr auto jacobian = sub_tag(integration & physics, "jacobian");
|
||||
inline constexpr auto residuals = sub_tag(integration & physics, "residuals");
|
||||
inline constexpr auto volume = sub_tag(mesh & geometry, "volume");
|
||||
inline constexpr auto quadrature = sub_tag(mesh & geometry & solver, "quadrature");
|
||||
inline constexpr auto convergence = sub_tag(solver, "convergence");
|
||||
inline constexpr auto transformations = sub_tag(mesh & geometry, "transformations");
|
||||
|
||||
inline constexpr auto h_refinement =
|
||||
sub_tag(mesh & convergence, "h_refinement");
|
||||
inline constexpr auto p_refinement =
|
||||
sub_tag(mesh & convergence, "p_refinement");
|
||||
inline constexpr auto h_refinement = sub_tag(mesh & convergence, "h_refinement");
|
||||
inline constexpr auto p_refinement = sub_tag(mesh & convergence, "p_refinement");
|
||||
|
||||
inline constexpr auto analytic_comparison =
|
||||
sub_tag(solver & physics & residuals, "analytic_comparison");
|
||||
inline constexpr auto self_consistency =
|
||||
sub_tag(solver & physics, "self_consistency");
|
||||
inline constexpr auto analytic_comparison = sub_tag(solver & physics & residuals, "analytic_comparison");
|
||||
inline constexpr auto self_consistency = sub_tag(solver & physics, "self_consistency");
|
||||
|
||||
inline constexpr auto centrifugal = sub_tag(solver & physics, "centrifugal");
|
||||
inline constexpr auto advection = sub_tag(solver & physics, "advection");
|
||||
inline constexpr auto coriolis = sub_tag(solver & physics, "coriolis");
|
||||
inline constexpr auto gravity = sub_tag(solver & physics, "gravity");
|
||||
inline constexpr auto enthalpy = sub_tag(solver & physics, "enthalpy");
|
||||
inline constexpr auto barotrope = sub_tag(physics, "barotrope");
|
||||
inline constexpr auto mass_continuity =
|
||||
sub_tag(solver & physics, "mass_continuity");
|
||||
inline constexpr auto pressure_gradient =
|
||||
sub_tag(solver & physics, "pressure_gradient");
|
||||
inline constexpr auto viscosity = sub_tag(solver & physics, "viscosity");
|
||||
inline constexpr auto centrifugal = sub_tag(solver & physics, "centrifugal");
|
||||
inline constexpr auto advection = sub_tag(solver & physics, "advection");
|
||||
inline constexpr auto coriolis = sub_tag(solver & physics, "coriolis");
|
||||
inline constexpr auto gravity = sub_tag(solver & physics, "gravity");
|
||||
inline constexpr auto enthalpy = sub_tag(solver & physics, "enthalpy");
|
||||
inline constexpr auto barotrope = sub_tag(physics, "barotrope");
|
||||
inline constexpr auto mass_continuity = sub_tag(solver & physics, "mass_continuity");
|
||||
inline constexpr auto pressure_gradient = sub_tag(solver & physics, "pressure_gradient");
|
||||
inline constexpr auto viscosity = sub_tag(solver & physics, "viscosity");
|
||||
|
||||
inline constexpr auto compactification =
|
||||
sub_tag(mesh & mapping, "compactification");
|
||||
inline constexpr auto kelvin = sub_tag(compactification, "kelvin");
|
||||
inline constexpr auto mapping_evaluator =
|
||||
mapping & make_tag("grid_function_evaluator");
|
||||
inline constexpr auto mapping_evaluator_unit = mapping_evaluator & unit;
|
||||
inline constexpr auto compactification = sub_tag(mesh & mapping, "compactification");
|
||||
inline constexpr auto kelvin = sub_tag(compactification, "kelvin");
|
||||
inline constexpr auto mapping_evaluator = mapping & make_tag("grid_function_evaluator");
|
||||
inline constexpr auto mapping_evaluator_unit = mapping_evaluator & unit;
|
||||
|
||||
inline constexpr auto prepared = sub_tag(solver & physics, "prepared");
|
||||
inline constexpr auto contexts = sub_tag(solver, "contexts");
|
||||
inline constexpr auto prepared = sub_tag(solver & physics, "prepared");
|
||||
inline constexpr auto contexts = sub_tag(solver, "contexts");
|
||||
|
||||
inline constexpr auto domain = sub_tag(mesh, "domain");
|
||||
inline constexpr auto domain = sub_tag(mesh, "domain");
|
||||
|
||||
// Canonical gravity-suite tags. These intentionally compose leaf tags
|
||||
// exactly once so Catch2 output remains useful and free of repeated
|
||||
// [solver]/[physics] entries inherited from older composite tags.
|
||||
inline constexpr auto gravity_unit = gravity & unit;
|
||||
inline constexpr auto gravity_integration = gravity & integration;
|
||||
inline constexpr auto gravity_operator = gravity & mfem_operators;
|
||||
inline constexpr auto gravity_prepared = gravity & make_tag("prepared");
|
||||
inline constexpr auto gravity_context = gravity & make_tag("context");
|
||||
inline constexpr auto gravity_kernel = gravity & kernels;
|
||||
inline constexpr auto gravity_accuracy = gravity & accuracy;
|
||||
inline constexpr auto gravity_operator_unit = gravity_operator & unit;
|
||||
inline constexpr auto gravity_operator_integration =
|
||||
gravity_operator & integration;
|
||||
inline constexpr auto gravity_operator_convergence =
|
||||
gravity_operator & integration & make_tag("convergence");
|
||||
inline constexpr auto gravity_analytic =
|
||||
gravity & integration & make_tag("analytic_comparison");
|
||||
inline constexpr auto gravity_consistency =
|
||||
gravity & integration & make_tag("self_consistency");
|
||||
inline constexpr auto gravity_prepared_jacobian =
|
||||
gravity_prepared & integration & make_tag("jacobian");
|
||||
inline constexpr auto gravity_prepared_unit = gravity_prepared & unit;
|
||||
inline constexpr auto gravity_prepared_jacobian_accuracy =
|
||||
gravity_prepared_jacobian & accuracy;
|
||||
inline constexpr auto gravity_kernel_accuracy = gravity_kernel & accuracy;
|
||||
inline constexpr auto gravity_kernel_integration = gravity_kernel & integration;
|
||||
inline constexpr auto gravity_kernel_convergence =
|
||||
gravity_kernel & integration & make_tag("convergence");
|
||||
inline constexpr auto gravity_analytic_accuracy = gravity_analytic & accuracy;
|
||||
inline constexpr auto gravity_consistency_accuracy =
|
||||
gravity_consistency & accuracy;
|
||||
inline constexpr auto gravity_integrator_unit = gravity & integrator & unit;
|
||||
// Canonical gravity-suite tags. These intentionally compose leaf tags
|
||||
// exactly once so Catch2 output remains useful and free of repeated
|
||||
// [solver]/[physics] entries inherited from older composite tags.
|
||||
inline constexpr auto gravity_unit = gravity & unit;
|
||||
inline constexpr auto gravity_integration = gravity & integration;
|
||||
inline constexpr auto gravity_operator = gravity & mfem_operators;
|
||||
inline constexpr auto gravity_prepared = gravity & make_tag("prepared");
|
||||
inline constexpr auto gravity_context = gravity & make_tag("context");
|
||||
inline constexpr auto gravity_kernel = gravity & kernels;
|
||||
inline constexpr auto gravity_accuracy = gravity & accuracy;
|
||||
inline constexpr auto gravity_operator_unit = gravity_operator & unit;
|
||||
inline constexpr auto gravity_operator_integration = gravity_operator & integration;
|
||||
inline constexpr auto gravity_operator_convergence = gravity_operator & integration & make_tag("convergence");
|
||||
inline constexpr auto gravity_analytic = gravity & integration & make_tag("analytic_comparison");
|
||||
inline constexpr auto gravity_consistency = gravity & integration & make_tag("self_consistency");
|
||||
inline constexpr auto gravity_prepared_jacobian = gravity_prepared & integration & make_tag("jacobian");
|
||||
inline constexpr auto gravity_prepared_unit = gravity_prepared & unit;
|
||||
inline constexpr auto gravity_prepared_jacobian_accuracy = gravity_prepared_jacobian & accuracy;
|
||||
inline constexpr auto gravity_kernel_accuracy = gravity_kernel & accuracy;
|
||||
inline constexpr auto gravity_kernel_integration = gravity_kernel & integration;
|
||||
inline constexpr auto gravity_kernel_convergence = gravity_kernel & integration & make_tag("convergence");
|
||||
inline constexpr auto gravity_analytic_accuracy = gravity_analytic & accuracy;
|
||||
inline constexpr auto gravity_consistency_accuracy = gravity_consistency & accuracy;
|
||||
inline constexpr auto gravity_integrator_unit = gravity & integrator & unit;
|
||||
|
||||
inline constexpr auto barotrope_prepared =
|
||||
barotrope & solver & make_tag("prepared");
|
||||
inline constexpr auto barotrope_eos_unit = barotrope & unit & make_tag("eos");
|
||||
inline constexpr auto barotrope_eos_jacobian =
|
||||
barotrope_eos_unit & integration & make_tag("jacobian");
|
||||
inline constexpr auto barotrope_pressure_quadrature =
|
||||
barotrope & mesh & geometry & solver & make_tag("pressure") &
|
||||
make_tag("pressure_gradient") & make_tag("quadrature");
|
||||
inline constexpr auto barotrope_pressure_quadrature_unit =
|
||||
barotrope_pressure_quadrature & unit;
|
||||
inline constexpr auto barotrope_pressure_quadrature_accuracy =
|
||||
barotrope_pressure_quadrature & accuracy;
|
||||
inline constexpr auto barotrope_prepared_jacobian =
|
||||
barotrope_prepared & integration & make_tag("jacobian");
|
||||
inline constexpr auto barotrope_context =
|
||||
barotrope & solver & make_tag("context");
|
||||
inline constexpr auto barotrope_context_integration =
|
||||
barotrope_context & integration;
|
||||
inline constexpr auto barotrope_prepared_analytic =
|
||||
barotrope_prepared & integration & make_tag("analytic_comparison");
|
||||
inline constexpr auto barotrope_prepared_jacobian_accuracy =
|
||||
barotrope_prepared_jacobian & accuracy;
|
||||
inline constexpr auto barotrope_prepared_jacobian_geometry =
|
||||
barotrope_prepared_jacobian & geometry;
|
||||
inline constexpr auto barotrope_prepared_jacobian_unit =
|
||||
barotrope_prepared_jacobian & unit;
|
||||
inline constexpr auto barotrope_prepared = barotrope & solver & make_tag("prepared");
|
||||
inline constexpr auto barotrope_eos_unit = barotrope & unit & make_tag("eos");
|
||||
inline constexpr auto barotrope_eos_jacobian = barotrope_eos_unit & integration & make_tag("jacobian");
|
||||
inline constexpr auto polytropic_eos_characterization =
|
||||
barotrope & unit & make_tag("eos") & make_tag("characterization");
|
||||
inline constexpr auto polytropic_eos_relation_contract =
|
||||
barotrope & unit & make_tag("eos") & make_tag("relation_contract");
|
||||
inline constexpr auto polytropic_eos_compatibility = barotrope & unit & make_tag("eos") & make_tag("compatibility");
|
||||
inline constexpr auto equation_of_state = physics & make_tag("eos");
|
||||
inline constexpr auto equation_of_state_type_system = equation_of_state & unit & make_tag("type_system");
|
||||
inline constexpr auto equation_of_state_quantity_types = equation_of_state_type_system & make_tag("quantity_types");
|
||||
inline constexpr auto equation_of_state_relation_contract =
|
||||
equation_of_state_type_system & make_tag("relation_contract");
|
||||
inline constexpr auto equation_of_state_runtime_view = equation_of_state & unit & make_tag("runtime_view");
|
||||
inline constexpr auto equation_of_state_runtime_contract =
|
||||
equation_of_state_runtime_view & make_tag("relation_contract");
|
||||
inline constexpr auto equation_of_state_runtime_compatibility =
|
||||
equation_of_state_runtime_view & make_tag("compatibility");
|
||||
inline constexpr auto equation_of_state_consumer_contract =
|
||||
equation_of_state & unit & make_tag("consumer_contract");
|
||||
inline constexpr auto barotropic_closure_equation_of_state_contract =
|
||||
equation_of_state_consumer_contract & make_tag("barotropic_closure");
|
||||
inline constexpr auto pressure_force_equation_of_state_contract =
|
||||
equation_of_state_consumer_contract & make_tag("pressure_force");
|
||||
inline constexpr auto structure_seed_equation_of_state_contract =
|
||||
equation_of_state_consumer_contract & make_tag("structure_seed");
|
||||
inline constexpr auto stellar_model_type_contract = barotrope & model & unit & make_tag("type_contract");
|
||||
inline constexpr auto stellar_model_runtime_view = barotrope & model & unit & make_tag("runtime_view");
|
||||
inline constexpr auto surface_prescription_type_contract =
|
||||
surface & physics & unit & make_tag("prescription") & make_tag("type_contract");
|
||||
inline constexpr auto surface_constraint_compilation =
|
||||
surface & physics & unit & make_tag("constraint_compilation");
|
||||
inline constexpr auto surface_constraint_jacobian = surface_constraint_compilation & jacobian;
|
||||
inline constexpr auto surface_constraint_lifetime = surface & model & unit & make_tag("constraint_lifetime");
|
||||
inline constexpr auto surface_boundary_dof_topology =
|
||||
surface & field_dof & integration & make_tag("boundary_topology");
|
||||
inline constexpr auto surface_row_replacement =
|
||||
surface & barotrope_prepared & integration & make_tag("row_replacement");
|
||||
inline constexpr auto translational_centering = geometry & solver & make_tag("translational_centering");
|
||||
inline constexpr auto translational_centering_topology =
|
||||
translational_centering & field_dof & integration & make_tag("point_topology");
|
||||
inline constexpr auto translational_centering_enforcement =
|
||||
translational_centering & barotrope_prepared & integration & make_tag("row_replacement");
|
||||
inline constexpr auto barotrope_pressure_quadrature = barotrope & mesh & geometry & solver & make_tag("pressure") &
|
||||
make_tag("pressure_gradient") & make_tag("quadrature");
|
||||
inline constexpr auto barotrope_pressure_quadrature_unit = barotrope_pressure_quadrature & unit;
|
||||
inline constexpr auto barotrope_pressure_quadrature_accuracy = barotrope_pressure_quadrature & accuracy;
|
||||
inline constexpr auto barotrope_prepared_jacobian = barotrope_prepared & integration & make_tag("jacobian");
|
||||
inline constexpr auto barotrope_context = barotrope & solver & make_tag("context");
|
||||
inline constexpr auto barotrope_context_integration = barotrope_context & integration;
|
||||
inline constexpr auto barotrope_prepared_analytic =
|
||||
barotrope_prepared & integration & make_tag("analytic_comparison");
|
||||
inline constexpr auto barotrope_prepared_jacobian_accuracy = barotrope_prepared_jacobian & accuracy;
|
||||
inline constexpr auto barotrope_prepared_jacobian_geometry = barotrope_prepared_jacobian & geometry;
|
||||
inline constexpr auto barotrope_prepared_jacobian_unit = barotrope_prepared_jacobian & unit;
|
||||
|
||||
// Canonical hydrostatic-suite tags. The leaf tags are composed directly
|
||||
// so inherited [physics]/[solver] tags appear only once.
|
||||
inline constexpr auto barotrope_hydrostatic =
|
||||
barotrope & solver & make_tag("hydro");
|
||||
inline constexpr auto barotrope_hydrostatic_context =
|
||||
barotrope_hydrostatic & make_tag("context");
|
||||
inline constexpr auto barotrope_hydrostatic_prepared =
|
||||
barotrope_hydrostatic & make_tag("prepared");
|
||||
inline constexpr auto barotrope_hydrostatic_prepared_residual =
|
||||
barotrope_hydrostatic_prepared & integration & make_tag("residual");
|
||||
inline constexpr auto barotrope_hydrostatic_prepared_jacobian =
|
||||
barotrope_hydrostatic_prepared & integration & make_tag("jacobian");
|
||||
inline constexpr auto barotrope_hydrostatic_prepared_analytic =
|
||||
barotrope_hydrostatic_prepared & integration &
|
||||
make_tag("analytic_comparison");
|
||||
// Canonical hydrostatic-suite tags. The leaf tags are composed directly
|
||||
// so inherited [physics]/[solver] tags appear only once.
|
||||
inline constexpr auto barotrope_hydrostatic = barotrope & solver & make_tag("hydro");
|
||||
inline constexpr auto barotrope_hydrostatic_context = barotrope_hydrostatic & make_tag("context");
|
||||
inline constexpr auto barotrope_hydrostatic_prepared = barotrope_hydrostatic & make_tag("prepared");
|
||||
inline constexpr auto barotrope_hydrostatic_prepared_residual =
|
||||
barotrope_hydrostatic_prepared & integration & make_tag("residual");
|
||||
inline constexpr auto barotrope_hydrostatic_prepared_jacobian =
|
||||
barotrope_hydrostatic_prepared & integration & make_tag("jacobian");
|
||||
inline constexpr auto barotrope_hydrostatic_prepared_analytic =
|
||||
barotrope_hydrostatic_prepared & integration & make_tag("analytic_comparison");
|
||||
|
||||
inline constexpr auto barotrope_mass_normalization =
|
||||
barotrope & solver & make_tag("mass_normalization");
|
||||
inline constexpr auto barotrope_mass_normalization_context =
|
||||
barotrope_mass_normalization & make_tag("context");
|
||||
inline constexpr auto barotrope_mass_normalization_prepared =
|
||||
barotrope_mass_normalization & make_tag("prepared");
|
||||
inline constexpr auto barotrope_mass_normalization_jacobian =
|
||||
barotrope_mass_normalization_prepared & integration & make_tag("jacobian");
|
||||
inline constexpr auto barotrope_mass_normalization_analytic =
|
||||
barotrope_mass_normalization_prepared & integration &
|
||||
make_tag("analytic_comparison");
|
||||
inline constexpr auto barotrope_mass_normalization = barotrope & solver & make_tag("mass_normalization");
|
||||
inline constexpr auto barotrope_mass_normalization_context = barotrope_mass_normalization & make_tag("context");
|
||||
inline constexpr auto barotrope_mass_normalization_prepared = barotrope_mass_normalization & make_tag("prepared");
|
||||
inline constexpr auto barotrope_mass_normalization_jacobian =
|
||||
barotrope_mass_normalization_prepared & integration & make_tag("jacobian");
|
||||
inline constexpr auto barotrope_mass_normalization_analytic =
|
||||
barotrope_mass_normalization_prepared & integration & make_tag("analytic_comparison");
|
||||
|
||||
inline constexpr auto rotation_prepared = centrifugal & make_tag("prepared");
|
||||
inline constexpr auto rotation_context = centrifugal & make_tag("context");
|
||||
inline constexpr auto rotation_analytic =
|
||||
centrifugal & integration & make_tag("analytic_comparison");
|
||||
inline constexpr auto rotation_context_unit = rotation_context & unit;
|
||||
inline constexpr auto rotation_prepared_unit = rotation_prepared & unit;
|
||||
inline constexpr auto rotation_prepared_jacobian =
|
||||
rotation_prepared & integration & make_tag("jacobian");
|
||||
inline constexpr auto rotation_prepared_jacobian_accuracy =
|
||||
rotation_prepared_jacobian & accuracy;
|
||||
inline constexpr auto rotation_kernel_accuracy =
|
||||
centrifugal & kernels & accuracy;
|
||||
inline constexpr auto rotation_integrator_unit = centrifugal & integrator & unit;
|
||||
inline constexpr auto rotation_integrator_integration =
|
||||
centrifugal & integrator & integration;
|
||||
inline constexpr auto rotation_integrator_convergence =
|
||||
rotation_integrator_integration & convergence & h_refinement;
|
||||
inline constexpr auto rotation_analytic_unit = rotation_analytic & unit;
|
||||
inline constexpr auto rotation_analytic_accuracy = rotation_analytic & accuracy;
|
||||
inline constexpr auto rotation_analytic_accuracy_geometry =
|
||||
rotation_analytic_accuracy & geometry;
|
||||
inline constexpr auto rotation_prepared = centrifugal & make_tag("prepared");
|
||||
inline constexpr auto rotation_context = centrifugal & make_tag("context");
|
||||
inline constexpr auto rotation_analytic = centrifugal & integration & make_tag("analytic_comparison");
|
||||
inline constexpr auto rotation_context_unit = rotation_context & unit;
|
||||
inline constexpr auto rotation_prepared_unit = rotation_prepared & unit;
|
||||
inline constexpr auto rotation_prepared_jacobian = rotation_prepared & integration & make_tag("jacobian");
|
||||
inline constexpr auto rotation_prepared_jacobian_accuracy = rotation_prepared_jacobian & accuracy;
|
||||
inline constexpr auto rotation_kernel_accuracy = centrifugal & kernels & accuracy;
|
||||
inline constexpr auto rotation_integrator_unit = centrifugal & integrator & unit;
|
||||
inline constexpr auto rotation_integrator_integration = centrifugal & integrator & integration;
|
||||
inline constexpr auto rotation_integrator_convergence =
|
||||
rotation_integrator_integration & convergence & h_refinement;
|
||||
inline constexpr auto rotation_analytic_unit = rotation_analytic & unit;
|
||||
inline constexpr auto rotation_analytic_accuracy = rotation_analytic & accuracy;
|
||||
inline constexpr auto rotation_analytic_accuracy_geometry = rotation_analytic_accuracy & geometry;
|
||||
|
||||
} // namespace tags
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <catch2/catch_test_case_info.hpp>
|
||||
#include <catch2/reporters/catch_reporter_registrars.hpp>
|
||||
#include <catch2/reporters/catch_reporter_streaming_base.hpp>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
@@ -223,6 +224,7 @@ class CheckReporter : public Catch::StreamingReporterBase {
|
||||
bool passed;
|
||||
std::size_t assertionsPassed;
|
||||
std::size_t assertionsFailed;
|
||||
double durationSeconds;
|
||||
std::vector<std::string> failureMessages;
|
||||
std::vector<std::string> infoMessages;
|
||||
};
|
||||
@@ -231,6 +233,7 @@ class CheckReporter : public Catch::StreamingReporterBase {
|
||||
std::vector<std::string> m_currentInfos;
|
||||
std::unordered_set<unsigned int> m_currentInfoSequences;
|
||||
std::vector<TestCaseData> m_testRunData;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_testStartTime;
|
||||
|
||||
void captureInfoMessages(Catch::AssertionStats const &assertionStats) {
|
||||
for (auto const &message : assertionStats.infoMessages) {
|
||||
@@ -253,9 +256,8 @@ public:
|
||||
}
|
||||
|
||||
static std::string getDescription() {
|
||||
return "Console reporter with wrapping, tags, and collapsible HTML "
|
||||
"export "
|
||||
"with ANSI color rendering.";
|
||||
return "Console reporter with wrapping, tags, live test progress, and collapsible HTML "
|
||||
"export with ANSI color rendering.";
|
||||
}
|
||||
|
||||
void testRunStarting(Catch::TestRunInfo const &_testRunInfo) override {
|
||||
@@ -263,8 +265,20 @@ public:
|
||||
|
||||
std::cout << '\n';
|
||||
std::cout << std::left << std::setw(85) << "Test Case Name"
|
||||
<< "Status " << std::right << std::setw(8) << "Passed" << std::setw(8) << "Failed" << '\n';
|
||||
std::cout << std::string(121, '-') << '\n';
|
||||
<< "Status " << std::right << std::setw(8) << "Passed" << std::setw(8) << "Failed" << std::setw(12)
|
||||
<< "Time (s)" << '\n';
|
||||
std::cout << std::string(133, '-') << '\n';
|
||||
}
|
||||
|
||||
void testCaseStarting(Catch::TestCaseInfo const &testInfo) override {
|
||||
StreamingReporterBase::testCaseStarting(testInfo);
|
||||
|
||||
m_testStartTime = std::chrono::steady_clock::now();
|
||||
std::string name = testInfo.name;
|
||||
auto wrappedName = wrapText(name, 83);
|
||||
|
||||
// Print progress line, \r to overwrite later, \033[K to clear till end of line
|
||||
std::cout << "\r\033[K" << std::left << std::setw(85) << (wrappedName[0] + " ...") << std::flush;
|
||||
}
|
||||
|
||||
void assertionEnded(Catch::AssertionStats const &assertionStats) override {
|
||||
@@ -300,14 +314,20 @@ public:
|
||||
void testCaseEnded(Catch::TestCaseStats const &stats) override {
|
||||
StreamingReporterBase::testCaseEnded(stats);
|
||||
|
||||
bool passed = stats.totals.assertions.allPassed();
|
||||
std::string mark = passed ? "\033[32m✓\033[0m" : "\033[31m✗\033[0m";
|
||||
auto endTime = std::chrono::steady_clock::now();
|
||||
std::chrono::duration<double> elapsed = endTime - m_testStartTime;
|
||||
double duration_s = elapsed.count();
|
||||
|
||||
std::string name = stats.testInfo->name;
|
||||
auto wrappedName = wrapText(name, 83);
|
||||
bool passed = stats.totals.assertions.allPassed();
|
||||
std::string mark = passed ? "\033[32m✓\033[0m" : "\033[31m✗\033[0m";
|
||||
|
||||
std::cout << std::left << std::setw(85) << wrappedName[0] << mark << " " << std::right << std::setw(8)
|
||||
<< stats.totals.assertions.passed << std::setw(8) << stats.totals.assertions.failed << '\n';
|
||||
std::string name = stats.testInfo->name;
|
||||
auto wrappedName = wrapText(name, 83);
|
||||
|
||||
// Overwrite the loading line with the actual result
|
||||
std::cout << "\r\033[K" << std::left << std::setw(85) << wrappedName[0] << mark << " " << std::right
|
||||
<< std::setw(8) << stats.totals.assertions.passed << std::setw(8) << stats.totals.assertions.failed
|
||||
<< std::setw(11) << std::fixed << std::setprecision(3) << duration_s << "s\n";
|
||||
|
||||
for (size_t i = 1; i < wrappedName.size(); ++i) {
|
||||
std::cout << " \033[90m↳ \033[0m" // Dim indent arrow
|
||||
@@ -327,12 +347,12 @@ public:
|
||||
for (auto const &failure : m_currentFailures) {
|
||||
std::cout << failure << '\n';
|
||||
}
|
||||
std::cout << std::string(121, '-') << '\n';
|
||||
std::cout << std::string(133, '-') << '\n';
|
||||
}
|
||||
|
||||
m_testRunData.push_back(
|
||||
{name, tagsStr, passed, stats.totals.assertions.passed, stats.totals.assertions.failed, m_currentFailures,
|
||||
m_currentInfos}
|
||||
{name, tagsStr, passed, stats.totals.assertions.passed, stats.totals.assertions.failed, duration_s,
|
||||
m_currentFailures, m_currentInfos}
|
||||
);
|
||||
|
||||
m_currentFailures.clear();
|
||||
@@ -343,7 +363,7 @@ public:
|
||||
void testRunEnded(Catch::TestRunStats const &_testRunStats) override {
|
||||
StreamingReporterBase::testRunEnded(_testRunStats);
|
||||
|
||||
std::cout << std::string(121, '=') << '\n';
|
||||
std::cout << std::string(133, '=') << '\n';
|
||||
|
||||
auto const &tc = _testRunStats.totals.testCases;
|
||||
auto const &as = _testRunStats.totals.assertions;
|
||||
@@ -444,7 +464,9 @@ private:
|
||||
html << " </div>\n";
|
||||
html << " <div class='stats'>\n";
|
||||
html << " <span class='text-green'>✓ " << test.assertionsPassed << "</span> | ";
|
||||
html << " <span class='text-red'>✗ " << test.assertionsFailed << "</span>\n";
|
||||
html << " <span class='text-red'>✗ " << test.assertionsFailed << "</span> | ";
|
||||
html << " <span style='color: #34495e;'>⌛ " << std::fixed << std::setprecision(3)
|
||||
<< test.durationSeconds << "s</span>\n";
|
||||
html << " </div>\n";
|
||||
html << " </div>\n";
|
||||
|
||||
@@ -589,4 +611,4 @@ int main(
|
||||
test_utils::set_args(std::move(test_args));
|
||||
|
||||
return session.run();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user