feat(FieldDofMap): Completed FieldDofMap migration

also removed legacy BarotropicPolytrope implementation
This commit is contained in:
2026-08-29 08:56:36 -04:00
parent 177ae8b38a
commit 36adfa1174
104 changed files with 26967 additions and 26916 deletions

View File

@@ -1,6 +1,7 @@
#include <algorithm>
#include <array>
#include <catch2/catch_test_macros.hpp>
#include <cmath>
#include <cstddef>
#include <mfem.hpp>
#include <mpi.h>
@@ -79,6 +80,11 @@ namespace field_dof_map_test_utils {
concept CanMakeFieldDofMap =
requires(const mfem::ParFiniteElementSpace &space) { field::make_field_dof_map<FieldT, Schema>(space); };
template <typename FieldT>
concept CanMakeFieldDofGridFunctionAdapter = requires(const mfem::ParFiniteElementSpace &space) {
field::make_field_dof_grid_function_adapter<FieldT, Schema>(space);
};
using AlternateSchema = domain::DomainSchema<
domain::MaterialList<
domain::Material<domain::Core, 11>,
@@ -90,7 +96,7 @@ namespace field_dof_map_test_utils {
TEST_CASE(
"Field DOF Map Preserves Canonical Bidirectional Indexing",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -166,7 +172,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Rejects Invalid Canonical Mappings",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -191,7 +197,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Rejects Out Of Range Index Queries",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -212,7 +218,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Gather Selects Exactly The Active True DOFs",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -247,7 +253,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Scatter Produces The Canonical Supported Projection",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -281,7 +287,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Gather Scatter Projects A Full Vector Onto Field Support",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -310,7 +316,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Scatter Into Preserves Unsupported True DOFs",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -337,7 +343,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Scatter Add Accumulates Only Onto Active True DOFs",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -364,7 +370,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Operations Support MFEM Vector Views Without Resizing",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -406,7 +412,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Operations Reject Incompatible Vector Sizes",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -433,7 +439,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Identity Mapping Is An Exact Vector Identity",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -463,7 +469,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Validates Field DOF Support Consistency",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -495,7 +501,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Factory Is Available Only For Spatial Registered Fields",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -509,12 +515,24 @@ TEST_CASE(
STATIC_REQUIRE_FALSE(field_dof_map_test_utils::CanMakeFieldDofMap<field::BarotropicConstant>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Density>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Enthalpy>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Gravity>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Displacement>);
STATIC_REQUIRE_FALSE(
field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::BarotropicConstant>
);
CHECK(true);
}
TEST_CASE(
"Field DOF Map Factory Exactly Preserves Density Support",
tags::integration &tags::field
tags::field_dof_integration
) {
namespace field = mean_field::field;
@@ -567,7 +585,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Factory Exactly Preserves H1 Enthalpy Support",
tags::integration &tags::field
tags::field_dof_integration
) {
namespace field = mean_field::field;
@@ -614,7 +632,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Factory Produces Identity Maps For All Supported Fields",
tags::integration &tags::field
tags::field_dof_integration
) {
namespace field = mean_field::field;
@@ -648,7 +666,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Factory Uses Schema Material Bindings Rather Than Numeric Conventions",
tags::integration &tags::field
tags::field_dof_integration
) {
namespace field = mean_field::field;
@@ -681,7 +699,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Reduced Vectors Round Trip Through Real Field Support",
tags::integration &tags::field
tags::field_dof_integration
) {
namespace field = mean_field::field;
@@ -721,4 +739,246 @@ TEST_CASE(
CHECK(full(trueDof) == 0.0);
}
}
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Gathers Exactly The Supported True DOFs",
tags::field_dof_integration
) {
namespace field = mean_field::field;
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);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Density, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
mfem::Vector full(adapter.dof_map().full_size());
for (int trueDof = 0; trueDof < full.Size(); ++trueDof) {
full(trueDof) = 1.25 + 0.375 * static_cast<double>(trueDof + 1);
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
gridFunction.SetFromTrueDofs(full);
const mfem::Vector expected = adapter.dof_map().gather(full);
const mfem::Vector actual = adapter.gather(gridFunction);
REQUIRE(actual.Size() == expected.Size());
for (int reducedDof = 0; reducedDof < actual.Size(); ++reducedDof) {
CAPTURE(reducedDof);
CHECK(actual(reducedDof) == expected(reducedDof));
}
mfem::Vector output(adapter.dof_map().reduced_size());
adapter.gather(gridFunction, output);
for (int reducedDof = 0; reducedDof < output.Size(); ++reducedDof) {
CAPTURE(reducedDof);
CHECK(output(reducedDof) == expected(reducedDof));
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Scatter Projects And Round Trips Reduced Fields",
tags::field_dof_integration
) {
namespace field = mean_field::field;
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);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Enthalpy, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
mfem::Vector reduced(adapter.dof_map().reduced_size());
for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) {
reduced(reducedDof) = -0.75 + 0.0625 * static_cast<double>(reducedDof + 1);
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
gridFunction = 91.0;
adapter.scatter(reduced, gridFunction);
mfem::Vector actualFull;
gridFunction.GetTrueDofs(actualFull);
const mfem::Vector expectedFull = adapter.dof_map().scatter(reduced);
REQUIRE(actualFull.Size() == expectedFull.Size());
for (int trueDof = 0; trueDof < actualFull.Size(); ++trueDof) {
CAPTURE(trueDof);
CHECK(actualFull(trueDof) == expectedFull(trueDof));
if (!adapter.dof_map().contains_true_dof(trueDof)) {
CHECK(actualFull(trueDof) == 0.0);
}
}
const mfem::Vector recovered = adapter.gather(gridFunction);
REQUIRE(recovered.Size() == reduced.Size());
for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) {
CAPTURE(reducedDof);
CHECK(recovered(reducedDof) == reduced(reducedDof));
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Scatter Into Preserves Unsupported True DOFs",
tags::field_dof_integration
) {
namespace field = mean_field::field;
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);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Density, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
mfem::Vector initialFull(adapter.dof_map().full_size());
for (int trueDof = 0; trueDof < initialFull.Size(); ++trueDof) {
initialFull(trueDof) = 40.0 + static_cast<double>(trueDof);
}
mfem::Vector reduced(adapter.dof_map().reduced_size());
for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) {
reduced(reducedDof) = -10.0 - static_cast<double>(reducedDof);
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
gridFunction.SetFromTrueDofs(initialFull);
adapter.scatter_into(reduced, gridFunction);
mfem::Vector actualFull;
gridFunction.GetTrueDofs(actualFull);
mfem::Vector expectedFull(initialFull);
adapter.dof_map().scatter_into(reduced, expectedFull);
REQUIRE(actualFull.Size() == expectedFull.Size());
for (int trueDof = 0; trueDof < actualFull.Size(); ++trueDof) {
CAPTURE(trueDof);
CHECK(actualFull(trueDof) == expectedFull(trueDof));
if (!adapter.dof_map().contains_true_dof(trueDof)) {
CHECK(actualFull(trueDof) == initialFull(trueDof));
}
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Is Exact For Identity Vector Field Maps",
tags::field_dof_integration
) {
namespace field = mean_field::field;
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);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Displacement, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
REQUIRE(adapter.dof_map().is_identity());
mfem::Vector reduced(adapter.dof_map().reduced_size());
for (int dof = 0; dof < reduced.Size(); ++dof) {
reduced(dof) = std::sin(0.23 * static_cast<double>(dof + 1));
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
adapter.scatter(reduced, gridFunction);
const mfem::Vector recovered = adapter.gather(gridFunction);
REQUIRE(recovered.Size() == reduced.Size());
for (int dof = 0; dof < reduced.Size(); ++dof) {
CAPTURE(dof);
CHECK(recovered(dof) == reduced(dof));
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Rejects Incompatible Maps Spaces And Vectors",
tags::field_dof_integration
) {
namespace field = mean_field::field;
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 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);
REQUIRE(finiteElementSpace->GetTrueVSize() == otherFiniteElementSpace->GetTrueVSize());
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Density, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
const mfem::Array<int> empty;
CHECK_THROWS_AS(
(field::FieldDofGridFunctionAdapter(
field::FieldDofMap(finiteElementSpace->GetTrueVSize() + 1, empty),
*finiteElementSpace
)),
std::invalid_argument
);
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
mfem::ParGridFunction otherGridFunction(otherFiniteElementSpace.get());
mfem::Vector reduced(adapter.dof_map().reduced_size());
reduced = 1.0;
mfem::Vector wrongReduced(adapter.dof_map().reduced_size() + 1);
mfem::Vector wrongOutput(adapter.dof_map().reduced_size() + 1);
CHECK_THROWS_AS(adapter.gather(otherGridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter(reduced, otherGridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter_into(reduced, otherGridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.gather(gridFunction, wrongOutput), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter(wrongReduced, gridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter_into(wrongReduced, gridFunction), std::invalid_argument);
}

View File

@@ -9,6 +9,20 @@ import test_helpers;
using namespace mean_field;
namespace {
struct SerialMappingData {
explicit SerialMappingData(mfem::Mesh &mesh)
: compactification_fes(&mesh, &compactification_fec),
compactification_coordinate(&compactification_fes),
mapper(field_dof_test_utils::make_domain_mapper()) {
compactification_coordinate = 0.0;
}
mfem::H1_FECollection compactification_fec{1, 3};
mfem::FiniteElementSpace compactification_fes;
mfem::GridFunction compactification_coordinate;
mapping::DomainMapper mapper;
};
double compute_roche_surface_scale(
const double rotation_fraction,
const double sine_theta_squared
@@ -29,7 +43,7 @@ namespace {
TEST_CASE(
"Centrifugal Integrator Matches Manufactured Cartesian Load",
tags::unit &tags::solver &tags::integrator &tags::centrifugal
tags::rotation_integrator_unit
) {
constexpr int dim = 3;
constexpr double density = 1.7;
@@ -49,13 +63,15 @@ TEST_CASE(
mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0;
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
SerialMappingData mapping_data(mesh);
mfem::Vector omega(dim);
omega = 0.0;
omega(2) = omega_value;
integrators::CentrifugalForceIntegrator integrator(domain_mapper, omega);
integrators::CentrifugalForceIntegrator integrator(
mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega
);
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
@@ -66,8 +82,7 @@ TEST_CASE(
quadrature::Policy policy(std::move(rule_set));
quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind =
!domain_mapper.HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general;
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal(
@@ -127,7 +142,7 @@ TEST_CASE(
TEST_CASE(
"Centrifugal Integrator Jacobian Matches Residual Linearization",
tags::unit &tags::solver &tags::integrator &tags::centrifugal
tags::rotation_integrator_unit
) {
constexpr int dim = 3;
constexpr double step = 1.0e-6;
@@ -147,14 +162,16 @@ TEST_CASE(
mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0;
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
SerialMappingData mapping_data(mesh);
mfem::Vector omega(dim);
omega(0) = 0.7;
omega(1) = -1.1;
omega(2) = 1.6;
integrators::CentrifugalForceIntegrator integrator(domain_mapper, omega);
integrators::CentrifugalForceIntegrator integrator(
mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega
);
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
@@ -165,8 +182,7 @@ TEST_CASE(
quadrature::Policy policy(std::move(rule_set));
quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind =
!domain_mapper.HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general;
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal(
@@ -287,7 +303,7 @@ TEST_CASE(
TEST_CASE(
"Centrifugal Integrator Preserves Rotation Identities",
tags::unit &tags::solver &tags::integrator &tags::centrifugal
tags::rotation_integrator_unit
) {
constexpr int dim = 3;
constexpr double density = 1.4;
@@ -307,14 +323,16 @@ TEST_CASE(
mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0;
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
SerialMappingData mapping_data(mesh);
mfem::Vector omega(dim);
omega(0) = 0.7;
omega(1) = -1.1;
omega(2) = 1.6;
integrators::CentrifugalForceIntegrator integrator(domain_mapper, omega);
integrators::CentrifugalForceIntegrator integrator(
mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega
);
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
@@ -325,8 +343,7 @@ TEST_CASE(
quadrature::Policy policy(std::move(rule_set));
quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind =
!domain_mapper.HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general;
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal(
@@ -424,7 +441,7 @@ TEST_CASE(
TEST_CASE(
"Centrifugal Integrator Matches Rotational Virial On Roche Mappings",
tags::integration &tags::solver &tags::integrator &tags::centrifugal
tags::rotation_integrator_integration
) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
@@ -486,16 +503,20 @@ TEST_CASE(
mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement);
displacement.ProjectCoefficient(displacement_coefficient);
f.mapping->SetDisplacement(displacement);
*f.displacement = displacement;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate
);
mfem::Vector omega(dim);
omega = 0.0;
omega(2) = rotation_fraction;
integrators::CentrifugalForceIntegrator integrator(*f.mapping, omega);
integrators::CentrifugalForceIntegrator integrator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate, omega
);
const quadrature::MappingKind mapping_kind =
!f.mapping->HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general;
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
f.quadratureFactory->configure_centrifugal(
integrator, quadrature::QuadratureRole::discretization, representative_density_element,
representative_velocity_element, representative_transformation, position_order, utils::DOMAINS::STELLAR,
@@ -560,7 +581,7 @@ TEST_CASE(
for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node);
f.mapping->GetPhysicalPoint(*transformation, node, x_physical);
mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical);
for (int d = 0; d < dim; ++d) {
position_test_dofs(i + d * velocity_dofs_count) = x_physical(d);
@@ -581,14 +602,14 @@ TEST_CASE(
const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point);
const double signed_map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point);
const mapping::VolumeQuadratureContext context =
f.mapping->GetQuadratureContext(*transformation, integration_point);
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);
f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical);
mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
velocity_element->CalcShape(integration_point, velocity_shape);
position_test_value = 0.0;
@@ -661,13 +682,13 @@ TEST_CASE(
CHECK_THAT(relative_position_error, Catch::Matchers::WithinAbs(0.0, position_tolerance));
}
f.mapping->ResetDisplacement();
*f.displacement = 0.0;
}
TEST_CASE(
"Centrifugal Virial Position Representation Is Consistent At The "
"Registered Order",
tags::integration &tags::solver &tags::integrator &tags::centrifugal
tags::rotation_integrator_integration
) {
constexpr int dim = 3;
constexpr double concentration = 4.0;
@@ -728,7 +749,10 @@ TEST_CASE(
mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement);
displacement.ProjectCoefficient(displacement_coefficient);
f.mapping->SetDisplacement(displacement);
*f.displacement = displacement;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate
);
mfem::Vector omega(dim);
omega = 0.0;
@@ -761,7 +785,7 @@ TEST_CASE(
for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node);
f.mapping->GetPhysicalPoint(*transformation, node, x_physical);
mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical);
for (int d = 0; d < dim; ++d) {
position_test_dofs(i + d * velocity_dofs_count) = x_physical(d);
@@ -780,13 +804,13 @@ TEST_CASE(
const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point);
const double signed_map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point);
const mapping::VolumeQuadratureContext context =
f.mapping->GetQuadratureContext(*transformation, integration_point);
mapping_evaluator.GetQuadratureContext(*transformation, integration_point);
const double signed_map_determinant = context.detJ;
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical);
mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
velocity_element->CalcShape(integration_point, velocity_shape);
position_test_value = 0.0;
@@ -833,7 +857,7 @@ TEST_CASE(
minimum_determinants[rotation_index][order_index] = global_minimum_determinant;
}
f.mapping->ResetDisplacement();
*f.displacement = 0.0;
}
for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) {
@@ -853,7 +877,7 @@ TEST_CASE(
TEST_CASE(
"Centrifugal Virial Position Representation Converges Under H Refinement",
tags::integration &tags::solver &tags::integrator &tags::convergence &tags::h_refinement &tags::centrifugal
tags::rotation_integrator_convergence
) {
constexpr int dim = 3;
constexpr double concentration = 4.0;
@@ -916,7 +940,10 @@ TEST_CASE(
mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement);
displacement.ProjectCoefficient(displacement_coefficient);
f.mapping->SetDisplacement(displacement);
*f.displacement = displacement;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate
);
mfem::Vector omega(dim);
omega = 0.0;
@@ -949,7 +976,7 @@ TEST_CASE(
for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node);
f.mapping->GetPhysicalPoint(*transformation, node, x_physical);
mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical);
for (int d = 0; d < dim; ++d) {
position_test_dofs(i + d * velocity_dofs_count) = x_physical(d);
@@ -968,13 +995,13 @@ TEST_CASE(
const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point);
const double signed_map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point);
const mapping::VolumeQuadratureContext context =
f.mapping->GetQuadratureContext(*transformation, integration_point);
mapping_evaluator.GetQuadratureContext(*transformation, integration_point);
const double signed_map_determinant = context.detJ;
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical);
mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
velocity_element->CalcShape(integration_point, velocity_shape);
position_test_value = 0.0;
@@ -1021,7 +1048,7 @@ TEST_CASE(
minimum_determinants[rotation_index][refinement_index] = global_minimum_determinant;
}
f.mapping->ResetDisplacement();
*f.displacement = 0.0;
}
for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) {

View File

@@ -10,7 +10,7 @@ using namespace mean_field;
TEST_CASE(
"Gravity Force Integrator Jacobian Matches Residual Linearization",
tags::unit &tags::solver &tags::integrator &tags::gravity
tags::gravity_integrator_unit
) {
constexpr int dim = 3;
constexpr double finite_difference_step = 1.0e-3;
@@ -31,21 +31,20 @@ TEST_CASE(
mfem::RT_FECollection gravity_gradient_fec(1, dim);
mfem::L2_FECollection gravity_potential_fec(1, dim);
mfem::H1_FECollection displacement_fec(2, dim);
mfem::H1_FECollection compactification_fec(1, dim);
mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
mfem::FiniteElementSpace gravity_potential_fes(&mesh, &gravity_potential_fec);
mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace compactification_fes(&mesh, &compactification_fec);
mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0;
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
INFO(std::format("Domain mapping is has displacement field: {}", domain_mapper.HasDisplacementField()));
INFO(std::format("Domain mapping is identity: {}", domain_mapper.CalcIsIdentity()));
REQUIRE(domain_mapper.CalcIsIdentity());
mfem::GridFunction compactification_coordinate(&compactification_fes);
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);
@@ -130,7 +129,8 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator(
domain_mapper, integrators::GravityForceJacobianMode::field_coupled
domain_mapper, displacement, compactification_coordinate,
integrators::GravityForceJacobianMode::field_coupled
);
const int maximum_order = std::max(
@@ -268,7 +268,7 @@ TEST_CASE(
TEST_CASE(
"Gravity Force Integrator Matches Manufactured Cartesian Load",
tags::unit &tags::solver &tags::integrator &tags::gravity
tags::gravity_integrator_unit
) {
constexpr int dim = 3;
constexpr double tolerance = 1.0e-12;
@@ -286,18 +286,23 @@ TEST_CASE(
mfem::L2_FECollection density_fec(1, dim);
mfem::RT_FECollection gravity_gradient_fec(0, dim);
mfem::H1_FECollection displacement_fec(1, dim);
mfem::H1_FECollection compactification_fec(1, dim);
mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace compactification_fes(&mesh, &compactification_fec);
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();
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
REQUIRE(domain_mapper.CalcIsIdentity());
mapping::GridFunctionMappingEvaluator mapping_evaluator(
domain_mapper, displacement, compactification_coordinate
);
auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); };
@@ -376,7 +381,8 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator(
domain_mapper, 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);
@@ -395,7 +401,7 @@ TEST_CASE(
for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node);
domain_mapper.GetPhysicalPoint(*transformation, node, x_physical);
mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical);
test_dofs(i + component * velocity_dofs_count) =
coordinate_weight < 0 ? 1.0 : x_physical(coordinate_weight);
}
@@ -433,7 +439,7 @@ TEST_CASE(
}
TEST_CASE(
"Gravity Force Integrator Preserves Gravity Identities",
tags::unit &tags::solver &tags::integrator &tags::gravity
tags::gravity_integrator_unit
) {
constexpr int dim = 3;
constexpr double density_value = 1.7;
@@ -454,18 +460,22 @@ TEST_CASE(
mfem::L2_FECollection density_fec(0, dim);
mfem::RT_FECollection gravity_gradient_fec(0, dim);
mfem::H1_FECollection displacement_fec(1, dim);
mfem::H1_FECollection compactification_fec(1, dim);
mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace compactification_fes(&mesh, &compactification_fec);
mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0;
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
REQUIRE(domain_mapper.HasDisplacementField());
mfem::GridFunction compactification_coordinate(&compactification_fes);
compactification_coordinate = 0.0;
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
mapping::GridFunctionMappingEvaluator mapping_evaluator(
domain_mapper, displacement, compactification_coordinate
);
auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) {
gravity.SetSize(3);
@@ -543,7 +553,8 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator(
domain_mapper, 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);
@@ -609,7 +620,7 @@ TEST_CASE(
for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node);
domain_mapper.GetPhysicalPoint(*transformation, node, x_physical);
mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical);
for (int component = 0; component < dim; ++component) {
centered_position(component) = x_physical(component) - 0.5;
@@ -714,4 +725,4 @@ TEST_CASE(
Catch::Matchers::WithinAbs(0.0, tolerance)
);
CHECK_THAT(zero_density_field_action.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -43,11 +43,18 @@ TEST_CASE(
CHECK(initial_report.geometry.reconstructed_operators);
CHECK(initial_report.geometry.rebuilt_mass_operator);
CHECK(initial_report.geometry.rebuilt_source_operator);
CHECK(initial_report.geometry.rebuilt_divergence_operator);
CHECK(initial_report.geometry.refreshed_variation_state);
CHECK(initial_report.updated_density);
CHECK(initial_report.updated_gravity_gradient);
CHECK(initial_report.DidAnyWork());
const auto &geometry_context = context.GetGeometryContext();
CHECK(geometry_context.GetDivergenceOperator().Width() == f.gravityFluxFes->GetTrueVSize());
CHECK(geometry_context.GetDivergenceOperator().Height() == f.gravityPotentialFes->GetTrueVSize());
CHECK(geometry_context.GetTransposeDivergenceOperator().Width() == f.gravityPotentialFes->GetTrueVSize());
CHECK(geometry_context.GetTransposeDivergenceOperator().Height() == f.gravityFluxFes->GetTrueVSize());
const auto initial_mass_preparations = context.GetGeometryContext().GetMassOperator().GetPreparationCount();
const auto initial_source_preparations = context.GetGeometryContext().GetSourceOperator().GetPreparationCount();
@@ -93,6 +100,7 @@ TEST_CASE(
CHECK_FALSE(displacement_report.geometry.reconstructed_operators);
CHECK(displacement_report.geometry.rebuilt_mass_operator);
CHECK(displacement_report.geometry.rebuilt_source_operator);
CHECK_FALSE(displacement_report.geometry.rebuilt_divergence_operator);
CHECK(displacement_report.geometry.refreshed_variation_state);
CHECK_FALSE(displacement_report.updated_density);
CHECK_FALSE(displacement_report.updated_gravity_gradient);
@@ -107,6 +115,7 @@ TEST_CASE(
CHECK(discretization_report.geometry.reconstructed_operators);
CHECK(discretization_report.geometry.rebuilt_mass_operator);
CHECK(discretization_report.geometry.rebuilt_source_operator);
CHECK(discretization_report.geometry.rebuilt_divergence_operator);
CHECK(discretization_report.updated_density);
CHECK(discretization_report.updated_gravity_gradient);
CHECK(context.GetGeometryContext().GetMassOperator().GetPreparationCount() == 1);

File diff suppressed because it is too large Load Diff

View File

@@ -11,371 +11,386 @@ 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.mapping != 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.mapping != nullptr);
f.mapping->ResetDisplacement();
mean_field::physics::update_stiffness_matrix(f);
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::grav_potential_new(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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -9,423 +9,430 @@ 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 = f.domainMapperStateless->GetVacuumElementAttribute();
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;
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}}}};
constexpr double deformationX = 1.08;
constexpr double deformationY = 0.96;
auto args = test_utils::setup_args();
/*
* The third scale makes the affine deformation
* volume-preserving:
*
* det(F) = sx * sy * sz = 1.
*/
constexpr double deformationZ = 1.0 / (deformationX * deformationY);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
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 MPI_Comm communicator = f.mesh->GetComm();
auto args = test_utils::setup_args();
const mean_field::field::FieldDofMap enthalpyMap =
field_dof_test_utils::make_map<mean_field::field::Enthalpy>(
*f.enthalpyFes);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::field::FieldDofMap gravityPotentialMap =
field_dof_test_utils::make_map<mean_field::field::Gravity>(
*f.gravityPotentialFes);
const MPI_Comm communicator = f.mesh->GetComm();
const mean_field::field::FieldDofMap displacementMap =
field_dof_test_utils::make_map<mean_field::field::Displacement>(
*f.displacementFes);
const mean_field::field::FieldDofMap enthalpyMap =
field_dof_test_utils::make_map<mean_field::field::Enthalpy>(*f.enthalpyFes);
const mfem::Array<int> stellarElementMarker =
prepared_hydrostatic_analytic_solve_test_utils::
make_stellar_element_marker(f);
const mean_field::field::FieldDofMap gravityPotentialMap =
field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityPotentialFes);
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 displacementMap =
field_dof_test_utils::make_map<mean_field::field::Displacement>(*f.displacementFes);
REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14);
const mfem::Array<int> stellarElementMarker =
prepared_hydrostatic_analytic_solve_test_utils::make_stellar_element_marker(f);
const mean_field::physics::RigidRotation rotation =
prepared_hydrostatic_analytic_solve_test_utils::make_rotation(
analyticCase);
for (const AnalyticCase &analyticCase : analyticCases) {
DYNAMIC_SECTION(analyticCase.name) {
const double deformationDeterminant =
analyticCase.deformationScale[0] * analyticCase.deformationScale[1] * analyticCase.deformationScale[2];
auto displacementFunction =
[&analyticCase](const mfem::Vector &referencePosition,
mfem::Vector &displacementValue) {
mfem::Vector physicalPosition;
REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14);
prepared_hydrostatic_analytic_solve_test_utils::map_to_physical(
referencePosition, analyticCase, physicalPosition);
const mean_field::physics::RigidRotation rotation =
prepared_hydrostatic_analytic_solve_test_utils::make_rotation(analyticCase);
displacementValue.SetSize(3);
displacementValue = physicalPosition;
displacementValue -= referencePosition;
};
auto displacementFunction =
[&analyticCase](const mfem::Vector &referencePosition, mfem::Vector &displacementValue) {
mfem::Vector physicalPosition;
auto potentialFunction = [&analyticCase, &rotation](
const mfem::Vector &referencePosition) {
return prepared_hydrostatic_analytic_solve_test_utils::
exact_potential_value(referencePosition, analyticCase, rotation);
};
prepared_hydrostatic_analytic_solve_test_utils::map_to_physical(
referencePosition, analyticCase, physicalPosition
);
auto enthalpyFunction = [](const mfem::Vector &referencePosition) {
return prepared_hydrostatic_analytic_solve_test_utils::
exact_enthalpy_value(referencePosition);
};
displacementValue.SetSize(3);
displacementValue = physicalPosition;
displacementValue -= referencePosition;
};
mfem::VectorFunctionCoefficient displacementCoefficient(
f.mesh->Dimension(), displacementFunction);
auto potentialFunction = [&analyticCase, &rotation](const mfem::Vector &referencePosition) {
return prepared_hydrostatic_analytic_solve_test_utils::exact_potential_value(
referencePosition, analyticCase, rotation
);
};
mfem::FunctionCoefficient potentialCoefficient(potentialFunction);
auto enthalpyFunction = [](const mfem::Vector &referencePosition) {
return prepared_hydrostatic_analytic_solve_test_utils::exact_enthalpy_value(referencePosition);
};
mfem::FunctionCoefficient exactEnthalpyCoefficient(enthalpyFunction);
mfem::VectorFunctionCoefficient displacementCoefficient(f.mesh->Dimension(), displacementFunction);
/*
* Project the prescribed geometry and potential.
*/
mfem::ParGridFunction displacementField(f.displacementFes.get());
mfem::FunctionCoefficient potentialCoefficient(potentialFunction);
mfem::ParGridFunction potentialField(f.gravityPotentialFes.get());
mfem::FunctionCoefficient exactEnthalpyCoefficient(enthalpyFunction);
displacementField.ProjectCoefficient(displacementCoefficient);
/*
* Project the prescribed geometry and potential.
*/
mfem::ParGridFunction displacementField(f.displacementFes.get());
potentialField.ProjectCoefficient(potentialCoefficient);
mfem::ParGridFunction potentialField(f.gravityPotentialFes.get());
mfem::Vector displacementTrue;
mfem::Vector gravityPotentialTrue;
displacementField.ProjectCoefficient(displacementCoefficient);
displacementField.GetTrueDofs(displacementTrue);
potentialField.GetTrueDofs(gravityPotentialTrue);
potentialField.ProjectCoefficient(potentialCoefficient);
const mfem::Vector displacement =
displacementMap.gather(displacementTrue);
const mfem::Vector gravityPotential =
gravityPotentialMap.gather(gravityPotentialTrue);
mfem::Vector displacementTrue;
mfem::Vector gravityPotentialTrue;
/*
* 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());
displacementField.GetTrueDofs(displacementTrue);
potentialField.GetTrueDofs(gravityPotentialTrue);
projectedEnthalpyField.ProjectCoefficient(exactEnthalpyCoefficient);
const mfem::Vector displacement = displacementMap.gather(displacementTrue);
const mfem::Vector gravityPotential = gravityPotentialMap.gather(gravityPotentialTrue);
mfem::ParGridFunction zeroEnthalpyField(f.enthalpyFes.get());
/*
* 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());
zeroEnthalpyField = 0.0;
projectedEnthalpyField.ProjectCoefficient(exactEnthalpyCoefficient);
const double exactEnthalpyNorm = zeroEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
mfem::ParGridFunction zeroEnthalpyField(f.enthalpyFes.get());
const double projectionError = projectedEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
zeroEnthalpyField = 0.0;
REQUIRE(exactEnthalpyNorm > 0.0);
const double exactEnthalpyNorm =
zeroEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
const double relativeProjectionError =
projectionError / exactEnthalpyNorm;
const double projectionError =
projectedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
/*
* Begin deliberately far from equilibrium.
*/
mfem::Vector enthalpy(enthalpyMap.reduced_size());
REQUIRE(exactEnthalpyNorm > 0.0);
enthalpy = 0.0;
const double relativeProjectionError = projectionError / exactEnthalpyNorm;
auto dependencies =
prepared_hydrostatic_analytic_solve_test_utils::make_dependencies();
/*
* Begin deliberately far from equilibrium.
*/
mfem::Vector enthalpy(enthalpyMap.reduced_size());
mean_field::operators::PreparedHydrostaticEquilibriumOperator
preparedOperator(f, *f.domainMapperStateless);
enthalpy = 0.0;
const auto initialReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(
enthalpy, gravityPotential, displacement),
dependencies, rotation);
auto dependencies = prepared_hydrostatic_analytic_solve_test_utils::make_dependencies();
REQUIRE(initialReport.preparedResidual);
REQUIRE(initialReport.preparedAlgebraicJacobianBlocks);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
mfem::Vector initialResidual;
const auto initialReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
dependencies, rotation
);
preparedOperator.BuildResidual(initialResidual);
REQUIRE(initialReport.preparedResidual);
REQUIRE(initialReport.preparedAlgebraicJacobianBlocks);
const double initialResidualNorm =
gravity_prepared_test_utils::global_norm(initialResidual,
communicator);
mfem::Vector initialResidual;
REQUIRE(initialResidualNorm > 1.0e-12);
preparedOperator.BuildResidual(initialResidual);
/*
* One discrete Newton step:
*
* M_h delta_h = -R_h.
*
* The full four-block Bernoulli Jacobian is rectangular
* and underdetermined in isolation. Freezing Phi, C,
* rotation, and displacement makes this a well-defined
* enthalpy solve.
*/
prepared_hydrostatic_analytic_solve_test_utils::EnthalpyJacobianOperator
enthalpyJacobian(enthalpyMap.reduced_size(), preparedOperator);
const double initialResidualNorm = gravity_prepared_test_utils::global_norm(initialResidual, communicator);
mfem::Vector rightHandSide(initialResidual);
rightHandSide *= -1.0;
REQUIRE(initialResidualNorm > 1.0e-12);
mfem::Vector enthalpyCorrection(enthalpyMap.reduced_size());
/*
* 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
);
enthalpyCorrection = 0.0;
mfem::Vector rightHandSide(initialResidual);
rightHandSide *= -1.0;
/*
* 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 enthalpyCorrection(enthalpyMap.reduced_size());
linearSolver.SetOperator(enthalpyJacobian);
enthalpyCorrection = 0.0;
linearSolver.SetRelTol(1.0e-13);
linearSolver.SetAbsTol(1.0e-14);
linearSolver.SetMaxIter(2000);
linearSolver.SetPrintLevel(0);
/*
* The reduced operator contains only stellar-supported
* enthalpy DOFs and is positive definite. MINRES remains
* appropriate for this symmetric system.
*/
mfem::MINRESSolver linearSolver(communicator);
linearSolver.Mult(rightHandSide, enthalpyCorrection);
linearSolver.SetOperator(enthalpyJacobian);
INFO("Linear solver converged = " << linearSolver.GetConverged());
linearSolver.SetRelTol(1.0e-13);
linearSolver.SetAbsTol(1.0e-14);
linearSolver.SetMaxIter(2000);
linearSolver.SetPrintLevel(0);
INFO("Linear solver iterations = " << linearSolver.GetNumIterations());
linearSolver.Mult(rightHandSide, enthalpyCorrection);
INFO("Linear solver final norm = " << linearSolver.GetFinalNorm());
INFO("Linear solver converged = " << linearSolver.GetConverged());
REQUIRE(linearSolver.GetConverged());
INFO("Linear solver iterations = " << linearSolver.GetNumIterations());
enthalpy += enthalpyCorrection;
INFO("Linear solver final norm = " << linearSolver.GetFinalNorm());
/*
* Only the enthalpy state changed. Geometry, rotation,
* and algebraic Jacobian data must remain reusable.
*/
++dependencies.enthalpy.revision;
REQUIRE(linearSolver.GetConverged());
const auto solvedReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(
enthalpy, gravityPotential, displacement),
dependencies, rotation);
enthalpy += enthalpyCorrection;
CHECK(solvedReport.contextReport.updatedEnthalpy);
/*
* Only the enthalpy state changed. Geometry, rotation,
* and algebraic Jacobian data must remain reusable.
*/
++dependencies.enthalpy.revision;
CHECK(solvedReport.contextReport.preparedBaseState);
const auto solvedReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
dependencies, rotation
);
CHECK_FALSE(solvedReport.contextReport.preparedGeometryState);
CHECK(solvedReport.contextReport.updatedEnthalpy);
CHECK_FALSE(solvedReport.preparedAlgebraicJacobianBlocks);
CHECK(solvedReport.contextReport.preparedBaseState);
mfem::Vector solvedResidual;
CHECK_FALSE(solvedReport.contextReport.preparedGeometryState);
preparedOperator.BuildResidual(solvedResidual);
CHECK_FALSE(solvedReport.preparedAlgebraicJacobianBlocks);
const double solvedResidualNorm =
gravity_prepared_test_utils::global_norm(solvedResidual,
communicator);
mfem::Vector solvedResidual;
const double residualReduction = solvedResidualNorm / initialResidualNorm;
preparedOperator.BuildResidual(solvedResidual);
/*
* Compare the solved field with the continuum analytic
* enthalpy over stellar elements only.
*
* All three mappings have determinant one, so this
* normalized L2 error is also unchanged by the physical
* volume transformation.
*/
mfem::ParGridFunction solvedEnthalpyField(f.enthalpyFes.get());
const double solvedResidualNorm = gravity_prepared_test_utils::global_norm(solvedResidual, communicator);
mfem::Vector enthalpyTrue(enthalpyMap.full_size());
enthalpyMap.scatter(enthalpy, enthalpyTrue);
solvedEnthalpyField.SetFromTrueDofs(enthalpyTrue);
const double residualReduction = solvedResidualNorm / initialResidualNorm;
const double solvedAnalyticError = solvedEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
/*
* 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());
const double relativeSolvedAnalyticError =
solvedAnalyticError / exactEnthalpyNorm;
mfem::Vector enthalpyTrue(enthalpyMap.full_size());
enthalpyMap.scatter(enthalpy, enthalpyTrue);
solvedEnthalpyField.SetFromTrueDofs(enthalpyTrue);
INFO("Deformation determinant = " << deformationDeterminant);
const double solvedAnalyticError =
solvedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
INFO("Initial weak residual norm = " << initialResidualNorm);
const double relativeSolvedAnalyticError = solvedAnalyticError / exactEnthalpyNorm;
INFO("Solved weak residual norm = " << solvedResidualNorm);
INFO("Deformation determinant = " << deformationDeterminant);
INFO("Weak residual reduction = " << residualReduction);
INFO("Initial weak residual norm = " << initialResidualNorm);
INFO("Relative analytic projection floor = " << relativeProjectionError);
INFO("Solved weak residual norm = " << solvedResidualNorm);
INFO("Relative solved analytic L2 error = "
<< relativeSolvedAnalyticError);
INFO("Weak residual reduction = " << residualReduction);
/*
* The discrete Bernoulli equation must be solved essentially
* to the linear-solver floor.
*/
CHECK(residualReduction < 1.0e-10);
INFO("Relative analytic projection floor = " << relativeProjectionError);
/*
* The directly projected analytic enthalpy provides a lower
* representation bound, but it is not the expected solution
* of the cross-space discrete Bernoulli equation. The latter
* also contains potential-projection and mapped-space
* compatibility errors.
*/
CHECK(relativeSolvedAnalyticError <
std::max(5.0 * relativeProjectionError, 1.25e-4));
INFO("Relative solved analytic L2 error = " << relativeSolvedAnalyticError);
/*
* 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);
}
/*
* Record that the analytic error remains within one order of
* magnitude of the direct enthalpy projection floor.
*/
CHECK(relativeSolvedAnalyticError / relativeProjectionError < 5.0);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,106 +8,115 @@
import mean_field;
import test_helpers;
TEST_CASE(
"Polytropic Barotrope Satisfies Its Analytic Identities",
tags::hydro &tags::unit &tags::barotrope
) {
constexpr double polytropic_index = 3.0;
constexpr double polytropic_constant = 1.5;
TEST_CASE("Polytropic EOS Satisfies Its Analytic Identities",
tags::barotrope_eos_unit) {
constexpr double polytropic_index = 3.0;
constexpr double polytropic_constant = 1.5;
const mean_field::physics::PolytropicBarotrope barotrope(polytropic_index, polytropic_constant);
const mean_field::eos::Polytrope barotrope(polytropic_index,
polytropic_constant);
const std::array<double, 5> densities{1.0e-6, 1.0e-3, 0.1, 0.7, 2.0};
const std::array<double, 5> densities{1.0e-6, 1.0e-3, 0.1, 0.7, 2.0};
for (const double density : densities) {
const double pressure = barotrope.pressure_from_density(density);
for (const double density : densities) {
const double pressure = barotrope.pressure_from_density(density);
const double enthalpy = barotrope.enthalpy_from_density(density);
const double enthalpy = barotrope.enthalpy_from_density(density);
const double reconstructed_density = barotrope.density_from_enthalpy(enthalpy);
const double reconstructed_density =
barotrope.density_from_enthalpy(enthalpy);
const double reconstructed_pressure = barotrope.pressure_from_enthalpy(enthalpy);
const double reconstructed_pressure =
barotrope.pressure_from_enthalpy(enthalpy);
CHECK_THAT(reconstructed_density, Catch::Matchers::WithinRel(density, 2.0e-14));
const double reconstructed_enthalpy =
barotrope.enthalpy_from_pressure(pressure);
CHECK_THAT(reconstructed_pressure, Catch::Matchers::WithinRel(pressure, 2.0e-14));
CHECK_THAT(reconstructed_density,
Catch::Matchers::WithinRel(density, 2.0e-14));
CHECK_THAT(pressure, Catch::Matchers::WithinRel(density * enthalpy / (polytropic_index + 1.0), 2.0e-14));
CHECK_THAT(reconstructed_pressure,
Catch::Matchers::WithinRel(pressure, 2.0e-14));
CHECK_THAT(barotrope.pressure_derivative_from_enthalpy(enthalpy), Catch::Matchers::WithinRel(density, 2.0e-14));
CHECK_THAT(reconstructed_enthalpy,
Catch::Matchers::WithinRel(enthalpy, 2.0e-14));
CHECK_THAT(
barotrope.pressure_derivative_from_density(density),
Catch::Matchers::WithinRel(enthalpy / polytropic_index, 2.0e-14)
);
}
CHECK_THAT(pressure,
Catch::Matchers::WithinRel(
density * enthalpy / (polytropic_index + 1.0), 2.0e-14));
CHECK_THAT(barotrope.pressure_derivative_from_enthalpy(enthalpy),
Catch::Matchers::WithinRel(density, 2.0e-14));
CHECK_THAT(
barotrope.pressure_derivative_from_density(density),
Catch::Matchers::WithinRel(enthalpy / polytropic_index, 2.0e-14));
}
}
TEST_CASE(
"Polytropic Barotrope Derivatives Match Centered Differences",
tags::hydro &tags::jacobian &tags::unit &tags::barotrope
) {
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
TEST_CASE("Polytropic EOS Derivatives Match Centered Differences",
tags::barotrope_eos_jacobian) {
const mean_field::eos::Polytrope barotrope(3.0, 1.5);
const std::array<double, 4> enthalpies{0.05, 0.2, 0.7, 1.4};
const std::array<double, 4> enthalpies{0.05, 0.2, 0.7, 1.4};
for (const double enthalpy : enthalpies) {
const double step = 1.0e-6 * std::max(1.0, enthalpy);
for (const double enthalpy : enthalpies) {
const double step = 1.0e-6 * std::max(1.0, enthalpy);
const double density_difference =
(barotrope.density_from_enthalpy(enthalpy + step) - barotrope.density_from_enthalpy(enthalpy - step)) /
(2.0 * step);
const double density_difference =
(barotrope.density_from_enthalpy(enthalpy + step) -
barotrope.density_from_enthalpy(enthalpy - step)) /
(2.0 * step);
const double pressure_difference =
(barotrope.pressure_from_enthalpy(enthalpy + step) - barotrope.pressure_from_enthalpy(enthalpy - step)) /
(2.0 * step);
const double pressure_difference =
(barotrope.pressure_from_enthalpy(enthalpy + step) -
barotrope.pressure_from_enthalpy(enthalpy - step)) /
(2.0 * step);
CHECK_THAT(
density_difference,
Catch::Matchers::WithinRel(barotrope.density_derivative_from_enthalpy(enthalpy), 5.0e-10)
);
CHECK_THAT(
density_difference,
Catch::Matchers::WithinRel(
barotrope.density_derivative_from_enthalpy(enthalpy), 5.0e-10));
CHECK_THAT(
pressure_difference,
Catch::Matchers::WithinRel(barotrope.pressure_derivative_from_enthalpy(enthalpy), 5.0e-10)
);
}
CHECK_THAT(
pressure_difference,
Catch::Matchers::WithinRel(
barotrope.pressure_derivative_from_enthalpy(enthalpy), 5.0e-10));
}
}
TEST_CASE(
"Polytropic Barotrope Has An Exact Zero Density Surface",
tags::hydro &tags::unit &tags::barotrope
) {
const mean_field::physics::PolytropicBarotrope 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(barotrope.density_from_enthalpy(-1.0) == 0.0);
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0);
CHECK(barotrope.pressure_from_enthalpy(-1.0) == 0.0);
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0);
CHECK(barotrope.pressure_from_enthalpy(-1.0) == 0.0);
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0);
CHECK(barotrope.density_derivative_from_enthalpy(-1.0) == 0.0);
CHECK(barotrope.density_derivative_from_enthalpy(-1.0) == 0.0);
CHECK(barotrope.density_derivative_from_enthalpy(0.0) == 0.0);
CHECK(barotrope.density_derivative_from_enthalpy(0.0) == 0.0);
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0);
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0);
}
TEST_CASE(
"Polytropic Barotrope Rejects Invalid Material Parameters",
tags::hydro &tags::unit
) {
CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(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::physics::PolytropicBarotrope(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::physics::PolytropicBarotrope(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::physics::PolytropicBarotrope 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(barotrope.pressure_from_density(-1.0), std::domain_error);
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-1.0), std::domain_error);
}
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-1.0), std::domain_error);
CHECK_THROWS_AS(barotrope.enthalpy_from_pressure(-1.0), std::domain_error);
}

View File

@@ -13,520 +13,561 @@
import mean_field;
import test_helpers;
namespace polytropic_barotrope_test_utils {
template <typename Function>
double centered_derivative(
Function &&function,
const double position,
const double step
) {
return (function(position + step) - function(position - step)) / (2.0 * step);
}
template <typename Integrand>
double integrate_cube(
const mfem::IntegrationRule &integrationRule,
Integrand &&integrand
) {
double integral = 0.0;
for (int pointIndex = 0; pointIndex < integrationRule.GetNPoints(); ++pointIndex) {
const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(pointIndex);
integral += integrationPoint.weight * integrand(integrationPoint);
}
return integral;
}
} // namespace polytropic_barotrope_test_utils
TEST_CASE(
"Polytropic Barotrope Satisfies Its Thermodynamic Identities",
tags::barotrope &tags::physics &tags::unit
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 4> densities{1.0e-4, 0.02, 0.37, 2.4};
constexpr double polytropicConstant = 0.73;
for (const double polytropicIndex : polytropicIndices) {
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant);
const double expectedEnthalpyScale = (polytropicIndex + 1.0) * polytropicConstant;
CHECK(barotrope.polytropic_index() == polytropicIndex);
CHECK(barotrope.polytropic_constant() == polytropicConstant);
CHECK(barotrope.enthalpy_scale() == expectedEnthalpyScale);
for (const double density : densities) {
CAPTURE(polytropicIndex, polytropicConstant, density);
const double expectedPressure = polytropicConstant * std::pow(density, 1.0 + 1.0 / polytropicIndex);
const double expectedEnthalpy = expectedEnthalpyScale * std::pow(density, 1.0 / polytropicIndex);
const double pressureFromDensity = barotrope.pressure_from_density(density);
const double enthalpyFromDensity = barotrope.enthalpy_from_density(density);
const double recoveredDensity = barotrope.density_from_enthalpy(enthalpyFromDensity);
const double pressureFromEnthalpy = barotrope.pressure_from_enthalpy(enthalpyFromDensity);
CHECK_THAT(pressureFromDensity, Catch::Matchers::WithinRel(expectedPressure, 2.0e-13));
CHECK_THAT(enthalpyFromDensity, Catch::Matchers::WithinRel(expectedEnthalpy, 2.0e-13));
CHECK_THAT(recoveredDensity, Catch::Matchers::WithinRel(density, 5.0e-13));
CHECK_THAT(pressureFromEnthalpy, Catch::Matchers::WithinRel(expectedPressure, 5.0e-13));
/*
* Polytropic identity:
*
* P = rho h / (n + 1).
*/
CHECK_THAT(
pressureFromEnthalpy,
Catch::Matchers::WithinRel(density * enthalpyFromDensity / (polytropicIndex + 1.0), 5.0e-13)
);
/*
* Polytropic identity:
*
* dP / dh = rho.
*
* The implementation should return the same
* value as density_from_enthalpy().
*/
CHECK(
barotrope.pressure_derivative_from_enthalpy(enthalpyFromDensity) ==
barotrope.density_from_enthalpy(enthalpyFromDensity)
);
/*
* Since
*
* h = (n + 1) K rho^(1/n),
*
* it follows that
*
* dP / d rho = h / n.
*/
CHECK_THAT(
barotrope.pressure_derivative_from_density(density),
Catch::Matchers::WithinRel(enthalpyFromDensity / polytropicIndex, 5.0e-13)
);
}
}
}
namespace polytropic_eos_test_utils {
template <typename Function>
double centered_derivative(Function &&function, const double position,
const double step) {
return (function(position + step) - function(position - step)) / (2.0 * step);
}
TEST_CASE(
"Polytropic Barotrope Pressure Derivatives Match Centered Differences",
tags::barotrope &tags::physics &tags::unit &tags::jacobian &tags::pressure
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
template <typename Integrand>
double integrate_cube(const mfem::IntegrationRule &integrationRule,
Integrand &&integrand) {
double integral = 0.0;
constexpr std::array<double, 3> positiveValues{0.2, 0.73, 1.8};
for (int pointIndex = 0; pointIndex < integrationRule.GetNPoints();
++pointIndex) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(pointIndex);
constexpr double polytropicConstant = 0.61;
integral += integrationPoint.weight * integrand(integrationPoint);
}
for (const double polytropicIndex : polytropicIndices) {
const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant);
return integral;
}
} // namespace polytropic_eos_test_utils
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
for (const double enthalpy : positiveValues) {
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
TEST_CASE("Polytropic EOS Satisfies Its Thermodynamic Identities",
tags::barotrope_eos_unit) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
const double numericalDerivative = polytropic_barotrope_test_utils::centered_derivative(
[&barotrope](const double perturbedEnthalpy) {
return barotrope.pressure_from_enthalpy(perturbedEnthalpy);
},
enthalpy, step
);
constexpr std::array<double, 4> densities{1.0e-4, 0.02, 0.37, 2.4};
const double analyticDerivative = barotrope.pressure_derivative_from_enthalpy(enthalpy);
constexpr double polytropicConstant = 0.73;
CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative, analyticDerivative);
for (const double polytropicIndex : polytropicIndices) {
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
const mean_field::eos::Polytrope barotrope(polytropicIndex,
polytropicConstant);
CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
}
const double expectedEnthalpyScale =
(polytropicIndex + 1.0) * polytropicConstant;
for (const double density : positiveValues) {
const double step = 2.0e-6 * std::max(1.0, std::abs(density));
CHECK(barotrope.polytropic_index() == polytropicIndex);
const double numericalDerivative = polytropic_barotrope_test_utils::centered_derivative(
[&barotrope](const double perturbedDensity) {
return barotrope.pressure_from_density(perturbedDensity);
},
density, step
);
CHECK(barotrope.polytropic_constant() == polytropicConstant);
const double analyticDerivative = barotrope.pressure_derivative_from_density(density);
CHECK(barotrope.enthalpy_scale() == expectedEnthalpyScale);
CAPTURE(polytropicIndex, density, step, numericalDerivative, analyticDerivative);
for (const double density : densities) {
CAPTURE(polytropicIndex, polytropicConstant, density);
CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
}
}
const double expectedPressure =
polytropicConstant * std::pow(density, 1.0 + 1.0 / polytropicIndex);
const double expectedEnthalpy =
expectedEnthalpyScale * std::pow(density, 1.0 / polytropicIndex);
const double pressureFromDensity =
barotrope.pressure_from_density(density);
const double enthalpyFromDensity =
barotrope.enthalpy_from_density(density);
const double recoveredDensity =
barotrope.density_from_enthalpy(enthalpyFromDensity);
const double pressureFromEnthalpy =
barotrope.pressure_from_enthalpy(enthalpyFromDensity);
CHECK_THAT(pressureFromDensity,
Catch::Matchers::WithinRel(expectedPressure, 2.0e-13));
CHECK_THAT(enthalpyFromDensity,
Catch::Matchers::WithinRel(expectedEnthalpy, 2.0e-13));
CHECK_THAT(recoveredDensity,
Catch::Matchers::WithinRel(density, 5.0e-13));
CHECK_THAT(pressureFromEnthalpy,
Catch::Matchers::WithinRel(expectedPressure, 5.0e-13));
/*
* Polytropic identity:
*
* P = rho h / (n + 1).
*/
CHECK_THAT(pressureFromEnthalpy,
Catch::Matchers::WithinRel(density * enthalpyFromDensity /
(polytropicIndex + 1.0),
5.0e-13));
/*
* Polytropic identity:
*
* dP / dh = rho.
*
* The implementation should return the same
* value as density_from_enthalpy().
*/
CHECK(
barotrope.pressure_derivative_from_enthalpy(enthalpyFromDensity) ==
barotrope.density_from_enthalpy(enthalpyFromDensity));
/*
* Since
*
* h = (n + 1) K rho^(1/n),
*
* it follows that
*
* dP / d rho = h / n.
*/
CHECK_THAT(barotrope.pressure_derivative_from_density(density),
Catch::Matchers::WithinRel(
enthalpyFromDensity / polytropicIndex, 5.0e-13));
}
}
}
}
TEST_CASE(
"Polytropic Barotrope Density Derivative Matches Centered Differences",
tags::barotrope &tags::physics &tags::unit &tags::jacobian &tags::pressure
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
TEST_CASE("Polytropic EOS Pressure Derivatives Match Centered Differences",
tags::barotrope_eos_jacobian) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 3> enthalpies{0.2, 0.73, 1.8};
constexpr std::array<double, 3> positiveValues{0.2, 0.73, 1.8};
constexpr double polytropicConstant = 0.61;
constexpr double polytropicConstant = 0.61;
for (const double polytropicIndex : polytropicIndices) {
const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant);
for (const double polytropicIndex : polytropicIndices) {
const mean_field::eos::Polytrope barotrope(polytropicIndex,
polytropicConstant);
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
for (const double enthalpy : enthalpies) {
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
for (const double enthalpy : positiveValues) {
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
const double numericalDerivative = polytropic_barotrope_test_utils::centered_derivative(
[&barotrope](const double perturbedEnthalpy) {
return barotrope.density_from_enthalpy(perturbedEnthalpy);
},
enthalpy, step
);
const double numericalDerivative =
polytropic_eos_test_utils::centered_derivative(
[&barotrope](const double perturbedEnthalpy) {
return barotrope.pressure_from_enthalpy(perturbedEnthalpy);
},
enthalpy, step);
const double analyticDerivative = barotrope.density_derivative_from_enthalpy(enthalpy);
const double analyticDerivative =
barotrope.pressure_derivative_from_enthalpy(enthalpy);
CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative, analyticDerivative);
CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative,
analyticDerivative);
CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
}
}
CHECK_THAT(numericalDerivative,
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
}
for (const double density : positiveValues) {
const double step = 2.0e-6 * std::max(1.0, std::abs(density));
const double numericalDerivative =
polytropic_eos_test_utils::centered_derivative(
[&barotrope](const double perturbedDensity) {
return barotrope.pressure_from_density(perturbedDensity);
},
density, step);
const double analyticDerivative =
barotrope.pressure_derivative_from_density(density);
CAPTURE(polytropicIndex, density, step, numericalDerivative,
analyticDerivative);
CHECK_THAT(numericalDerivative,
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
}
}
}
}
TEST_CASE(
"Polytropic Barotrope Defines Consistent Surface And Exterior Behavior",
tags::barotrope &tags::physics &tags::unit &tags::pressure
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
TEST_CASE("Polytropic EOS Density Derivative Matches Centered Differences",
tags::barotrope_eos_jacobian) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr double polytropicConstant = 0.47;
constexpr double exteriorEnthalpy = -0.3;
constexpr std::array<double, 3> enthalpies{0.2, 0.73, 1.8};
for (const double polytropicIndex : polytropicIndices) {
const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant);
constexpr double polytropicConstant = 0.61;
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
/*
* Exact surface values.
*/
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0);
for (const double polytropicIndex : polytropicIndices) {
const mean_field::eos::Polytrope barotrope(polytropicIndex,
polytropicConstant);
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0);
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
for (const double enthalpy : enthalpies) {
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0);
const double numericalDerivative =
polytropic_eos_test_utils::centered_derivative(
[&barotrope](const double perturbedEnthalpy) {
return barotrope.density_from_enthalpy(perturbedEnthalpy);
},
enthalpy, step);
CHECK(barotrope.pressure_from_density(0.0) == 0.0);
const double analyticDerivative =
barotrope.density_derivative_from_enthalpy(enthalpy);
CHECK(barotrope.enthalpy_from_density(0.0) == 0.0);
CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative,
analyticDerivative);
CHECK(barotrope.pressure_derivative_from_density(0.0) == 0.0);
/*
* Positive-part extension into h < 0.
*/
CHECK(barotrope.density_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK(barotrope.pressure_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK(barotrope.density_derivative_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK(barotrope.pressure_derivative_from_enthalpy(exteriorEnthalpy) == 0.0);
/*
* At h = 0, rho(h) has a nonzero right
* derivative only for n = 1.
*/
const double expectedSurfaceDensityDerivative =
polytropicIndex == 1.0 ? 1.0 / barotrope.enthalpy_scale() : 0.0;
CHECK(barotrope.density_derivative_from_enthalpy(0.0) == expectedSurfaceDensityDerivative);
}
CHECK_THAT(numericalDerivative,
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
}
}
}
}
TEST_CASE(
"Polytropic Barotrope Rejects Invalid Physical Inputs",
tags::barotrope &tags::physics &tags::unit &tags::pressure
) {
CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(0.999, 1.0), std::invalid_argument);
TEST_CASE("Polytropic EOS Defines Consistent Surface And Exterior Behavior",
tags::barotrope_eos_unit) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
CHECK_THROWS_AS(
mean_field::physics::PolytropicBarotrope(std::numeric_limits<double>::infinity(), 1.0), std::invalid_argument
);
constexpr double polytropicConstant = 0.47;
constexpr double exteriorEnthalpy = -0.3;
CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(3.0, 0.0), std::invalid_argument);
for (const double polytropicIndex : polytropicIndices) {
const mean_field::eos::Polytrope barotrope(polytropicIndex,
polytropicConstant);
CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(3.0, -1.0), std::invalid_argument);
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
/*
* Exact surface values.
*/
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.75);
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0);
CHECK_THROWS_AS(barotrope.pressure_from_density(-0.1), std::domain_error);
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0);
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-0.1), std::domain_error);
CHECK(barotrope.pressure_from_density(0.0) == 0.0);
CHECK_THROWS_AS(barotrope.pressure_derivative_from_density(-0.1), std::domain_error);
CHECK(barotrope.enthalpy_from_density(0.0) == 0.0);
constexpr std::array<double, 3> nonfiniteValues{
std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::quiet_NaN()
};
CHECK(barotrope.pressure_derivative_from_density(0.0) == 0.0);
for (const double nonfiniteValue : nonfiniteValues) {
CAPTURE(nonfiniteValue);
/*
* Positive-part extension into h < 0.
*/
CHECK(barotrope.density_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK_THROWS_AS(barotrope.density_from_enthalpy(nonfiniteValue), std::domain_error);
CHECK(barotrope.pressure_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK_THROWS_AS(barotrope.pressure_from_enthalpy(nonfiniteValue), std::domain_error);
CHECK(barotrope.density_derivative_from_enthalpy(exteriorEnthalpy) ==
0.0);
CHECK_THROWS_AS(barotrope.density_derivative_from_enthalpy(nonfiniteValue), std::domain_error);
CHECK(barotrope.pressure_derivative_from_enthalpy(exteriorEnthalpy) ==
0.0);
CHECK_THROWS_AS(barotrope.pressure_derivative_from_enthalpy(nonfiniteValue), std::domain_error);
/*
* At h = 0, rho(h) has a nonzero right
* derivative only for n = 1.
*/
const double expectedSurfaceDensityDerivative =
polytropicIndex == 1.0 ? 1.0 / barotrope.enthalpy_scale() : 0.0;
CHECK(barotrope.density_derivative_from_enthalpy(0.0) ==
expectedSurfaceDensityDerivative);
}
}
}
TEST_CASE(
"Pressure Force And Pressure Integral Have Distinct Registered Forms",
tags::barotrope &tags::pressure &tags::pressure_gradient &tags::quadrature &tags::unit
) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
TEST_CASE("Polytropic EOS Rejects Invalid Physical Inputs",
tags::barotrope_eos_unit) {
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.999, 1.0),
std::invalid_argument);
/*
* For the registered H1 order p = 3 and n = 3:
*
* h has degree p,
* P(h) has degree 4p,
*
* so the nonlinear EOS contributes an additional
*
* 4p - p = 3p = 9
*
* beyond the registered enthalpy operand.
*/
constexpr int enthalpyOrder = mean_field::field::Enthalpy::Scalar::familyOrder;
CHECK_THROWS_AS(
mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0),
std::invalid_argument);
constexpr int pressureExtraOrder = 3 * enthalpyOrder;
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument);
constexpr int geometryWeightOrder = 2;
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, -1.0), std::invalid_argument);
constexpr mean_field::quadrature::Query pressureIntegralQuery =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic, geometryWeightOrder,
std::array<int, 1>{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const mean_field::eos::Polytrope barotrope(3.0, 0.75);
constexpr mean_field::quadrature::Query pressureForceQuery =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder,
std::array<int, 1>{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
CHECK_THROWS_AS(barotrope.pressure_from_density(-0.1), std::domain_error);
STATIC_CHECK(mean_field::field::Enthalpy::Form::PressureIntegral::dynamicOrderCount == 1);
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-0.1), std::domain_error);
STATIC_CHECK(mean_field::field::Enthalpy::Form::PressureForce::dynamicOrderCount == 1);
CHECK_THROWS_AS(barotrope.pressure_derivative_from_density(-0.1),
std::domain_error);
STATIC_CHECK(
mean_field::field::Enthalpy::Form::PressureIntegral::policyKey !=
mean_field::field::Enthalpy::Form::PressureForce::policyKey
);
constexpr std::array<double, 3> nonfiniteValues{
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::quiet_NaN()};
REQUIRE(pressureIntegralQuery.base_order.has_value());
for (const double nonfiniteValue : nonfiniteValues) {
CAPTURE(nonfiniteValue);
REQUIRE(pressureForceQuery.base_order.has_value());
CHECK_THROWS_AS(barotrope.density_from_enthalpy(nonfiniteValue),
std::domain_error);
/*
* Pressure integral:
*
* degree(P) + degree(J)
* = 12 + 2
* = 14.
*/
CHECK(*pressureIntegralQuery.base_order == 14);
CHECK_THROWS_AS(barotrope.pressure_from_enthalpy(nonfiniteValue),
std::domain_error);
/*
* Pressure force:
*
* degree(P)
* + degree(grad w)
* + degree(J)
*
* = 12 + 2 + 2
* = 16.
*/
CHECK(*pressureForceQuery.base_order == 16);
CHECK_THROWS_AS(barotrope.density_derivative_from_enthalpy(nonfiniteValue),
std::domain_error);
CHECK(pressureIntegralQuery.term == mean_field::quadrature::Term::pressure_integral);
CHECK(pressureForceQuery.term == mean_field::quadrature::Term::pressure_force);
CHECK(pressureIntegralQuery.role == mean_field::quadrature::QuadratureRole::diagnostic);
CHECK(pressureForceQuery.role == mean_field::quadrature::QuadratureRole::discretization);
CHECK(pressureIntegralQuery.domain == mean_field::utils::DOMAINS::STELLAR);
CHECK(pressureForceQuery.domain == mean_field::utils::DOMAINS::STELLAR);
/*
* Verify that the two terms route to independent policy
* controls.
*/
mean_field::quadrature::RuleSet ruleSet =
mean_field::quadrature::make_rule_set(mean_field::quadrature::Mode::production);
ruleSet.pressure_integral.boost = 3;
ruleSet.pressure_force.boost = 5;
const mean_field::quadrature::Policy policy(std::move(ruleSet));
const mean_field::quadrature::Resolution pressureIntegralResolution = policy.resolve(pressureIntegralQuery);
const mean_field::quadrature::Resolution pressureForceResolution = policy.resolve(pressureForceQuery);
CHECK(pressureIntegralResolution.base_order == 14);
CHECK(pressureIntegralResolution.boost == 3);
CHECK(pressureIntegralResolution.order == 17);
CHECK(pressureForceResolution.base_order == 16);
CHECK(pressureForceResolution.boost == 5);
CHECK(pressureForceResolution.order == 21);
CHECK_THROWS_AS(barotrope.pressure_derivative_from_enthalpy(nonfiniteValue),
std::domain_error);
}
}
TEST_CASE(
"Pressure Quadrature Exactly Integrates An N Three Polynomial",
tags::barotrope &tags::pressure &tags::pressure_gradient &tags::quadrature &tags::accuracy
) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
TEST_CASE("Pressure Force And Pressure Integral Have Distinct Registered Forms",
tags::barotrope_pressure_quadrature_unit) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
constexpr int enthalpyOrder = mean_field::field::Enthalpy::Scalar::familyOrder;
/*
* For the registered H1 order p = 3 and n = 3:
*
* h has degree p,
* P(h) has degree 4p,
*
* so the nonlinear EOS contributes an additional
*
* 4p - p = 3p = 9
*
* beyond the registered enthalpy operand.
*/
constexpr int enthalpyOrder =
mean_field::field::Enthalpy::Scalar::familyOrder;
constexpr int pressureExtraOrder = 3 * enthalpyOrder;
constexpr int pressureExtraOrder = 3 * enthalpyOrder;
/*
* K = 1/4 and n = 3 give
*
* (n + 1) K = 1,
* rho(h) = h^3,
* P(h) = h^4 / 4.
*/
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
constexpr int geometryWeightOrder = 2;
constexpr mean_field::quadrature::Query pressureIntegralQuery =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic, 0, std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::affine
);
constexpr mean_field::quadrature::Query pressureIntegralQuery =
EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic,
geometryWeightOrder, std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
constexpr mean_field::quadrature::Query pressureForceQuery =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization, 0, std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::affine
);
constexpr mean_field::quadrature::Query pressureForceQuery =
EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization,
geometryWeightOrder, std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
const mean_field::quadrature::RuleFactory ruleFactory{
mean_field::quadrature::Policy(mean_field::quadrature::make_rule_set(mean_field::quadrature::Mode::production))
};
STATIC_CHECK(
mean_field::field::Enthalpy::Form::PressureIntegral::dynamicOrderCount ==
1);
const mean_field::quadrature::MfemRule pressureIntegralRule =
ruleFactory.get(pressureIntegralQuery, mfem::Geometry::CUBE);
STATIC_CHECK(
mean_field::field::Enthalpy::Form::PressureForce::dynamicOrderCount == 1);
const mean_field::quadrature::MfemRule pressureForceRule =
ruleFactory.get(pressureForceQuery, mfem::Geometry::CUBE);
STATIC_CHECK(mean_field::field::Enthalpy::Form::PressureIntegral::policyKey !=
mean_field::field::Enthalpy::Form::PressureForce::policyKey);
/*
* On the reference cube [0,1]^3 choose
*
* h = x^3 y^3 z^3.
*
* This is representable by the order-three H1 space.
* Then
*
* P = x^12 y^12 z^12 / 4.
*/
const double numericalPressureIntegral = polytropic_barotrope_test_utils::integrate_cube(
*pressureIntegralRule.integration_rule, [&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct = integrationPoint.x * integrationPoint.y * integrationPoint.z;
REQUIRE(pressureIntegralQuery.base_order.has_value());
const double enthalpy = std::pow(coordinateProduct, 3.0);
REQUIRE(pressureForceQuery.base_order.has_value());
/*
* Pressure integral:
*
* degree(P) + degree(J)
* = 12 + 2
* = 14.
*/
CHECK(*pressureIntegralQuery.base_order == 14);
/*
* Pressure force:
*
* degree(P)
* + degree(grad w)
* + degree(J)
*
* = 12 + 2 + 2
* = 16.
*/
CHECK(*pressureForceQuery.base_order == 16);
CHECK(pressureIntegralQuery.term ==
mean_field::quadrature::Term::pressure_integral);
CHECK(pressureForceQuery.term ==
mean_field::quadrature::Term::pressure_force);
CHECK(pressureIntegralQuery.role ==
mean_field::quadrature::QuadratureRole::diagnostic);
CHECK(pressureForceQuery.role ==
mean_field::quadrature::QuadratureRole::discretization);
CHECK(pressureIntegralQuery.domain == mean_field::utils::DOMAINS::STELLAR);
CHECK(pressureForceQuery.domain == mean_field::utils::DOMAINS::STELLAR);
/*
* Verify that the two terms route to independent policy
* controls.
*/
mean_field::quadrature::RuleSet ruleSet =
mean_field::quadrature::make_rule_set(
mean_field::quadrature::Mode::production);
ruleSet.pressure_integral.boost = 3;
ruleSet.pressure_force.boost = 5;
const mean_field::quadrature::Policy policy(std::move(ruleSet));
const mean_field::quadrature::Resolution pressureIntegralResolution =
policy.resolve(pressureIntegralQuery);
const mean_field::quadrature::Resolution pressureForceResolution =
policy.resolve(pressureForceQuery);
CHECK(pressureIntegralResolution.base_order == 14);
CHECK(pressureIntegralResolution.boost == 3);
CHECK(pressureIntegralResolution.order == 17);
CHECK(pressureForceResolution.base_order == 16);
CHECK(pressureForceResolution.boost == 5);
CHECK(pressureForceResolution.order == 21);
}
TEST_CASE("Pressure Quadrature Exactly Integrates An N Three Polynomial",
tags::barotrope_pressure_quadrature_accuracy) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
constexpr int enthalpyOrder =
mean_field::field::Enthalpy::Scalar::familyOrder;
constexpr int pressureExtraOrder = 3 * enthalpyOrder;
/*
* K = 1/4 and n = 3 give
*
* (n + 1) K = 1,
* rho(h) = h^3,
* P(h) = h^4 / 4.
*/
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
constexpr mean_field::quadrature::Query pressureIntegralQuery =
EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic, 0,
std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::affine);
constexpr mean_field::quadrature::Query pressureForceQuery =
EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization, 0,
std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::affine);
const mean_field::quadrature::RuleFactory ruleFactory{
mean_field::quadrature::Policy(mean_field::quadrature::make_rule_set(
mean_field::quadrature::Mode::production))};
const mean_field::quadrature::MfemRule pressureIntegralRule =
ruleFactory.get(pressureIntegralQuery, mfem::Geometry::CUBE);
const mean_field::quadrature::MfemRule pressureForceRule =
ruleFactory.get(pressureForceQuery, mfem::Geometry::CUBE);
/*
* On the reference cube [0,1]^3 choose
*
* h = x^3 y^3 z^3.
*
* This is representable by the order-three H1 space.
* Then
*
* P = x^12 y^12 z^12 / 4.
*/
const double numericalPressureIntegral =
polytropic_eos_test_utils::integrate_cube(
*pressureIntegralRule.integration_rule,
[&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct =
integrationPoint.x * integrationPoint.y * integrationPoint.z;
const double enthalpy = std::pow(coordinateProduct, 3.0);
return barotrope.pressure_from_enthalpy(enthalpy);
}
);
});
const double analyticPressureIntegral = 0.25 / std::pow(13.0, 3.0);
const double analyticPressureIntegral = 0.25 / std::pow(13.0, 3.0);
/*
* Choose a representable vector test function whose
* divergence is
*
* div(w) = x^2 y^2 z^2.
*
* Therefore
*
* -P div(w)
* = -x^14 y^14 z^14 / 4.
*/
const double numericalPressureForceIntegral = polytropic_barotrope_test_utils::integrate_cube(
*pressureForceRule.integration_rule, [&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct = integrationPoint.x * integrationPoint.y * integrationPoint.z;
/*
* Choose a representable vector test function whose
* divergence is
*
* div(w) = x^2 y^2 z^2.
*
* Therefore
*
* -P div(w)
* = -x^14 y^14 z^14 / 4.
*/
const double numericalPressureForceIntegral =
polytropic_eos_test_utils::integrate_cube(
*pressureForceRule.integration_rule,
[&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct =
integrationPoint.x * integrationPoint.y * integrationPoint.z;
const double enthalpy = std::pow(coordinateProduct, 3.0);
const double enthalpy = std::pow(coordinateProduct, 3.0);
const double pressure = barotrope.pressure_from_enthalpy(enthalpy);
const double pressure = barotrope.pressure_from_enthalpy(enthalpy);
const double testDivergence = integrationPoint.x * integrationPoint.x * integrationPoint.y *
integrationPoint.y * integrationPoint.z * integrationPoint.z;
const double testDivergence =
integrationPoint.x * integrationPoint.x * integrationPoint.y *
integrationPoint.y * integrationPoint.z * integrationPoint.z;
return -pressure * testDivergence;
}
);
});
const double analyticPressureForceIntegral = -0.25 / std::pow(15.0, 3.0);
const double analyticPressureForceIntegral = -0.25 / std::pow(15.0, 3.0);
INFO("Pressure-integral quadrature order = " << pressureIntegralRule.resolution.order);
INFO("Pressure-integral quadrature order = "
<< pressureIntegralRule.resolution.order);
INFO("Pressure-force quadrature order = " << pressureForceRule.resolution.order);
INFO("Pressure-force quadrature order = "
<< pressureForceRule.resolution.order);
INFO("Numerical pressure integral = " << numericalPressureIntegral);
INFO("Numerical pressure integral = " << numericalPressureIntegral);
INFO("Analytic pressure integral = " << analyticPressureIntegral);
INFO("Analytic pressure integral = " << analyticPressureIntegral);
INFO("Numerical pressure-force integral = " << numericalPressureForceIntegral);
INFO(
"Numerical pressure-force integral = " << numericalPressureForceIntegral);
INFO("Analytic pressure-force integral = " << analyticPressureForceIntegral);
INFO("Analytic pressure-force integral = " << analyticPressureForceIntegral);
CHECK(pressureIntegralRule.resolution.base_order == 12);
CHECK(pressureIntegralRule.resolution.base_order == 12);
CHECK(pressureIntegralRule.resolution.order == 12);
CHECK(pressureIntegralRule.resolution.order == 12);
CHECK(pressureForceRule.resolution.base_order == 14);
CHECK(pressureForceRule.resolution.base_order == 14);
CHECK(pressureForceRule.resolution.order == 14);
CHECK(pressureForceRule.resolution.order == 14);
CHECK_THAT(numericalPressureIntegral, Catch::Matchers::WithinAbs(analyticPressureIntegral, 5.0e-14));
CHECK_THAT(numericalPressureIntegral,
Catch::Matchers::WithinAbs(analyticPressureIntegral, 5.0e-14));
CHECK_THAT(numericalPressureForceIntegral, Catch::Matchers::WithinAbs(analyticPressureForceIntegral, 5.0e-14));
}
CHECK_THAT(
numericalPressureForceIntegral,
Catch::Matchers::WithinAbs(analyticPressureForceIntegral, 5.0e-14));
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -643,17 +643,15 @@ TEST_CASE(
CHECK(fem.displacementFes->GetOrdering() == mfem::Ordering::byNODES);
CHECK(fem.enthalpyFes->GetVDim() == 1);
REQUIRE(fem.blockTrueOffsets.Size() == 3);
CHECK(fem.blockTrueOffsets[0] == 0);
CHECK(fem.blockTrueOffsets[1] == fem.displacementFes->GetTrueVSize());
CHECK(fem.blockTrueOffsets[2] == fem.displacementFes->GetTrueVSize() + fem.densityFes->GetTrueVSize());
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
REQUIRE(fem.gravityBlockTrueOffsets.Size() == 3);
CHECK(fem.gravityBlockTrueOffsets[0] == 0);
CHECK(fem.gravityBlockTrueOffsets[1] == fem.gravityFluxFes->GetTrueVSize());
CHECK(
fem.gravityBlockTrueOffsets[2] == fem.gravityFluxFes->GetTrueVSize() + fem.gravityPotentialFes->GetTrueVSize()
);
const mean_field::field::FieldDofMap densityMap =
mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*fem.densityFes);
const mean_field::field::FieldDofMap displacementMap =
mean_field::field::make_field_dof_map<mean_field::field::Displacement, DomainSchema>(*fem.displacementFes);
CHECK(fem.gravityContext.source_form->Height() == fem.gravityPotentialFes->GetTrueVSize());
}
CHECK(densityMap.full_size() == fem.densityFes->GetTrueVSize());
CHECK(densityMap.reduced_size() < densityMap.full_size());
CHECK(displacementMap.full_size() == fem.displacementFes->GetTrueVSize());
CHECK(displacementMap.reduced_size() == displacementMap.full_size());
}

View File

@@ -3,6 +3,7 @@ module;
#include <array>
#include <catch2/internal/catch_stringref.hpp>
#include <concepts>
#include <memory>
#include <string>
#include <mfem.hpp>
@@ -12,410 +13,456 @@ 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 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 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::PWConstCoefficient coefficient(attribute_values);
mfem::ParGridFunction density(f.densityFes.get());
density.ProjectCoefficient(coefficient);
inline mfem::Vector make_deterministic_vector(
const int size,
const double phase = 0.0
) {
mfem::Vector vector(size);
mfem::Vector density_true;
density.GetTrueDofs(density_true);
return density_true;
}
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);
}
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.");
return vector;
}
mfem::Vector combination(first);
combination *= first_scale;
combination.Add(second_scale, second);
return combination;
}
inline mfem::Vector make_displacement(
const mean_field::fem::FEM &f,
const double scale
) {
mfem::ParGridFunction displacement(f.displacementFes.get());
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);
}
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));
};
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.");
mfem::VectorFunctionCoefficient coefficient(f.mesh->Dimension(), displacement_function);
displacement.ProjectCoefficient(coefficient);
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::Vector displacement_true;
displacement.GetTrueDofs(displacement_true);
return displacement_true;
}
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.");
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;
mfem::Vector difference(computed);
difference -= reference;
const int vacuum_attribute = f.domainMapperStateless->GetVacuumElementAttribute();
return global_norm(difference, communicator) /
std::max(global_norm(reference, communicator),
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 = attribute != vacuum_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());
}
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;
template <typename FieldT>
inline mean_field::field::FieldDofMap make_map(const mfem::ParFiniteElementSpace &finiteElementSpace) {
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(finiteElementSpace);
}
inline mean_field::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));
}
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 constexpr int vacuum_material_attribute =
DomainSchema::template material_attribute<
mean_field::utils::domain::Vacuum>();
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));
}
template <typename FieldT>
inline mean_field::field::FieldDofMap
make_map(const mfem::ParFiniteElementSpace &finiteElementSpace) {
return mean_field::field::make_field_dof_map<FieldT, DomainSchema>(
finiteElementSpace);
}
inline 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);
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);
}
mfem::Vector enthalpyTrue(enthalpyMap.full_size());
mfem::Vector gravityPotentialTrue(gravityPotentialMap.full_size());
mfem::Vector displacementTrue(displacementMap.full_size());
mfem::Vector residualTrue;
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));
}
enthalpyMap.scatter(enthalpy, enthalpyTrue);
gravityPotentialMap.scatter(gravityPotential, gravityPotentialTrue);
displacementMap.scatter(displacement, displacementTrue);
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);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpyTrue, gravityPotentialTrue, displacementTrue,
bernoulliConstant, residualTrue
);
mfem::Vector enthalpyTrue(enthalpyMap.full_size());
mfem::Vector gravityPotentialTrue(gravityPotentialMap.full_size());
mfem::Vector displacementTrue(displacementMap.full_size());
mfem::Vector residualTrue;
residual.SetSize(enthalpyMap.reduced_size());
enthalpyMap.gather(residualTrue, residual);
}
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);
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 = 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 legacy_comparison = make_tag("legacy_comparison");
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 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_legacy = gravity & legacy_comparison;
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_analytic_initialization = gravity_analytic & initialization;
inline constexpr auto gravity_consistency_initialization = gravity_consistency & initialization;
// 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_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 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_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

File diff suppressed because it is too large Load Diff