feat(FieldDofMap): Completed FieldDofMap migration
also removed legacy BarotropicPolytrope implementation
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user