feat(libmeanfield): centrifugal + pressure

This commit is contained in:
2026-08-04 14:24:55 -04:00
parent 9bc4f2758a
commit dc912fd15e
115 changed files with 260058 additions and 163261 deletions

View File

@@ -0,0 +1,628 @@
#include <memory>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace {
mfem::Vector make_zero_displacement(const mean_field::fem::FEM &f) {
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0;
return displacement;
}
mfem::Vector project_constant(
mfem::ParFiniteElementSpace &finiteElementSpace,
const double value
) {
mfem::ConstantCoefficient coefficient(value);
mfem::ParGridFunction field(&finiteElementSpace);
field.ProjectCoefficient(coefficient);
mfem::Vector trueVector;
field.GetTrueDofs(trueVector);
return trueVector;
}
namespace barotropic_closure_geometry_test_utils {
mfem::Vector project_scalar_field(
mfem::ParFiniteElementSpace &finiteElementSpace,
mfem::Coefficient &coefficient
) {
mfem::ParGridFunction field(&finiteElementSpace);
field.ProjectCoefficient(coefficient);
mfem::Vector trueVector;
field.GetTrueDofs(trueVector);
return trueVector;
}
mfem::Vector make_base_density(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient(
[](const mfem::Vector &position) {
return 0.55 + 0.025 * position(0) - 0.010 * position(1) +
0.006 * position(2);
}
);
return project_scalar_field(*f.densityFes, coefficient);
}
mfem::Vector make_base_enthalpy(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient(
[](const mfem::Vector &position) {
return 0.90 + 0.020 * position(0) - 0.010 * position(1) +
0.005 * position(2);
}
);
return project_scalar_field(*f.enthalpyFes, coefficient);
}
} // namespace barotropic_closure_geometry_test_utils
} // namespace
TEST_CASE(
"Barotropic Closure Vanishes For A Representable Constant State",
tags::hydro &tags::residuals &tags::unit &tags::closure &tags::kernels
&tags::barotrope
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
constexpr double enthalpyValue = 0.8;
const double densityValue = barotrope.density_from_enthalpy(enthalpyValue);
const mfem::Vector enthalpy =
project_constant(*f.enthalpyFes, enthalpyValue);
const mfem::Vector density = project_constant(*f.densityFes, densityValue);
const mfem::Vector displacement = make_zero_displacement(f);
mfem::Vector residual;
mfem::Vector scale;
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, barotrope, density, enthalpy, displacement,
residual
);
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, barotrope, density, displacement, scale
);
const MPI_Comm communicator = f.mesh->GetComm();
const double relativeResidual =
gravity_prepared_test_utils::global_norm(residual, communicator) /
gravity_prepared_test_utils::global_norm(scale, communicator);
INFO("Relative constant-state closure residual = " << relativeResidual);
CHECK(relativeResidual < 5.0e-12);
}
TEST_CASE(
"Barotropic Closure Density Action Matches The Stellar Mass Matrix",
tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels
&tags::barotrope
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
const mfem::Vector displacement = make_zero_displacement(f);
const mfem::Vector densityVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.densityFes->GetTrueVSize(), 0.37
);
mfem::Vector kernelAction;
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, barotrope, densityVariation, displacement,
kernelAction
);
mfem::Array<int> stellarMarker(f.mesh->attributes.Max());
stellarMarker = 0;
const int vacuumAttribute =
f.domainMapperStateless->GetVacuumElementAttribute();
for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size();
++attributeIndex) {
const int attribute = f.mesh->attributes[attributeIndex];
if (attribute != vacuumAttribute) {
stellarMarker[attribute - 1] = 1;
}
}
mfem::ParBilinearForm massForm(f.densityFes.get());
massForm.AddDomainIntegrator(new mfem::MassIntegrator(), stellarMarker);
massForm.Assemble();
massForm.Finalize();
std::unique_ptr<mfem::HypreParMatrix> massMatrix(
massForm.ParallelAssemble()
);
REQUIRE(massMatrix != nullptr);
REQUIRE(massMatrix->Width() == densityVariation.Size());
mfem::Vector referenceAction(massMatrix->Height());
referenceAction = 0.0;
massMatrix->Mult(densityVariation, referenceAction);
const double relativeError = gravity_prepared_test_utils::relative_error(
kernelAction, referenceAction, f.mesh->GetComm()
);
INFO("Density-action mass-matrix error = " << relativeError);
CHECK(relativeError < 5.0e-12);
}
TEST_CASE(
"Barotropic Closure Jacobian Matches A Combined Centered Difference",
tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels
&tags::barotrope
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
mfem::FunctionCoefficient densityCoefficient(
[](const mfem::Vector &position) {
return 0.4 + 0.03 * position(0) - 0.01 * position(1);
}
);
mfem::FunctionCoefficient enthalpyCoefficient(
[](const mfem::Vector &position) {
return 0.9 + 0.02 * position(0) - 0.01 * position(1);
}
);
mfem::FunctionCoefficient enthalpyVariationCoefficient(
[](const mfem::Vector &position) {
return 0.07 + 0.015 * position(0) + 0.008 * position(2);
}
);
mfem::ParGridFunction densityField(f.densityFes.get());
mfem::ParGridFunction enthalpyField(f.enthalpyFes.get());
mfem::ParGridFunction enthalpyVariationField(f.enthalpyFes.get());
densityField.ProjectCoefficient(densityCoefficient);
enthalpyField.ProjectCoefficient(enthalpyCoefficient);
enthalpyVariationField.ProjectCoefficient(enthalpyVariationCoefficient);
mfem::Vector density;
mfem::Vector enthalpy;
mfem::Vector enthalpyVariation;
densityField.GetTrueDofs(density);
enthalpyField.GetTrueDofs(enthalpy);
enthalpyVariationField.GetTrueDofs(enthalpyVariation);
const mfem::Vector densityVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.densityFes->GetTrueVSize(), 0.63
);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 1.0);
constexpr double differenceStep = 1.0e-6;
const mfem::Vector plusDensity =
gravity_prepared_test_utils::linear_combination(
density, 1.0, densityVariation, differenceStep
);
const mfem::Vector minusDensity =
gravity_prepared_test_utils::linear_combination(
density, 1.0, densityVariation, -differenceStep
);
const mfem::Vector plusEnthalpy =
gravity_prepared_test_utils::linear_combination(
enthalpy, 1.0, enthalpyVariation, differenceStep
);
const mfem::Vector minusEnthalpy =
gravity_prepared_test_utils::linear_combination(
enthalpy, 1.0, enthalpyVariation, -differenceStep
);
mfem::Vector plusResidual;
mfem::Vector minusResidual;
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, barotrope, plusDensity, plusEnthalpy,
displacement, plusResidual
);
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, barotrope, minusDensity, minusEnthalpy,
displacement, minusResidual
);
mfem::Vector finiteDifference(plusResidual);
finiteDifference -= minusResidual;
finiteDifference *= 1.0 / (2.0 * differenceStep);
mfem::Vector densityAction;
mfem::Vector enthalpyAction;
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, barotrope, densityVariation, displacement,
densityAction
);
mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action(
f, *f.domainMapperStateless, barotrope, enthalpy, enthalpyVariation,
displacement, enthalpyAction
);
mfem::Vector analyticAction(densityAction);
analyticAction += enthalpyAction;
const double relativeError = gravity_prepared_test_utils::relative_error(
analyticAction, finiteDifference, f.mesh->GetComm()
);
INFO("Combined EOS Jacobian error = " << relativeError);
CHECK(relativeError < 2.0e-8);
}
TEST_CASE(
"Barotropic Closure Density Action Excludes Vacuum And Uses Mapped Volume",
tags::hydro &tags::mapping &tags::unit &tags::closure &tags::barotrope
&tags::kernels
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
const mfem::Vector stellarDensity =
gravity_prepared_test_utils::make_domain_supported_density(f, true);
const mfem::Vector vacuumDensity =
gravity_prepared_test_utils::make_domain_supported_density(f, false);
const mfem::Vector identityDisplacement =
gravity_prepared_test_utils::make_displacement(f, 0.0);
const mfem::Vector deformedDisplacement =
gravity_prepared_test_utils::make_displacement(f, 1.0);
mfem::Vector stellarAction;
mfem::Vector vacuumAction;
mfem::Vector deformedAction;
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, barotrope, stellarDensity,
identityDisplacement, stellarAction
);
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, barotrope, vacuumDensity,
identityDisplacement, vacuumAction
);
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, barotrope, stellarDensity,
deformedDisplacement, deformedAction
);
const MPI_Comm communicator = f.mesh->GetComm();
const double stellarNorm =
gravity_prepared_test_utils::global_norm(stellarAction, communicator);
const double vacuumNorm =
gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
const double geometryChange = gravity_prepared_test_utils::relative_error(
deformedAction, stellarAction, communicator
);
INFO("Stellar action norm = " << stellarNorm);
INFO("Vacuum action norm = " << vacuumNorm);
INFO("Relative mapped-volume change = " << geometryChange);
CHECK(stellarNorm > 0.0);
CHECK(vacuumNorm <= 1.0e-13 * stellarNorm);
CHECK(geometryChange > 1.0e-5);
}
TEST_CASE(
"Barotropic Closure Displacement Action Matches Centered Differences",
tags::barotrope &tags::closure &tags::hydro &tags::integration
&tags::jacobian &tags::mapping &tags::physics
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.domainMapperStateless != nullptr);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
const mfem::Vector baseDensity =
barotropic_closure_geometry_test_utils::make_base_density(f);
const mfem::Vector baseEnthalpy =
barotropic_closure_geometry_test_utils::make_base_enthalpy(f);
const mfem::Vector displacementVariation =
gravity_prepared_test_utils::make_displacement(f, 0.65);
constexpr double differenceStep = 1.0e-5;
const MPI_Comm communicator = f.mesh->GetComm();
for (const double deformationScale : {0.0, 1.0}) {
DYNAMIC_SECTION("Base deformation scale = " << deformationScale) {
const mfem::Vector baseDisplacement =
gravity_prepared_test_utils::make_displacement(
f, deformationScale
);
mfem::Vector plusDisplacement(baseDisplacement);
mfem::Vector minusDisplacement(baseDisplacement);
plusDisplacement.Add(differenceStep, displacementVariation);
minusDisplacement.Add(-differenceStep, displacementVariation);
mfem::Vector plusResidual;
mfem::Vector minusResidual;
mfem::Vector analyticAction;
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, barotrope, baseDensity,
baseEnthalpy, plusDisplacement, plusResidual
);
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, barotrope, baseDensity,
baseEnthalpy, minusDisplacement, minusResidual
);
mean_field::operators::kernels::
apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, barotrope, baseDensity,
baseEnthalpy, baseDisplacement, displacementVariation,
analyticAction
);
mfem::Vector finiteDifference(plusResidual);
finiteDifference -= minusResidual;
finiteDifference *= 1.0 / (2.0 * differenceStep);
const double analyticNorm =
gravity_prepared_test_utils::global_norm(
analyticAction, communicator
);
const double finiteDifferenceNorm =
gravity_prepared_test_utils::global_norm(
finiteDifference, communicator
);
const double relativeError =
gravity_prepared_test_utils::relative_error(
analyticAction, finiteDifference, communicator
);
INFO("Base deformation scale = " << deformationScale);
INFO("Analytic geometry-action norm = " << analyticNorm);
INFO(
"Finite-difference geometry-action norm = "
<< finiteDifferenceNorm
);
INFO("Geometry-action relative error = " << relativeError);
REQUIRE(analyticNorm > 1.0e-12);
REQUIRE(finiteDifferenceNorm > 1.0e-12);
CHECK(relativeError < 5.0e-8);
}
}
}
TEST_CASE(
"Barotropic Closure Displacement Action Is Linear In Its Direction",
tags::barotrope &tags::closure &tags::hydro &tags::jacobian &tags::mapping
&tags::physics &tags::unit
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.domainMapperStateless != nullptr);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
const mfem::Vector baseDensity =
barotropic_closure_geometry_test_utils::make_base_density(f);
const mfem::Vector baseEnthalpy =
barotropic_closure_geometry_test_utils::make_base_enthalpy(f);
const mfem::Vector baseDisplacement =
gravity_prepared_test_utils::make_displacement(f, 0.8);
const mfem::Vector firstDirection =
gravity_prepared_test_utils::make_displacement(f, 0.4);
mfem::Vector secondDirection =
gravity_prepared_test_utils::make_deterministic_vector(
f.displacementFes->GetTrueVSize(), 0.91
);
secondDirection *= 0.01;
constexpr double firstScale = 1.7;
constexpr double secondScale = -0.43;
mfem::Vector combinedDirection(firstDirection);
combinedDirection *= firstScale;
combinedDirection.Add(secondScale, secondDirection);
mfem::Vector zeroDirection(f.displacementFes->GetTrueVSize());
zeroDirection = 0.0;
mfem::Vector firstAction;
mfem::Vector secondAction;
mfem::Vector combinedAction;
mfem::Vector zeroAction;
mean_field::operators::kernels::
apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
baseDisplacement, firstDirection, firstAction
);
mean_field::operators::kernels::
apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
baseDisplacement, secondDirection, secondAction
);
mean_field::operators::kernels::
apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
baseDisplacement, combinedDirection, combinedAction
);
mean_field::operators::kernels::
apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
baseDisplacement, zeroDirection, zeroAction
);
mfem::Vector expectedAction(firstAction);
expectedAction *= firstScale;
expectedAction.Add(secondScale, secondAction);
const MPI_Comm communicator = f.mesh->GetComm();
const double expectedNorm =
gravity_prepared_test_utils::global_norm(expectedAction, communicator);
const double linearityError = gravity_prepared_test_utils::relative_error(
combinedAction, expectedAction, communicator
);
const double zeroActionNorm =
gravity_prepared_test_utils::global_norm(zeroAction, communicator);
INFO("Expected combined-action norm = " << expectedNorm);
INFO("Directional-linearity error = " << linearityError);
INFO("Zero-direction action norm = " << zeroActionNorm);
REQUIRE(expectedNorm > 1.0e-12);
CHECK(linearityError < 5.0e-12);
CHECK(zeroActionNorm <= 5.0e-14 * expectedNorm);
}
TEST_CASE(
"Barotropic Closure Displacement Action Excludes Vacuum",
tags::barotrope &tags::closure &tags::hydro &tags::mapping &tags::physics
&tags::unit
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.domainMapperStateless != nullptr);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
const mfem::Vector stellarDensity =
gravity_prepared_test_utils::make_domain_supported_density(f, true);
const mfem::Vector vacuumDensity =
gravity_prepared_test_utils::make_domain_supported_density(f, false);
mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize());
zeroEnthalpy = 0.0;
const mfem::Vector baseDisplacement =
gravity_prepared_test_utils::make_displacement(f, 0.7);
const mfem::Vector displacementVariation =
gravity_prepared_test_utils::make_displacement(f, 0.5);
mfem::Vector stellarAction;
mfem::Vector vacuumAction;
mean_field::operators::kernels::
apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, barotrope, stellarDensity,
zeroEnthalpy, baseDisplacement, displacementVariation, stellarAction
);
mean_field::operators::kernels::
apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, barotrope, vacuumDensity, zeroEnthalpy,
baseDisplacement, displacementVariation, vacuumAction
);
const MPI_Comm communicator = f.mesh->GetComm();
const double stellarNorm =
gravity_prepared_test_utils::global_norm(stellarAction, communicator);
const double vacuumNorm =
gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
INFO("Stellar geometry-action norm = " << stellarNorm);
INFO("Vacuum geometry-action norm = " << vacuumNorm);
REQUIRE(stellarNorm > 1.0e-12);
CHECK(vacuumNorm <= 1.0e-13 * stellarNorm);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,424 @@
#include <cmath>
#include <limits>
#include <array>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace pressure_force_kernel_test_utils {
[[nodiscard]] mfem::Vector make_deterministic_vector(
const int size,
const double phase
) {
mfem::Vector vector(size);
for (int index = 0; index < size; ++index) {
const double position = static_cast<double>(index + 1);
vector(index) = 0.71 + 0.19 * std::sin(0.31 * position + phase) +
0.08 * std::cos(0.17 * position - 0.5 * phase);
}
return vector;
}
[[nodiscard]] mfem::Vector
make_zero_displacement(const mean_field::fem::FEM &f) {
mfem::Vector displacementTrue(f.displacementFes->GetTrueVSize());
displacementTrue = 0.0;
return displacementTrue;
}
[[nodiscard]] mfem::Vector
make_vacuum_only_enthalpy(const mean_field::fem::FEM &f) {
mfem::Vector enthalpyTrue =
make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.43);
mfem::Array<int> stellarElementMask;
mean_field::utils::populate_element_mask(
f.mesh.get(), mean_field::utils::DOMAINS::STELLAR,
stellarElementMask
);
mfem::Array<int> stellarEnthalpyTrueDofs;
mean_field::utils::populate_domain_tdofs(
f.enthalpyFes.get(), stellarElementMask, stellarEnthalpyTrueDofs
);
for (int listIndex = 0; listIndex < stellarEnthalpyTrueDofs.Size();
++listIndex) {
const int trueDof = stellarEnthalpyTrueDofs[listIndex];
MFEM_VERIFY(
trueDof >= 0 && trueDof < enthalpyTrue.Size(),
"The stellar enthalpy true-DOF mask contains an "
"invalid index."
);
enthalpyTrue(trueDof) = 0.0;
}
return enthalpyTrue;
}
[[nodiscard]] mfem::Vector
make_positive_asymmetric_enthalpy(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
return 1.10 + 0.07 * position(0) - 0.04 * position(1) +
0.03 * position(2);
});
mfem::ParGridFunction enthalpyField(f.enthalpyFes.get());
enthalpyField.ProjectCoefficient(coefficient);
mfem::Vector enthalpyTrue;
enthalpyField.GetTrueDofs(enthalpyTrue);
return enthalpyTrue;
}
[[nodiscard]] mfem::Vector make_component_test_field(
const mean_field::fem::FEM &f,
const int component,
const int coordinate
) {
const int dimension = f.mesh->Dimension();
MFEM_VERIFY(
component >= 0 && component < dimension,
"The requested vector component is invalid."
);
MFEM_VERIFY(
coordinate >= -1 && coordinate < dimension,
"The requested coordinate is invalid."
);
/*
* coordinate == -1 gives the rigid translation e_component.
*
* Otherwise this gives
*
* w = x_coordinate e_component.
*/
mfem::VectorFunctionCoefficient coefficient(
dimension,
[component, coordinate,
dimension](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(dimension);
value = 0.0;
value(component) = coordinate < 0 ? 1.0 : position(coordinate);
}
);
mfem::ParGridFunction field(f.displacementFes.get());
field.ProjectCoefficient(coefficient);
mfem::Vector fieldTrue;
field.GetTrueDofs(fieldTrue);
return fieldTrue;
}
[[nodiscard]] double global_dot(
const mfem::Vector &left,
const mfem::Vector &right,
MPI_Comm communicator
) {
MFEM_VERIFY(
left.Size() == right.Size(),
"The global dot-product vectors have different sizes."
);
const double localDot = left * right;
double globalDot = 0.0;
MPI_Allreduce(
&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator
);
return globalDot;
}
} // namespace pressure_force_kernel_test_utils
TEST_CASE(
"Pressure Force Residual Vanishes For Zero Enthalpy",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize());
enthalpyTrue = 0.0;
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(f);
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
residualTrue
);
REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize());
const double residualNorm = gravity_prepared_test_utils::global_norm(
residualTrue, f.mesh->GetComm()
);
CHECK(residualNorm == 0.0);
}
TEST_CASE(
"Pressure Force Residual Excludes Vacuum Enthalpy Exactly",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
const mfem::Vector enthalpyTrue =
pressure_force_kernel_test_utils::make_vacuum_only_enthalpy(f);
const double enthalpyNorm = gravity_prepared_test_utils::global_norm(
enthalpyTrue, f.mesh->GetComm()
);
/*
* Ensure this is a real exclusion test rather than another
* all-zero-input test.
*/
REQUIRE(enthalpyNorm > 0.0);
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(f);
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
residualTrue
);
REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize());
const double residualNorm = gravity_prepared_test_utils::global_norm(
residualTrue, f.mesh->GetComm()
);
CHECK(residualNorm == 0.0);
}
TEST_CASE(
"Pressure Force Residual Is Nonzero For Positive Stellar Pressure",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
/*
* With n = 3 and K = 1/4:
*
* P(1) = 1/4.
*/
mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize());
enthalpyTrue = 1.0;
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(f);
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
residualTrue
);
const double residualNorm = gravity_prepared_test_utils::global_norm(
residualTrue, f.mesh->GetComm()
);
INFO("Positive-pressure residual norm = " << residualNorm);
CHECK(std::isfinite(residualNorm));
CHECK(residualNorm > 100.0 * std::numeric_limits<double>::epsilon());
}
TEST_CASE(
"Pressure Force Residual Does No Work Against Rigid Translations",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
&tags::accuracy
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
REQUIRE(f.displacementFes->GetOrdering() == mfem::Ordering::byNODES);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
const mfem::Vector enthalpyTrue =
pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(f);
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
residualTrue
);
const double residualNorm = gravity_prepared_test_utils::global_norm(
residualTrue, f.mesh->GetComm()
);
REQUIRE(residualNorm > 0.0);
const int dimension = f.mesh->Dimension();
for (int component = 0; component < dimension; ++component) {
const mfem::Vector translationTrue =
pressure_force_kernel_test_utils::make_component_test_field(
f, component, -1
);
const double translationNorm = gravity_prepared_test_utils::global_norm(
translationTrue, f.mesh->GetComm()
);
const double translationWork =
pressure_force_kernel_test_utils::global_dot(
translationTrue, residualTrue, f.mesh->GetComm()
);
const double dotProductScale =
std::fmax(residualNorm * translationNorm, 1.0);
CAPTURE(component, translationWork, dotProductScale);
CHECK(std::abs(translationWork) <= 5.0e-12 * dotProductScale);
}
}
TEST_CASE(
"Pressure Force Residual Respects byNODES Component Layout",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
&tags::accuracy
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
REQUIRE(f.displacementFes->GetOrdering() == mfem::Ordering::byNODES);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
const mfem::Vector enthalpyTrue =
pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(f);
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
residualTrue
);
const int dimension = f.mesh->Dimension();
REQUIRE(dimension == 3);
mfem::DenseMatrix virtualWork(dimension, dimension);
for (int component = 0; component < dimension; ++component) {
for (int coordinate = 0; coordinate < dimension; ++coordinate) {
const mfem::Vector affineTestTrue =
pressure_force_kernel_test_utils::make_component_test_field(
f, component, coordinate
);
virtualWork(component, coordinate) =
pressure_force_kernel_test_utils::global_dot(
affineTestTrue, residualTrue, f.mesh->GetComm()
);
}
}
double meanDiagonalWork = 0.0;
for (int component = 0; component < dimension; ++component) {
meanDiagonalWork += virtualWork(component, component);
}
meanDiagonalWork /= static_cast<double>(dimension);
// INFO(
// "Affine pressure virtual-work tensor:\n"
// << virtualWork
// );
INFO("Mean diagonal virtual work = " << meanDiagonalWork);
REQUIRE(
std::abs(meanDiagonalWork) >
100.0 * std::numeric_limits<double>::epsilon()
);
const double comparisonTolerance = 1.0e-8 * std::abs(meanDiagonalWork);
for (int component = 0; component < dimension; ++component) {
for (int coordinate = 0; coordinate < dimension; ++coordinate) {
const double computedWork = virtualWork(component, coordinate);
CAPTURE(
component, coordinate, computedWork, meanDiagonalWork,
comparisonTolerance
);
if (component == coordinate) {
CHECK(
std::abs(computedWork - meanDiagonalWork) <=
comparisonTolerance
);
} else {
CHECK(std::abs(computedWork) <= comparisonTolerance);
}
}
}
}