feat(field-support): added field support system, mid migration

currently the barotope and the pressure force operator are migrated to the new support system
This commit is contained in:
2026-08-23 10:13:53 -04:00
parent dc912fd15e
commit 0f3ca8050b
137 changed files with 29975 additions and 16389 deletions

View File

@@ -1,297 +1,416 @@
#include <cstdint>
#include <catch2/catch_test_macros.hpp>
#include <cmath>
#include <cstdint>
#include <mfem.hpp>
#include <type_traits>
import mean_field;
import test_helpers;
namespace barotropic_closure_context_test_utils {
mfem::Vector project_field(
mfem::ParFiniteElementSpace &finiteElementSpace,
mfem::Coefficient &coefficient
namespace field = mean_field::field;
namespace domain = mean_field::utils::domain;
namespace context = mean_field::operators::context::barotropic;
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
struct Maps final {
field::FieldDofMap density;
field::FieldDofMap enthalpy;
field::FieldDofMap displacement;
explicit Maps(const mean_field::fem::FEM &f)
: density(
field::make_field_dof_map<
field::Density,
Schema>(*f.densityFes)
),
enthalpy(
field::make_field_dof_map<
field::Enthalpy,
Schema>(*f.enthalpyFes)
),
displacement(
field::make_field_dof_map<
field::Displacement,
Schema>(*f.displacementFes)
) {
}
};
[[nodiscard]] context::BarotropicClosureDependencies make_dependencies() {
return {
.discretization = {.identity = 101, .revision = 2},
.density = {.identity = 103, .revision = 3},
.enthalpy = {.identity = 107, .revision = 5},
.displacement = {.identity = 109, .revision = 7}
};
}
[[nodiscard]] context::BarotropicClosureStateView make_state(
const mfem::Vector &density,
const mfem::Vector &enthalpy,
const mfem::Vector &displacement
) {
mfem::ParGridFunction field(&finiteElementSpace);
field.ProjectCoefficient(coefficient);
mfem::Vector trueVector;
field.GetTrueDofs(trueVector);
return trueVector;
return {.density = density, .enthalpy = enthalpy, .displacement = displacement};
}
mfem::Vector make_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);
});
return project_field(*f.densityFes, coefficient);
[[nodiscard]] mfem::Vector reduce(
const field::FieldDofMap &map,
const mfem::Vector &full
) {
return map.gather(full);
}
mfem::Vector make_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);
[[nodiscard]] mfem::Vector make_density(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction value(f.densityFes.get());
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
return 0.71 + 0.05 * std::sin(0.63 * position(0) + phase) + 0.02 * position(1);
});
value.ProjectCoefficient(coefficient);
mfem::Vector result;
value.GetTrueDofs(result);
return result;
}
return project_field(*f.enthalpyFes, coefficient);
[[nodiscard]] mfem::Vector make_enthalpy(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction value(f.enthalpyFes.get());
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
return 0.93 + 0.04 * std::cos(0.57 * position(1) - phase) + 0.015 * position(2);
});
value.ProjectCoefficient(coefficient);
mfem::Vector result;
value.GetTrueDofs(result);
return result;
}
[[nodiscard]] double relative_error(
const mfem::Vector &left,
const mfem::Vector &right,
const MPI_Comm communicator
) {
return gravity_prepared_test_utils::relative_error(left, right, communicator);
}
} // namespace barotropic_closure_context_test_utils
TEST_CASE(
"Barotropic Closure Context Tracks Independent Revisions",
tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::unit
"Prepared Barotropic Closure Owns Its Linearization Context",
tags::barotrope &tags::closure &tags::contexts &tags::prepared &tags::field &tags::unit
) {
auto args = test_utils::setup_args();
using Operator = mean_field::operators::PreparedBarotropicClosureOperator;
using Context = mean_field::operators::context::barotropic::BarotropicClosureLinearizationContext;
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
STATIC_REQUIRE_FALSE(std::is_copy_constructible_v<Context>);
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<Context>);
STATIC_REQUIRE_FALSE(std::is_move_constructible_v<Context>);
STATIC_REQUIRE_FALSE(std::is_move_assignable_v<Context>);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
mean_field::operators::context::barotropic::
BarotropicClosureLinearizationContext context(
f, *f.domainMapperStateless, barotrope
);
const mean_field::eos::Polytrope equationOfState(3.0, 1.5);
Operator preparedOperator(f, *f.domainMapperStateless, equationOfState);
mfem::Vector density =
barotropic_closure_context_test_utils::make_density(f);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_FALSE(preparedOperator.GetContext().IsPrepared());
CHECK(&preparedOperator.GetContext() == &preparedOperator.GetContext());
mfem::Vector enthalpy =
barotropic_closure_context_test_utils::make_enthalpy(f);
mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.0);
mean_field::operators::context::barotropic::BarotropicClosureRevisions
revisions{.density = 3, .enthalpy = 5, .displacement = 7};
CHECK_FALSE(context.IsPrepared());
CHECK(context.GetPreparationCount() == 0);
context.Prepare(density, enthalpy, displacement, revisions);
REQUIRE(context.IsPrepared());
CHECK(context.MatchesRevisions(revisions));
CHECK(context.GetRevisions() == revisions);
CHECK(context.GetPreparationCount() == 1);
CHECK(context.GetOperator().GetPreparationCount() == 1);
context.Prepare(density, enthalpy, displacement, revisions);
CHECK(context.GetPreparationCount() == 1);
const double frozenDensityValue = context.GetBaseDensityTrue()(0);
density(0) += 0.125;
CHECK(context.GetBaseDensityTrue()(0) == frozenDensityValue);
context.Prepare(density, enthalpy, displacement, revisions);
CHECK(context.GetPreparationCount() == 1);
CHECK(context.GetBaseDensityTrue()(0) == frozenDensityValue);
++revisions.density;
context.Prepare(density, enthalpy, displacement, revisions);
CHECK(context.GetPreparationCount() == 2);
CHECK(context.GetBaseDensityTrue()(0) == density(0));
enthalpy(0) += 0.050;
++revisions.enthalpy;
context.Prepare(density, enthalpy, displacement, revisions);
CHECK(context.GetPreparationCount() == 3);
CHECK(context.GetBaseEnthalpyTrue()(0) == enthalpy(0));
displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
++revisions.displacement;
context.Prepare(density, enthalpy, displacement, revisions);
CHECK(context.GetPreparationCount() == 4);
CHECK(context.GetRevisions() == revisions);
CHECK(context.MatchesRevisions(revisions));
CHECK(context.GetOperator().GetPreparationCount() == 4);
const auto statistics = preparedOperator.GetContextPreparationStatistics();
CHECK(statistics.staticPreparations == 0);
CHECK(statistics.geometryPreparations == 0);
CHECK(statistics.baseStatePreparations == 0);
}
TEST_CASE(
"Barotropic Closure Context Reprepares A Consistent Frozen State",
tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::unit
"Barotropic Closure Context Applies Selective Invalidation In Reduced Field Coordinates",
tags::barotrope &tags::closure &tags::contexts &tags::prepared &tags::field &tags::unit
) {
auto args = test_utils::setup_args();
using namespace barotropic_closure_context_test_utils;
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
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);
REQUIRE(f.okay());
mean_field::operators::context::barotropic::
BarotropicClosureLinearizationContext context(
f, *f.domainMapperStateless, barotrope
);
const Maps maps(f);
const mfem::Vector density =
barotropic_closure_context_test_utils::make_density(f);
const mean_field::eos::Polytrope equationOfState(3.0, 1.5);
const mfem::Vector enthalpy =
barotropic_closure_context_test_utils::make_enthalpy(f);
const mfem::Vector identityDisplacement =
gravity_prepared_test_utils::make_displacement(f, 0.0);
const mfem::Vector densityVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.densityFes->GetTrueVSize(), 0.37
);
const mfem::Vector enthalpyVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), 0.71
);
const mfem::Vector displacementVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.displacementFes->GetTrueVSize(), 0.37
);
mean_field::operators::context::barotropic::BarotropicClosureRevisions
revisions{.density = 11, .enthalpy = 13, .displacement = 17};
context.Prepare(density, enthalpy, identityDisplacement, revisions);
mfem::Vector initialResidual;
mfem::Vector initialAction;
context.BuildResidual(initialResidual);
context.GetOperator().Mult(
densityVariation, enthalpyVariation, displacementVariation,
initialAction
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState
);
mfem::Vector changedDensity(density);
changedDensity.Add(0.025, densityVariation);
mfem::Vector density = reduce(maps.density, make_density(f, 0.17));
mfem::Vector changedEnthalpy(enthalpy);
changedEnthalpy.Add(0.015, enthalpyVariation);
mfem::Vector enthalpy = reduce(maps.enthalpy, make_enthalpy(f, 0.29));
const mfem::Vector deformedDisplacement =
gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector initialDisplacement =
reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.35));
context.Prepare(
changedDensity, changedEnthalpy, deformedDisplacement, revisions
);
/*
* A second smooth, orientation-preserving geometry.
*
* Do not manufacture a new geometry by perturbing an arbitrary H1
* coefficient. A modest change in one high-order displacement DOF can
* correspond to a very large local displacement gradient and can invert
* an element.
*/
const mfem::Vector changedDisplacement =
reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.85));
mfem::Vector unchangedResidual;
mfem::Vector unchangedAction;
mfem::Vector displacement(initialDisplacement);
context.BuildResidual(unchangedResidual);
auto dependencies = make_dependencies();
context.GetOperator().Mult(
densityVariation, enthalpyVariation, displacementVariation,
unchangedAction
);
const auto initialReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
const auto &context = preparedOperator.GetContext();
REQUIRE(preparedOperator.IsPrepared());
REQUIRE(context.IsPrepared());
CHECK(context.MatchesDependencies(dependencies));
CHECK(context.GetDependencies() == dependencies);
CHECK(initialReport.contextReport.preparedStaticDependencies);
CHECK(initialReport.contextReport.preparedGeometryState);
CHECK(initialReport.contextReport.preparedBaseState);
CHECK(initialReport.contextReport.updatedDensity);
CHECK(initialReport.contextReport.updatedEnthalpy);
CHECK(initialReport.contextReport.updatedDisplacement);
CHECK(initialReport.preparedElementData);
CHECK(initialReport.DidAnyWork());
CHECK(preparedOperator.GetPreparationCount() == 1);
const mfem::Vector frozenDensity = context.GetBaseDensity();
const mfem::Vector frozenEnthalpy = context.GetBaseEnthalpy();
const mfem::Vector frozenDisplacement = context.GetDisplacement();
/*
* Modify all three candidate states without updating their dependency
* stamps.
*
* The context must continue exposing the previously frozen state.
*/
density(0) += 0.25;
enthalpy(0) -= 0.18;
displacement = changedDisplacement;
const auto repeatedReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
CHECK_FALSE(repeatedReport.DidAnyWork());
CHECK_FALSE(repeatedReport.contextReport.updatedDensity);
CHECK_FALSE(repeatedReport.contextReport.updatedEnthalpy);
CHECK_FALSE(repeatedReport.contextReport.updatedDisplacement);
CHECK_FALSE(repeatedReport.preparedElementData);
CHECK(preparedOperator.GetPreparationCount() == 1);
const MPI_Comm communicator = f.mesh->GetComm();
CHECK(context.GetPreparationCount() == 1);
CHECK(relative_error(context.GetBaseDensity(), frozenDensity, communicator) == 0.0);
CHECK(
gravity_prepared_test_utils::relative_error(
unchangedResidual, initialResidual, communicator
) < 5.0e-15
CHECK(relative_error(context.GetBaseEnthalpy(), frozenEnthalpy, communicator) == 0.0);
CHECK(relative_error(context.GetDisplacement(), frozenDisplacement, communicator) == 0.0);
/*
* Density invalidation.
*
* Geometry remains frozen because the displacement dependency did not
* change.
*/
++dependencies.density.revision;
const auto densityReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
CHECK_FALSE(densityReport.contextReport.preparedStaticDependencies);
CHECK_FALSE(densityReport.contextReport.preparedGeometryState);
CHECK(densityReport.contextReport.preparedBaseState);
CHECK(densityReport.contextReport.updatedDensity);
CHECK_FALSE(densityReport.contextReport.updatedEnthalpy);
CHECK_FALSE(densityReport.contextReport.updatedDisplacement);
CHECK(densityReport.preparedElementData);
CHECK(context.GetBaseDensity()(0) == density(0));
/*
* The candidate displacement has changed, but because its revision has
* not changed the context must still retain the original geometry.
*/
CHECK(relative_error(context.GetDisplacement(), frozenDisplacement, communicator) == 0.0);
/*
* Enthalpy invalidation.
*/
++dependencies.enthalpy.revision;
const auto enthalpyReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
CHECK_FALSE(enthalpyReport.contextReport.preparedStaticDependencies);
CHECK_FALSE(enthalpyReport.contextReport.preparedGeometryState);
CHECK(enthalpyReport.contextReport.preparedBaseState);
CHECK_FALSE(enthalpyReport.contextReport.updatedDensity);
CHECK(enthalpyReport.contextReport.updatedEnthalpy);
CHECK_FALSE(enthalpyReport.contextReport.updatedDisplacement);
CHECK(enthalpyReport.preparedElementData);
CHECK(context.GetBaseEnthalpy()(0) == enthalpy(0));
CHECK(relative_error(context.GetDisplacement(), frozenDisplacement, communicator) == 0.0);
/*
* Displacement invalidation.
*
* The changed geometry is now intentionally accepted. Because it came
* from the smooth test displacement projection rather than an arbitrary
* single H1 coefficient mutation, it remains a valid mapping.
*/
++dependencies.displacement.revision;
const auto displacementReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
CHECK_FALSE(displacementReport.contextReport.preparedStaticDependencies);
CHECK(displacementReport.contextReport.preparedGeometryState);
CHECK(displacementReport.contextReport.preparedBaseState);
CHECK_FALSE(displacementReport.contextReport.updatedDensity);
CHECK_FALSE(displacementReport.contextReport.updatedEnthalpy);
CHECK(displacementReport.contextReport.updatedDisplacement);
CHECK(displacementReport.preparedElementData);
CHECK(relative_error(context.GetDisplacement(), changedDisplacement, communicator) == 0.0);
/*
* Discretization invalidates everything.
*/
++dependencies.discretization.revision;
const auto discretizationReport =
preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
CHECK(discretizationReport.contextReport.preparedStaticDependencies);
CHECK(discretizationReport.contextReport.preparedGeometryState);
CHECK(discretizationReport.contextReport.preparedBaseState);
CHECK(discretizationReport.contextReport.updatedDensity);
CHECK(discretizationReport.contextReport.updatedEnthalpy);
CHECK(discretizationReport.contextReport.updatedDisplacement);
CHECK(discretizationReport.preparedElementData);
const auto finalStatistics = preparedOperator.GetContextPreparationStatistics();
CHECK(finalStatistics.staticPreparations == 2);
CHECK(finalStatistics.geometryPreparations == 3);
CHECK(finalStatistics.baseStatePreparations == 5);
CHECK(preparedOperator.GetPreparationCount() == 5);
}
TEST_CASE(
"Barotropic Closure Context Uses Identity And Revision For Every Dependency",
tags::barotrope &tags::closure &tags::contexts &tags::prepared &tags::field &tags::unit
) {
using namespace barotropic_closure_context_test_utils;
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 1.5);
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState
);
CHECK(
gravity_prepared_test_utils::relative_error(
unchangedAction, initialAction, communicator
) < 5.0e-15
);
mfem::Vector density = reduce(maps.density, make_density(f, 0.41));
mfem::Vector enthalpy = reduce(maps.enthalpy, make_enthalpy(f, 0.53));
mfem::Vector displacement = reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.60));
++revisions.density;
++revisions.enthalpy;
++revisions.displacement;
auto dependencies = make_dependencies();
preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
context.Prepare(
changedDensity, changedEnthalpy, deformedDisplacement, revisions
);
const mfem::Vector frozenDensity = preparedOperator.GetContext().GetBaseDensity();
density(0) += 0.19;
mfem::Vector preparedResidual;
mfem::Vector preparedAction;
const auto sameStampReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
CHECK_FALSE(sameStampReport.DidAnyWork());
CHECK(preparedOperator.GetContext().GetBaseDensity()(0) == frozenDensity(0));
context.BuildResidual(preparedResidual);
++dependencies.density.identity;
++dependencies.density.revision;
context.GetOperator().Mult(
densityVariation, enthalpyVariation, displacementVariation,
preparedAction
);
const auto newIdentityReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
CHECK_FALSE(newIdentityReport.contextReport.preparedStaticDependencies);
CHECK_FALSE(newIdentityReport.contextReport.preparedGeometryState);
CHECK(newIdentityReport.contextReport.preparedBaseState);
CHECK(newIdentityReport.contextReport.updatedDensity);
CHECK(preparedOperator.GetContext().GetBaseDensity()(0) == density(0));
CHECK(preparedOperator.GetContext().MatchesDependencies(dependencies));
mfem::Vector referenceResidual;
mfem::Vector referenceDensityAction;
mfem::Vector referenceEnthalpyAction;
mfem::Vector referenceDisplacementAction;
++dependencies.displacement.identity;
++dependencies.displacement.revision;
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, barotrope, changedDensity, changedEnthalpy,
deformedDisplacement, referenceResidual
);
const auto displacementIdentityReport =
preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
CHECK_FALSE(displacementIdentityReport.contextReport.preparedStaticDependencies);
CHECK(displacementIdentityReport.contextReport.preparedGeometryState);
CHECK(displacementIdentityReport.contextReport.preparedBaseState);
CHECK(displacementIdentityReport.contextReport.updatedDisplacement);
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, barotrope, densityVariation,
deformedDisplacement, referenceDensityAction
);
++dependencies.discretization.identity;
++dependencies.discretization.revision;
mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action(
f, *f.domainMapperStateless, barotrope, changedEnthalpy,
enthalpyVariation, deformedDisplacement, referenceEnthalpyAction
);
mean_field::operators::kernels::
apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, barotrope, changedDensity,
changedEnthalpy, deformedDisplacement, displacementVariation,
referenceDisplacementAction
);
mfem::Vector referenceAction(referenceDensityAction);
referenceAction += referenceEnthalpyAction;
referenceAction += referenceDisplacementAction;
const double residualError = gravity_prepared_test_utils::relative_error(
preparedResidual, referenceResidual, communicator
);
const double actionError = gravity_prepared_test_utils::relative_error(
preparedAction, referenceAction, communicator
);
const double residualChange = gravity_prepared_test_utils::relative_error(
preparedResidual, initialResidual, communicator
);
const double actionChange = gravity_prepared_test_utils::relative_error(
preparedAction, initialAction, communicator
);
INFO("Prepared-context residual error = " << residualError);
INFO("Prepared-context Jacobian error = " << actionError);
INFO("Residual change after valid revision = " << residualChange);
INFO("Jacobian change after valid revision = " << actionChange);
CHECK(context.GetPreparationCount() == 2);
CHECK(residualError < 5.0e-12);
CHECK(actionError < 5.0e-12);
CHECK(residualChange > 1.0e-6);
CHECK(actionChange > 1.0e-6);
}
const auto discretizationIdentityReport =
preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
CHECK(discretizationIdentityReport.contextReport.preparedStaticDependencies);
CHECK(discretizationIdentityReport.contextReport.preparedGeometryState);
CHECK(discretizationIdentityReport.contextReport.preparedBaseState);
}

View File

@@ -15,20 +15,13 @@ TEST_CASE(
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
gravity_context::GravityFieldLinearizationContext context(
f, *f.domainMapperStateless
);
gravity_context::GravityFieldLinearizationContext context(f, *f.domainMapperStateless);
mfem::Vector density = prepared_test::make_deterministic_vector(
f.densityFes->GetTrueVSize(), 0.11
);
mfem::Vector density = prepared_test::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.11);
mfem::Vector displacement = prepared_test::make_displacement(f, 0.0);
mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector(
f.gravityFluxFes->GetTrueVSize(), 0.37
);
mfem::Vector gravity_potential = prepared_test::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), 0.63
);
mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.37);
mfem::Vector gravity_potential =
prepared_test::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.63);
gravity_context::GravityFieldRevisions revisions;
@@ -43,8 +36,7 @@ TEST_CASE(
REQUIRE_FALSE(context.IsPrepared());
const gravity_context::GravityFieldPreparationReport initial_report =
context.Prepare(make_state(), revisions);
const gravity_context::GravityFieldPreparationReport initial_report = context.Prepare(make_state(), revisions);
REQUIRE(context.IsPrepared());
CHECK(initial_report.geometry.reconstructed_operators);
@@ -55,41 +47,27 @@ TEST_CASE(
CHECK(initial_report.updated_gravity_gradient);
CHECK(initial_report.DidAnyWork());
const auto initial_mass_preparations =
context.GetGeometryContext().GetMassOperator().GetPreparationCount();
const auto initial_source_preparations =
context.GetGeometryContext().GetSourceOperator().GetPreparationCount();
const auto initial_mass_preparations = context.GetGeometryContext().GetMassOperator().GetPreparationCount();
const auto initial_source_preparations = context.GetGeometryContext().GetSourceOperator().GetPreparationCount();
const gravity_context::GravityFieldPreparationReport repeated_report =
context.Prepare(make_state(), revisions);
const gravity_context::GravityFieldPreparationReport repeated_report = context.Prepare(make_state(), revisions);
CHECK_FALSE(repeated_report.DidAnyWork());
CHECK(
context.GetGeometryContext().GetMassOperator().GetPreparationCount() ==
initial_mass_preparations
);
CHECK(
context.GetGeometryContext()
.GetSourceOperator()
.GetPreparationCount() == initial_source_preparations
);
CHECK(context.GetGeometryContext().GetMassOperator().GetPreparationCount() == initial_mass_preparations);
CHECK(context.GetGeometryContext().GetSourceOperator().GetPreparationCount() == initial_source_preparations);
gravity_potential(0) += 0.25;
++revisions.gravity_potential.value;
const gravity_context::GravityFieldPreparationReport potential_report =
context.Prepare(make_state(), revisions);
const gravity_context::GravityFieldPreparationReport potential_report = context.Prepare(make_state(), revisions);
CHECK_FALSE(potential_report.DidAnyWork());
CHECK(
context.GetRevisions().gravity_potential == revisions.gravity_potential
);
CHECK(context.GetRevisions().gravity_potential == revisions.gravity_potential);
density(0) += 0.5;
++revisions.density.value;
const gravity_context::GravityFieldPreparationReport density_report =
context.Prepare(make_state(), revisions);
const gravity_context::GravityFieldPreparationReport density_report = context.Prepare(make_state(), revisions);
CHECK(density_report.updated_density);
CHECK_FALSE(density_report.updated_gravity_gradient);
@@ -99,8 +77,7 @@ TEST_CASE(
gravity_gradient(0) -= 0.4;
++revisions.gravity_gradient.value;
const gravity_context::GravityFieldPreparationReport gradient_report =
context.Prepare(make_state(), revisions);
const gravity_context::GravityFieldPreparationReport gradient_report = context.Prepare(make_state(), revisions);
CHECK_FALSE(gradient_report.updated_density);
CHECK(gradient_report.updated_gravity_gradient);
@@ -110,8 +87,7 @@ TEST_CASE(
displacement = prepared_test::make_displacement(f, 1.0);
++revisions.displacement.value;
const gravity_context::GravityFieldPreparationReport displacement_report =
context.Prepare(make_state(), revisions);
const gravity_context::GravityFieldPreparationReport displacement_report = context.Prepare(make_state(), revisions);
CHECK_FALSE(displacement_report.geometry.reconstructed_operators);
CHECK(displacement_report.geometry.rebuilt_mass_operator);
@@ -119,15 +95,8 @@ TEST_CASE(
CHECK(displacement_report.geometry.refreshed_variation_state);
CHECK_FALSE(displacement_report.updated_density);
CHECK_FALSE(displacement_report.updated_gravity_gradient);
CHECK(
context.GetGeometryContext().GetMassOperator().GetPreparationCount() ==
initial_mass_preparations + 1
);
CHECK(
context.GetGeometryContext()
.GetSourceOperator()
.GetPreparationCount() == initial_source_preparations + 1
);
CHECK(context.GetGeometryContext().GetMassOperator().GetPreparationCount() == initial_mass_preparations + 1);
CHECK(context.GetGeometryContext().GetSourceOperator().GetPreparationCount() == initial_source_preparations + 1);
++revisions.discretization.value;
@@ -139,15 +108,8 @@ TEST_CASE(
CHECK(discretization_report.geometry.rebuilt_source_operator);
CHECK(discretization_report.updated_density);
CHECK(discretization_report.updated_gravity_gradient);
CHECK(
context.GetGeometryContext().GetMassOperator().GetPreparationCount() ==
1
);
CHECK(
context.GetGeometryContext()
.GetSourceOperator()
.GetPreparationCount() == 1
);
CHECK(context.GetGeometryContext().GetMassOperator().GetPreparationCount() == 1);
CHECK(context.GetGeometryContext().GetSourceOperator().GetPreparationCount() == 1);
}
TEST_CASE(
@@ -157,20 +119,13 @@ TEST_CASE(
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
gravity_context::GravityFieldLinearizationContext context(
f, *f.domainMapperStateless
);
gravity_context::GravityFieldLinearizationContext context(f, *f.domainMapperStateless);
mfem::Vector density = prepared_test::make_deterministic_vector(
f.densityFes->GetTrueVSize(), 0.13
);
mfem::Vector density = prepared_test::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.13);
mfem::Vector displacement = prepared_test::make_displacement(f, 0.4);
mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector(
f.gravityFluxFes->GetTrueVSize(), 0.47
);
mfem::Vector gravity_potential = prepared_test::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), 0.71
);
mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.47);
mfem::Vector gravity_potential =
prepared_test::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.71);
gravity_context::GravityFieldRevisions revisions;
@@ -182,9 +137,8 @@ TEST_CASE(
revisions
);
const mfem::Vector frozen_density = context.GetDensity();
const mfem::Vector frozen_displacement =
context.GetGeometryContext().GetDisplacement();
const mfem::Vector frozen_density = context.GetDensity();
const mfem::Vector frozen_displacement = context.GetGeometryContext().GetDisplacement();
const mfem::Vector frozen_gravity_gradient = context.GetGravityGradient();
density = 0.0;
@@ -192,49 +146,36 @@ TEST_CASE(
gravity_gradient = 0.0;
gravity_potential = 0.0;
CHECK(prepared_test::relative_error(context.GetDensity(), frozen_density, f.mesh->GetComm()) == 0.0);
CHECK(
prepared_test::relative_error(
context.GetDensity(), frozen_density, f.mesh->GetComm()
context.GetGeometryContext().GetDisplacement(), frozen_displacement, f.displacementFes->GetComm()
) == 0.0
);
CHECK(
prepared_test::relative_error(
context.GetGeometryContext().GetDisplacement(), frozen_displacement,
f.displacementFes->GetComm()
) == 0.0
);
CHECK(
prepared_test::relative_error(
context.GetGravityGradient(), frozen_gravity_gradient,
f.gravityFluxFes->GetComm()
context.GetGravityGradient(), frozen_gravity_gradient, f.gravityFluxFes->GetComm()
) == 0.0
);
const gravity_context::GravityFieldPreparationReport
unchanged_revision_report = context.Prepare(
{.density = density,
.displacement = displacement,
.gravity_gradient = gravity_gradient,
.gravity_potential = gravity_potential},
revisions
);
const gravity_context::GravityFieldPreparationReport unchanged_revision_report = context.Prepare(
{.density = density,
.displacement = displacement,
.gravity_gradient = gravity_gradient,
.gravity_potential = gravity_potential},
revisions
);
CHECK_FALSE(unchanged_revision_report.DidAnyWork());
CHECK(prepared_test::relative_error(context.GetDensity(), frozen_density, f.mesh->GetComm()) == 0.0);
CHECK(
prepared_test::relative_error(
context.GetDensity(), frozen_density, f.mesh->GetComm()
context.GetGeometryContext().GetDisplacement(), frozen_displacement, f.displacementFes->GetComm()
) == 0.0
);
CHECK(
prepared_test::relative_error(
context.GetGeometryContext().GetDisplacement(), frozen_displacement,
f.displacementFes->GetComm()
) == 0.0
);
CHECK(
prepared_test::relative_error(
context.GetGravityGradient(), frozen_gravity_gradient,
f.gravityFluxFes->GetComm()
context.GetGravityGradient(), frozen_gravity_gradient, f.gravityFluxFes->GetComm()
) == 0.0
);
}
@@ -246,21 +187,13 @@ TEST_CASE(
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
gravity_context::GravityFieldGeometryContext first_context(
f, *f.domainMapperStateless
);
gravity_context::GravityFieldGeometryContext second_context(
f, *f.domainMapperStateless
);
gravity_context::GravityFieldGeometryContext first_context(f, *f.domainMapperStateless);
gravity_context::GravityFieldGeometryContext second_context(f, *f.domainMapperStateless);
const mfem::Vector first_displacement =
prepared_test::make_displacement(f, 0.0);
const mfem::Vector second_displacement =
prepared_test::make_displacement(f, 1.0);
const mfem::Vector first_displacement = prepared_test::make_displacement(f, 0.0);
const mfem::Vector second_displacement = prepared_test::make_displacement(f, 1.0);
const mfem::Vector gravity_gradient =
prepared_test::make_deterministic_vector(
f.gravityFluxFes->GetTrueVSize(), 0.35
);
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.35);
first_context.Prepare(first_displacement, {.value = 0}, {.value = 0});
second_context.Prepare(second_displacement, {.value = 0}, {.value = 0});
@@ -270,36 +203,21 @@ TEST_CASE(
mfem::Vector second_action_after;
first_context.GetMassOperator().Mult(gravity_gradient, first_action);
second_context.GetMassOperator().Mult(
gravity_gradient, second_action_before
);
second_context.GetMassOperator().Mult(gravity_gradient, second_action_before);
const mfem::Vector updated_first_displacement =
prepared_test::make_displacement(f, 0.6);
first_context.Prepare(
updated_first_displacement, {.value = 0}, {.value = 1}
);
const mfem::Vector updated_first_displacement = prepared_test::make_displacement(f, 0.6);
first_context.Prepare(updated_first_displacement, {.value = 0}, {.value = 1});
second_context.GetMassOperator().Mult(
gravity_gradient, second_action_after
);
second_context.GetMassOperator().Mult(gravity_gradient, second_action_after);
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
const double independent_context_error = prepared_test::relative_error(
second_action_after, second_action_before, communicator
);
const double distinct_geometry_difference = prepared_test::relative_error(
first_action, second_action_before, communicator
);
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
const double independent_context_error =
prepared_test::relative_error(second_action_after, second_action_before, communicator);
const double distinct_geometry_difference =
prepared_test::relative_error(first_action, second_action_before, communicator);
INFO(
"Second-context change after preparing first context = "
<< independent_context_error
);
INFO(
"Difference between independently prepared geometries = "
<< distinct_geometry_difference
);
INFO("Second-context change after preparing first context = " << independent_context_error);
INFO("Difference between independently prepared geometries = " << distinct_geometry_difference);
CHECK(independent_context_error < 2.0e-14);
CHECK(distinct_geometry_difference > 1.0e-5);

View File

@@ -5,9 +5,7 @@ import mean_field;
import test_helpers;
namespace hydrostatic_context_test_utils {
mean_field::operators::context::hydrostatic::
HydrostaticEquilibriumDependencies
make_dependencies() {
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() {
return {
.discretization = {.identity = 101, .revision = 2},
.enthalpy = {.identity = 103, .revision = 3},
@@ -18,8 +16,7 @@ namespace hydrostatic_context_test_utils {
};
}
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
make_state(
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state(
const mfem::Vector &enthalpy,
const mfem::Vector &gravityPotential,
const mfem::Vector &displacement,
@@ -33,10 +30,7 @@ namespace hydrostatic_context_test_utils {
};
}
void check_base_only(
const mean_field::operators::context::hydrostatic::
HydrostaticPreparationReport &report
) {
void check_base_only(const mean_field::operators::context::hydrostatic::HydrostaticPreparationReport &report) {
CHECK_FALSE(report.preparedStaticDependencies);
CHECK_FALSE(report.preparedGeometryState);
CHECK_FALSE(report.preparedRotationDependencies);
@@ -48,32 +42,23 @@ TEST_CASE(
"Hydrostatic Context Applies Selective Invalidation",
tags::barotrope &tags::contexts &tags::hydro &tags::prepared &tags::unit
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext
context(f, *f.domainMapperStateless);
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext context(f, *f.domainMapperStateless);
mfem::Vector enthalpy =
gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), 0.17
);
mfem::Vector enthalpy = gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.17);
mfem::Vector gravityPotential =
gravity_prepared_test_utils::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), 0.31
);
gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.31);
mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.35);
mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.35);
double bernoulliConstant = 0.73;
double bernoulliConstant = 0.73;
mean_field::operators::context::hydrostatic::
HydrostaticEquilibriumDependencies dependencies =
hydrostatic_context_test_utils::make_dependencies();
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies dependencies =
hydrostatic_context_test_utils::make_dependencies();
CHECK_FALSE(context.IsPrepared());
CHECK_FALSE(context.MatchesDependencies(dependencies));
@@ -86,9 +71,7 @@ TEST_CASE(
CHECK(initialStatistics.baseStatePreparations == 0);
const auto initialReport = context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);
@@ -106,14 +89,13 @@ TEST_CASE(
CHECK(initialReport.updatedBernoulliConstant);
CHECK(initialReport.DidAnyWork());
const mfem::Vector frozenEnthalpy = context.GetBaseEnthalpyTrue();
const mfem::Vector frozenEnthalpy = context.GetBaseEnthalpyTrue();
const mfem::Vector frozenGravityPotential =
context.GetBaseGravityPotentialTrue();
const mfem::Vector frozenGravityPotential = context.GetBaseGravityPotentialTrue();
const mfem::Vector frozenDisplacement = context.GetDisplacementTrue();
const mfem::Vector frozenDisplacement = context.GetDisplacementTrue();
const double frozenBernoulliConstant = context.GetBernoulliConstant();
const double frozenBernoulliConstant = context.GetBernoulliConstant();
enthalpy(0) += 0.125;
gravityPotential(0) -= 0.075;
@@ -121,9 +103,7 @@ TEST_CASE(
bernoulliConstant += 0.20;
const auto repeatedReport = context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);
@@ -136,22 +116,18 @@ TEST_CASE(
const MPI_Comm communicator = f.mesh->GetComm();
CHECK(
gravity_prepared_test_utils::relative_error(
context.GetBaseEnthalpyTrue(), frozenEnthalpy, communicator
) == 0.0
gravity_prepared_test_utils::relative_error(context.GetBaseEnthalpyTrue(), frozenEnthalpy, communicator) == 0.0
);
CHECK(
gravity_prepared_test_utils::relative_error(
context.GetBaseGravityPotentialTrue(), frozenGravityPotential,
communicator
context.GetBaseGravityPotentialTrue(), frozenGravityPotential, communicator
) == 0.0
);
CHECK(
gravity_prepared_test_utils::relative_error(
context.GetDisplacementTrue(), frozenDisplacement, communicator
) == 0.0
gravity_prepared_test_utils::relative_error(context.GetDisplacementTrue(), frozenDisplacement, communicator) ==
0.0
);
CHECK(context.GetBernoulliConstant() == frozenBernoulliConstant);
@@ -159,9 +135,7 @@ TEST_CASE(
++dependencies.enthalpy.revision;
const auto enthalpyReport = context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);
@@ -176,9 +150,7 @@ TEST_CASE(
++dependencies.gravityPotential.revision;
const auto gravityPotentialReport = context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);
@@ -194,9 +166,7 @@ TEST_CASE(
++dependencies.bernoulliConstant.revision;
const auto bernoulliReport = context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);
@@ -212,9 +182,7 @@ TEST_CASE(
++dependencies.rotation.revision;
const auto rotationReport = context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);
@@ -230,9 +198,7 @@ TEST_CASE(
++dependencies.displacement.revision;
const auto displacementReport = context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);
@@ -251,9 +217,7 @@ TEST_CASE(
++dependencies.discretization.revision;
const auto discretizationReport = context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);
@@ -278,32 +242,23 @@ TEST_CASE(
"Hydrostatic Context Uses Identity In Every Dependency",
tags::barotrope &tags::contexts &tags::hydro &tags::prepared &tags::unit
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext
context(f, *f.domainMapperStateless);
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext context(f, *f.domainMapperStateless);
mfem::Vector enthalpy =
gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), 0.23
);
mfem::Vector enthalpy = gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.23);
const mfem::Vector gravityPotential =
gravity_prepared_test_utils::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), 0.41
);
gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.41);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.60);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.60);
constexpr double bernoulliConstant = 0.81;
mean_field::operators::context::hydrostatic::
HydrostaticEquilibriumDependencies dependencies =
hydrostatic_context_test_utils::make_dependencies();
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies dependencies =
hydrostatic_context_test_utils::make_dependencies();
const auto preparedEnthalpyDependency = dependencies.enthalpy;
@@ -319,9 +274,7 @@ TEST_CASE(
CHECK(resetNewIdentity.CanFollow(preparedEnthalpyDependency));
context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);
@@ -330,9 +283,7 @@ TEST_CASE(
enthalpy(0) += 0.33;
const auto sameStampReport = context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);
@@ -343,9 +294,7 @@ TEST_CASE(
dependencies.enthalpy.revision = 0;
const auto newIdentityReport = context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);
@@ -359,9 +308,7 @@ TEST_CASE(
dependencies.rotation.revision = 0;
const auto newRotationIdentityReport = context.Prepare(
hydrostatic_context_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies
);

View File

@@ -0,0 +1,325 @@
#include <algorithm>
#include <cmath>
#include <limits>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace pressure_force_context_test_utils {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
struct Maps final {
mean_field::field::FieldDofMap enthalpy;
mean_field::field::FieldDofMap displacement;
explicit Maps(const mean_field::fem::FEM &f)
: enthalpy(
mean_field::field::make_field_dof_map<
mean_field::field::Enthalpy,
DomainSchema>(*f.enthalpyFes)
),
displacement(
mean_field::field::make_field_dof_map<
mean_field::field::Displacement,
DomainSchema>(*f.displacementFes)
) {
}
};
[[nodiscard]]
mfem::Vector make_enthalpy_true(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::Vector enthalpy(f.enthalpyFes->GetTrueVSize());
for (int index = 0; index < enthalpy.Size(); ++index) {
const double position = static_cast<double>(index + 1);
enthalpy(index) =
0.95 + 0.08 * std::sin(0.17 * position + phase) + 0.03 * std::cos(0.11 * position - 0.5 * phase);
}
return enthalpy;
}
[[nodiscard]]
double relative_difference(
const mfem::Vector &left,
const mfem::Vector &right,
const MPI_Comm communicator
) {
MFEM_VERIFY(
left.Size() == right.Size(), "Cannot compare pressure-force context vectors with "
"different sizes."
);
mfem::Vector difference(left);
difference -= right;
const double scale = std::max(
{gravity_prepared_test_utils::global_norm(left, communicator),
gravity_prepared_test_utils::global_norm(right, communicator),
100.0 * std::numeric_limits<double>::epsilon()}
);
return gravity_prepared_test_utils::global_norm(difference, communicator) / scale;
}
[[nodiscard]]
mean_field::operators::context::pressure_force::PressureForceDependencies make_dependencies() {
return {
.discretization = {.identity = 101, .revision = 7},
.enthalpy = {.identity = 103, .revision = 11},
.displacement = {.identity = 107, .revision = 13}
};
}
} // namespace pressure_force_context_test_utils
TEST_CASE(
"Pressure Force Context Applies Selective Invalidation In FieldDof Coordinates",
tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::unit
) {
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 pressure_force_context_test_utils::Maps maps(f);
mean_field::operators::context::pressure_force::PressureForceLinearizationContext context(
f, *f.domainMapperStateless, maps.enthalpy, maps.displacement
);
mfem::Vector enthalpy = maps.enthalpy.gather(pressure_force_context_test_utils::make_enthalpy_true(f, 0.23));
const mfem::Vector initialDisplacement =
maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.41));
const mfem::Vector changedDisplacement =
maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.79));
mfem::Vector displacement(initialDisplacement);
auto dependencies = pressure_force_context_test_utils::make_dependencies();
const mean_field::operators::context::pressure_force::PressureForceStateView state{
.enthalpy = enthalpy, .displacement = displacement
};
CHECK_FALSE(context.IsPrepared());
const auto initialReport = context.Prepare(state, dependencies);
REQUIRE(context.IsPrepared());
CHECK(context.MatchesDependencies(dependencies));
CHECK(initialReport.DidAnyWork());
CHECK(initialReport.preparedStaticDependencies);
CHECK(initialReport.preparedGeometryState);
CHECK(initialReport.preparedMaterialState);
CHECK(initialReport.updatedEnthalpy);
CHECK(initialReport.updatedDisplacement);
REQUIRE(maps.enthalpy.reduced_size() < maps.enthalpy.full_size());
CHECK(maps.displacement.is_identity());
CHECK(context.GetBaseEnthalpy().Size() == maps.enthalpy.reduced_size());
CHECK(context.GetDisplacement().Size() == maps.displacement.reduced_size());
const auto unchangedReport = context.Prepare(state, dependencies);
CHECK_FALSE(unchangedReport.DidAnyWork());
const mfem::Vector frozenEnthalpy(context.GetBaseEnthalpy());
const mfem::Vector frozenDisplacement(context.GetDisplacement());
enthalpy(0) += 0.125;
displacement = changedDisplacement;
const auto unstampedReport = context.Prepare(state, dependencies);
CHECK_FALSE(unstampedReport.DidAnyWork());
CHECK(
pressure_force_context_test_utils::relative_difference(
context.GetBaseEnthalpy(), frozenEnthalpy, f.mesh->GetComm()
) == 0.0
);
CHECK(
pressure_force_context_test_utils::relative_difference(
context.GetDisplacement(), frozenDisplacement, f.mesh->GetComm()
) == 0.0
);
++dependencies.enthalpy.revision;
const auto enthalpyReport = context.Prepare(state, dependencies);
CHECK(enthalpyReport.DidAnyWork());
CHECK_FALSE(enthalpyReport.preparedStaticDependencies);
CHECK_FALSE(enthalpyReport.preparedGeometryState);
CHECK(enthalpyReport.preparedMaterialState);
CHECK(enthalpyReport.updatedEnthalpy);
CHECK_FALSE(enthalpyReport.updatedDisplacement);
CHECK(context.GetBaseEnthalpy()(0) == enthalpy(0));
CHECK(
pressure_force_context_test_utils::relative_difference(
context.GetDisplacement(), frozenDisplacement, f.mesh->GetComm()
) == 0.0
);
++dependencies.displacement.revision;
const auto displacementReport = context.Prepare(state, dependencies);
CHECK(displacementReport.DidAnyWork());
CHECK_FALSE(displacementReport.preparedStaticDependencies);
CHECK(displacementReport.preparedGeometryState);
CHECK(displacementReport.preparedMaterialState);
CHECK_FALSE(displacementReport.updatedEnthalpy);
CHECK(displacementReport.updatedDisplacement);
CHECK(
pressure_force_context_test_utils::relative_difference(
context.GetDisplacement(), changedDisplacement, f.mesh->GetComm()
) == 0.0
);
++dependencies.discretization.revision;
const auto discretizationReport = context.Prepare(state, dependencies);
CHECK(discretizationReport.preparedStaticDependencies);
CHECK(discretizationReport.preparedGeometryState);
CHECK(discretizationReport.preparedMaterialState);
CHECK(discretizationReport.updatedEnthalpy);
CHECK(discretizationReport.updatedDisplacement);
const auto &statistics = context.GetPreparationStatistics();
CHECK(statistics.staticPreparations == 2);
CHECK(statistics.geometryPreparations == 3);
CHECK(statistics.materialPreparations == 4);
}
TEST_CASE(
"Pressure Force Context Uses Identity And Revision In Every Dependency",
tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::unit
) {
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 pressure_force_context_test_utils::Maps maps(f);
mean_field::operators::context::pressure_force::PressureForceLinearizationContext context(
f, *f.domainMapperStateless, maps.enthalpy, maps.displacement
);
const mfem::Vector enthalpy = maps.enthalpy.gather(pressure_force_context_test_utils::make_enthalpy_true(f, 0.61));
const mfem::Vector displacement = maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.73));
const mean_field::operators::context::pressure_force::PressureForceStateView state{
.enthalpy = enthalpy, .displacement = displacement
};
auto dependencies = pressure_force_context_test_utils::make_dependencies();
context.Prepare(state, dependencies);
++dependencies.enthalpy.identity;
dependencies.enthalpy.revision = 0;
const auto enthalpyIdentityReport = context.Prepare(state, dependencies);
CHECK_FALSE(enthalpyIdentityReport.preparedStaticDependencies);
CHECK_FALSE(enthalpyIdentityReport.preparedGeometryState);
CHECK(enthalpyIdentityReport.preparedMaterialState);
CHECK(enthalpyIdentityReport.updatedEnthalpy);
CHECK_FALSE(enthalpyIdentityReport.updatedDisplacement);
++dependencies.displacement.identity;
dependencies.displacement.revision = 0;
const auto displacementIdentityReport = context.Prepare(state, dependencies);
CHECK_FALSE(displacementIdentityReport.preparedStaticDependencies);
CHECK(displacementIdentityReport.preparedGeometryState);
CHECK(displacementIdentityReport.preparedMaterialState);
CHECK_FALSE(displacementIdentityReport.updatedEnthalpy);
CHECK(displacementIdentityReport.updatedDisplacement);
++dependencies.discretization.identity;
dependencies.discretization.revision = 0;
const auto discretizationIdentityReport = context.Prepare(state, dependencies);
CHECK(discretizationIdentityReport.preparedStaticDependencies);
CHECK(discretizationIdentityReport.preparedGeometryState);
CHECK(discretizationIdentityReport.preparedMaterialState);
CHECK(discretizationIdentityReport.updatedEnthalpy);
CHECK(discretizationIdentityReport.updatedDisplacement);
CHECK(context.MatchesDependencies(dependencies));
const auto &statistics = context.GetPreparationStatistics();
CHECK(statistics.staticPreparations == 2);
CHECK(statistics.geometryPreparations == 3);
CHECK(statistics.materialPreparations == 4);
}

View File

@@ -0,0 +1,181 @@
#include <algorithm>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace rotational_displacement_force_context_test_utils {
using Context =
mean_field::operators::context::rotational_displacement_force::RotationalDisplacementForceLinearizationContext;
using Dependencies =
mean_field::operators::context::rotational_displacement_force::RotationalDisplacementForceDependencies;
[[nodiscard]] Dependencies make_dependencies() {
return {
.discretization = {.identity = 101, .revision = 3},
.density = {.identity = 103, .revision = 5},
.displacement = {.identity = 107, .revision = 7},
.rotation = {.identity = 109, .revision = 11}
};
}
[[nodiscard]] mfem::Vector make_density(
const mean_field::fem::FEM &f,
const double offset
) {
mfem::ParGridFunction density(f.densityFes.get());
mfem::FunctionCoefficient coefficient([offset](const mfem::Vector &position) {
return offset + 0.04 * position(0) - 0.03 * position(1) + 0.02 * position(2);
});
density.ProjectCoefficient(coefficient);
mfem::Vector densityTrue;
density.GetTrueDofs(densityTrue);
return densityTrue;
}
[[nodiscard]] double relative_difference(
const mfem::Vector &left,
const mfem::Vector &right
) {
mfem::Vector difference(left);
difference -= right;
return difference.Norml2() / std::max(right.Norml2(), 1.0e-30);
}
} // namespace rotational_displacement_force_context_test_utils
TEST_CASE(
"Rotational Displacement Force Context Applies Selective Invalidation",
tags::centrifugal &tags::contexts &tags::unit
) {
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());
mfem::Vector density = rotational_displacement_force_context_test_utils::make_density(f, 0.83);
mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.47);
auto dependencies = rotational_displacement_force_context_test_utils::make_dependencies();
rotational_displacement_force_context_test_utils::Context context(f, *f.domainMapperStateless);
const auto initialReport = context.Prepare({.density = density, .displacement = displacement}, dependencies);
REQUIRE(context.IsPrepared());
CHECK(context.MatchesDependencies(dependencies));
CHECK(initialReport.preparedStaticDependencies);
CHECK(initialReport.preparedGeometryState);
CHECK(initialReport.preparedRotationDependencies);
CHECK(initialReport.preparedBaseState);
CHECK(initialReport.updatedDensity);
CHECK(initialReport.updatedDisplacement);
CHECK(
rotational_displacement_force_context_test_utils::relative_difference(context.GetBaseDensityTrue(), density) <
1.0e-14
);
CHECK(
rotational_displacement_force_context_test_utils::relative_difference(
context.GetDisplacementTrue(), displacement
) < 1.0e-14
);
const auto unchangedReport = context.Prepare({.density = density, .displacement = displacement}, dependencies);
CHECK_FALSE(unchangedReport.DidAnyWork());
density = rotational_displacement_force_context_test_utils::make_density(f, 1.17);
++dependencies.density.revision;
const auto densityReport = context.Prepare({.density = density, .displacement = displacement}, dependencies);
CHECK_FALSE(densityReport.preparedStaticDependencies);
CHECK_FALSE(densityReport.preparedGeometryState);
CHECK_FALSE(densityReport.preparedRotationDependencies);
CHECK(densityReport.preparedBaseState);
CHECK(densityReport.updatedDensity);
CHECK_FALSE(densityReport.updatedDisplacement);
const mfem::Vector densityAfterDensityRevision(context.GetBaseDensityTrue());
displacement = gravity_prepared_test_utils::make_displacement(f, 0.81);
++dependencies.displacement.revision;
const auto displacementReport = context.Prepare({.density = density, .displacement = displacement}, dependencies);
CHECK_FALSE(displacementReport.preparedStaticDependencies);
CHECK(displacementReport.preparedGeometryState);
CHECK_FALSE(displacementReport.preparedRotationDependencies);
CHECK(displacementReport.preparedBaseState);
CHECK_FALSE(displacementReport.updatedDensity);
CHECK(displacementReport.updatedDisplacement);
CHECK(
rotational_displacement_force_context_test_utils::relative_difference(
context.GetBaseDensityTrue(), densityAfterDensityRevision
) < 1.0e-14
);
++dependencies.rotation.revision;
const auto rotationReport = context.Prepare({.density = density, .displacement = displacement}, dependencies);
CHECK_FALSE(rotationReport.preparedStaticDependencies);
CHECK_FALSE(rotationReport.preparedGeometryState);
CHECK(rotationReport.preparedRotationDependencies);
CHECK(rotationReport.preparedBaseState);
CHECK_FALSE(rotationReport.updatedDensity);
CHECK_FALSE(rotationReport.updatedDisplacement);
const auto statistics = context.GetPreparationStatistics();
CHECK(statistics.staticPreparations == 1);
CHECK(statistics.geometryPreparations == 2);
CHECK(statistics.rotationPreparations == 2);
CHECK(statistics.baseStatePreparations == 4);
}
TEST_CASE(
"Rotational Displacement Force Context Treats New Identities As New "
"Dependency Streams",
tags::centrifugal &tags::contexts &tags::unit
) {
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 mfem::Vector density = rotational_displacement_force_context_test_utils::make_density(f, 0.91);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.39);
auto dependencies = rotational_displacement_force_context_test_utils::make_dependencies();
rotational_displacement_force_context_test_utils::Context context(f, *f.domainMapperStateless);
context.Prepare({.density = density, .displacement = displacement}, dependencies);
dependencies.density.identity += 1000;
dependencies.density.revision = 0;
const auto report = context.Prepare({.density = density, .displacement = displacement}, dependencies);
CHECK(report.preparedBaseState);
CHECK(report.updatedDensity);
CHECK_FALSE(report.preparedGeometryState);
CHECK_FALSE(report.preparedRotationDependencies);
}

View File

@@ -0,0 +1,710 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace gravity_displacement_force_test_utils {
using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto gravityGradientValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term
);
constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term
);
constexpr auto densityResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
[[nodiscard]] mean_field::operators::GravityDisplacementForceLayout make_layout(const mean_field::fem::FEM &f) {
const std::array<int, CoupledForm::value_block_count> valueSizes{
f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(),
f.gravityPotentialFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1
};
const std::array<int, CoupledForm::residual_block_count> residualSizes{
f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize(), f.densityFes->GetTrueVSize(),
f.displacementFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1
};
return {valueSizes, residualSizes};
}
[[nodiscard]] mfem::Vector make_density(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction densityField(f.densityFes.get());
mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) {
return 0.82 + 0.07 * std::sin(0.8 * position(0) + phase) +
0.05 * std::cos(0.6 * position(1) - 0.3 * phase) + 0.03 * position(2) * position(2);
});
densityField.ProjectCoefficient(densityCoefficient);
mfem::Vector densityTrue;
densityField.GetTrueDofs(densityTrue);
return densityTrue;
}
[[nodiscard]] mfem::Vector make_density_direction(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction densityField(f.densityFes.get());
mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) {
return 0.19 * std::sin(0.9 * position(0) + phase) - 0.13 * std::cos(0.7 * position(1) - phase) +
0.08 * position(2);
});
densityField.ProjectCoefficient(densityCoefficient);
mfem::Vector densityTrue;
densityField.GetTrueDofs(densityTrue);
return densityTrue;
}
[[nodiscard]] mfem::Vector make_gravity_gradient(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
auto gravityFunction = [phase](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(3);
value(0) = 0.31 + 0.08 * position(0) + 0.03 * phase * position(1);
value(1) = -0.17 + 0.06 * position(1) - 0.02 * phase * position(2);
value(2) = 0.23 - 0.05 * position(2) + 0.025 * phase * position(0);
};
mfem::VectorFunctionCoefficient gravityCoefficient(3, gravityFunction);
gravityField.ProjectCoefficient(gravityCoefficient);
mfem::Vector gravityTrue;
gravityField.GetTrueDofs(gravityTrue);
return gravityTrue;
}
[[nodiscard]] mfem::Vector make_gravity_gradient_direction(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
auto gravityFunction = [phase](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(3);
value(0) = 0.14 * std::sin(position(0) + phase) + 0.03 * position(1);
value(1) = -0.11 * std::cos(position(1) - phase) + 0.04 * position(2);
value(2) = 0.09 * std::sin(position(2) + 0.5 * phase) - 0.02 * position(0);
};
mfem::VectorFunctionCoefficient gravityCoefficient(3, gravityFunction);
gravityField.ProjectCoefficient(gravityCoefficient);
mfem::Vector gravityTrue;
gravityField.GetTrueDofs(gravityTrue);
return gravityTrue;
}
[[nodiscard]] mfem::Vector make_displacement_direction(const mean_field::fem::FEM &f) {
mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, 0.83);
const mfem::Vector second = gravity_prepared_test_utils::make_displacement(f, 0.29);
direction -= second;
return direction;
}
[[nodiscard]] mfem::Vector make_vacuum_only_density(const mean_field::fem::FEM &f) {
mfem::ParGridFunction densityField(f.densityFes.get());
densityField = 0.0;
const int vacuumAttribute = f.domainMapperStateless->GetVacuumElementAttribute();
mfem::Array<int> densityDofs;
int localVacuumElements = 0;
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
REQUIRE(transformation != nullptr);
if (transformation->Attribute != vacuumAttribute) {
continue;
}
f.densityFes->GetElementDofs(elementId, densityDofs);
mfem::Vector elementDensity(densityDofs.Size());
elementDensity = 1.0;
densityField.SetSubVector(densityDofs, elementDensity);
++localVacuumElements;
}
int globalVacuumElements = 0;
MPI_Allreduce(&localVacuumElements, &globalVacuumElements, 1, MPI_INT, MPI_SUM, f.mesh->GetComm());
REQUIRE(globalVacuumElements > 0);
mfem::Vector densityTrue;
densityField.GetTrueDofs(densityTrue);
return densityTrue;
}
[[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions make_revisions() {
return {
.discretization = {.value = 3},
.displacement = {.value = 5},
.density = {.value = 7},
.gravity_gradient = {.value = 11},
.gravity_potential = {.value = 13}
};
}
void prepare_gravity_context(
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &context,
const mfem::Vector &density,
const mfem::Vector &displacement,
const mfem::Vector &gravityGradient,
const mfem::Vector &gravityPotential,
const mean_field::operators::context::gravity_field::GravityFieldRevisions &revisions
) {
context.Prepare(
{.density = density,
.displacement = displacement,
.gravity_gradient = gravityGradient,
.gravity_potential = gravityPotential},
revisions
);
}
[[nodiscard]] double relative_difference(
const mfem::Vector &left,
const mfem::Vector &right,
const MPI_Comm communicator
) {
MFEM_VERIFY(
left.Size() == right.Size(), "Cannot compare gravity-displacement-force vectors with "
"different sizes."
);
mfem::Vector difference(left);
difference -= right;
const double scale = std::max(
{gravity_prepared_test_utils::global_norm(left, communicator),
gravity_prepared_test_utils::global_norm(right, communicator),
100.0 * std::numeric_limits<double>::epsilon()}
);
return gravity_prepared_test_utils::global_norm(difference, communicator) / scale;
}
[[nodiscard]] mfem::Vector centered_difference(
const mean_field::fem::FEM &f,
const mfem::Vector &baseDensity,
const mfem::Vector &densityDirection,
const mfem::Vector &baseGravityGradient,
const mfem::Vector &gravityGradientDirection,
const mfem::Vector &baseDisplacement,
const mfem::Vector &displacementDirection,
const double step
) {
mfem::Vector plusDensity(baseDensity);
plusDensity.Add(step, densityDirection);
mfem::Vector minusDensity(baseDensity);
minusDensity.Add(-step, densityDirection);
mfem::Vector plusGravity(baseGravityGradient);
plusGravity.Add(step, gravityGradientDirection);
mfem::Vector minusGravity(baseGravityGradient);
minusGravity.Add(-step, gravityGradientDirection);
mfem::Vector plusDisplacement(baseDisplacement);
plusDisplacement.Add(step, displacementDirection);
mfem::Vector minusDisplacement(baseDisplacement);
minusDisplacement.Add(-step, displacementDirection);
mfem::Vector plusResidual;
mfem::Vector minusResidual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, plusDensity, plusGravity, plusDisplacement, plusResidual
);
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, minusDensity, minusGravity, minusDisplacement, minusResidual
);
plusResidual -= minusResidual;
plusResidual /= 2.0 * step;
return plusResidual;
}
template <int index>
[[nodiscard]] mfem::Vector copy_residual_block(
const mfem::Vector &action,
const mean_field::operators::GravityDisplacementForceLayout &layout,
const mean_field::utils::blocks::residual_block<index> block
) {
mfem::Vector result(layout.size(block));
const int offset = layout.offset(block);
for (int entry = 0; entry < result.Size(); ++entry) {
result(entry) = action(offset + entry);
}
return result;
}
} // namespace gravity_displacement_force_test_utils
TEST_CASE(
"Gravity Displacement Force Query Includes Every Registered Operand",
tags::gravity &tags::quadrature &tags::unit
) {
using DisplacementField = mean_field::field::Field<mean_field::field::Displacement>;
constexpr int geometryWeightOrder = 4;
constexpr mean_field::quadrature::Query query =
DisplacementField::make_query<mean_field::field::Displacement::Form::GravityForce>(
mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder, {},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
/*
* rho: 2
* RT value: family order 2 + 1 = 3
* geometry displacement gradient: 3 - 1 = 2
* displacement test value: 3
* reference-element geometry weight: 4
*/
constexpr int expectedBaseOrder = 2 + 3 + 2 + 3 + 4;
STATIC_REQUIRE(query.term == mean_field::quadrature::Term::gravity_force);
STATIC_REQUIRE(query.role == mean_field::quadrature::QuadratureRole::discretization);
STATIC_REQUIRE(query.domain == mean_field::utils::DOMAINS::STELLAR);
STATIC_REQUIRE(query.mapping == mean_field::quadrature::MappingKind::general);
STATIC_REQUIRE(query.base_order.has_value());
STATIC_REQUIRE(*query.base_order == expectedBaseOrder);
}
TEST_CASE(
"Gravity Displacement Force Uses Positive Grad-Phi Sign And Excludes "
"Vacuum",
tags::gravity &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());
mfem::ParGridFunction densityField(f.densityFes.get());
mfem::ConstantCoefficient densityCoefficient(1.0);
densityField.ProjectCoefficient(densityCoefficient);
mfem::Vector density;
densityField.GetTrueDofs(density);
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
auto constantGravityFunction = [](const mfem::Vector &, mfem::Vector &value) {
value.SetSize(3);
value = 0.0;
value(0) = 1.0;
};
mfem::VectorFunctionCoefficient gravityCoefficient(3, constantGravityFunction);
gravityField.ProjectCoefficient(gravityCoefficient);
mfem::Vector gravityGradient;
gravityField.GetTrueDofs(gravityGradient);
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0;
mfem::Vector residual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
);
mfem::ParGridFunction testField(f.displacementFes.get());
testField.ProjectCoefficient(gravityCoefficient);
mfem::Vector testDirection;
testField.GetTrueDofs(testDirection);
const double signedWork = gravity_prepared_test_utils::global_dot(residual, testDirection, f.mesh->GetComm());
INFO("Constant +x gravity-force work = " << signedWork);
CHECK(signedWork > 0.0);
const mfem::Vector vacuumDensity = gravity_displacement_force_test_utils::make_vacuum_only_density(f);
mfem::Vector vacuumResidual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, vacuumDensity, gravityGradient, displacement, vacuumResidual
);
CHECK(gravity_prepared_test_utils::global_norm(vacuumResidual, f.mesh->GetComm()) == 0.0);
}
TEST_CASE(
"Prepared Gravity Displacement Force Reuses Shared Gravity Revisions",
tags::gravity &tags::prepared &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());
mfem::Vector density = gravity_displacement_force_test_utils::make_density(f, 0.31);
const mfem::Vector gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.47);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.61);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
auto revisions = gravity_displacement_force_test_utils::make_revisions();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, revisions
);
mean_field::operators::PreparedGravityDisplacementForceOperator preparedOperator(
f, *f.domainMapperStateless, gravityContext
);
const auto initialReport = preparedOperator.Prepare();
REQUIRE(initialReport.DidAnyWork());
REQUIRE(preparedOperator.IsPrepared());
mfem::Vector preparedResidual;
mfem::Vector kernelResidual;
preparedOperator.BuildResidual(preparedResidual);
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement, kernelResidual
);
CHECK(
gravity_displacement_force_test_utils::relative_difference(
preparedResidual, kernelResidual, f.mesh->GetComm()
) < 2.0e-12
);
CHECK_FALSE(preparedOperator.Prepare().DidAnyWork());
++revisions.gravity_potential.value;
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, revisions
);
CHECK(preparedOperator.IsPrepared());
CHECK_FALSE(preparedOperator.Prepare().DidAnyWork());
density = gravity_displacement_force_test_utils::make_density(f, 0.79);
++revisions.density.value;
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, revisions
);
CHECK_FALSE(preparedOperator.IsPrepared());
const auto densityReport = preparedOperator.Prepare();
CHECK(densityReport.DidAnyWork());
CHECK(preparedOperator.IsPrepared());
CHECK(preparedOperator.GetResidualPreparationCount() == 2);
CHECK(preparedOperator.GetResidualApplicationCount() == 1);
}
TEST_CASE(
"Gravity Displacement Force Jacobian Matches All Columns And Centered "
"Differences",
tags::gravity &tags::prepared &tags::jacobian &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());
const mfem::Vector density = gravity_displacement_force_test_utils::make_density(f, 0.37);
const mfem::Vector densityDirection = gravity_displacement_force_test_utils::make_density_direction(f, 0.53);
const mfem::Vector gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.67);
const mfem::Vector gravityGradientDirection =
gravity_displacement_force_test_utils::make_gravity_gradient_direction(f, 0.71);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.59);
const mfem::Vector displacementDirection = gravity_displacement_force_test_utils::make_displacement_direction(f);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential,
gravity_displacement_force_test_utils::make_revisions()
);
mean_field::operators::PreparedGravityDisplacementForceOperator preparedOperator(
f, *f.domainMapperStateless, gravityContext
);
preparedOperator.Prepare();
mfem::Vector densityAction;
mfem::Vector gravityAction;
mfem::Vector displacementAction;
mfem::Vector completeAction;
preparedOperator.ApplyDensityJacobianAction(densityDirection, densityAction);
preparedOperator.ApplyGravityGradientJacobianAction(gravityGradientDirection, gravityAction);
preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, displacementAction);
preparedOperator.ApplyCompleteJacobianAction(
densityDirection, displacementDirection, gravityGradientDirection, completeAction
);
mfem::Vector summedColumns(densityAction);
summedColumns += gravityAction;
summedColumns += displacementAction;
CHECK(
gravity_displacement_force_test_utils::relative_difference(completeAction, summedColumns, f.mesh->GetComm()) <
2.0e-12
);
mfem::Vector zeroDensity(densityDirection.Size());
mfem::Vector zeroGravity(gravityGradientDirection.Size());
mfem::Vector zeroDisplacement(displacementDirection.Size());
zeroDensity = 0.0;
zeroGravity = 0.0;
zeroDisplacement = 0.0;
constexpr double step = 1.0e-5;
const mfem::Vector densityDifference = gravity_displacement_force_test_utils::centered_difference(
f, density, densityDirection, gravityGradient, zeroGravity, displacement, zeroDisplacement, step
);
const mfem::Vector gravityDifference = gravity_displacement_force_test_utils::centered_difference(
f, density, zeroDensity, gravityGradient, gravityGradientDirection, displacement, zeroDisplacement, step
);
const mfem::Vector displacementDifference = gravity_displacement_force_test_utils::centered_difference(
f, density, zeroDensity, gravityGradient, zeroGravity, displacement, displacementDirection, step
);
const mfem::Vector completeDifference = gravity_displacement_force_test_utils::centered_difference(
f, density, densityDirection, gravityGradient, gravityGradientDirection, displacement, displacementDirection,
step
);
const double densityError =
gravity_displacement_force_test_utils::relative_difference(densityAction, densityDifference, f.mesh->GetComm());
const double gravityError =
gravity_displacement_force_test_utils::relative_difference(gravityAction, gravityDifference, f.mesh->GetComm());
const double displacementError = gravity_displacement_force_test_utils::relative_difference(
displacementAction, displacementDifference, f.mesh->GetComm()
);
const double completeError = gravity_displacement_force_test_utils::relative_difference(
completeAction, completeDifference, f.mesh->GetComm()
);
INFO("Density-column centered-difference error = " << densityError);
INFO("Gravity-column centered-difference error = " << gravityError);
INFO("Displacement-column centered-difference error = " << displacementError);
INFO("Complete centered-difference error = " << completeError);
CHECK(densityError < 2.0e-9);
CHECK(gravityError < 2.0e-9);
CHECK(displacementError < 2.0e-8);
CHECK(completeError < 3.0e-8);
}
TEST_CASE(
"Prepared Gravity Displacement Force MFEM Adapter Routes Only R-d",
tags::gravity &tags::prepared &tags::mfem_operators &tags::unit
) {
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 mfem::Vector density = gravity_displacement_force_test_utils::make_density(f, 0.41);
const mfem::Vector densityDirection = gravity_displacement_force_test_utils::make_density_direction(f, 0.57);
const mfem::Vector gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.63);
const mfem::Vector gravityGradientDirection =
gravity_displacement_force_test_utils::make_gravity_gradient_direction(f, 0.77);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.51);
const mfem::Vector displacementDirection = gravity_displacement_force_test_utils::make_displacement_direction(f);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential,
gravity_displacement_force_test_utils::make_revisions()
);
mean_field::operators::PreparedGravityDisplacementForceOperator preparedOperator(
f, *f.domainMapperStateless, gravityContext
);
preparedOperator.Prepare();
const auto layout = gravity_displacement_force_test_utils::make_layout(f);
mean_field::operators::PreparedGravityDisplacementForceJacobianOperator adapter(layout, preparedOperator);
mfem::BlockVector direction(layout.value_offsets());
direction = 0.0;
direction.GetBlock(gravity_displacement_force_test_utils::densityValue) = densityDirection;
direction.GetBlock(gravity_displacement_force_test_utils::displacementValue) = displacementDirection;
direction.GetBlock(gravity_displacement_force_test_utils::gravityGradientValue) = gravityGradientDirection;
direction.GetBlock(gravity_displacement_force_test_utils::gravityPotentialValue) = 0.29;
direction.GetBlock(gravity_displacement_force_test_utils::enthalpyValue) = -0.37;
direction.GetBlock(gravity_displacement_force_test_utils::barotropicConstantValue) = 0.43;
mfem::Vector action;
adapter.Mult(direction, action);
mfem::Vector expectedDisplacementAction;
preparedOperator.ApplyCompleteJacobianAction(
densityDirection, displacementDirection, gravityGradientDirection, expectedDisplacementAction
);
const mfem::Vector actualDisplacementAction = gravity_displacement_force_test_utils::copy_residual_block(
action, layout, gravity_displacement_force_test_utils::displacementResidual
);
CHECK(
gravity_displacement_force_test_utils::relative_difference(
actualDisplacementAction, expectedDisplacementAction, f.mesh->GetComm()
) < 2.0e-12
);
const std::array<mfem::Vector, 5> zeroRows{
gravity_displacement_force_test_utils::copy_residual_block(
action, layout, gravity_displacement_force_test_utils::gravityGradientResidual
),
gravity_displacement_force_test_utils::copy_residual_block(
action, layout, gravity_displacement_force_test_utils::gravityPotentialResidual
),
gravity_displacement_force_test_utils::copy_residual_block(
action, layout, gravity_displacement_force_test_utils::densityResidual
),
gravity_displacement_force_test_utils::copy_residual_block(
action, layout, gravity_displacement_force_test_utils::enthalpyResidual
),
gravity_displacement_force_test_utils::copy_residual_block(
action, layout, gravity_displacement_force_test_utils::massResidual
)
};
for (const mfem::Vector &row : zeroRows) {
CHECK(gravity_prepared_test_utils::global_norm(row, f.mesh->GetComm()) == 0.0);
}
}

View File

@@ -0,0 +1,381 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <numbers>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace gravity_displacement_force_analytic_test_utils {
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 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]] 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;
}
[[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);
for (int component = 0; component < 3; ++component) {
value(component) = referenceGravity[static_cast<std::size_t>(component)];
}
}
);
gravityField.ProjectCoefficient(gravityCoefficient);
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());
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);
}
}
);
gravityField.ProjectCoefficient(gravityCoefficient);
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());
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);
}
}
);
displacementField.ProjectCoefficient(displacementCoefficient);
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());
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);
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());
mfem::VectorFunctionCoefficient testCoefficient(
f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) { value = position; }
);
testField.ProjectCoefficient(testCoefficient);
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);
densityField.SetFromTrueDofs(stellarDensityTrue);
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.");
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();
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);
constexpr double densityValue = 1.37;
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}}}
};
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);
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);
REQUIRE(mapDeterminant > 0.0);
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)];
}
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);
mfem::Vector 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);
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 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);
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();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
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 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 gravityGradient =
gravity_displacement_force_analytic_test_utils::make_radial_gravity(f, radialGravityCoefficient);
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0;
mfem::Vector 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 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 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);
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);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
REQUIRE(f.domainMapperStateless != nullptr);
mfem::ParGridFunction displacementField(f.displacementFes.get());
displacementField = 0.0;
REQUIRE(f.mapping != nullptr);
f.mapping->ResetDisplacement();
mean_field::physics::update_stiffness_matrix(f);
const double radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS;
mfem::ParGridFunction densityField(f.densityFes.get());
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);
mfem::Vector densityTrue;
mfem::Vector gravityGradientTrue;
mfem::Vector displacementTrue;
densityField.GetTrueDofs(densityTrue);
gravitySolution.gradPhi.GetTrueDofs(gravityGradientTrue);
displacementField.GetTrueDofs(displacementTrue);
mfem::Vector 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 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 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);
REQUIRE(computedWork > 0.0);
CHECK(relativeError < 1.0e-5);
}

File diff suppressed because it is too large Load Diff

View File

@@ -44,23 +44,17 @@ namespace {
}
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);
}
);
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);
}
);
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);
}
@@ -69,23 +63,20 @@ namespace {
TEST_CASE(
"Barotropic Closure Vanishes For A Representable Constant State",
tags::hydro &tags::residuals &tags::unit &tags::closure &tags::kernels
&tags::barotrope
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);
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 mean_field::eos::Polytrope barotrope(3.0, 1.5);
constexpr double enthalpyValue = 0.8;
constexpr double enthalpyValue = 0.8;
const double densityValue = barotrope.density_from_enthalpy(enthalpyValue);
const double densityValue = barotrope.density_from_enthalpy(enthalpyValue);
const mfem::Vector enthalpy =
project_constant(*f.enthalpyFes, enthalpyValue);
const mfem::Vector enthalpy = project_constant(*f.enthalpyFes, enthalpyValue);
const mfem::Vector density = project_constant(*f.densityFes, densityValue);
const mfem::Vector density = project_constant(*f.densityFes, densityValue);
const mfem::Vector displacement = make_zero_displacement(f);
@@ -93,19 +84,17 @@ TEST_CASE(
mfem::Vector scale;
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, barotrope, density, enthalpy, displacement,
residual
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 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);
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);
@@ -114,40 +103,34 @@ TEST_CASE(
TEST_CASE(
"Barotropic Closure Density Action Matches The Stellar Mass Matrix",
tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels
&tags::barotrope
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);
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 mean_field::eos::Polytrope 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
);
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
f, *f.domainMapperStateless, barotrope, densityVariation, displacement, kernelAction
);
using Schema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using Stellar = mean_field::utils::domain::Stellar;
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) {
for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size(); ++attributeIndex) {
const int attribute = f.mesh->attributes[attributeIndex];
if (attribute != vacuumAttribute) {
if (Schema::template attribute_belongs_to<Stellar>(attribute)) {
stellarMarker[attribute - 1] = 1;
}
}
@@ -159,9 +142,7 @@ TEST_CASE(
massForm.Assemble();
massForm.Finalize();
std::unique_ptr<mfem::HypreParMatrix> massMatrix(
massForm.ParallelAssemble()
);
std::unique_ptr<mfem::HypreParMatrix> massMatrix(massForm.ParallelAssemble());
REQUIRE(massMatrix != nullptr);
REQUIRE(massMatrix->Width() == densityVariation.Size());
@@ -170,9 +151,8 @@ TEST_CASE(
referenceAction = 0.0;
massMatrix->Mult(densityVariation, referenceAction);
const double relativeError = gravity_prepared_test_utils::relative_error(
kernelAction, referenceAction, f.mesh->GetComm()
);
const double relativeError =
gravity_prepared_test_utils::relative_error(kernelAction, referenceAction, f.mesh->GetComm());
INFO("Density-action mass-matrix error = " << relativeError);
@@ -181,32 +161,24 @@ TEST_CASE(
TEST_CASE(
"Barotropic Closure Jacobian Matches A Combined Centered Difference",
tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels
&tags::barotrope
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);
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 mean_field::eos::Polytrope 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 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 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::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());
@@ -225,46 +197,33 @@ TEST_CASE(
enthalpyVariationField.GetTrueDofs(enthalpyVariation);
const mfem::Vector densityVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.densityFes->GetTrueVSize(), 0.63
);
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);
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
);
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
);
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
);
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
);
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
f, *f.domainMapperStateless, barotrope, plusDensity, plusEnthalpy, displacement, plusResidual
);
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, barotrope, minusDensity, minusEnthalpy,
displacement, minusResidual
f, *f.domainMapperStateless, barotrope, minusDensity, minusEnthalpy, displacement, minusResidual
);
mfem::Vector finiteDifference(plusResidual);
@@ -275,21 +234,18 @@ TEST_CASE(
mfem::Vector enthalpyAction;
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, barotrope, densityVariation, displacement,
densityAction
f, *f.domainMapperStateless, barotrope, densityVariation, displacement, densityAction
);
mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action(
f, *f.domainMapperStateless, barotrope, enthalpy, enthalpyVariation,
displacement, enthalpyAction
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()
);
const double relativeError =
gravity_prepared_test_utils::relative_error(analyticAction, finiteDifference, f.mesh->GetComm());
INFO("Combined EOS Jacobian error = " << relativeError);
@@ -298,57 +254,45 @@ TEST_CASE(
TEST_CASE(
"Barotropic Closure Density Action Excludes Vacuum And Uses Mapped Volume",
tags::hydro &tags::mapping &tags::unit &tags::closure &tags::barotrope
&tags::kernels
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);
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 mean_field::eos::Polytrope barotrope(3.0, 1.5);
const mfem::Vector stellarDensity =
gravity_prepared_test_utils::make_domain_supported_density(f, true);
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 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 identityDisplacement = gravity_prepared_test_utils::make_displacement(f, 0.0);
const mfem::Vector deformedDisplacement =
gravity_prepared_test_utils::make_displacement(f, 1.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
f, *f.domainMapperStateless, barotrope, stellarDensity, identityDisplacement, stellarAction
);
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, barotrope, vacuumDensity,
identityDisplacement, vacuumAction
f, *f.domainMapperStateless, barotrope, vacuumDensity, identityDisplacement, vacuumAction
);
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, barotrope, stellarDensity,
deformedDisplacement, deformedAction
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 stellarNorm = gravity_prepared_test_utils::global_norm(stellarAction, communicator);
const double vacuumNorm =
gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
const double vacuumNorm = gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
const double geometryChange = gravity_prepared_test_utils::relative_error(
deformedAction, stellarAction, communicator
);
const double geometryChange =
gravity_prepared_test_utils::relative_error(deformedAction, stellarAction, communicator);
INFO("Stellar action norm = " << stellarNorm);
INFO("Vacuum action norm = " << vacuumNorm);
@@ -361,37 +305,30 @@ TEST_CASE(
TEST_CASE(
"Barotropic Closure Displacement Action Matches Centered Differences",
tags::barotrope &tags::closure &tags::hydro &tags::integration
&tags::jacobian &tags::mapping &tags::physics
tags::barotrope &tags::closure &tags::hydro &tags::integration &tags::jacobian &tags::mapping &tags::physics
&tags::kernels
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.domainMapperStateless != nullptr);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
const mean_field::eos::Polytrope barotrope(3.0, 1.5);
const mfem::Vector baseDensity =
barotropic_closure_geometry_test_utils::make_base_density(f);
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 baseEnthalpy = barotropic_closure_geometry_test_utils::make_base_enthalpy(f);
const mfem::Vector displacementVariation =
gravity_prepared_test_utils::make_displacement(f, 0.65);
const mfem::Vector displacementVariation = gravity_prepared_test_utils::make_displacement(f, 0.65);
constexpr double differenceStep = 1.0e-5;
constexpr double differenceStep = 1.0e-5;
const MPI_Comm communicator = f.mesh->GetComm();
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
);
const mfem::Vector baseDisplacement = gravity_prepared_test_utils::make_displacement(f, deformationScale);
mfem::Vector plusDisplacement(baseDisplacement);
@@ -406,50 +343,36 @@ TEST_CASE(
mfem::Vector analyticAction;
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, barotrope, baseDensity,
baseEnthalpy, plusDisplacement, plusResidual
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, plusDisplacement, plusResidual
);
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, barotrope, baseDensity,
baseEnthalpy, minusDisplacement, minusResidual
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
);
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 analyticNorm = gravity_prepared_test_utils::global_norm(analyticAction, communicator);
const double finiteDifferenceNorm =
gravity_prepared_test_utils::global_norm(
finiteDifference, communicator
);
gravity_prepared_test_utils::global_norm(finiteDifference, communicator);
const double relativeError =
gravity_prepared_test_utils::relative_error(
analyticAction, finiteDifference, communicator
);
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("Finite-difference geometry-action norm = " << finiteDifferenceNorm);
INFO("Geometry-action relative error = " << relativeError);
@@ -463,34 +386,26 @@ TEST_CASE(
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
tags::barotrope &tags::closure &tags::hydro &tags::jacobian &tags::mapping &tags::physics &tags::unit &tags::kernels
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.domainMapperStateless != nullptr);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
const mean_field::eos::Polytrope barotrope(3.0, 1.5);
const mfem::Vector baseDensity =
barotropic_closure_geometry_test_utils::make_base_density(f);
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 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 baseDisplacement = gravity_prepared_test_utils::make_displacement(f, 0.8);
const mfem::Vector firstDirection =
gravity_prepared_test_utils::make_displacement(f, 0.4);
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
);
gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.91);
secondDirection *= 0.01;
@@ -511,29 +426,23 @@ TEST_CASE(
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, 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, 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, combinedDirection,
combinedAction
);
mean_field::operators::kernels::
apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
baseDisplacement, zeroDirection, zeroAction
);
mean_field::operators::kernels::apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, baseDisplacement, zeroDirection, zeroAction
);
mfem::Vector expectedAction(firstAction);
@@ -543,15 +452,12 @@ TEST_CASE(
const MPI_Comm communicator = f.mesh->GetComm();
const double expectedNorm =
gravity_prepared_test_utils::global_norm(expectedAction, communicator);
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 linearityError =
gravity_prepared_test_utils::relative_error(combinedAction, expectedAction, communicator);
const double zeroActionNorm =
gravity_prepared_test_utils::global_norm(zeroAction, communicator);
const double zeroActionNorm = gravity_prepared_test_utils::global_norm(zeroAction, communicator);
INFO("Expected combined-action norm = " << expectedNorm);
@@ -568,55 +474,45 @@ TEST_CASE(
TEST_CASE(
"Barotropic Closure Displacement Action Excludes Vacuum",
tags::barotrope &tags::closure &tags::hydro &tags::mapping &tags::physics
&tags::unit
tags::barotrope &tags::closure &tags::hydro &tags::mapping &tags::physics &tags::unit &tags::kernels
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.domainMapperStateless != nullptr);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
const mean_field::eos::Polytrope barotrope(3.0, 1.5);
const mfem::Vector stellarDensity =
gravity_prepared_test_utils::make_domain_supported_density(f, true);
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 vacuumDensity = gravity_prepared_test_utils::make_domain_supported_density(f, false);
mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize());
zeroEnthalpy = 0.0;
zeroEnthalpy = 0.0;
const mfem::Vector baseDisplacement =
gravity_prepared_test_utils::make_displacement(f, 0.7);
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);
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, stellarDensity, zeroEnthalpy, baseDisplacement, displacementVariation,
stellarAction
);
mean_field::operators::kernels::
apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, barotrope, vacuumDensity, zeroEnthalpy,
baseDisplacement, displacementVariation, vacuumAction
);
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 stellarNorm = gravity_prepared_test_utils::global_norm(stellarAction, communicator);
const double vacuumNorm =
gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
const double vacuumNorm = gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
INFO("Stellar geometry-action norm = " << stellarNorm);

View File

@@ -34,8 +34,7 @@ namespace hydrostatic_kernel_test_utils {
mfem::Vector make_enthalpy(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
return 1.10 + 0.035 * position(0) - 0.021 * position(1) +
0.014 * position(2);
return 1.10 + 0.035 * position(0) - 0.021 * position(1) + 0.014 * position(2);
});
return project_scalar(*f.enthalpyFes, coefficient);
@@ -43,8 +42,7 @@ namespace hydrostatic_kernel_test_utils {
mfem::Vector make_potential(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
return -0.72 + 0.018 * position(0) + 0.011 * position(1) -
0.025 * position(2);
return -0.72 + 0.018 * position(0) + 0.011 * position(1) - 0.025 * position(2);
});
return project_scalar(*f.gravityPotentialFes, coefficient);
@@ -98,23 +96,18 @@ namespace hydrostatic_kernel_test_utils {
mfem::Vector difference(computed);
difference -= reference;
return gravity_prepared_test_utils::global_norm(
difference, communicator
) /
return gravity_prepared_test_utils::global_norm(difference, communicator) /
std::max(normalization, std::numeric_limits<double>::epsilon());
}
mfem::Vector
make_vacuum_supported_potential(const mean_field::fem::FEM &f) {
mfem::Vector make_vacuum_supported_potential(const mean_field::fem::FEM &f) {
mfem::Vector attributeValues(f.mesh->attributes.Max());
attributeValues = 0.0;
attributeValues = 0.0;
const int vacuumAttribute =
f.domainMapperStateless->GetVacuumElementAttribute();
const int vacuumAttribute = f.domainMapperStateless->GetVacuumElementAttribute();
for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size();
++attributeIndex) {
for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size(); ++attributeIndex) {
const int attribute = f.mesh->attributes[attributeIndex];
if (attribute == vacuumAttribute) {
@@ -144,10 +137,9 @@ namespace hydrostatic_kernel_test_utils {
const mfem::Vector &input,
mfem::Vector &output
) const override {
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_enthalpy_action(
f_, domainMapper_, input, displacementTrue_, output
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_enthalpy_action(
f_, domainMapper_, input, displacementTrue_, output
);
}
private:
@@ -161,10 +153,9 @@ namespace hydrostatic_kernel_test_utils {
TEST_CASE(
"Rigid Rotation Potential Derivative Matches Centered Differences",
tags::barotrope &tags::hydro &tags::jacobian &tags::physics &tags::unit
tags::barotrope &tags::hydro &tags::jacobian &tags::physics &tags::unit &tags::kernels
) {
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_rotation();
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
mfem::Vector position(3);
mfem::Vector direction(3);
@@ -186,17 +177,12 @@ TEST_CASE(
minusPosition.Add(-epsilon, direction);
const double centeredDerivative =
(rotation.potential(plusPosition) - rotation.potential(minusPosition)) /
(2.0 * epsilon);
(rotation.potential(plusPosition) - rotation.potential(minusPosition)) / (2.0 * epsilon);
const double analyticDerivative =
rotation.potential_directional_derivative(position, direction);
const double analyticDerivative = rotation.potential_directional_derivative(position, direction);
const double relativeError =
std::abs(centeredDerivative - analyticDerivative) /
std::max(
std::abs(analyticDerivative), std::numeric_limits<double>::epsilon()
);
const double relativeError = std::abs(centeredDerivative - analyticDerivative) /
std::max(std::abs(analyticDerivative), std::numeric_limits<double>::epsilon());
INFO("Rigid-rotation derivative error = " << relativeError);
@@ -205,42 +191,31 @@ TEST_CASE(
TEST_CASE(
"Hydrostatic Residual Vanishes For A Manufactured Rotating State",
tags::barotrope &tags::hydro &tags::integration &tags::kernels
&tags::physics &tags::residuals
tags::barotrope &tags::hydro &tags::integration &tags::kernels &tags::physics &tags::residuals
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_rotation();
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
constexpr double bernoulliConstant = 0.73;
constexpr double potentialValue = -0.21;
constexpr double constantOffset = 0.40;
constexpr double bernoulliConstant = 0.73;
constexpr double potentialValue = -0.21;
constexpr double constantOffset = 0.40;
mfem::FunctionCoefficient enthalpyCoefficient(
[&rotation](const mfem::Vector &position) {
return bernoulliConstant - potentialValue +
rotation.potential(position);
}
);
mfem::FunctionCoefficient enthalpyCoefficient([&rotation](const mfem::Vector &position) {
return bernoulliConstant - potentialValue + rotation.potential(position);
});
const mfem::Vector interpolatedEnthalpy =
hydrostatic_kernel_test_utils::project_scalar(
*f.enthalpyFes, enthalpyCoefficient
);
hydrostatic_kernel_test_utils::project_scalar(*f.enthalpyFes, enthalpyCoefficient);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_constant_field(
*f.gravityPotentialFes, potentialValue
);
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, potentialValue);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.0);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.0);
const MPI_Comm communicator = f.mesh->GetComm();
const MPI_Comm communicator = f.mesh->GetComm();
/*
* First measure the residual of the nodally interpolated
@@ -253,35 +228,26 @@ TEST_CASE(
mfem::Vector interpolatedReferenceResidual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential,
displacement, bernoulliConstant, interpolatedResidual
f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential, displacement, bernoulliConstant,
interpolatedResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential,
displacement, bernoulliConstant + constantOffset,
interpolatedReferenceResidual
f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential, displacement,
bernoulliConstant + constantOffset, interpolatedReferenceResidual
);
const double interpolatedResidualNorm =
gravity_prepared_test_utils::global_norm(
interpolatedResidual, communicator
);
gravity_prepared_test_utils::global_norm(interpolatedResidual, communicator);
const double interpolatedReferenceNorm =
gravity_prepared_test_utils::global_norm(
interpolatedReferenceResidual, communicator
);
gravity_prepared_test_utils::global_norm(interpolatedReferenceResidual, communicator);
REQUIRE(interpolatedReferenceNorm > 1.0e-12);
const double representationFloor =
interpolatedResidualNorm / interpolatedReferenceNorm;
const double representationFloor = interpolatedResidualNorm / interpolatedReferenceNorm;
INFO(
"Interpolated rotating-state residual norm = "
<< interpolatedResidualNorm
);
INFO("Interpolated rotating-state residual norm = " << interpolatedResidualNorm);
INFO(
"Interpolated rotating-state relative "
@@ -310,8 +276,9 @@ TEST_CASE(
* side is in the range of M_h. Starting CG from zero keeps the
* iteration in the active stellar subspace.
*/
hydrostatic_kernel_test_utils::HydrostaticEnthalpyMassOperator
enthalpyMassOperator(f, *f.domainMapperStateless, displacement);
hydrostatic_kernel_test_utils::HydrostaticEnthalpyMassOperator enthalpyMassOperator(
f, *f.domainMapperStateless, displacement
);
mfem::Vector correctionRightHandSide(interpolatedResidual);
@@ -332,20 +299,11 @@ TEST_CASE(
projectionSolver.Mult(correctionRightHandSide, enthalpyCorrection);
INFO(
"Discrete-equilibrium projection converged = "
<< projectionSolver.GetConverged()
);
INFO("Discrete-equilibrium projection converged = " << projectionSolver.GetConverged());
INFO(
"Discrete-equilibrium projection iterations = "
<< projectionSolver.GetNumIterations()
);
INFO("Discrete-equilibrium projection iterations = " << projectionSolver.GetNumIterations());
INFO(
"Discrete-equilibrium projection final norm = "
<< projectionSolver.GetFinalNorm()
);
INFO("Discrete-equilibrium projection final norm = " << projectionSolver.GetFinalNorm());
REQUIRE(projectionSolver.GetConverged());
@@ -356,9 +314,7 @@ TEST_CASE(
correctionEquationResidual -= correctionRightHandSide;
const double correctionEquationNorm =
gravity_prepared_test_utils::global_norm(
correctionEquationResidual, communicator
);
gravity_prepared_test_utils::global_norm(correctionEquationResidual, communicator);
INFO(
"Discrete-equilibrium correction-equation "
@@ -366,10 +322,7 @@ TEST_CASE(
<< correctionEquationNorm
);
CHECK(
correctionEquationNorm <=
std::max(5.0e-12 * interpolatedResidualNorm, 5.0e-15)
);
CHECK(correctionEquationNorm <= std::max(5.0e-12 * interpolatedResidualNorm, 5.0e-15));
mfem::Vector discreteEnthalpy(interpolatedEnthalpy);
@@ -379,25 +332,20 @@ TEST_CASE(
mfem::Vector referenceResidual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential,
displacement, bernoulliConstant, exactResidual
f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential, displacement, bernoulliConstant,
exactResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential,
displacement, bernoulliConstant + constantOffset, referenceResidual
f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential, displacement,
bernoulliConstant + constantOffset, referenceResidual
);
const double exactNorm =
gravity_prepared_test_utils::global_norm(exactResidual, communicator);
const double exactNorm = gravity_prepared_test_utils::global_norm(exactResidual, communicator);
const double referenceNorm = gravity_prepared_test_utils::global_norm(
referenceResidual, communicator
);
const double referenceNorm = gravity_prepared_test_utils::global_norm(referenceResidual, communicator);
const double correctionNorm = gravity_prepared_test_utils::global_norm(
enthalpyCorrection, communicator
);
const double correctionNorm = gravity_prepared_test_utils::global_norm(enthalpyCorrection, communicator);
INFO("Enthalpy representation correction norm = " << correctionNorm);
@@ -412,43 +360,31 @@ TEST_CASE(
TEST_CASE(
"Exact Constant Hydrostatic Equilibrium Remains Zero Under Deformation",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian
&tags::kernels &tags::mapping &tags::physics
tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::kernels &tags::mapping &tags::physics
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_zero_rotation();
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_zero_rotation();
constexpr double enthalpyValue = 1.20;
constexpr double potentialValue = -0.35;
constexpr double enthalpyValue = 1.20;
constexpr double potentialValue = -0.35;
constexpr double bernoulliConstant = enthalpyValue + potentialValue;
constexpr double bernoulliConstant = enthalpyValue + potentialValue;
const mfem::Vector enthalpy =
hydrostatic_kernel_test_utils::make_constant_field(
*f.enthalpyFes, enthalpyValue
);
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_constant_field(*f.enthalpyFes, enthalpyValue);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_constant_field(
*f.gravityPotentialFes, potentialValue
);
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, potentialValue);
const mfem::Vector displacementVariation =
gravity_prepared_test_utils::make_displacement(f, 0.67);
const mfem::Vector displacementVariation = gravity_prepared_test_utils::make_displacement(f, 0.67);
const MPI_Comm communicator = f.mesh->GetComm();
const MPI_Comm communicator = f.mesh->GetComm();
for (const double deformationScale : {0.0, 0.5, 1.0}) {
DYNAMIC_SECTION("Deformation scale = " << deformationScale) {
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(
f, deformationScale
);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, deformationScale);
mfem::Vector exactResidual;
mfem::Vector referenceResidual;
@@ -456,48 +392,35 @@ TEST_CASE(
mfem::Vector referenceGeometryAction;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, exactResidual
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant,
exactResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant + 0.50, referenceResidual
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant + 0.50,
referenceResidual
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, displacementVariation,
exactGeometryAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant,
displacementVariation, exactGeometryAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant + 0.50,
displacementVariation, referenceGeometryAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant + 0.50,
displacementVariation, referenceGeometryAction
);
const double exactResidualNorm =
gravity_prepared_test_utils::global_norm(
exactResidual, communicator
);
const double exactResidualNorm = gravity_prepared_test_utils::global_norm(exactResidual, communicator);
const double referenceResidualNorm =
gravity_prepared_test_utils::global_norm(
referenceResidual, communicator
);
gravity_prepared_test_utils::global_norm(referenceResidual, communicator);
const double exactGeometryNorm =
gravity_prepared_test_utils::global_norm(
exactGeometryAction, communicator
);
gravity_prepared_test_utils::global_norm(exactGeometryAction, communicator);
const double referenceGeometryNorm =
gravity_prepared_test_utils::global_norm(
referenceGeometryAction, communicator
);
gravity_prepared_test_utils::global_norm(referenceGeometryAction, communicator);
REQUIRE(referenceResidualNorm > 1.0e-12);
@@ -512,64 +435,49 @@ TEST_CASE(
TEST_CASE(
"Hydrostatic Equilibrium Excludes Vacuum Elements",
tags::barotrope &tags::hydro &tags::kernels &tags::mapping &tags::physics
&tags::unit
tags::barotrope &tags::hydro &tags::kernels &tags::mapping &tags::physics &tags::unit
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_zero_rotation();
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_zero_rotation();
const mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize());
mfem::Vector enthalpy(zeroEnthalpy);
enthalpy = 0.0;
enthalpy = 0.0;
const mfem::Vector vacuumPotential =
hydrostatic_kernel_test_utils::make_vacuum_supported_potential(f);
const mfem::Vector vacuumPotential = hydrostatic_kernel_test_utils::make_vacuum_supported_potential(f);
const mfem::Vector stellarPotential =
hydrostatic_kernel_test_utils::make_constant_field(
*f.gravityPotentialFes, 1.0
);
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, 1.0);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
mfem::Vector residual;
mfem::Vector vacuumAction;
mfem::Vector stellarAction;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, vacuumPotential,
displacement, 0.0, residual
f, *f.domainMapperStateless, rotation, enthalpy, vacuumPotential, displacement, 0.0, residual
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, vacuumPotential, displacement,
vacuumAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, vacuumPotential, displacement, vacuumAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, stellarPotential, displacement,
stellarAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, stellarPotential, displacement, stellarAction
);
const MPI_Comm communicator = f.mesh->GetComm();
const MPI_Comm communicator = f.mesh->GetComm();
const double residualNorm =
gravity_prepared_test_utils::global_norm(residual, communicator);
const double residualNorm = gravity_prepared_test_utils::global_norm(residual, communicator);
const double vacuumActionNorm =
gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
const double vacuumActionNorm = gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
const double stellarActionNorm =
gravity_prepared_test_utils::global_norm(stellarAction, communicator);
const double stellarActionNorm = gravity_prepared_test_utils::global_norm(stellarAction, communicator);
REQUIRE(stellarActionNorm > 1.0e-12);
@@ -580,40 +488,28 @@ TEST_CASE(
TEST_CASE(
"Hydrostatic Jacobian Matches Blocks And Centered Differences",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian
&tags::kernels &tags::mapping &tags::physics
tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::kernels &tags::mapping &tags::physics
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_rotation();
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
const mfem::Vector enthalpy =
hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector enthalpyVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), 0.23
);
gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.23);
const mfem::Vector potentialVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), 0.47
);
gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.47);
const mfem::Vector displacementVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.displacementFes->GetTrueVSize(), 0.71
);
gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.71);
constexpr double bernoulliConstant = 0.41;
constexpr double constantVariation = -0.37;
@@ -625,35 +521,26 @@ TEST_CASE(
mfem::Vector displacementAction;
mfem::Vector completeAction;
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_enthalpy_action(
f, *f.domainMapperStateless, enthalpyVariation, displacement,
enthalpyAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_enthalpy_action(
f, *f.domainMapperStateless, enthalpyVariation, displacement, enthalpyAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, potentialVariation, displacement,
potentialAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, potentialVariation, displacement, potentialAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_constant_action(
f, *f.domainMapperStateless, constantVariation, displacement,
constantAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_constant_action(
f, *f.domainMapperStateless, constantVariation, displacement, constantAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, displacementVariation,
displacementAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant,
displacementVariation, displacementAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, enthalpyVariation, potentialVariation,
constantVariation, displacementVariation, completeAction
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, enthalpyVariation,
potentialVariation, constantVariation, displacementVariation, completeAction
);
mfem::Vector blockAction(enthalpyAction);
@@ -663,25 +550,21 @@ TEST_CASE(
const MPI_Comm communicator = f.mesh->GetComm();
const double blockError = gravity_prepared_test_utils::relative_error(
completeAction, blockAction, communicator
);
const double blockError = gravity_prepared_test_utils::relative_error(completeAction, blockAction, communicator);
INFO("Hydrostatic block reconstruction error = " << blockError);
CHECK(blockError < 5.0e-13);
auto evaluate_residual = [&f, &rotation](
const mfem::Vector &trialEnthalpy,
const mfem::Vector &trialPotential,
const mfem::Vector &trialDisplacement,
const double trialConstant
const mfem::Vector &trialEnthalpy, const mfem::Vector &trialPotential,
const mfem::Vector &trialDisplacement, const double trialConstant
) {
mfem::Vector residual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, trialEnthalpy,
trialPotential, trialDisplacement, trialConstant, residual
f, *f.domainMapperStateless, rotation, trialEnthalpy, trialPotential, trialDisplacement, trialConstant,
residual
);
return residual;
@@ -694,16 +577,10 @@ TEST_CASE(
minusEnthalpy.Add(-epsilon, enthalpyVariation);
const mfem::Vector enthalpyDifference =
hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(
plusEnthalpy, potential, displacement, bernoulliConstant
),
evaluate_residual(
minusEnthalpy, potential, displacement, bernoulliConstant
),
epsilon
);
const mfem::Vector enthalpyDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(plusEnthalpy, potential, displacement, bernoulliConstant),
evaluate_residual(minusEnthalpy, potential, displacement, bernoulliConstant), epsilon
);
mfem::Vector plusPotential(potential);
mfem::Vector minusPotential(potential);
@@ -712,29 +589,15 @@ TEST_CASE(
minusPotential.Add(-epsilon, potentialVariation);
const mfem::Vector potentialDifference =
hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(
enthalpy, plusPotential, displacement, bernoulliConstant
),
evaluate_residual(
enthalpy, minusPotential, displacement, bernoulliConstant
),
epsilon
);
const mfem::Vector potentialDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, plusPotential, displacement, bernoulliConstant),
evaluate_residual(enthalpy, minusPotential, displacement, bernoulliConstant), epsilon
);
const mfem::Vector constantDifference =
hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(
enthalpy, potential, displacement,
bernoulliConstant + epsilon * constantVariation
),
evaluate_residual(
enthalpy, potential, displacement,
bernoulliConstant - epsilon * constantVariation
),
epsilon
);
const mfem::Vector constantDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, potential, displacement, bernoulliConstant + epsilon * constantVariation),
evaluate_residual(enthalpy, potential, displacement, bernoulliConstant - epsilon * constantVariation), epsilon
);
mfem::Vector plusDisplacement(displacement);
mfem::Vector minusDisplacement(displacement);
@@ -743,33 +606,22 @@ TEST_CASE(
minusDisplacement.Add(-epsilon, displacementVariation);
const mfem::Vector displacementDifference =
hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(
enthalpy, potential, plusDisplacement, bernoulliConstant
),
evaluate_residual(
enthalpy, potential, minusDisplacement, bernoulliConstant
),
epsilon
);
const double enthalpyError = gravity_prepared_test_utils::relative_error(
enthalpyAction, enthalpyDifference, communicator
const mfem::Vector displacementDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, potential, plusDisplacement, bernoulliConstant),
evaluate_residual(enthalpy, potential, minusDisplacement, bernoulliConstant), epsilon
);
const double potentialError = gravity_prepared_test_utils::relative_error(
potentialAction, potentialDifference, communicator
);
const double enthalpyError =
gravity_prepared_test_utils::relative_error(enthalpyAction, enthalpyDifference, communicator);
const double constantError = gravity_prepared_test_utils::relative_error(
constantAction, constantDifference, communicator
);
const double potentialError =
gravity_prepared_test_utils::relative_error(potentialAction, potentialDifference, communicator);
const double constantError =
gravity_prepared_test_utils::relative_error(constantAction, constantDifference, communicator);
const double displacementError =
gravity_prepared_test_utils::relative_error(
displacementAction, displacementDifference, communicator
);
gravity_prepared_test_utils::relative_error(displacementAction, displacementDifference, communicator);
INFO("Hydrostatic enthalpy-block error = " << enthalpyError);
@@ -803,35 +655,26 @@ TEST_CASE(
combinedMinusDisplacement.Add(-epsilon, displacementVariation);
const mfem::Vector combinedDifference =
hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(
combinedPlusEnthalpy, combinedPlusPotential,
combinedPlusDisplacement,
bernoulliConstant + epsilon * constantVariation
),
evaluate_residual(
combinedMinusEnthalpy, combinedMinusPotential,
combinedMinusDisplacement,
bernoulliConstant - epsilon * constantVariation
),
epsilon
);
const mfem::Vector combinedDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(
combinedPlusEnthalpy, combinedPlusPotential, combinedPlusDisplacement,
bernoulliConstant + epsilon * constantVariation
),
evaluate_residual(
combinedMinusEnthalpy, combinedMinusPotential, combinedMinusDisplacement,
bernoulliConstant - epsilon * constantVariation
),
epsilon
);
const double blockNormSum =
gravity_prepared_test_utils::global_norm(enthalpyAction, communicator) +
gravity_prepared_test_utils::global_norm(
potentialAction, communicator
) +
gravity_prepared_test_utils::global_norm(constantAction, communicator) +
gravity_prepared_test_utils::global_norm(
displacementAction, communicator
);
const double blockNormSum = gravity_prepared_test_utils::global_norm(enthalpyAction, communicator) +
gravity_prepared_test_utils::global_norm(potentialAction, communicator) +
gravity_prepared_test_utils::global_norm(constantAction, communicator) +
gravity_prepared_test_utils::global_norm(displacementAction, communicator);
const double simultaneousError =
hydrostatic_kernel_test_utils::sum_normalized_error(
completeAction, combinedDifference, blockNormSum, communicator
);
const double simultaneousError = hydrostatic_kernel_test_utils::sum_normalized_error(
completeAction, combinedDifference, blockNormSum, communicator
);
INFO("Hydrostatic simultaneous Jacobian error = " << simultaneousError);
@@ -840,75 +683,58 @@ TEST_CASE(
TEST_CASE(
"Hydrostatic Displacement Action Is Linear In Its Direction",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian
&tags::mapping &tags::physics &tags::unit
tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::mapping &tags::physics &tags::unit
&tags::kernels
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation =
hydrostatic_kernel_test_utils::make_rotation();
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
const mfem::Vector enthalpy =
hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector firstDirection =
gravity_prepared_test_utils::make_deterministic_vector(
f.displacementFes->GetTrueVSize(), 0.31
);
gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.31);
const mfem::Vector secondDirection =
gravity_prepared_test_utils::make_deterministic_vector(
f.displacementFes->GetTrueVSize(), 0.83
);
gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.83);
constexpr double firstScale = 0.43;
constexpr double secondScale = -0.29;
constexpr double bernoulliConstant = 0.41;
const mfem::Vector combinedDirection =
gravity_prepared_test_utils::linear_combination(
firstDirection, firstScale, secondDirection, secondScale
);
gravity_prepared_test_utils::linear_combination(firstDirection, firstScale, secondDirection, secondScale);
mfem::Vector firstAction;
mfem::Vector secondAction;
mfem::Vector combinedAction;
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, firstDirection, firstAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, firstDirection,
firstAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, secondDirection, secondAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, secondDirection,
secondAction
);
mean_field::operators::kernels::
apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential,
displacement, bernoulliConstant, combinedDirection, combinedAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, combinedDirection,
combinedAction
);
const mfem::Vector expectedAction =
gravity_prepared_test_utils::linear_combination(
firstAction, firstScale, secondAction, secondScale
);
gravity_prepared_test_utils::linear_combination(firstAction, firstScale, secondAction, secondScale);
const double linearityError = gravity_prepared_test_utils::relative_error(
combinedAction, expectedAction, f.mesh->GetComm()
);
const double linearityError =
gravity_prepared_test_utils::relative_error(combinedAction, expectedAction, f.mesh->GetComm());
INFO("Hydrostatic displacement-linearity error = " << linearityError);
@@ -917,13 +743,11 @@ TEST_CASE(
TEST_CASE(
"Hydrostatic Residual Is Translationally Invariant On Deformed Geometry",
tags::barotrope &tags::hydro &tags::integration &tags::kernels
&tags::mapping &tags::physics &tags::residuals
tags::barotrope &tags::hydro &tags::integration &tags::kernels &tags::mapping &tags::physics &tags::residuals
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mfem::Vector angularVelocity(3);
@@ -946,33 +770,25 @@ TEST_CASE(
mfem::Vector translatedCenter(center);
translatedCenter += translation;
const mean_field::physics::RigidRotation baseRotation(
angularVelocity, center
);
const mean_field::physics::RigidRotation baseRotation(angularVelocity, center);
const mean_field::physics::RigidRotation translatedRotation(
angularVelocity, translatedCenter
);
const mean_field::physics::RigidRotation translatedRotation(angularVelocity, translatedCenter);
const mfem::Vector enthalpy =
hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f);
/*
* Use a nontrivially deformed base state so this checks rotation
* and mapped geometry simultaneously. The comparison state adds
* an exactly representable rigid translation to that deformation.
*/
const mfem::Vector baseDisplacement =
gravity_prepared_test_utils::make_displacement(f, 0.73);
const mfem::Vector baseDisplacement = gravity_prepared_test_utils::make_displacement(f, 0.73);
mfem::ParGridFunction translationField(f.displacementFes.get());
mfem::VectorFunctionCoefficient translationCoefficient(
f.mesh->Dimension(),
[&translation](const mfem::Vector &, mfem::Vector &value) {
f.mesh->Dimension(), [&translation](const mfem::Vector &, mfem::Vector &value) {
value.SetSize(translation.Size());
value = translation;
}
@@ -994,13 +810,13 @@ TEST_CASE(
mfem::Vector untranslatedCenterResidual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, baseRotation, enthalpy, potential,
baseDisplacement, bernoulliConstant, baseResidual
f, *f.domainMapperStateless, baseRotation, enthalpy, potential, baseDisplacement, bernoulliConstant,
baseResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, translatedRotation, enthalpy, potential,
translatedDisplacement, bernoulliConstant, translatedResidual
f, *f.domainMapperStateless, translatedRotation, enthalpy, potential, translatedDisplacement, bernoulliConstant,
translatedResidual
);
/*
@@ -1008,43 +824,29 @@ TEST_CASE(
* center fixed. This must not agree with the covariant result.
*/
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, baseRotation, enthalpy, potential,
translatedDisplacement, bernoulliConstant, untranslatedCenterResidual
f, *f.domainMapperStateless, baseRotation, enthalpy, potential, translatedDisplacement, bernoulliConstant,
untranslatedCenterResidual
);
const MPI_Comm communicator = f.mesh->GetComm();
const MPI_Comm communicator = f.mesh->GetComm();
const double baseResidualNorm =
gravity_prepared_test_utils::global_norm(baseResidual, communicator);
const double baseResidualNorm = gravity_prepared_test_utils::global_norm(baseResidual, communicator);
const double translatedResidualNorm =
gravity_prepared_test_utils::global_norm(
translatedResidual, communicator
);
const double translatedResidualNorm = gravity_prepared_test_utils::global_norm(translatedResidual, communicator);
const double translationInvarianceError =
gravity_prepared_test_utils::relative_error(
translatedResidual, baseResidual, communicator
);
gravity_prepared_test_utils::relative_error(translatedResidual, baseResidual, communicator);
const double fixedCenterDifference =
gravity_prepared_test_utils::relative_error(
untranslatedCenterResidual, translatedResidual, communicator
);
gravity_prepared_test_utils::relative_error(untranslatedCenterResidual, translatedResidual, communicator);
INFO("Base deformed hydrostatic residual norm = " << baseResidualNorm);
INFO("Translated hydrostatic residual norm = " << translatedResidualNorm);
INFO(
"Mapped-rotation translation invariance error = "
<< translationInvarianceError
);
INFO("Mapped-rotation translation invariance error = " << translationInvarianceError);
INFO(
"Relative change with untranslated rotation center = "
<< fixedCenterDifference
);
INFO("Relative change with untranslated rotation center = " << fixedCenterDifference);
REQUIRE(baseResidualNorm > 1.0e-12);
REQUIRE(translatedResidualNorm > 1.0e-12);

View File

@@ -1,6 +1,6 @@
#include <array>
#include <cmath>
#include <limits>
#include <array>
#include <catch2/catch_test_macros.hpp>
@@ -19,44 +19,34 @@ namespace pressure_force_kernel_test_utils {
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);
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) {
[[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);
[[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
);
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
);
mean_field::utils::populate_domain_tdofs(f.enthalpyFes.get(), stellarElementMask, stellarEnthalpyTrueDofs);
for (int listIndex = 0; listIndex < stellarEnthalpyTrueDofs.Size();
++listIndex) {
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."
trueDof >= 0 && trueDof < enthalpyTrue.Size(), "The stellar enthalpy true-DOF mask contains an "
"invalid index."
);
enthalpyTrue(trueDof) = 0.0;
@@ -65,11 +55,9 @@ namespace pressure_force_kernel_test_utils {
return enthalpyTrue;
}
[[nodiscard]] mfem::Vector
make_positive_asymmetric_enthalpy(const mean_field::fem::FEM &f) {
[[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);
return 1.10 + 0.07 * position(0) - 0.04 * position(1) + 0.03 * position(2);
});
mfem::ParGridFunction enthalpyField(f.enthalpyFes.get());
@@ -89,15 +77,9 @@ namespace pressure_force_kernel_test_utils {
) {
const int dimension = f.mesh->Dimension();
MFEM_VERIFY(
component >= 0 && component < dimension,
"The requested vector component is invalid."
);
MFEM_VERIFY(component >= 0 && component < dimension, "The requested vector component is invalid.");
MFEM_VERIFY(
coordinate >= -1 && coordinate < dimension,
"The requested coordinate is invalid."
);
MFEM_VERIFY(coordinate >= -1 && coordinate < dimension, "The requested coordinate is invalid.");
/*
* coordinate == -1 gives the rigid translation e_component.
@@ -107,9 +89,7 @@ namespace pressure_force_kernel_test_utils {
* w = x_coordinate e_component.
*/
mfem::VectorFunctionCoefficient coefficient(
dimension,
[component, coordinate,
dimension](const mfem::Vector &position, mfem::Vector &value) {
dimension, [component, coordinate, dimension](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(dimension);
value = 0.0;
@@ -132,20 +112,192 @@ namespace pressure_force_kernel_test_utils {
const mfem::Vector &right,
MPI_Comm communicator
) {
MFEM_VERIFY(
left.Size() == right.Size(),
"The global dot-product vectors have different sizes."
);
MFEM_VERIFY(left.Size() == right.Size(), "The global dot-product vectors have different sizes.");
const double localDot = left * right;
double globalDot = 0.0;
MPI_Allreduce(
&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator
);
MPI_Allreduce(&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator);
return globalDot;
}
[[nodiscard]] double integrate_pressure(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mean_field::eos::Polytrope &barotrope,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue
) {
MFEM_VERIFY(
enthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(),
"The pressure-integral enthalpy vector has the wrong size."
);
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The pressure-integral displacement vector has the wrong size."
);
mfem::Vector enthalpyLocal(f.enthalpyFes->GetVSize());
const mfem::Operator *enthalpyProlongation = f.enthalpyFes->GetProlongationMatrix();
if (enthalpyProlongation != nullptr) {
enthalpyProlongation->Mult(enthalpyTrue, enthalpyLocal);
} else {
enthalpyLocal = enthalpyTrue;
}
mfem::Vector displacementLocal(f.displacementFes->GetVSize());
const mfem::Operator *displacementProlongation = f.displacementFes->GetProlongationMatrix();
if (displacementProlongation != nullptr) {
displacementProlongation->Mult(displacementTrue, displacementLocal);
} else {
displacementLocal = displacementTrue;
}
const double pressureExtraOrderValue =
barotrope.polytropic_index() * static_cast<double>(mean_field::field::Enthalpy::Scalar::familyOrder);
MFEM_VERIFY(
std::isfinite(pressureExtraOrderValue) && pressureExtraOrderValue >= 0.0 &&
pressureExtraOrderValue <= static_cast<double>(std::numeric_limits<int>::max()),
"The pressure-integral EOS order is invalid."
);
const int pressureExtraOrder = static_cast<int>(std::ceil(pressureExtraOrderValue));
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension());
mean_field::mapping::VolumeMappingContext mappingContext;
mfem::Array<int> enthalpyDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::Vector elementEnthalpy;
mfem::Vector elementDisplacement;
mfem::Vector elementCompactification;
mfem::Vector enthalpyShape;
double localPressureIntegral = 0.0;
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr, "The pressure-integral reference received a null "
"element transformation."
);
if (transformation->Attribute == vacuumAttribute) {
continue;
}
const mfem::FiniteElement &enthalpyElement = *f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *enthalpyDofTransformation = f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
f.compactificationFes->GetElementDofs(elementId, compactificationDofs);
enthalpyLocal.GetSubVector(enthalpyDofs, elementEnthalpy);
displacementLocal.GetSubVector(displacementDofs, elementDisplacement);
f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification);
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(elementEnthalpy);
}
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->InvTransformPrimal(elementDisplacement);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
const mean_field::mapping::ElementDisplacementData displacementData =
mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement);
const mean_field::mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification
);
const mean_field::mapping::ElementMappingData mappingData{
.displacement = displacementData, .compactification = compactificationData
};
const mean_field::quadrature::Query query =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic, transformation->OrderW(),
std::array<int, 1>{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const mean_field::quadrature::MfemRule rule =
f.quadratureFactory->get(query, transformation->GetGeometryType());
MFEM_VERIFY(rule.integration_rule != nullptr, "The pressure-integral quadrature rule is null.");
enthalpyShape.SetSize(enthalpyElement.GetDof());
for (int quadratureIndex = 0; quadratureIndex < rule.integration_rule->GetNPoints(); ++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint = rule.integration_rule->IntPoint(quadratureIndex);
transformation->SetIntPoint(&integrationPoint);
const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mean_field::mapping::MappingStatus::valid,
"Stateless mapping failed in the "
"independent pressure integral. Element: "
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex << ", status: " << static_cast<int>(mappingStatus)
);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
const double enthalpyValue = elementEnthalpy * enthalpyShape;
const double pressureValue = barotrope.pressure_from_enthalpy(enthalpyValue);
const double contribution = pressureValue * mappingContext.quadrature.weight;
MFEM_VERIFY(
std::isfinite(pressureValue) && std::isfinite(contribution), "The independent pressure integral "
"encountered a non-finite value."
);
localPressureIntegral += contribution;
}
}
double globalPressureIntegral = 0.0;
MPI_Allreduce(&localPressureIntegral, &globalPressureIntegral, 1, MPI_DOUBLE, MPI_SUM, f.mesh->GetComm());
return globalPressureIntegral;
}
} // namespace pressure_force_kernel_test_utils
TEST_CASE(
@@ -154,31 +306,26 @@ TEST_CASE(
) {
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());
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize());
enthalpyTrue = 0.0;
enthalpyTrue = 0.0;
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(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
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()
);
const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
CHECK(residualNorm == 0.0);
}
@@ -189,19 +336,15 @@ TEST_CASE(
) {
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());
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
const mfem::Vector enthalpyTrue =
pressure_force_kernel_test_utils::make_vacuum_only_enthalpy(f);
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()
);
const double enthalpyNorm = gravity_prepared_test_utils::global_norm(enthalpyTrue, f.mesh->GetComm());
/*
* Ensure this is a real exclusion test rather than another
@@ -209,21 +352,17 @@ TEST_CASE(
*/
REQUIRE(enthalpyNorm > 0.0);
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(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
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()
);
const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
CHECK(residualNorm == 0.0);
}
@@ -234,12 +373,11 @@ TEST_CASE(
) {
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());
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
/*
* With n = 3 and K = 1/4:
@@ -247,21 +385,17 @@ TEST_CASE(
* P(1) = 1/4.
*/
mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize());
enthalpyTrue = 1.0;
enthalpyTrue = 1.0;
const mfem::Vector displacementTrue =
pressure_force_kernel_test_utils::make_zero_displacement(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
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue
);
const double residualNorm = gravity_prepared_test_utils::global_norm(
residualTrue, f.mesh->GetComm()
);
const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
INFO("Positive-pressure residual norm = " << residualNorm);
@@ -272,36 +406,29 @@ TEST_CASE(
TEST_CASE(
"Pressure Force Residual Does No Work Against Rigid Translations",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
&tags::accuracy
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);
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 mean_field::eos::Polytrope barotrope(3.0, 0.25);
const mfem::Vector enthalpyTrue =
pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
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);
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
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue
);
const double residualNorm = gravity_prepared_test_utils::global_norm(
residualTrue, f.mesh->GetComm()
);
const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm());
REQUIRE(residualNorm > 0.0);
@@ -309,21 +436,14 @@ TEST_CASE(
for (int component = 0; component < dimension; ++component) {
const mfem::Vector translationTrue =
pressure_force_kernel_test_utils::make_component_test_field(
f, component, -1
);
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 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()
);
pressure_force_kernel_test_utils::global_dot(translationTrue, residualTrue, f.mesh->GetComm());
const double dotProductScale =
std::fmax(residualNorm * translationNorm, 1.0);
const double dotProductScale = std::fmax(residualNorm * translationNorm, 1.0);
CAPTURE(component, translationWork, dotProductScale);
@@ -332,32 +452,27 @@ TEST_CASE(
}
TEST_CASE(
"Pressure Force Residual Respects byNODES Component Layout",
tags::barotrope &tags::pressure &tags::kernels &tags::integration
&tags::accuracy
"Pressure Force Residual Matches Independent Pressure Integral",
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);
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 mean_field::eos::Polytrope barotrope(3.0, 0.25);
const mfem::Vector enthalpyTrue =
pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
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);
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
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue
);
const int dimension = f.mesh->Dimension();
@@ -369,17 +484,21 @@ TEST_CASE(
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
);
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()
);
pressure_force_kernel_test_utils::global_dot(affineTestTrue, residualTrue, f.mesh->GetComm());
}
}
const double pressureIntegral = pressure_force_kernel_test_utils::integrate_pressure(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue
);
REQUIRE(std::isfinite(pressureIntegral));
REQUIRE(pressureIntegral > 100.0 * std::numeric_limits<double>::epsilon());
double meanDiagonalWork = 0.0;
for (int component = 0; component < dimension; ++component) {
@@ -388,37 +507,173 @@ TEST_CASE(
meanDiagonalWork /= static_cast<double>(dimension);
// INFO(
// "Affine pressure virtual-work tensor:\n"
// << virtualWork
// );
const double comparisonTolerance = 1.0e-6 * std::abs(pressureIntegral);
INFO("Independent pressure integral = " << pressureIntegral);
INFO("Expected diagonal virtual work = " << -pressureIntegral);
INFO("Mean diagonal virtual work = " << meanDiagonalWork);
REQUIRE(
std::abs(meanDiagonalWork) >
100.0 * std::numeric_limits<double>::epsilon()
);
INFO("Comparison tolerance = " << comparisonTolerance);
const double comparisonTolerance = 1.0e-8 * std::abs(meanDiagonalWork);
/*
* This separate mean check gives a compact diagnostic if all three
* diagonal components drift together.
*/
CHECK(std::abs(meanDiagonalWork + pressureIntegral) <= comparisonTolerance);
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
);
const double expectedWork = component == coordinate ? -pressureIntegral : 0.0;
if (component == coordinate) {
CHECK(
std::abs(computedWork - meanDiagonalWork) <=
comparisonTolerance
);
} else {
CHECK(std::abs(computedWork) <= comparisonTolerance);
}
CAPTURE(component, coordinate, computedWork, expectedWork, pressureIntegral, comparisonTolerance);
CHECK(std::abs(computedWork - expectedWork) <= comparisonTolerance);
}
}
}
const double relativeMeanError = std::abs(meanDiagonalWork + pressureIntegral) / std::abs(pressureIntegral);
INFO("Relative mean diagonal error = " << relativeMeanError);
CHECK(relativeMeanError <= 1.0e-6);
}
TEST_CASE(
"Pressure Force Residual Matches Deformed Pressure Volume Variation",
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());
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
/*
* This field is positive but spatially nonuniform, so the test
* exercises a genuinely nonuniform pressure distribution.
*/
const mfem::Vector enthalpyTrue =
pressure_force_kernel_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.37);
/*
* make_displacement() contains anisotropic diagonal terms and
* quadratic cross terms. A scale of 0.67 therefore provides a
* nonzero, nonspherical, valid base geometry.
*/
const mfem::Vector baseDisplacementTrue = gravity_prepared_test_utils::make_displacement(f, 0.67);
/*
* Differentiate along the same smooth deformation family. Thus
*
* d(epsilon) = (0.67 + epsilon) d_shape.
*
* This gives a controlled geometry path while still evaluating
* the derivative at a genuinely deformed base state.
*/
const mfem::Vector displacementVariationTrue = gravity_prepared_test_utils::make_displacement(f, 1.0);
const double baseDisplacementNorm =
gravity_prepared_test_utils::global_norm(baseDisplacementTrue, f.mesh->GetComm());
const double variationNorm = gravity_prepared_test_utils::global_norm(displacementVariationTrue, f.mesh->GetComm());
REQUIRE(baseDisplacementNorm > 100.0 * std::numeric_limits<double>::epsilon());
REQUIRE(variationNorm > 100.0 * std::numeric_limits<double>::epsilon());
mfem::Vector residualTrue;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, baseDisplacementTrue, residualTrue
);
REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize());
const double residualWork =
pressure_force_kernel_test_utils::global_dot(displacementVariationTrue, residualTrue, f.mesh->GetComm());
REQUIRE(std::isfinite(residualWork));
REQUIRE(std::abs(residualWork) > 100.0 * std::numeric_limits<double>::epsilon());
/*
* The relatively broad initial sweep lets us see the expected
* centered-difference convergence before reaching the quadrature
* and representation plateau.
*/
constexpr std::array<double, 4> differenceSteps{1.0e-2, 5.0e-3, 2.5e-3, 1.25e-3};
double bestRelativeDiscrepancy = std::numeric_limits<double>::infinity();
for (const double differenceStep : differenceSteps) {
mfem::Vector displacementPlus(baseDisplacementTrue);
mfem::Vector displacementMinus(baseDisplacementTrue);
displacementPlus.Add(differenceStep, displacementVariationTrue);
displacementMinus.Add(-differenceStep, displacementVariationTrue);
const double pressureIntegralPlus = pressure_force_kernel_test_utils::integrate_pressure(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementPlus
);
const double pressureIntegralMinus = pressure_force_kernel_test_utils::integrate_pressure(
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementMinus
);
REQUIRE(std::isfinite(pressureIntegralPlus));
REQUIRE(std::isfinite(pressureIntegralMinus));
const double pressureVolumeDerivative = (pressureIntegralPlus - pressureIntegralMinus) / (2.0 * differenceStep);
REQUIRE(std::isfinite(pressureVolumeDerivative));
double comparisonScale = std::abs(residualWork);
if (std::abs(pressureVolumeDerivative) > comparisonScale) {
comparisonScale = std::abs(pressureVolumeDerivative);
}
REQUIRE(comparisonScale > 100.0 * std::numeric_limits<double>::epsilon());
const double absoluteDiscrepancy = std::abs(residualWork + pressureVolumeDerivative);
const double relativeDiscrepancy = absoluteDiscrepancy / comparisonScale;
if (relativeDiscrepancy < bestRelativeDiscrepancy) {
bestRelativeDiscrepancy = relativeDiscrepancy;
}
INFO("Difference step = " << differenceStep);
INFO("Pressure residual work = " << residualWork);
INFO("Pressure-volume derivative = " << pressureVolumeDerivative);
INFO("Residual work plus derivative = " << residualWork + pressureVolumeDerivative);
INFO("Relative discrepancy = " << relativeDiscrepancy);
/*
* The signs must be opposite because the implemented pressure
* force is the negative variation of the pressure-volume
* functional.
*/
CHECK(residualWork * pressureVolumeDerivative < 0.0);
}
INFO("Best pressure-volume relative discrepancy = " << bestRelativeDiscrepancy);
/*
* This is intentionally a provisional but meaningful threshold.
* We will tighten it after measuring the convergence plateau.
*/
CHECK(bestRelativeDiscrepancy < 1.0e-8);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,954 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
#include <mpi.h>
import mean_field;
import test_helpers;
namespace prepared_displacement_residual_test_utils {
using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto gravityGradientValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term
);
constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term
);
constexpr auto densityResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
[[nodiscard]] mean_field::field::FieldDofMap make_enthalpy_map(const mean_field::fem::FEM &f) {
return mean_field::field::make_field_dof_map<mean_field::field::Enthalpy, DomainSchema>(*f.enthalpyFes);
}
[[nodiscard]] mean_field::operators::DisplacementResidualLayout make_layout(const mean_field::fem::FEM &f) {
const auto enthalpyMap = make_enthalpy_map(f);
/*
* Transitional displacement-composer layout.
*
* Pressure has now migrated its h column to supported FieldDof
* coordinates, while the density-consuming mechanical children are
* intentionally still full-space until the next migration slice.
*
* The adapter only writes R_d. The unrelated residual-row sizes remain
* at their current full-space values in this standalone adapter test.
*/
const std::array<int, CoupledForm::value_block_count> valueSizes{
f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(),
f.gravityPotentialFes->GetTrueVSize(), enthalpyMap.reduced_size(), 1
};
const std::array<int, CoupledForm::residual_block_count> residualSizes{
f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize(), f.densityFes->GetTrueVSize(),
f.displacementFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1
};
return {valueSizes, residualSizes};
}
[[nodiscard]] mfem::Vector make_density(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction field(f.densityFes.get());
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
return 0.84 + 0.06 * std::sin(0.73 * position(0) + phase) + 0.04 * std::cos(0.61 * position(1) - phase) +
0.025 * position(2) * position(2);
});
field.ProjectCoefficient(coefficient);
mfem::Vector trueDofs;
field.GetTrueDofs(trueDofs);
return trueDofs;
}
[[nodiscard]] mfem::Vector make_density_direction(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction field(f.densityFes.get());
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
return 0.17 * std::sin(0.91 * position(0) + phase) - 0.11 * std::cos(0.79 * position(1) - phase) +
0.07 * position(2);
});
field.ProjectCoefficient(coefficient);
mfem::Vector trueDofs;
field.GetTrueDofs(trueDofs);
return trueDofs;
}
[[nodiscard]] mfem::Vector make_gravity_gradient(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction field(f.gravityFluxFes.get());
auto function = [phase](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(3);
value(0) = 0.31 + 0.08 * position(0) + 0.03 * phase * position(1);
value(1) = -0.17 + 0.06 * position(1) - 0.02 * phase * position(2);
value(2) = 0.23 - 0.05 * position(2) + 0.025 * phase * position(0);
};
mfem::VectorFunctionCoefficient coefficient(3, function);
field.ProjectCoefficient(coefficient);
mfem::Vector trueDofs;
field.GetTrueDofs(trueDofs);
return trueDofs;
}
[[nodiscard]] mfem::Vector make_gravity_gradient_direction(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction field(f.gravityFluxFes.get());
auto function = [phase](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(3);
value(0) = 0.14 * std::sin(position(0) + phase) + 0.03 * position(1);
value(1) = -0.11 * std::cos(position(1) - phase) + 0.04 * position(2);
value(2) = 0.09 * std::sin(position(2) + 0.5 * phase) - 0.02 * position(0);
};
mfem::VectorFunctionCoefficient coefficient(3, function);
field.ProjectCoefficient(coefficient);
mfem::Vector trueDofs;
field.GetTrueDofs(trueDofs);
return trueDofs;
}
[[nodiscard]] mfem::Vector make_positive_enthalpy(
const mean_field::fem::FEM &f,
const double phase
) {
/*
* Build a full H1 test state first, then return exactly the
* solver-facing supported FieldDof coordinates consumed by the
* migrated pressure-force operator.
*
* Keeping the public helper name unchanged means every existing
* displacement-composer test automatically migrates to the new
* pressure contract without inventing a parallel "*_full" helper.
*/
mfem::Vector fullEnthalpy(f.enthalpyFes->GetTrueVSize());
for (int index = 0; index < fullEnthalpy.Size(); ++index) {
const double coordinate = static_cast<double>(index + 1);
fullEnthalpy(index) =
0.93 + 0.09 * std::sin(0.23 * coordinate + phase) + 0.04 * std::cos(0.17 * coordinate - 0.5 * phase);
}
return make_enthalpy_map(f).gather(fullEnthalpy);
}
[[nodiscard]] mfem::Vector make_enthalpy_direction(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::Vector fullDirection(f.enthalpyFes->GetTrueVSize());
for (int index = 0; index < fullDirection.Size(); ++index) {
const double coordinate = static_cast<double>(index + 1);
fullDirection(index) =
0.27 * std::sin(0.19 * coordinate + phase) + 0.14 * std::cos(0.13 * coordinate - 0.5 * phase);
}
return make_enthalpy_map(f).gather(fullDirection);
}
[[nodiscard]] mfem::Vector make_displacement_direction(const mean_field::fem::FEM &f) {
mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, 0.91);
const mfem::Vector second = gravity_prepared_test_utils::make_displacement(f, 0.27);
direction -= second;
return direction;
}
[[nodiscard]] mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
mfem::Vector angularVelocity(3);
angularVelocity(0) = scale * 0.17;
angularVelocity(1) = scale * -0.09;
angularVelocity(2) = scale * 0.62;
mfem::Vector center(3);
center(0) = 0.04;
center(1) = -0.03;
center(2) = 0.02;
return mean_field::physics::RigidRotation(angularVelocity, center);
}
[[nodiscard]] mean_field::operators::DisplacementResidualDependencies make_dependencies() {
return {
.discretization = {.identity = 401, .revision = 3},
.density = {.identity = 409, .revision = 5},
.displacement = {.identity = 419, .revision = 7},
.gravityGradient = {.identity = 421, .revision = 11},
.enthalpy = {.identity = 431, .revision = 13},
.rotation = {.identity = 433, .revision = 17}
};
}
[[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions make_gravity_revisions(
const mean_field::operators::DisplacementResidualDependencies &dependencies,
const std::uint64_t potentialRevision
) {
return {
.discretization = {.value = dependencies.discretization.revision},
.displacement = {.value = dependencies.displacement.revision},
.density = {.value = dependencies.density.revision},
.gravity_gradient = {.value = dependencies.gravityGradient.revision},
.gravity_potential = {.value = potentialRevision}
};
}
void prepare_gravity_context(
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &context,
const mfem::Vector &density,
const mfem::Vector &displacement,
const mfem::Vector &gravityGradient,
const mfem::Vector &gravityPotential,
const mean_field::operators::DisplacementResidualDependencies &dependencies,
const std::uint64_t potentialRevision
) {
context.Prepare(
{.density = density,
.displacement = displacement,
.gravity_gradient = gravityGradient,
.gravity_potential = gravityPotential},
make_gravity_revisions(dependencies, potentialRevision)
);
}
[[nodiscard]] double relative_difference(
const mfem::Vector &left,
const mfem::Vector &right,
const MPI_Comm communicator
) {
REQUIRE(left.Size() == right.Size());
mfem::Vector difference(left);
difference -= right;
const double scale = std::max(
{gravity_prepared_test_utils::global_norm(left, communicator),
gravity_prepared_test_utils::global_norm(right, communicator),
100.0 * std::numeric_limits<double>::epsilon()}
);
return gravity_prepared_test_utils::global_norm(difference, communicator) / scale;
}
[[nodiscard]] mfem::Vector
explicit_residual_sum(const mean_field::operators::PreparedDisplacementResidualOperator &preparedOperator) {
mfem::Vector pressure;
mfem::Vector gravity;
mfem::Vector rotation;
preparedOperator.GetPressureOperator().BuildResidual(pressure);
preparedOperator.GetGravityOperator().BuildResidual(gravity);
preparedOperator.GetRotationalOperator().BuildResidual(rotation);
pressure += gravity;
pressure += rotation;
return pressure;
}
template <int index>
[[nodiscard]] mfem::Vector copy_residual_block(
const mfem::Vector &action,
const mean_field::operators::DisplacementResidualLayout &layout,
const mean_field::utils::blocks::residual_block<index> block
) {
mfem::Vector result(layout.size(block));
const int offset = layout.offset(block);
for (int entry = 0; entry < result.Size(); ++entry) {
result(entry) = action(offset + entry);
}
return result;
}
} // namespace prepared_displacement_residual_test_utils
TEST_CASE(
"Prepared Displacement Residual Equals The Three Prepared Contributors",
tags::barotrope &tags::prepared &tags::integration &tags::residuals
) {
using Operator = mean_field::operators::PreparedDisplacementResidualOperator;
STATIC_REQUIRE_FALSE(std::is_copy_constructible_v<Operator>);
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<Operator>);
STATIC_REQUIRE_FALSE(std::is_move_constructible_v<Operator>);
STATIC_REQUIRE_FALSE(std::is_move_assignable_v<Operator>);
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 auto enthalpyMap = prepared_displacement_residual_test_utils::make_enthalpy_map(f);
const mfem::Vector density = prepared_displacement_residual_test_utils::make_density(f, 0.31);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.67);
const mfem::Vector gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.47);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
const mfem::Vector enthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.53);
REQUIRE(enthalpyMap.reduced_size() < enthalpyMap.full_size());
REQUIRE(enthalpy.Size() == enthalpyMap.reduced_size());
const auto dependencies = prepared_displacement_residual_test_utils::make_dependencies();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, 19
);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.83);
Operator preparedOperator(f, *f.domainMapperStateless, equationOfState, gravityContext);
REQUIRE(preparedOperator.GetPressureOperator().GetEnthalpySize() == enthalpy.Size());
const auto initialReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation);
REQUIRE(initialReport.pressure.DidAnyWork());
REQUIRE(initialReport.gravity.DidAnyWork());
REQUIRE(initialReport.rotation.DidAnyWork());
REQUIRE(initialReport.assembledResidual);
REQUIRE(preparedOperator.IsPrepared());
CHECK(&preparedOperator.GetFEM() == &f);
CHECK(&preparedOperator.GetGravityContext() == &gravityContext);
CHECK(&preparedOperator.GetGravityOperator().GetGravityContext() == &gravityContext);
mfem::Vector compositeResidual;
preparedOperator.BuildResidual(compositeResidual);
const mfem::Vector explicitResidual =
prepared_displacement_residual_test_utils::explicit_residual_sum(preparedOperator);
const double compositionError = prepared_displacement_residual_test_utils::relative_difference(
compositeResidual, explicitResidual, f.mesh->GetComm()
);
INFO("Prepared residual composition error = " << compositionError);
CHECK(compositionError < 2.0e-15);
const std::uint64_t preparationCount = preparedOperator.GetResidualPreparationCount();
const auto repeatedReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation);
CHECK_FALSE(repeatedReport.DidAnyWork());
CHECK_FALSE(repeatedReport.assembledResidual);
CHECK(preparedOperator.GetResidualPreparationCount() == preparationCount);
CHECK(preparedOperator.IsPrepared());
}
TEST_CASE(
"Prepared Displacement Residual Selectively Orchestrates Its Children",
tags::barotrope &tags::prepared &tags::contexts &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());
mfem::Vector density = prepared_displacement_residual_test_utils::make_density(f, 0.29);
mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.61);
mfem::Vector gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.43);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
mfem::Vector enthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.51);
auto dependencies = prepared_displacement_residual_test_utils::make_dependencies();
std::uint64_t potentialRevision = 19;
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, potentialRevision
);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.79);
mean_field::operators::PreparedDisplacementResidualOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState, gravityContext
);
preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation);
gravityPotential = 0.17;
++potentialRevision;
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, potentialRevision
);
const auto potentialReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation);
CHECK_FALSE(potentialReport.DidAnyWork());
CHECK_FALSE(potentialReport.assembledResidual);
enthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.83);
++dependencies.enthalpy.revision;
const auto enthalpyReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation);
CHECK(enthalpyReport.pressure.DidAnyWork());
CHECK_FALSE(enthalpyReport.gravity.DidAnyWork());
CHECK_FALSE(enthalpyReport.rotation.DidAnyWork());
CHECK(enthalpyReport.assembledResidual);
gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.91);
++dependencies.gravityGradient.revision;
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, potentialRevision
);
const auto gravityReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation);
CHECK_FALSE(gravityReport.pressure.DidAnyWork());
CHECK(gravityReport.gravity.DidAnyWork());
CHECK_FALSE(gravityReport.rotation.DidAnyWork());
CHECK(gravityReport.assembledResidual);
density = prepared_displacement_residual_test_utils::make_density(f, 1.07);
++dependencies.density.revision;
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, potentialRevision
);
const auto densityReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation);
CHECK_FALSE(densityReport.pressure.DidAnyWork());
CHECK(densityReport.gravity.DidAnyWork());
CHECK(densityReport.rotation.DidAnyWork());
CHECK(densityReport.assembledResidual);
rotation = prepared_displacement_residual_test_utils::make_rotation(1.13);
++dependencies.rotation.revision;
const auto rotationReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation);
CHECK_FALSE(rotationReport.pressure.DidAnyWork());
CHECK_FALSE(rotationReport.gravity.DidAnyWork());
CHECK(rotationReport.rotation.DidAnyWork());
CHECK(rotationReport.assembledResidual);
displacement = gravity_prepared_test_utils::make_displacement(f, 0.89);
++dependencies.displacement.revision;
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, potentialRevision
);
const auto displacementReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation);
CHECK(displacementReport.pressure.DidAnyWork());
CHECK(displacementReport.gravity.DidAnyWork());
CHECK(displacementReport.rotation.DidAnyWork());
CHECK(displacementReport.assembledResidual);
}
TEST_CASE(
"Prepared Displacement Residual Jacobian Equals The Contributor Sums",
tags::barotrope &tags::prepared &tags::jacobian &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());
const mfem::Vector density = prepared_displacement_residual_test_utils::make_density(f, 0.37);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.73);
const mfem::Vector gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.59);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
const mfem::Vector enthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.61);
const mfem::Vector densityDirection = prepared_displacement_residual_test_utils::make_density_direction(f, 0.71);
const mfem::Vector displacementDirection =
prepared_displacement_residual_test_utils::make_displacement_direction(f);
const mfem::Vector gravityDirection =
prepared_displacement_residual_test_utils::make_gravity_gradient_direction(f, 0.83);
const mfem::Vector enthalpyDirection = prepared_displacement_residual_test_utils::make_enthalpy_direction(f, 0.97);
const auto dependencies = prepared_displacement_residual_test_utils::make_dependencies();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, 19
);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.91);
mean_field::operators::PreparedDisplacementResidualOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState, gravityContext
);
preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation);
mfem::Vector densityAction;
mfem::Vector displacementAction;
mfem::Vector gravityAction;
mfem::Vector enthalpyAction;
mfem::Vector completeAction;
preparedOperator.ApplyDensityJacobianAction(densityDirection, densityAction);
preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, displacementAction);
preparedOperator.ApplyGravityGradientJacobianAction(gravityDirection, gravityAction);
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyDirection, enthalpyAction);
preparedOperator.ApplyCompleteJacobianAction(
densityDirection, displacementDirection, gravityDirection, enthalpyDirection, completeAction
);
mfem::Vector expectedDensity;
mfem::Vector expectedRotationDensity;
preparedOperator.GetGravityOperator().ApplyDensityJacobianAction(densityDirection, expectedDensity);
preparedOperator.GetRotationalOperator().ApplyDensityJacobianAction(densityDirection, expectedRotationDensity);
expectedDensity += expectedRotationDensity;
mfem::Vector expectedDisplacement;
mfem::Vector expectedGravityDisplacement;
mfem::Vector expectedRotationDisplacement;
preparedOperator.GetPressureOperator().ApplyDisplacementJacobianAction(displacementDirection, expectedDisplacement);
preparedOperator.GetGravityOperator().ApplyDisplacementJacobianAction(
displacementDirection, expectedGravityDisplacement
);
preparedOperator.GetRotationalOperator().ApplyDisplacementJacobianAction(
displacementDirection, expectedRotationDisplacement
);
expectedDisplacement += expectedGravityDisplacement;
expectedDisplacement += expectedRotationDisplacement;
mfem::Vector expectedGravity;
preparedOperator.GetGravityOperator().ApplyGravityGradientJacobianAction(gravityDirection, expectedGravity);
mfem::Vector expectedEnthalpy;
preparedOperator.GetPressureOperator().ApplyEnthalpyJacobianAction(enthalpyDirection, expectedEnthalpy);
mfem::Vector summedColumns(densityAction);
summedColumns += displacementAction;
summedColumns += gravityAction;
summedColumns += enthalpyAction;
const MPI_Comm communicator = f.mesh->GetComm();
CHECK(
prepared_displacement_residual_test_utils::relative_difference(densityAction, expectedDensity, communicator) <
2.0e-15
);
CHECK(
prepared_displacement_residual_test_utils::relative_difference(
displacementAction, expectedDisplacement, communicator
) < 2.0e-15
);
CHECK(
prepared_displacement_residual_test_utils::relative_difference(gravityAction, expectedGravity, communicator) <
2.0e-15
);
CHECK(
prepared_displacement_residual_test_utils::relative_difference(enthalpyAction, expectedEnthalpy, communicator) <
2.0e-15
);
CHECK(
prepared_displacement_residual_test_utils::relative_difference(completeAction, summedColumns, communicator) <
2.0e-15
);
}
TEST_CASE(
"Prepared Displacement Residual Jacobian Matches A Simultaneous "
"Centered Difference On Deformed Geometry",
tags::barotrope &tags::prepared &tags::jacobian &tags::accuracy &tags::geometry
) {
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 mfem::Vector baseDensity = prepared_displacement_residual_test_utils::make_density(f, 0.41);
const mfem::Vector baseDisplacement = gravity_prepared_test_utils::make_displacement(f, 0.79);
const mfem::Vector baseGravity = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.63);
const mfem::Vector baseEnthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.67);
const mfem::Vector densityDirection = prepared_displacement_residual_test_utils::make_density_direction(f, 0.73);
const mfem::Vector displacementDirection =
prepared_displacement_residual_test_utils::make_displacement_direction(f);
const mfem::Vector gravityDirection =
prepared_displacement_residual_test_utils::make_gravity_gradient_direction(f, 0.89);
const mfem::Vector enthalpyDirection = prepared_displacement_residual_test_utils::make_enthalpy_direction(f, 1.01);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
auto dependencies = prepared_displacement_residual_test_utils::make_dependencies();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, baseDensity, baseDisplacement, baseGravity, gravityPotential, dependencies, 19
);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.87);
mean_field::operators::PreparedDisplacementResidualOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState, gravityContext
);
preparedOperator.Prepare({.enthalpy = baseEnthalpy}, dependencies, rotation);
mfem::Vector jacobianAction;
preparedOperator.ApplyCompleteJacobianAction(
densityDirection, displacementDirection, gravityDirection, enthalpyDirection, jacobianAction
);
constexpr double step = 1.0e-5;
mfem::Vector plusDensity(baseDensity);
plusDensity.Add(step, densityDirection);
mfem::Vector minusDensity(baseDensity);
minusDensity.Add(-step, densityDirection);
mfem::Vector plusDisplacement(baseDisplacement);
plusDisplacement.Add(step, displacementDirection);
mfem::Vector minusDisplacement(baseDisplacement);
minusDisplacement.Add(-step, displacementDirection);
mfem::Vector plusGravity(baseGravity);
plusGravity.Add(step, gravityDirection);
mfem::Vector minusGravity(baseGravity);
minusGravity.Add(-step, gravityDirection);
mfem::Vector plusEnthalpy(baseEnthalpy);
plusEnthalpy.Add(step, enthalpyDirection);
mfem::Vector minusEnthalpy(baseEnthalpy);
minusEnthalpy.Add(-step, enthalpyDirection);
++dependencies.density.revision;
++dependencies.displacement.revision;
++dependencies.gravityGradient.revision;
++dependencies.enthalpy.revision;
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, plusDensity, plusDisplacement, plusGravity, gravityPotential, dependencies, 19
);
preparedOperator.Prepare({.enthalpy = plusEnthalpy}, dependencies, rotation);
mfem::Vector plusResidual;
preparedOperator.BuildResidual(plusResidual);
++dependencies.density.revision;
++dependencies.displacement.revision;
++dependencies.gravityGradient.revision;
++dependencies.enthalpy.revision;
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, minusDensity, minusDisplacement, minusGravity, gravityPotential, dependencies, 19
);
preparedOperator.Prepare({.enthalpy = minusEnthalpy}, dependencies, rotation);
mfem::Vector minusResidual;
preparedOperator.BuildResidual(minusResidual);
plusResidual -= minusResidual;
plusResidual /= 2.0 * step;
const double centeredDifferenceError =
prepared_displacement_residual_test_utils::relative_difference(jacobianAction, plusResidual, f.mesh->GetComm());
INFO("Composite simultaneous centered-difference error = " << centeredDifferenceError);
CHECK(centeredDifferenceError < 8.0e-8);
}
TEST_CASE(
"Prepared Displacement Residual MFEM Adapter Routes Only R-d",
tags::barotrope &tags::prepared &tags::jacobian &tags::mfem_operators &tags::unit
) {
using JacobianForm = mean_field::utils::blocks::barotropic_equilibrium_jacobian_form;
using DisplacementResidualType = mean_field::utils::blocks::displacement::geometry::residual;
STATIC_REQUIRE(
mean_field::utils::blocks::has_jacobian_coupling_v<
DisplacementResidualType, mean_field::utils::blocks::density::mass::value, JacobianForm>
);
STATIC_REQUIRE(
mean_field::utils::blocks::has_jacobian_coupling_v<
DisplacementResidualType, mean_field::utils::blocks::displacement::geometry::value, JacobianForm>
);
STATIC_REQUIRE(
mean_field::utils::blocks::has_jacobian_coupling_v<
DisplacementResidualType, mean_field::utils::blocks::gravity::gradient::value, JacobianForm>
);
STATIC_REQUIRE(
mean_field::utils::blocks::has_jacobian_coupling_v<
DisplacementResidualType, mean_field::utils::blocks::enthalpy::specific::value, JacobianForm>
);
STATIC_REQUIRE_FALSE(
mean_field::utils::blocks::has_jacobian_coupling_v<
DisplacementResidualType, mean_field::utils::blocks::gravity::poisson::value, JacobianForm>
);
STATIC_REQUIRE_FALSE(
mean_field::utils::blocks::has_jacobian_coupling_v<
DisplacementResidualType, mean_field::utils::blocks::barotropic_constant::mass_normalization::value,
JacobianForm>
);
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 mfem::Vector density = prepared_displacement_residual_test_utils::make_density(f, 0.43);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.71);
const mfem::Vector gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.57);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
const mfem::Vector enthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.69);
const mfem::Vector densityDirection = prepared_displacement_residual_test_utils::make_density_direction(f, 0.77);
const mfem::Vector displacementDirection =
prepared_displacement_residual_test_utils::make_displacement_direction(f);
const mfem::Vector gravityDirection =
prepared_displacement_residual_test_utils::make_gravity_gradient_direction(f, 0.93);
const mfem::Vector enthalpyDirection = prepared_displacement_residual_test_utils::make_enthalpy_direction(f, 1.03);
const auto dependencies = prepared_displacement_residual_test_utils::make_dependencies();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, 19
);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.95);
mean_field::operators::PreparedDisplacementResidualOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState, gravityContext
);
preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation);
const mean_field::operators::DisplacementResidualLayout layout =
prepared_displacement_residual_test_utils::make_layout(f);
mean_field::operators::PreparedDisplacementResidualJacobianOperator adapter(layout, preparedOperator);
CHECK(adapter.Width() == layout.value_offsets().Last());
CHECK(adapter.Height() == layout.residual_offsets().Last());
CHECK(
layout.size(prepared_displacement_residual_test_utils::enthalpyValue) ==
preparedOperator.GetPressureOperator().GetEnthalpySize()
);
mfem::BlockVector direction(layout.value_offsets());
direction = 0.0;
direction.GetBlock(prepared_displacement_residual_test_utils::densityValue) = densityDirection;
direction.GetBlock(prepared_displacement_residual_test_utils::displacementValue) = displacementDirection;
direction.GetBlock(prepared_displacement_residual_test_utils::gravityGradientValue) = gravityDirection;
direction.GetBlock(prepared_displacement_residual_test_utils::enthalpyValue) = enthalpyDirection;
direction.GetBlock(prepared_displacement_residual_test_utils::gravityPotentialValue) = 0.59;
direction.GetBlock(prepared_displacement_residual_test_utils::barotropicConstantValue) = -0.73;
mfem::Vector expectedDisplacementAction;
preparedOperator.ApplyCompleteJacobianAction(
densityDirection, displacementDirection, gravityDirection, enthalpyDirection, expectedDisplacementAction
);
mfem::Vector action;
adapter.Mult(direction, action);
const mfem::Vector routedDisplacement = prepared_displacement_residual_test_utils::copy_residual_block(
action, layout, prepared_displacement_residual_test_utils::displacementResidual
);
CHECK(
prepared_displacement_residual_test_utils::relative_difference(
routedDisplacement, expectedDisplacementAction, f.mesh->GetComm()
) < 2.0e-15
);
const mfem::Vector gravityGradientBlock = prepared_displacement_residual_test_utils::copy_residual_block(
action, layout, prepared_displacement_residual_test_utils::gravityGradientResidual
);
const mfem::Vector gravityPotentialBlock = prepared_displacement_residual_test_utils::copy_residual_block(
action, layout, prepared_displacement_residual_test_utils::gravityPotentialResidual
);
const mfem::Vector densityBlock = prepared_displacement_residual_test_utils::copy_residual_block(
action, layout, prepared_displacement_residual_test_utils::densityResidual
);
const mfem::Vector enthalpyBlock = prepared_displacement_residual_test_utils::copy_residual_block(
action, layout, prepared_displacement_residual_test_utils::enthalpyResidual
);
const mfem::Vector massBlock = prepared_displacement_residual_test_utils::copy_residual_block(
action, layout, prepared_displacement_residual_test_utils::massResidual
);
CHECK(gravity_prepared_test_utils::global_norm(gravityGradientBlock, f.mesh->GetComm()) == 0.0);
CHECK(gravity_prepared_test_utils::global_norm(gravityPotentialBlock, f.mesh->GetComm()) == 0.0);
CHECK(gravity_prepared_test_utils::global_norm(densityBlock, f.mesh->GetComm()) == 0.0);
CHECK(gravity_prepared_test_utils::global_norm(enthalpyBlock, f.mesh->GetComm()) == 0.0);
CHECK(gravity_prepared_test_utils::global_norm(massBlock, f.mesh->GetComm()) == 0.0);
}

View File

@@ -16,25 +16,18 @@ TEST_CASE(
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedGravitySourceOperator prepared_operator(
f, *f.domainMapperStateless
);
operators::PreparedMappedGravitySourceOperator prepared_operator(f, *f.domainMapperStateless);
REQUIRE(prepared_operator.Width() == f.densityFes->GetTrueVSize());
REQUIRE(
prepared_operator.Height() == f.gravityPotentialFes->GetTrueVSize()
);
REQUIRE(prepared_operator.Height() == f.gravityPotentialFes->GetTrueVSize());
const mfem::Vector density = prepared_test::make_deterministic_vector(
f.densityFes->GetTrueVSize(), 0.41
);
const mfem::Vector density = prepared_test::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.41);
const MPI_Comm communicator = f.mesh->GetComm();
mfem::Vector identity_action;
mfem::Vector deformed_action;
for (const double deformation_scale : {0.0, 1.0}) {
const mfem::Vector displacement =
prepared_test::make_displacement(f, deformation_scale);
const mfem::Vector displacement = prepared_test::make_displacement(f, deformation_scale);
prepared_operator.Prepare(displacement);
@@ -42,23 +35,13 @@ TEST_CASE(
mfem::Vector reference_action;
prepared_operator.Mult(density, prepared_action);
operators::kernels::apply_mapped_source(
f, *f.domainMapperStateless, density, displacement, reference_action
);
operators::kernels::apply_mapped_source(f, *f.domainMapperStateless, density, displacement, reference_action);
const double relative_error = prepared_test::relative_error(
prepared_action, reference_action, communicator
);
const double relative_error = prepared_test::relative_error(prepared_action, reference_action, communicator);
INFO("Deformation scale = " << deformation_scale);
INFO(
"Prepared source norm = "
<< prepared_test::global_norm(prepared_action, communicator)
);
INFO(
"Reference source norm = "
<< prepared_test::global_norm(reference_action, communicator)
);
INFO("Prepared source norm = " << prepared_test::global_norm(prepared_action, communicator));
INFO("Reference source norm = " << prepared_test::global_norm(reference_action, communicator));
INFO("Relative prepared-source error = " << relative_error);
REQUIRE(prepared_operator.IsPrepared());
@@ -71,9 +54,7 @@ TEST_CASE(
}
}
const double geometry_change = prepared_test::relative_error(
deformed_action, identity_action, communicator
);
const double geometry_change = prepared_test::relative_error(deformed_action, identity_action, communicator);
INFO("Relative source change under deformation = " << geometry_change);
@@ -88,28 +69,17 @@ TEST_CASE(
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedGravitySourceOperator prepared_operator(
f, *f.domainMapperStateless
);
operators::PreparedMappedGravitySourceOperator prepared_operator(f, *f.domainMapperStateless);
REQUIRE(prepared_operator.Width() == f.densityFes->GetTrueVSize());
REQUIRE(
prepared_operator.Height() == f.gravityPotentialFes->GetTrueVSize()
);
REQUIRE(prepared_operator.Height() == f.gravityPotentialFes->GetTrueVSize());
const mfem::Vector displacement = prepared_test::make_displacement(f, 1.0);
prepared_operator.Prepare(displacement);
const mfem::Vector first = prepared_test::make_deterministic_vector(
f.densityFes->GetTrueVSize(), 0.27
);
const mfem::Vector second = prepared_test::make_deterministic_vector(
f.densityFes->GetTrueVSize(), 0.79
);
const mfem::Vector combination =
prepared_test::linear_combination(first, 1.3, second, -0.6);
const mfem::Vector stellar_density =
prepared_test::make_domain_supported_density(f, true);
const mfem::Vector vacuum_density =
prepared_test::make_domain_supported_density(f, false);
const mfem::Vector first = prepared_test::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.27);
const mfem::Vector second = prepared_test::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.79);
const mfem::Vector combination = prepared_test::linear_combination(first, 1.3, second, -0.6);
const mfem::Vector stellar_density = prepared_test::make_domain_supported_density(f, true);
const mfem::Vector vacuum_density = prepared_test::make_domain_supported_density(f, false);
mfem::Vector first_action;
mfem::Vector second_action;
@@ -123,20 +93,14 @@ TEST_CASE(
prepared_operator.Mult(stellar_density, stellar_action);
prepared_operator.Mult(vacuum_density, vacuum_action);
const mfem::Vector expected_combination = prepared_test::linear_combination(
first_action, 1.3, second_action, -0.6
);
const MPI_Comm communicator = f.mesh->GetComm();
const mfem::Vector expected_combination = prepared_test::linear_combination(first_action, 1.3, second_action, -0.6);
const MPI_Comm communicator = f.mesh->GetComm();
const double linearity_error = prepared_test::relative_error(
combination_action, expected_combination, communicator
);
const double stellar_norm =
prepared_test::global_norm(stellar_action, communicator);
const double vacuum_norm =
prepared_test::global_norm(vacuum_action, communicator);
const std::uint64_t preparation_count =
prepared_operator.GetPreparationCount();
const double linearity_error =
prepared_test::relative_error(combination_action, expected_combination, communicator);
const double stellar_norm = prepared_test::global_norm(stellar_action, communicator);
const double vacuum_norm = prepared_test::global_norm(vacuum_action, communicator);
const std::uint64_t preparation_count = prepared_operator.GetPreparationCount();
mfem::Vector repeated_action;
prepared_operator.Mult(first, repeated_action);
@@ -148,10 +112,6 @@ TEST_CASE(
CHECK_THAT(linearity_error, WithinAbs(0.0, 2.0e-12));
CHECK(stellar_norm > 0.0);
CHECK(vacuum_norm <= 1.0e-13 * stellar_norm);
CHECK(
prepared_test::relative_error(
repeated_action, first_action, communicator
) < 2.0e-14
);
CHECK(prepared_test::relative_error(repeated_action, first_action, communicator) < 2.0e-14);
CHECK(prepared_operator.GetPreparationCount() == preparation_count);
}

View File

@@ -16,22 +16,17 @@ TEST_CASE(
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedHDivMassOperator prepared_operator(
f, *f.domainMapperStateless
);
operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless);
const mfem::Vector gravity_gradient =
prepared_test::make_deterministic_vector(
f.gravityFluxFes->GetTrueVSize(), 0.21
);
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.21);
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
mfem::Vector identity_action;
mfem::Vector deformed_action;
for (const double deformation_scale : {0.0, 1.0}) {
const mfem::Vector displacement =
prepared_test::make_displacement(f, deformation_scale);
const mfem::Vector displacement = prepared_test::make_displacement(f, deformation_scale);
prepared_operator.Prepare(displacement);
@@ -40,23 +35,14 @@ TEST_CASE(
prepared_operator.Mult(gravity_gradient, prepared_action);
operators::kernels::apply_mapped_hdiv_mass(
f, *f.domainMapperStateless, gravity_gradient, displacement,
reference_action
f, *f.domainMapperStateless, gravity_gradient, displacement, reference_action
);
const double relative_error = prepared_test::relative_error(
prepared_action, reference_action, communicator
);
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("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());
@@ -69,9 +55,7 @@ TEST_CASE(
}
}
const double geometry_change = prepared_test::relative_error(
deformed_action, identity_action, communicator
);
const double geometry_change = prepared_test::relative_error(deformed_action, identity_action, communicator);
INFO("Relative action change under deformation = " << geometry_change);
@@ -86,20 +70,13 @@ TEST_CASE(
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedHDivMassOperator prepared_operator(
f, *f.domainMapperStateless
);
operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless);
const mfem::Vector displacement = prepared_test::make_displacement(f, 1.0);
prepared_operator.Prepare(displacement);
const mfem::Vector first = prepared_test::make_deterministic_vector(
f.gravityFluxFes->GetTrueVSize(), 0.17
);
const mfem::Vector second = prepared_test::make_deterministic_vector(
f.gravityFluxFes->GetTrueVSize(), 0.83
);
const mfem::Vector combination =
prepared_test::linear_combination(first, 1.7, second, -0.4);
const mfem::Vector first = prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.17);
const mfem::Vector second = 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;
@@ -110,32 +87,22 @@ TEST_CASE(
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 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 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();
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);
@@ -149,16 +116,9 @@ TEST_CASE(
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_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_test::relative_error(repeated_action, first_action, communicator) < 2.0e-14);
CHECK(prepared_operator.GetPreparationCount() == preparation_count);
}

View File

@@ -5,9 +5,7 @@ import mean_field;
import test_helpers;
namespace prepared_hydrostatic_test_utils {
static mean_field::operators::context::hydrostatic::
HydrostaticEquilibriumDependencies
make_dependencies() {
static mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() {
return {
.discretization = {.identity = 211, .revision = 2},
.enthalpy = {.identity = 223, .revision = 3},
@@ -18,8 +16,7 @@ namespace prepared_hydrostatic_test_utils {
};
}
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
make_state(
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state(
const mfem::Vector &enthalpy,
const mfem::Vector &gravityPotential,
const mfem::Vector &displacement,
@@ -37,18 +34,14 @@ namespace prepared_hydrostatic_test_utils {
const mean_field::fem::FEM &f,
const double phase = 0.19
) {
return gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), phase
);
return gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), phase);
}
mfem::Vector make_gravity_potential(
const mean_field::fem::FEM &f,
const double phase = 0.37
) {
return gravity_prepared_test_utils::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), phase
);
return gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), phase);
}
mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
@@ -72,39 +65,30 @@ TEST_CASE(
"Prepared Hydrostatic Residual Matches Stateless Kernel",
tags::barotrope &tags::hydro &tags::prepared &tags::residuals &tags::unit
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::operators::PreparedHydrostaticEquilibriumOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
const mfem::Vector enthalpy =
prepared_hydrostatic_test_utils::make_enthalpy(f);
const mfem::Vector enthalpy = prepared_hydrostatic_test_utils::make_enthalpy(f);
const mfem::Vector gravityPotential =
prepared_hydrostatic_test_utils::make_gravity_potential(f);
const mfem::Vector gravityPotential = prepared_hydrostatic_test_utils::make_gravity_potential(f);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.73);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.73);
constexpr double bernoulliConstant = 0.41;
constexpr double bernoulliConstant = 0.41;
const mean_field::physics::RigidRotation rotation =
prepared_hydrostatic_test_utils::make_rotation();
const mean_field::physics::RigidRotation rotation = prepared_hydrostatic_test_utils::make_rotation();
const auto dependencies =
prepared_hydrostatic_test_utils::make_dependencies();
const auto dependencies = prepared_hydrostatic_test_utils::make_dependencies();
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK(preparedOperator.GetResidualPreparationCount() == 0);
CHECK(preparedOperator.GetResidualApplicationCount() == 0);
const auto report = preparedOperator.Prepare(
prepared_hydrostatic_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies, rotation
);
@@ -114,13 +98,12 @@ TEST_CASE(
preparedOperator.BuildResidual(preparedResidual);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential,
displacement, bernoulliConstant, referenceResidual
f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential, displacement, bernoulliConstant,
referenceResidual
);
const double relativeError = gravity_prepared_test_utils::relative_error(
preparedResidual, referenceResidual, f.mesh->GetComm()
);
const double relativeError =
gravity_prepared_test_utils::relative_error(preparedResidual, referenceResidual, f.mesh->GetComm());
INFO("Prepared hydrostatic residual relative error = " << relativeError);
@@ -150,41 +133,33 @@ TEST_CASE(
"Prepared Hydrostatic Residual Reuses And Selectively Rebuilds Data",
tags::barotrope &tags::hydro &tags::prepared &tags::residuals &tags::unit
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::operators::PreparedHydrostaticEquilibriumOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
mfem::Vector enthalpy = prepared_hydrostatic_test_utils::make_enthalpy(f);
mfem::Vector enthalpy = prepared_hydrostatic_test_utils::make_enthalpy(f);
mfem::Vector gravityPotential =
prepared_hydrostatic_test_utils::make_gravity_potential(f);
mfem::Vector gravityPotential = prepared_hydrostatic_test_utils::make_gravity_potential(f);
mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.42);
mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.42);
double bernoulliConstant = 0.36;
double bernoulliConstant = 0.36;
const mfem::Vector initialEnthalpy(enthalpy);
const mfem::Vector initialGravityPotential(gravityPotential);
const mfem::Vector initialDisplacement(displacement);
const double initialBernoulliConstant = bernoulliConstant;
const double initialBernoulliConstant = bernoulliConstant;
const mean_field::physics::RigidRotation initialRotation =
prepared_hydrostatic_test_utils::make_rotation(0.80);
const mean_field::physics::RigidRotation initialRotation = prepared_hydrostatic_test_utils::make_rotation(0.80);
const mean_field::physics::RigidRotation changedRotation =
prepared_hydrostatic_test_utils::make_rotation(1.25);
const mean_field::physics::RigidRotation changedRotation = prepared_hydrostatic_test_utils::make_rotation(1.25);
auto dependencies = prepared_hydrostatic_test_utils::make_dependencies();
auto dependencies = prepared_hydrostatic_test_utils::make_dependencies();
preparedOperator.Prepare(
prepared_hydrostatic_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies, initialRotation
);
@@ -197,9 +172,7 @@ TEST_CASE(
bernoulliConstant += 0.23;
const auto unchangedReport = preparedOperator.Prepare(
prepared_hydrostatic_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies, changedRotation
);
@@ -209,11 +182,7 @@ TEST_CASE(
CHECK_FALSE(unchangedReport.DidAnyWork());
CHECK_FALSE(unchangedReport.updatedRotation);
CHECK_FALSE(unchangedReport.preparedResidual);
CHECK(
gravity_prepared_test_utils::relative_error(
unchangedResidual, initialResidual, f.mesh->GetComm()
) == 0.0
);
CHECK(gravity_prepared_test_utils::relative_error(unchangedResidual, initialResidual, f.mesh->GetComm()) == 0.0);
CHECK(preparedOperator.GetResidualPreparationCount() == 1);
// Only the enthalpy stamp changes. The altered potential,
@@ -221,9 +190,7 @@ TEST_CASE(
++dependencies.enthalpy.revision;
const auto enthalpyReport = preparedOperator.Prepare(
prepared_hydrostatic_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies, changedRotation
);
@@ -233,9 +200,8 @@ TEST_CASE(
preparedOperator.BuildResidual(enthalpyResidual);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, initialRotation, enthalpy,
initialGravityPotential, initialDisplacement, initialBernoulliConstant,
enthalpyReference
f, *f.domainMapperStateless, initialRotation, enthalpy, initialGravityPotential, initialDisplacement,
initialBernoulliConstant, enthalpyReference
);
CHECK_FALSE(enthalpyReport.contextReport.preparedStaticDependencies);
@@ -245,17 +211,13 @@ TEST_CASE(
CHECK_FALSE(enthalpyReport.updatedRotation);
CHECK(enthalpyReport.preparedResidual);
CHECK(
gravity_prepared_test_utils::relative_error(
enthalpyResidual, enthalpyReference, f.mesh->GetComm()
) < 2.0e-12
gravity_prepared_test_utils::relative_error(enthalpyResidual, enthalpyReference, f.mesh->GetComm()) < 2.0e-12
);
++dependencies.rotation.revision;
const auto rotationReport = preparedOperator.Prepare(
prepared_hydrostatic_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies, changedRotation
);
@@ -265,9 +227,8 @@ TEST_CASE(
preparedOperator.BuildResidual(rotationResidual);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, changedRotation, enthalpy,
initialGravityPotential, initialDisplacement, initialBernoulliConstant,
rotationReference
f, *f.domainMapperStateless, changedRotation, enthalpy, initialGravityPotential, initialDisplacement,
initialBernoulliConstant, rotationReference
);
CHECK_FALSE(rotationReport.contextReport.preparedStaticDependencies);
@@ -277,17 +238,13 @@ TEST_CASE(
CHECK(rotationReport.updatedRotation);
CHECK(rotationReport.preparedResidual);
CHECK(
gravity_prepared_test_utils::relative_error(
rotationResidual, rotationReference, f.mesh->GetComm()
) < 2.0e-12
gravity_prepared_test_utils::relative_error(rotationResidual, rotationReference, f.mesh->GetComm()) < 2.0e-12
);
++dependencies.displacement.revision;
const auto displacementReport = preparedOperator.Prepare(
prepared_hydrostatic_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant),
dependencies, changedRotation
);
@@ -297,9 +254,8 @@ TEST_CASE(
preparedOperator.BuildResidual(displacementResidual);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, changedRotation, enthalpy,
initialGravityPotential, displacement, initialBernoulliConstant,
displacementReference
f, *f.domainMapperStateless, changedRotation, enthalpy, initialGravityPotential, displacement,
initialBernoulliConstant, displacementReference
);
CHECK_FALSE(displacementReport.contextReport.preparedStaticDependencies);
@@ -309,9 +265,8 @@ TEST_CASE(
CHECK_FALSE(displacementReport.updatedRotation);
CHECK(displacementReport.preparedResidual);
CHECK(
gravity_prepared_test_utils::relative_error(
displacementResidual, displacementReference, f.mesh->GetComm()
) < 2.0e-12
gravity_prepared_test_utils::relative_error(displacementResidual, displacementReference, f.mesh->GetComm()) <
2.0e-12
);
const auto &statistics = preparedOperator.GetContextPreparationStatistics();
@@ -324,9 +279,7 @@ TEST_CASE(
CHECK(preparedOperator.GetResidualApplicationCount() == 5);
const double displacementEffect =
gravity_prepared_test_utils::relative_error(
displacementResidual, rotationResidual, f.mesh->GetComm()
);
gravity_prepared_test_utils::relative_error(displacementResidual, rotationResidual, f.mesh->GetComm());
INFO("Residual change after displacement update = " << displacementEffect);

View File

@@ -24,8 +24,7 @@ namespace prepared_hydrostatic_analytic_solve_test_utils {
public:
EnthalpyJacobianOperator(
const int enthalpySize,
const mean_field::operators::PreparedHydrostaticEquilibriumOperator
&preparedOperator
const mean_field::operators::PreparedHydrostaticEquilibriumOperator &preparedOperator
)
: mfem::Operator(enthalpySize),
m_preparedOperator(preparedOperator) {
@@ -39,13 +38,10 @@ namespace prepared_hydrostatic_analytic_solve_test_utils {
}
private:
const mean_field::operators::PreparedHydrostaticEquilibriumOperator
&m_preparedOperator;
const mean_field::operators::PreparedHydrostaticEquilibriumOperator &m_preparedOperator;
};
mean_field::operators::context::hydrostatic::
HydrostaticEquilibriumDependencies
make_dependencies() {
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() {
return {
.discretization = {.identity = 701, .revision = 2},
.enthalpy = {.identity = 709, .revision = 3},
@@ -56,8 +52,7 @@ namespace prepared_hydrostatic_analytic_solve_test_utils {
};
}
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
make_state(
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state(
const mfem::Vector &enthalpy,
const mfem::Vector &gravityPotential,
const mfem::Vector &displacement
@@ -84,11 +79,9 @@ namespace prepared_hydrostatic_analytic_solve_test_utils {
return vector;
}
mean_field::physics::RigidRotation
make_rotation(const AnalyticCase &analyticCase) {
mean_field::physics::RigidRotation make_rotation(const AnalyticCase &analyticCase) {
return mean_field::physics::RigidRotation(
make_vector(analyticCase.angularVelocity),
make_vector(analyticCase.rotationCenter)
make_vector(analyticCase.angularVelocity), make_vector(analyticCase.rotationCenter)
);
}
@@ -101,9 +94,7 @@ namespace prepared_hydrostatic_analytic_solve_test_utils {
for (int component = 0; component < 3; ++component) {
physicalPosition(component) =
analyticCase
.deformationScale[static_cast<std::size_t>(component)] *
referencePosition(component);
analyticCase.deformationScale[static_cast<std::size_t>(component)] * referencePosition(component);
}
}
@@ -111,11 +102,9 @@ namespace prepared_hydrostatic_analytic_solve_test_utils {
double normalizedRadiusSquared = 0.0;
for (int component = 0; component < 3; ++component) {
const double normalizedCoordinate =
referencePosition(component) / mean_field::utils::RADIUS;
const double normalizedCoordinate = referencePosition(component) / mean_field::utils::RADIUS;
normalizedRadiusSquared +=
normalizedCoordinate * normalizedCoordinate;
normalizedRadiusSquared += normalizedCoordinate * normalizedCoordinate;
}
return enthalpyAmplitude * std::max(0.0, 1.0 - normalizedRadiusSquared);
@@ -137,20 +126,16 @@ namespace prepared_hydrostatic_analytic_solve_test_utils {
*
* analytically.
*/
return bernoulliConstant + rotation.potential(physicalPosition) -
exact_enthalpy_value(referencePosition);
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> 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 = f.domainMapperStateless->GetVacuumElementAttribute();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
stellarElementMarker[elementId] =
f.mesh->GetAttribute(elementId) != vacuumAttribute;
stellarElementMarker[elementId] = f.mesh->GetAttribute(elementId) != vacuumAttribute;
}
return stellarElementMarker;
@@ -159,9 +144,8 @@ namespace prepared_hydrostatic_analytic_solve_test_utils {
TEST_CASE(
"Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
tags::barotrope &tags::hydro &tags::prepared &tags::integration
&tags::solver &tags::convergence &tags::accuracy
&tags::analytic_comparison
tags::barotrope &tags::hydro &tags::prepared &tags::integration &tags::solver &tags::convergence &tags::accuracy
&tags::analytic_comparison
) {
using prepared_hydrostatic_analytic_solve_test_utils::AnalyticCase;
@@ -191,70 +175,53 @@ TEST_CASE(
.rotationCenter = {0.031, -0.024, 0.018}}}
};
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const MPI_Comm communicator = f.mesh->GetComm();
const mfem::Array<int> stellarElementMarker =
prepared_hydrostatic_analytic_solve_test_utils::
make_stellar_element_marker(f);
prepared_hydrostatic_analytic_solve_test_utils::make_stellar_element_marker(f);
for (const AnalyticCase &analyticCase : analyticCases) {
DYNAMIC_SECTION(analyticCase.name) {
const double deformationDeterminant =
analyticCase.deformationScale[0] *
analyticCase.deformationScale[1] *
analyticCase.deformationScale[2];
analyticCase.deformationScale[0] * analyticCase.deformationScale[1] * analyticCase.deformationScale[2];
REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14);
const mean_field::physics::RigidRotation rotation =
prepared_hydrostatic_analytic_solve_test_utils::make_rotation(
analyticCase
);
prepared_hydrostatic_analytic_solve_test_utils::make_rotation(analyticCase);
auto displacementFunction = [&analyticCase](
const mfem::Vector
&referencePosition,
mfem::Vector &displacementValue
) {
mfem::Vector physicalPosition;
auto displacementFunction =
[&analyticCase](const mfem::Vector &referencePosition, mfem::Vector &displacementValue) {
mfem::Vector physicalPosition;
prepared_hydrostatic_analytic_solve_test_utils::map_to_physical(
referencePosition, analyticCase, physicalPosition
);
displacementValue.SetSize(3);
displacementValue = physicalPosition;
displacementValue -= referencePosition;
};
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
);
displacementValue.SetSize(3);
displacementValue = physicalPosition;
displacementValue -= referencePosition;
};
auto potentialFunction = [&analyticCase, &rotation](const mfem::Vector &referencePosition) {
return prepared_hydrostatic_analytic_solve_test_utils::exact_potential_value(
referencePosition, analyticCase, rotation
);
};
auto enthalpyFunction = [](const mfem::Vector &referencePosition) {
return prepared_hydrostatic_analytic_solve_test_utils::
exact_enthalpy_value(referencePosition);
return prepared_hydrostatic_analytic_solve_test_utils::exact_enthalpy_value(referencePosition);
};
mfem::VectorFunctionCoefficient displacementCoefficient(
f.mesh->Dimension(), displacementFunction
);
mfem::VectorFunctionCoefficient displacementCoefficient(f.mesh->Dimension(), displacementFunction);
mfem::FunctionCoefficient potentialCoefficient(potentialFunction);
mfem::FunctionCoefficient exactEnthalpyCoefficient(
enthalpyFunction
);
mfem::FunctionCoefficient exactEnthalpyCoefficient(enthalpyFunction);
/*
* Project the prescribed geometry and potential.
@@ -284,21 +251,17 @@ TEST_CASE(
mfem::ParGridFunction zeroEnthalpyField(f.enthalpyFes.get());
zeroEnthalpyField = 0.0;
zeroEnthalpyField = 0.0;
const double exactEnthalpyNorm = zeroEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker
);
const double exactEnthalpyNorm =
zeroEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
const double projectionError =
projectedEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker
);
projectedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
REQUIRE(exactEnthalpyNorm > 0.0);
const double relativeProjectionError =
projectionError / exactEnthalpyNorm;
const double relativeProjectionError = projectionError / exactEnthalpyNorm;
/*
* Begin deliberately far from equilibrium.
@@ -307,16 +270,12 @@ TEST_CASE(
enthalpy = 0.0;
auto dependencies = prepared_hydrostatic_analytic_solve_test_utils::
make_dependencies();
auto dependencies = prepared_hydrostatic_analytic_solve_test_utils::make_dependencies();
mean_field::operators::PreparedHydrostaticEquilibriumOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
const auto initialReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(
enthalpy, gravityPotential, displacement
),
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
dependencies, rotation
);
@@ -327,10 +286,7 @@ TEST_CASE(
preparedOperator.BuildResidual(initialResidual);
const double initialResidualNorm =
gravity_prepared_test_utils::global_norm(
initialResidual, communicator
);
const double initialResidualNorm = gravity_prepared_test_utils::global_norm(initialResidual, communicator);
REQUIRE(initialResidualNorm > 1.0e-12);
@@ -344,10 +300,9 @@ TEST_CASE(
* rotation, and displacement makes this a well-defined
* enthalpy solve.
*/
prepared_hydrostatic_analytic_solve_test_utils::
EnthalpyJacobianOperator enthalpyJacobian(
f.enthalpyFes->GetTrueVSize(), preparedOperator
);
prepared_hydrostatic_analytic_solve_test_utils::EnthalpyJacobianOperator enthalpyJacobian(
f.enthalpyFes->GetTrueVSize(), preparedOperator
);
mfem::Vector rightHandSide(initialResidual);
rightHandSide *= -1.0;
@@ -375,9 +330,7 @@ TEST_CASE(
INFO("Linear solver converged = " << linearSolver.GetConverged());
INFO(
"Linear solver iterations = " << linearSolver.GetNumIterations()
);
INFO("Linear solver iterations = " << linearSolver.GetNumIterations());
INFO("Linear solver final norm = " << linearSolver.GetFinalNorm());
@@ -392,9 +345,7 @@ TEST_CASE(
++dependencies.enthalpy.revision;
const auto solvedReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(
enthalpy, gravityPotential, displacement
),
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
dependencies, rotation
);
@@ -410,13 +361,9 @@ TEST_CASE(
preparedOperator.BuildResidual(solvedResidual);
const double solvedResidualNorm =
gravity_prepared_test_utils::global_norm(
solvedResidual, communicator
);
const double solvedResidualNorm = gravity_prepared_test_utils::global_norm(solvedResidual, communicator);
const double residualReduction =
solvedResidualNorm / initialResidualNorm;
const double residualReduction = solvedResidualNorm / initialResidualNorm;
/*
* Compare the solved field with the continuum analytic
@@ -431,12 +378,9 @@ TEST_CASE(
solvedEnthalpyField.SetFromTrueDofs(enthalpy);
const double solvedAnalyticError =
solvedEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker
);
solvedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
const double relativeSolvedAnalyticError =
solvedAnalyticError / exactEnthalpyNorm;
const double relativeSolvedAnalyticError = solvedAnalyticError / exactEnthalpyNorm;
INFO("Deformation determinant = " << deformationDeterminant);
@@ -446,15 +390,9 @@ TEST_CASE(
INFO("Weak residual reduction = " << residualReduction);
INFO(
"Relative analytic projection floor = "
<< relativeProjectionError
);
INFO("Relative analytic projection floor = " << relativeProjectionError);
INFO(
"Relative solved analytic L2 error = "
<< relativeSolvedAnalyticError
);
INFO("Relative solved analytic L2 error = " << relativeSolvedAnalyticError);
/*
* The discrete Bernoulli equation must be solved essentially
@@ -469,10 +407,7 @@ TEST_CASE(
* also contains potential-projection and mapped-space
* compatibility errors.
*/
CHECK(
relativeSolvedAnalyticError <
std::max(5.0 * relativeProjectionError, 1.25e-4)
);
CHECK(relativeSolvedAnalyticError < std::max(5.0 * relativeProjectionError, 1.25e-4));
/*
* Record that the analytic error remains within one order of

View File

@@ -5,9 +5,7 @@ import mean_field;
import test_helpers;
namespace prepared_hydrostatic_complete_test_utils {
mean_field::operators::context::hydrostatic::
HydrostaticEquilibriumDependencies
make_dependencies() {
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() {
return {
.discretization = {.identity = 503, .revision = 2},
.enthalpy = {.identity = 509, .revision = 3},
@@ -18,8 +16,7 @@ namespace prepared_hydrostatic_complete_test_utils {
};
}
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
make_state(
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state(
const mfem::Vector &enthalpy,
const mfem::Vector &gravityPotential,
const mfem::Vector &displacement,
@@ -54,11 +51,9 @@ namespace prepared_hydrostatic_complete_test_utils {
const double firstPhase,
const double secondPhase
) {
mfem::Vector direction =
gravity_prepared_test_utils::make_displacement(f, firstPhase);
mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, firstPhase);
const mfem::Vector secondField =
gravity_prepared_test_utils::make_displacement(f, secondPhase);
const mfem::Vector secondField = gravity_prepared_test_utils::make_displacement(f, secondPhase);
direction -= secondField;
return direction;
@@ -95,25 +90,21 @@ namespace prepared_hydrostatic_complete_test_utils {
displacementPlus.Add(step, displacementVariation);
displacementMinus.Add(-step, displacementVariation);
const double bernoulliConstantPlus =
baseBernoulliConstant + step * bernoulliConstantVariation;
const double bernoulliConstantPlus = baseBernoulliConstant + step * bernoulliConstantVariation;
const double bernoulliConstantMinus =
baseBernoulliConstant - step * bernoulliConstantVariation;
const double bernoulliConstantMinus = baseBernoulliConstant - step * bernoulliConstantVariation;
mfem::Vector residualPlus;
mfem::Vector residualMinus;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpyPlus,
gravityPotentialPlus, displacementPlus, bernoulliConstantPlus,
residualPlus
f, *f.domainMapperStateless, rotation, enthalpyPlus, gravityPotentialPlus, displacementPlus,
bernoulliConstantPlus, residualPlus
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpyMinus,
gravityPotentialMinus, displacementMinus, bernoulliConstantMinus,
residualMinus
f, *f.domainMapperStateless, rotation, enthalpyMinus, gravityPotentialMinus, displacementMinus,
bernoulliConstantMinus, residualMinus
);
difference = residualPlus;
@@ -140,34 +131,25 @@ namespace prepared_hydrostatic_complete_test_utils {
TEST_CASE(
"Prepared Hydrostatic Complete Jacobian Matches Sum And Centered "
"Differences",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian
&tags::prepared &tags::self_consistency
tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::prepared &tags::self_consistency
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::operators::PreparedHydrostaticEquilibriumOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
const mfem::Vector enthalpy =
gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), 0.34
);
gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.34);
const mfem::Vector gravityPotential =
gravity_prepared_test_utils::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), 0.57
);
gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.57);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.68);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.68);
constexpr double bernoulliConstant = 0.43;
constexpr double bernoulliConstant = 0.43;
const mean_field::physics::RigidRotation rotation =
prepared_hydrostatic_complete_test_utils::make_rotation();
const mean_field::physics::RigidRotation rotation = prepared_hydrostatic_complete_test_utils::make_rotation();
preparedOperator.Prepare(
prepared_hydrostatic_complete_test_utils::make_state(
@@ -177,19 +159,13 @@ TEST_CASE(
);
const mfem::Vector enthalpyVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), 1.07
);
gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 1.07);
const mfem::Vector gravityPotentialVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), 1.31
);
gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 1.31);
const mfem::Vector displacementVariation =
prepared_hydrostatic_complete_test_utils::make_displacement_direction(
f, 1.19, 0.38
);
prepared_hydrostatic_complete_test_utils::make_displacement_direction(f, 1.19, 0.38);
constexpr double bernoulliConstantVariation = -0.37;
@@ -199,25 +175,16 @@ TEST_CASE(
mfem::Vector displacementAction;
mfem::Vector completeAction;
preparedOperator.ApplyEnthalpyJacobianAction(
enthalpyVariation, enthalpyAction
);
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyVariation, enthalpyAction);
preparedOperator.ApplyGravityPotentialJacobianAction(
gravityPotentialVariation, gravityPotentialAction
);
preparedOperator.ApplyGravityPotentialJacobianAction(gravityPotentialVariation, gravityPotentialAction);
preparedOperator.ApplyBernoulliConstantJacobianAction(
bernoulliConstantVariation, bernoulliConstantAction
);
preparedOperator.ApplyBernoulliConstantJacobianAction(bernoulliConstantVariation, bernoulliConstantAction);
preparedOperator.ApplyDisplacementJacobianAction(
displacementVariation, displacementAction
);
preparedOperator.ApplyDisplacementJacobianAction(displacementVariation, displacementAction);
preparedOperator.ApplyCompleteJacobianAction(
enthalpyVariation, gravityPotentialVariation,
bernoulliConstantVariation, displacementVariation, completeAction
enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, displacementVariation, completeAction
);
mfem::Vector summedAction(enthalpyAction);
@@ -230,27 +197,20 @@ TEST_CASE(
mfem::Vector centeredDifference;
prepared_hydrostatic_complete_test_utils::centered_complete_difference(
f, rotation, enthalpy, gravityPotential, displacement,
bernoulliConstant, enthalpyVariation, gravityPotentialVariation,
displacementVariation, bernoulliConstantVariation, finiteDifferenceStep,
f, rotation, enthalpy, gravityPotential, displacement, bernoulliConstant, enthalpyVariation,
gravityPotentialVariation, displacementVariation, bernoulliConstantVariation, finiteDifferenceStep,
centeredDifference
);
const double summationError = gravity_prepared_test_utils::relative_error(
completeAction, summedAction, f.mesh->GetComm()
);
const double summationError =
gravity_prepared_test_utils::relative_error(completeAction, summedAction, f.mesh->GetComm());
const double centeredDifferenceError =
gravity_prepared_test_utils::relative_error(
completeAction, centeredDifference, f.mesh->GetComm()
);
gravity_prepared_test_utils::relative_error(completeAction, centeredDifference, f.mesh->GetComm());
INFO("Complete-action summation error = " << summationError);
INFO(
"Complete-action centered-difference error = "
<< centeredDifferenceError
);
INFO("Complete-action centered-difference error = " << centeredDifferenceError);
CHECK(preparedOperator.GetCompleteJacobianStatistics().applications == 1);
@@ -261,29 +221,21 @@ TEST_CASE(
TEST_CASE(
"Prepared Hydrostatic MFEM Adapter Uses Four Block Layout And Reuses "
"Preparation",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian
&tags::mfem_operators &tags::prepared &tags::unit
tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::mfem_operators &tags::prepared &tags::unit
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::operators::PreparedHydrostaticEquilibriumOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
const mfem::Vector enthalpy =
gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), 0.41
);
gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.41);
const mfem::Vector gravityPotential =
gravity_prepared_test_utils::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), 0.63
);
gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.63);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.74);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.74);
constexpr double bernoulliConstant = 0.38;
@@ -295,49 +247,31 @@ TEST_CASE(
prepared_hydrostatic_complete_test_utils::make_rotation()
);
mean_field::operators::PreparedHydrostaticEquilibriumJacobianOperator
adapter(f, preparedOperator);
mean_field::operators::PreparedHydrostaticEquilibriumJacobianOperator adapter(f, preparedOperator);
const auto &layout = adapter.GetLayout();
CHECK(
layout.Offset(
mean_field::operators::HydrostaticJacobianInputBlock::enthalpy
) == 0
);
CHECK(layout.Offset(mean_field::operators::HydrostaticJacobianInputBlock::enthalpy) == 0);
CHECK(
layout.Offset(
mean_field::operators::HydrostaticJacobianInputBlock::
gravityPotential
) == f.enthalpyFes->GetTrueVSize()
layout.Offset(mean_field::operators::HydrostaticJacobianInputBlock::gravityPotential) ==
f.enthalpyFes->GetTrueVSize()
);
CHECK(
layout.Size(
mean_field::operators::HydrostaticJacobianInputBlock::
bernoulliConstant
) == 1
);
CHECK(layout.Size(mean_field::operators::HydrostaticJacobianInputBlock::bernoulliConstant) == 1);
CHECK(adapter.Width() == layout.GetTotalSize());
CHECK(adapter.Height() == layout.GetResidualSize());
CHECK(adapter.Height() == f.enthalpyFes->GetTrueVSize());
const mfem::Vector enthalpyVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), 1.12
);
gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 1.12);
const mfem::Vector gravityPotentialVariation =
gravity_prepared_test_utils::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), 1.39
);
gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 1.39);
const mfem::Vector displacementVariation =
prepared_hydrostatic_complete_test_utils::make_displacement_direction(
f, 1.28, 0.49
);
prepared_hydrostatic_complete_test_utils::make_displacement_direction(f, 1.28, 0.49);
constexpr double bernoulliConstantVariation = 0.29;
@@ -345,49 +279,39 @@ TEST_CASE(
packedDirection = 0.0;
prepared_hydrostatic_complete_test_utils::set_block(
packedDirection, layout,
mean_field::operators::HydrostaticJacobianInputBlock::enthalpy,
enthalpyVariation
packedDirection, layout, mean_field::operators::HydrostaticJacobianInputBlock::enthalpy, enthalpyVariation
);
prepared_hydrostatic_complete_test_utils::set_block(
packedDirection, layout,
mean_field::operators::HydrostaticJacobianInputBlock::gravityPotential,
packedDirection, layout, mean_field::operators::HydrostaticJacobianInputBlock::gravityPotential,
gravityPotentialVariation
);
prepared_hydrostatic_complete_test_utils::set_block(
packedDirection, layout,
mean_field::operators::HydrostaticJacobianInputBlock::displacement,
packedDirection, layout, mean_field::operators::HydrostaticJacobianInputBlock::displacement,
displacementVariation
);
packedDirection(layout.Offset(
mean_field::operators::HydrostaticJacobianInputBlock::bernoulliConstant
)) = bernoulliConstantVariation;
packedDirection(layout.Offset(mean_field::operators::HydrostaticJacobianInputBlock::bernoulliConstant)) =
bernoulliConstantVariation;
mfem::Vector directAction;
mfem::Vector adapterAction;
preparedOperator.ApplyCompleteJacobianAction(
enthalpyVariation, gravityPotentialVariation,
bernoulliConstantVariation, displacementVariation, directAction
enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, displacementVariation, directAction
);
adapter.Mult(packedDirection, adapterAction);
const double adapterError = gravity_prepared_test_utils::relative_error(
adapterAction, directAction, f.mesh->GetComm()
);
const double adapterError =
gravity_prepared_test_utils::relative_error(adapterAction, directAction, f.mesh->GetComm());
const auto contextStatisticsBefore =
preparedOperator.GetContextPreparationStatistics();
const auto contextStatisticsBefore = preparedOperator.GetContextPreparationStatistics();
const auto algebraicStatisticsBefore =
preparedOperator.GetAlgebraicJacobianStatistics();
const auto algebraicStatisticsBefore = preparedOperator.GetAlgebraicJacobianStatistics();
const auto displacementStatisticsBefore =
preparedOperator.GetDisplacementJacobianStatistics();
const auto displacementStatisticsBefore = preparedOperator.GetDisplacementJacobianStatistics();
mfem::Vector secondPackedDirection(packedDirection);
secondPackedDirection *= -0.61;
@@ -399,18 +323,13 @@ TEST_CASE(
expectedSecondAction *= -0.61;
const double adapterLinearityError =
gravity_prepared_test_utils::relative_error(
secondAdapterAction, expectedSecondAction, f.mesh->GetComm()
);
gravity_prepared_test_utils::relative_error(secondAdapterAction, expectedSecondAction, f.mesh->GetComm());
const auto &contextStatisticsAfter =
preparedOperator.GetContextPreparationStatistics();
const auto &contextStatisticsAfter = preparedOperator.GetContextPreparationStatistics();
const auto &algebraicStatisticsAfter =
preparedOperator.GetAlgebraicJacobianStatistics();
const auto &algebraicStatisticsAfter = preparedOperator.GetAlgebraicJacobianStatistics();
const auto &displacementStatisticsAfter =
preparedOperator.GetDisplacementJacobianStatistics();
const auto &displacementStatisticsAfter = preparedOperator.GetDisplacementJacobianStatistics();
INFO("MFEM adapter/direct-action error = " << adapterError);
@@ -422,15 +341,9 @@ TEST_CASE(
CHECK(contextStatisticsAfter == contextStatisticsBefore);
CHECK(
algebraicStatisticsAfter.preparations ==
algebraicStatisticsBefore.preparations
);
CHECK(algebraicStatisticsAfter.preparations == algebraicStatisticsBefore.preparations);
CHECK(
displacementStatisticsAfter.preparations ==
displacementStatisticsBefore.preparations
);
CHECK(displacementStatisticsAfter.preparations == displacementStatisticsBefore.preparations);
CHECK(preparedOperator.GetCompleteJacobianStatistics().applications == 3);
}

View File

@@ -5,9 +5,7 @@ import mean_field;
import test_helpers;
namespace prepared_hydrostatic_displacement_test_utils {
mean_field::operators::context::hydrostatic::
HydrostaticEquilibriumDependencies
make_dependencies() {
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() {
return {
.discretization = {.identity = 401, .revision = 2},
.enthalpy = {.identity = 409, .revision = 3},
@@ -18,8 +16,7 @@ namespace prepared_hydrostatic_displacement_test_utils {
};
}
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
make_state(
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state(
const mfem::Vector &enthalpy,
const mfem::Vector &gravityPotential,
const mfem::Vector &displacement,
@@ -37,18 +34,14 @@ namespace prepared_hydrostatic_displacement_test_utils {
const mean_field::fem::FEM &f,
const double phase = 0.29
) {
return gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), phase
);
return gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), phase);
}
mfem::Vector make_gravity_potential(
const mean_field::fem::FEM &f,
const double phase = 0.47
) {
return gravity_prepared_test_utils::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), phase
);
return gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), phase);
}
mfem::Vector make_displacement_direction(
@@ -56,11 +49,9 @@ namespace prepared_hydrostatic_displacement_test_utils {
const double firstPhase,
const double secondPhase
) {
mfem::Vector direction =
gravity_prepared_test_utils::make_displacement(f, firstPhase);
mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, firstPhase);
const mfem::Vector secondField =
gravity_prepared_test_utils::make_displacement(f, secondPhase);
const mfem::Vector secondField = gravity_prepared_test_utils::make_displacement(f, secondPhase);
direction -= secondField;
return direction;
@@ -103,13 +94,13 @@ namespace prepared_hydrostatic_displacement_test_utils {
mfem::Vector residualMinus;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential,
displacementPlus, bernoulliConstant, residualPlus
f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential, displacementPlus, bernoulliConstant,
residualPlus
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential,
displacementMinus, bernoulliConstant, residualMinus
f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential, displacementMinus, bernoulliConstant,
residualMinus
);
difference = residualPlus;
@@ -122,9 +113,7 @@ namespace prepared_hydrostatic_displacement_test_utils {
const mfem::Vector &expected,
const MPI_Comm communicator
) {
return gravity_prepared_test_utils::relative_error(
actual, expected, communicator
);
return gravity_prepared_test_utils::relative_error(actual, expected, communicator);
}
} // namespace prepared_hydrostatic_displacement_test_utils
@@ -132,32 +121,26 @@ TEST_CASE(
"Prepared Hydrostatic Displacement Jacobian Matches Centered Differences",
tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::operators::PreparedHydrostaticEquilibriumOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
const mfem::Vector enthalpy =
prepared_hydrostatic_displacement_test_utils::make_enthalpy(f);
const mfem::Vector enthalpy = prepared_hydrostatic_displacement_test_utils::make_enthalpy(f);
const mfem::Vector gravityPotential =
prepared_hydrostatic_displacement_test_utils::make_gravity_potential(f);
const mfem::Vector gravityPotential = prepared_hydrostatic_displacement_test_utils::make_gravity_potential(f);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.73);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.73);
constexpr double bernoulliConstant = 0.39;
constexpr double bernoulliConstant = 0.39;
const mean_field::physics::RigidRotation rotation =
prepared_hydrostatic_displacement_test_utils::make_rotation(0.9);
const auto dependencies =
prepared_hydrostatic_displacement_test_utils::make_dependencies();
const auto dependencies = prepared_hydrostatic_displacement_test_utils::make_dependencies();
const auto report = preparedOperator.Prepare(
const auto report = preparedOperator.Prepare(
prepared_hydrostatic_displacement_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
@@ -165,12 +148,10 @@ TEST_CASE(
);
const mfem::Vector firstVariation =
prepared_hydrostatic_displacement_test_utils::
make_displacement_direction(f, 1.17, 0.31);
prepared_hydrostatic_displacement_test_utils::make_displacement_direction(f, 1.17, 0.31);
const mfem::Vector secondVariation =
prepared_hydrostatic_displacement_test_utils::
make_displacement_direction(f, 1.43, 0.58);
prepared_hydrostatic_displacement_test_utils::make_displacement_direction(f, 1.43, 0.58);
mfem::Vector combinedVariation(firstVariation);
combinedVariation += secondVariation;
@@ -179,51 +160,36 @@ TEST_CASE(
mfem::Vector secondAction;
mfem::Vector combinedAction;
preparedOperator.ApplyDisplacementJacobianAction(
firstVariation, firstAction
);
preparedOperator.ApplyDisplacementJacobianAction(firstVariation, firstAction);
preparedOperator.ApplyDisplacementJacobianAction(
secondVariation, secondAction
);
preparedOperator.ApplyDisplacementJacobianAction(secondVariation, secondAction);
preparedOperator.ApplyDisplacementJacobianAction(
combinedVariation, combinedAction
);
preparedOperator.ApplyDisplacementJacobianAction(combinedVariation, combinedAction);
constexpr double finiteDifferenceStep = 1.0e-5;
mfem::Vector centeredDifference;
prepared_hydrostatic_displacement_test_utils::
centered_displacement_difference(
f, rotation, enthalpy, gravityPotential, displacement,
firstVariation, bernoulliConstant, finiteDifferenceStep,
centeredDifference
);
prepared_hydrostatic_displacement_test_utils::centered_displacement_difference(
f, rotation, enthalpy, gravityPotential, displacement, firstVariation, bernoulliConstant, finiteDifferenceStep,
centeredDifference
);
mfem::Vector sumOfActions(firstAction);
sumOfActions += secondAction;
const double centeredDifferenceError =
prepared_hydrostatic_displacement_test_utils::relative_error(
firstAction, centeredDifference, f.mesh->GetComm()
);
const double centeredDifferenceError = prepared_hydrostatic_displacement_test_utils::relative_error(
firstAction, centeredDifference, f.mesh->GetComm()
);
const double linearityError =
prepared_hydrostatic_displacement_test_utils::relative_error(
combinedAction, sumOfActions, f.mesh->GetComm()
);
prepared_hydrostatic_displacement_test_utils::relative_error(combinedAction, sumOfActions, f.mesh->GetComm());
INFO(
"Prepared displacement centered-difference error = "
<< centeredDifferenceError
);
INFO("Prepared displacement centered-difference error = " << centeredDifferenceError);
INFO("Prepared displacement linearity error = " << linearityError);
const auto &statistics =
preparedOperator.GetDisplacementJacobianStatistics();
const auto &statistics = preparedOperator.GetDisplacementJacobianStatistics();
CHECK(report.preparedDisplacementJacobianData);
CHECK(statistics.preparations == 1);
@@ -237,32 +203,23 @@ TEST_CASE(
"Data",
tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::operators::PreparedHydrostaticEquilibriumOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
const mfem::Vector enthalpy =
prepared_hydrostatic_displacement_test_utils::make_enthalpy(f, 0.37);
const mfem::Vector enthalpy = prepared_hydrostatic_displacement_test_utils::make_enthalpy(f, 0.37);
const mfem::Vector gravityPotential =
prepared_hydrostatic_displacement_test_utils::make_gravity_potential(
f, 0.53
);
const mfem::Vector gravityPotential = prepared_hydrostatic_displacement_test_utils::make_gravity_potential(f, 0.53);
mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.42);
mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.42);
constexpr double bernoulliConstant = 0.36;
constexpr double bernoulliConstant = 0.36;
mean_field::physics::RigidRotation rotation =
prepared_hydrostatic_displacement_test_utils::make_rotation(0.75);
mean_field::physics::RigidRotation rotation = prepared_hydrostatic_displacement_test_utils::make_rotation(0.75);
auto dependencies =
prepared_hydrostatic_displacement_test_utils::make_dependencies();
auto dependencies = prepared_hydrostatic_displacement_test_utils::make_dependencies();
preparedOperator.Prepare(
prepared_hydrostatic_displacement_test_utils::make_state(
@@ -272,30 +229,21 @@ TEST_CASE(
);
const mfem::Vector displacementVariation =
prepared_hydrostatic_displacement_test_utils::
make_displacement_direction(f, 1.09, 0.27);
prepared_hydrostatic_displacement_test_utils::make_displacement_direction(f, 1.09, 0.27);
const mfem::Vector secondVariation =
prepared_hydrostatic_displacement_test_utils::
make_displacement_direction(f, 1.36, 0.64);
prepared_hydrostatic_displacement_test_utils::make_displacement_direction(f, 1.36, 0.64);
mfem::Vector initialAction;
mfem::Vector secondDirectionAction;
preparedOperator.ApplyDisplacementJacobianAction(
displacementVariation, initialAction
);
preparedOperator.ApplyDisplacementJacobianAction(displacementVariation, initialAction);
preparedOperator.ApplyDisplacementJacobianAction(
secondVariation, secondDirectionAction
);
preparedOperator.ApplyDisplacementJacobianAction(secondVariation, secondDirectionAction);
CHECK(
preparedOperator.GetDisplacementJacobianStatistics().preparations == 1
);
CHECK(preparedOperator.GetDisplacementJacobianStatistics().preparations == 1);
rotation =
prepared_hydrostatic_displacement_test_utils::make_rotation(1.45);
rotation = prepared_hydrostatic_displacement_test_utils::make_rotation(1.45);
++dependencies.rotation.revision;
@@ -308,30 +256,24 @@ TEST_CASE(
mfem::Vector rotationUpdatedAction;
preparedOperator.ApplyDisplacementJacobianAction(
displacementVariation, rotationUpdatedAction
);
preparedOperator.ApplyDisplacementJacobianAction(displacementVariation, rotationUpdatedAction);
constexpr double finiteDifferenceStep = 1.0e-5;
mfem::Vector rotationReference;
prepared_hydrostatic_displacement_test_utils::
centered_displacement_difference(
f, rotation, enthalpy, gravityPotential, displacement,
displacementVariation, bernoulliConstant, finiteDifferenceStep,
rotationReference
);
prepared_hydrostatic_displacement_test_utils::centered_displacement_difference(
f, rotation, enthalpy, gravityPotential, displacement, displacementVariation, bernoulliConstant,
finiteDifferenceStep, rotationReference
);
const double rotationReferenceError =
prepared_hydrostatic_displacement_test_utils::relative_error(
rotationUpdatedAction, rotationReference, f.mesh->GetComm()
);
const double rotationReferenceError = prepared_hydrostatic_displacement_test_utils::relative_error(
rotationUpdatedAction, rotationReference, f.mesh->GetComm()
);
const double rotationEffect =
prepared_hydrostatic_displacement_test_utils::relative_error(
rotationUpdatedAction, initialAction, f.mesh->GetComm()
);
const double rotationEffect = prepared_hydrostatic_displacement_test_utils::relative_error(
rotationUpdatedAction, initialAction, f.mesh->GetComm()
);
CHECK_FALSE(rotationReport.contextReport.preparedGeometryState);
CHECK(rotationReport.contextReport.preparedRotationDependencies);
@@ -355,54 +297,34 @@ TEST_CASE(
mfem::Vector geometryUpdatedAction;
preparedOperator.ApplyDisplacementJacobianAction(
displacementVariation, geometryUpdatedAction
);
preparedOperator.ApplyDisplacementJacobianAction(displacementVariation, geometryUpdatedAction);
mfem::Vector geometryReference;
prepared_hydrostatic_displacement_test_utils::
centered_displacement_difference(
f, rotation, enthalpy, gravityPotential, displacement,
displacementVariation, bernoulliConstant, finiteDifferenceStep,
geometryReference
);
const double geometryReferenceError =
prepared_hydrostatic_displacement_test_utils::relative_error(
geometryUpdatedAction, geometryReference, f.mesh->GetComm()
);
const double geometryEffect =
prepared_hydrostatic_displacement_test_utils::relative_error(
geometryUpdatedAction, rotationUpdatedAction, f.mesh->GetComm()
);
INFO(
"Rotation-updated displacement Jacobian error = "
<< rotationReferenceError
prepared_hydrostatic_displacement_test_utils::centered_displacement_difference(
f, rotation, enthalpy, gravityPotential, displacement, displacementVariation, bernoulliConstant,
finiteDifferenceStep, geometryReference
);
INFO(
"Displacement Jacobian change after rotation update = "
<< rotationEffect
const double geometryReferenceError = prepared_hydrostatic_displacement_test_utils::relative_error(
geometryUpdatedAction, geometryReference, f.mesh->GetComm()
);
INFO(
"Geometry-updated displacement Jacobian error = "
<< geometryReferenceError
const double geometryEffect = prepared_hydrostatic_displacement_test_utils::relative_error(
geometryUpdatedAction, rotationUpdatedAction, f.mesh->GetComm()
);
INFO(
"Displacement Jacobian change after geometry update = "
<< geometryEffect
);
INFO("Rotation-updated displacement Jacobian error = " << rotationReferenceError);
const auto &contextStatistics =
preparedOperator.GetContextPreparationStatistics();
INFO("Displacement Jacobian change after rotation update = " << rotationEffect);
const auto &displacementStatistics =
preparedOperator.GetDisplacementJacobianStatistics();
INFO("Geometry-updated displacement Jacobian error = " << geometryReferenceError);
INFO("Displacement Jacobian change after geometry update = " << geometryEffect);
const auto &contextStatistics = preparedOperator.GetContextPreparationStatistics();
const auto &displacementStatistics = preparedOperator.GetDisplacementJacobianStatistics();
CHECK(geometryReport.contextReport.preparedGeometryState);
CHECK(geometryReport.contextReport.preparedRotationDependencies);

View File

@@ -5,9 +5,7 @@ import mean_field;
import test_helpers;
namespace prepared_hydrostatic_jacobian_test_utils {
mean_field::operators::context::hydrostatic::
HydrostaticEquilibriumDependencies
make_dependencies() {
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() {
return {
.discretization = {.identity = 307, .revision = 2},
.enthalpy = {.identity = 311, .revision = 3},
@@ -18,8 +16,7 @@ namespace prepared_hydrostatic_jacobian_test_utils {
};
}
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
make_state(
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state(
const mfem::Vector &enthalpy,
const mfem::Vector &gravityPotential,
const mfem::Vector &displacement,
@@ -37,18 +34,14 @@ namespace prepared_hydrostatic_jacobian_test_utils {
const mean_field::fem::FEM &f,
const double phase = 0.23
) {
return gravity_prepared_test_utils::make_deterministic_vector(
f.enthalpyFes->GetTrueVSize(), phase
);
return gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), phase);
}
mfem::Vector make_gravity_potential(
const mean_field::fem::FEM &f,
const double phase = 0.41
) {
return gravity_prepared_test_utils::make_deterministic_vector(
f.gravityPotentialFes->GetTrueVSize(), phase
);
return gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), phase);
}
mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
@@ -83,15 +76,13 @@ namespace prepared_hydrostatic_jacobian_test_utils {
mfem::Vector residualMinus;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpyPlus,
gravityPotentialPlus, displacement, bernoulliConstantPlus,
residualPlus
f, *f.domainMapperStateless, rotation, enthalpyPlus, gravityPotentialPlus, displacement,
bernoulliConstantPlus, residualPlus
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpyMinus,
gravityPotentialMinus, displacement, bernoulliConstantMinus,
residualMinus
f, *f.domainMapperStateless, rotation, enthalpyMinus, gravityPotentialMinus, displacement,
bernoulliConstantMinus, residualMinus
);
// The two states are separated by one complete variation:
@@ -105,9 +96,7 @@ namespace prepared_hydrostatic_jacobian_test_utils {
const mfem::Vector &expected,
MPI_Comm communicator
) {
return gravity_prepared_test_utils::relative_error(
actual, expected, communicator
);
return gravity_prepared_test_utils::relative_error(actual, expected, communicator);
}
} // namespace prepared_hydrostatic_jacobian_test_utils
@@ -116,42 +105,33 @@ TEST_CASE(
"Differences",
tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::operators::PreparedHydrostaticEquilibriumOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
const mfem::Vector enthalpy =
prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f);
const mfem::Vector enthalpy = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f);
const mfem::Vector gravityPotential =
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f);
const mfem::Vector gravityPotential = prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f);
const mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.73);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.73);
constexpr double bernoulliConstant = 0.39;
constexpr double bernoulliConstant = 0.39;
const mean_field::physics::RigidRotation rotation =
prepared_hydrostatic_jacobian_test_utils::make_rotation();
const mean_field::physics::RigidRotation rotation = prepared_hydrostatic_jacobian_test_utils::make_rotation();
const auto report = preparedOperator.Prepare(
const auto report = preparedOperator.Prepare(
prepared_hydrostatic_jacobian_test_utils::make_state(
enthalpy, gravityPotential, displacement, bernoulliConstant
),
prepared_hydrostatic_jacobian_test_utils::make_dependencies(), rotation
);
const mfem::Vector enthalpyVariation =
prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 0.71);
const mfem::Vector enthalpyVariation = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 0.71);
const mfem::Vector gravityPotentialVariation =
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(
f, 0.83
);
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f, 0.83);
constexpr double bernoulliConstantVariation = -0.31;
@@ -160,21 +140,14 @@ TEST_CASE(
mfem::Vector bernoulliConstantAction;
mfem::Vector combinedAction;
preparedOperator.ApplyEnthalpyJacobianAction(
enthalpyVariation, enthalpyAction
);
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyVariation, enthalpyAction);
preparedOperator.ApplyGravityPotentialJacobianAction(
gravityPotentialVariation, gravityPotentialAction
);
preparedOperator.ApplyGravityPotentialJacobianAction(gravityPotentialVariation, gravityPotentialAction);
preparedOperator.ApplyBernoulliConstantJacobianAction(
bernoulliConstantVariation, bernoulliConstantAction
);
preparedOperator.ApplyBernoulliConstantJacobianAction(bernoulliConstantVariation, bernoulliConstantAction);
preparedOperator.ApplyAlgebraicJacobianAction(
enthalpyVariation, gravityPotentialVariation,
bernoulliConstantVariation, combinedAction
enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, combinedAction
);
mfem::Vector enthalpyPlus(enthalpy);
@@ -188,9 +161,8 @@ TEST_CASE(
mfem::Vector enthalpyReference;
prepared_hydrostatic_jacobian_test_utils::centered_residual_difference(
f, rotation, enthalpyPlus, enthalpyMinus, gravityPotential,
gravityPotential, displacement, bernoulliConstant, bernoulliConstant,
enthalpyReference
f, rotation, enthalpyPlus, enthalpyMinus, gravityPotential, gravityPotential, displacement, bernoulliConstant,
bernoulliConstant, enthalpyReference
);
enthalpyPlus = enthalpy;
@@ -203,8 +175,7 @@ TEST_CASE(
mfem::Vector gravityPotentialReference;
prepared_hydrostatic_jacobian_test_utils::centered_residual_difference(
f, rotation, enthalpy, enthalpy, gravityPotentialPlus,
gravityPotentialMinus, displacement, bernoulliConstant,
f, rotation, enthalpy, enthalpy, gravityPotentialPlus, gravityPotentialMinus, displacement, bernoulliConstant,
bernoulliConstant, gravityPotentialReference
);
@@ -214,9 +185,8 @@ TEST_CASE(
mfem::Vector bernoulliConstantReference;
prepared_hydrostatic_jacobian_test_utils::centered_residual_difference(
f, rotation, enthalpy, enthalpy, gravityPotential, gravityPotential,
displacement, bernoulliConstant + 0.5 * bernoulliConstantVariation,
bernoulliConstant - 0.5 * bernoulliConstantVariation,
f, rotation, enthalpy, enthalpy, gravityPotential, gravityPotential, displacement,
bernoulliConstant + 0.5 * bernoulliConstantVariation, bernoulliConstant - 0.5 * bernoulliConstantVariation,
bernoulliConstantReference
);
@@ -230,10 +200,9 @@ TEST_CASE(
mfem::Vector combinedReference;
prepared_hydrostatic_jacobian_test_utils::centered_residual_difference(
f, rotation, enthalpyPlus, enthalpyMinus, gravityPotentialPlus,
gravityPotentialMinus, displacement,
bernoulliConstant + 0.5 * bernoulliConstantVariation,
bernoulliConstant - 0.5 * bernoulliConstantVariation, combinedReference
f, rotation, enthalpyPlus, enthalpyMinus, gravityPotentialPlus, gravityPotentialMinus, displacement,
bernoulliConstant + 0.5 * bernoulliConstantVariation, bernoulliConstant - 0.5 * bernoulliConstantVariation,
combinedReference
);
mfem::Vector sumOfBlocks(enthalpyAction);
@@ -241,30 +210,21 @@ TEST_CASE(
sumOfBlocks += bernoulliConstantAction;
const double enthalpyError =
prepared_hydrostatic_jacobian_test_utils::relative_error(
enthalpyAction, enthalpyReference, f.mesh->GetComm()
);
prepared_hydrostatic_jacobian_test_utils::relative_error(enthalpyAction, enthalpyReference, f.mesh->GetComm());
const double gravityPotentialError =
prepared_hydrostatic_jacobian_test_utils::relative_error(
gravityPotentialAction, gravityPotentialReference, f.mesh->GetComm()
);
const double gravityPotentialError = prepared_hydrostatic_jacobian_test_utils::relative_error(
gravityPotentialAction, gravityPotentialReference, f.mesh->GetComm()
);
const double bernoulliConstantError =
prepared_hydrostatic_jacobian_test_utils::relative_error(
bernoulliConstantAction, bernoulliConstantReference,
f.mesh->GetComm()
);
const double bernoulliConstantError = prepared_hydrostatic_jacobian_test_utils::relative_error(
bernoulliConstantAction, bernoulliConstantReference, f.mesh->GetComm()
);
const double combinedError =
prepared_hydrostatic_jacobian_test_utils::relative_error(
combinedAction, combinedReference, f.mesh->GetComm()
);
prepared_hydrostatic_jacobian_test_utils::relative_error(combinedAction, combinedReference, f.mesh->GetComm());
const double blockSumError =
prepared_hydrostatic_jacobian_test_utils::relative_error(
combinedAction, sumOfBlocks, f.mesh->GetComm()
);
prepared_hydrostatic_jacobian_test_utils::relative_error(combinedAction, sumOfBlocks, f.mesh->GetComm());
INFO("Enthalpy block error = " << enthalpyError);
INFO("Gravity-potential block error = " << gravityPotentialError);
@@ -293,30 +253,23 @@ TEST_CASE(
"Geometry",
tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit
) {
auto args = test_utils::setup_args();
auto args = test_utils::setup_args();
mean_field::fem::FEM f =
mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mean_field::operators::PreparedHydrostaticEquilibriumOperator
preparedOperator(f, *f.domainMapperStateless);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
mfem::Vector enthalpy =
prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f);
mfem::Vector enthalpy = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f);
mfem::Vector gravityPotential =
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f);
mfem::Vector gravityPotential = prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f);
mfem::Vector displacement =
gravity_prepared_test_utils::make_displacement(f, 0.42);
mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.42);
double bernoulliConstant = 0.37;
double bernoulliConstant = 0.37;
mean_field::physics::RigidRotation rotation =
prepared_hydrostatic_jacobian_test_utils::make_rotation(0.8);
mean_field::physics::RigidRotation rotation = prepared_hydrostatic_jacobian_test_utils::make_rotation(0.8);
auto dependencies =
prepared_hydrostatic_jacobian_test_utils::make_dependencies();
auto dependencies = prepared_hydrostatic_jacobian_test_utils::make_dependencies();
preparedOperator.Prepare(
prepared_hydrostatic_jacobian_test_utils::make_state(
@@ -325,32 +278,25 @@ TEST_CASE(
dependencies, rotation
);
const mfem::Vector enthalpyVariation =
prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 0.67);
const mfem::Vector enthalpyVariation = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 0.67);
const mfem::Vector gravityPotentialVariation =
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(
f, 0.79
);
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f, 0.79);
constexpr double bernoulliConstantVariation = 0.28;
mfem::Vector initialAction;
preparedOperator.ApplyAlgebraicJacobianAction(
enthalpyVariation, gravityPotentialVariation,
bernoulliConstantVariation, initialAction
enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, initialAction
);
enthalpy = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 1.13);
enthalpy = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 1.13);
gravityPotential =
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(
f, 1.31
);
gravityPotential = prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f, 1.31);
bernoulliConstant = 0.62;
rotation = prepared_hydrostatic_jacobian_test_utils::make_rotation(1.4);
rotation = prepared_hydrostatic_jacobian_test_utils::make_rotation(1.4);
++dependencies.enthalpy.revision;
++dependencies.gravityPotential.revision;
@@ -367,8 +313,7 @@ TEST_CASE(
mfem::Vector baseStateChangedAction;
preparedOperator.ApplyAlgebraicJacobianAction(
enthalpyVariation, gravityPotentialVariation,
bernoulliConstantVariation, baseStateChangedAction
enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, baseStateChangedAction
);
CHECK_FALSE(baseStateReport.contextReport.preparedGeometryState);
@@ -382,19 +327,15 @@ TEST_CASE(
) == 0.0
);
const mfem::Vector secondEnthalpyVariation =
prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 1.57);
const mfem::Vector secondEnthalpyVariation = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 1.57);
const mfem::Vector secondGravityPotentialVariation =
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(
f, 1.73
);
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f, 1.73);
mfem::Vector secondDirectionAction;
preparedOperator.ApplyAlgebraicJacobianAction(
secondEnthalpyVariation, secondGravityPotentialVariation, -0.19,
secondDirectionAction
secondEnthalpyVariation, secondGravityPotentialVariation, -0.19, secondDirectionAction
);
CHECK(preparedOperator.GetAlgebraicJacobianStatistics().preparations == 1);
@@ -413,8 +354,7 @@ TEST_CASE(
mfem::Vector geometryChangedAction;
preparedOperator.ApplyAlgebraicJacobianAction(
enthalpyVariation, gravityPotentialVariation,
bernoulliConstantVariation, geometryChangedAction
enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, geometryChangedAction
);
mfem::Vector enthalpyPlus(enthalpy);
@@ -432,31 +372,23 @@ TEST_CASE(
mfem::Vector geometryReference;
prepared_hydrostatic_jacobian_test_utils::centered_residual_difference(
f, rotation, enthalpyPlus, enthalpyMinus, gravityPotentialPlus,
gravityPotentialMinus, displacement,
bernoulliConstant + 0.5 * bernoulliConstantVariation,
bernoulliConstant - 0.5 * bernoulliConstantVariation, geometryReference
f, rotation, enthalpyPlus, enthalpyMinus, gravityPotentialPlus, gravityPotentialMinus, displacement,
bernoulliConstant + 0.5 * bernoulliConstantVariation, bernoulliConstant - 0.5 * bernoulliConstantVariation,
geometryReference
);
const double geometryReferenceError =
prepared_hydrostatic_jacobian_test_utils::relative_error(
geometryChangedAction, geometryReference, f.mesh->GetComm()
);
const double geometryEffect =
prepared_hydrostatic_jacobian_test_utils::relative_error(
geometryChangedAction, initialAction, f.mesh->GetComm()
);
INFO(
"Geometry-updated algebraic Jacobian error = " << geometryReferenceError
const double geometryReferenceError = prepared_hydrostatic_jacobian_test_utils::relative_error(
geometryChangedAction, geometryReference, f.mesh->GetComm()
);
INFO(
"Algebraic Jacobian change after deformation update = "
<< geometryEffect
const double geometryEffect = prepared_hydrostatic_jacobian_test_utils::relative_error(
geometryChangedAction, initialAction, f.mesh->GetComm()
);
INFO("Geometry-updated algebraic Jacobian error = " << geometryReferenceError);
INFO("Algebraic Jacobian change after deformation update = " << geometryEffect);
const auto &statistics = preparedOperator.GetAlgebraicJacobianStatistics();
CHECK(geometryReport.contextReport.preparedGeometryState);

View File

@@ -0,0 +1,508 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace mass_normalization_test_utils {
using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
[[nodiscard]] mean_field::operators::MassNormalizationLayout make_layout(const mean_field::fem::FEM &f) {
const std::array<int, CoupledForm::value_block_count> valueSizes{
f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(),
f.gravityPotentialFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1
};
const std::array<int, CoupledForm::residual_block_count> residualSizes{
f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize(), f.densityFes->GetTrueVSize(),
f.displacementFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1
};
return {valueSizes, residualSizes};
}
[[nodiscard]] mfem::Vector make_density(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction field(f.densityFes.get());
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
return 0.91 + 0.07 * std::sin(0.83 * position(0) + phase) + 0.05 * std::cos(0.61 * position(1) - phase) +
0.03 * position(2) * position(2);
});
field.ProjectCoefficient(coefficient);
mfem::Vector result;
field.GetTrueDofs(result);
return result;
}
[[nodiscard]] mfem::Vector make_constant_density(
const mean_field::fem::FEM &f,
const double value
) {
mfem::ParGridFunction field(f.densityFes.get());
mfem::ConstantCoefficient coefficient(value);
field.ProjectCoefficient(coefficient);
mfem::Vector result;
field.GetTrueDofs(result);
return result;
}
[[nodiscard]] mfem::Vector make_density_direction(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction field(f.densityFes.get());
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
return 0.19 * std::sin(0.71 * position(0) + phase) - 0.13 * std::cos(0.89 * position(1) - phase) +
0.08 * position(2);
});
field.ProjectCoefficient(coefficient);
mfem::Vector result;
field.GetTrueDofs(result);
return result;
}
[[nodiscard]] mfem::Vector make_affine_displacement(
const mean_field::fem::FEM &f,
const double scale
) {
mfem::ParGridFunction field(f.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int dimension = 0; dimension < position.Size(); ++dimension) {
value(dimension) = scale * position(dimension);
}
}
);
field.ProjectCoefficient(coefficient);
mfem::Vector result;
field.GetTrueDofs(result);
return result;
}
[[nodiscard]] mfem::Vector make_displacement_direction(
const mean_field::fem::FEM &f,
const double scale
) {
mfem::ParGridFunction field(f.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(3);
value(0) = scale * (0.07 * position(0) + 0.018 * position(1) * position(2));
value(1) = scale * (-0.05 * position(1) + 0.013 * position(0) * position(2));
value(2) = scale * (0.04 * position(2) - 0.011 * position(0) * position(1));
}
);
field.ProjectCoefficient(coefficient);
mfem::Vector result;
field.GetTrueDofs(result);
return result;
}
[[nodiscard]] mean_field::operators::MassNormalizationDependencies make_dependencies() {
return {
.discretization = {.identity = 701, .revision = 3},
.density = {.identity = 709, .revision = 5},
.displacement = {.identity = 719, .revision = 7},
.targetMass = {.identity = 727, .revision = 11}
};
}
[[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions make_gravity_revisions(
const mean_field::operators::MassNormalizationDependencies &dependencies,
const std::uint64_t gravityGradientRevision = 13,
const std::uint64_t gravityPotentialRevision = 17
) {
return {
.discretization = {.value = dependencies.discretization.revision},
.displacement = {.value = dependencies.displacement.revision},
.density = {.value = dependencies.density.revision},
.gravity_gradient = {.value = gravityGradientRevision},
.gravity_potential = {.value = gravityPotentialRevision}
};
}
void prepare_gravity_context(
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &context,
const mean_field::fem::FEM &f,
const mfem::Vector &density,
const mfem::Vector &displacement,
const mean_field::operators::MassNormalizationDependencies &dependencies,
const std::uint64_t gravityGradientRevision = 13,
const std::uint64_t gravityPotentialRevision = 17
) {
mfem::Vector gravityGradient(f.gravityFluxFes->GetTrueVSize());
gravityGradient = 0.0;
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
context.Prepare(
{.density = density,
.displacement = displacement,
.gravity_gradient = gravityGradient,
.gravity_potential = gravityPotential},
make_gravity_revisions(dependencies, gravityGradientRevision, gravityPotentialRevision)
);
}
[[nodiscard]] double residual_value(const mean_field::operators::PreparedMassNormalizationOperator &massOperator) {
mfem::Vector residual;
massOperator.BuildResidual(residual);
REQUIRE(residual.Size() == 1);
return residual(0);
}
[[nodiscard]] double relative_error(
const double computed,
const double reference
) {
return std::abs(computed - reference) /
std::max(std::abs(reference), 100.0 * std::numeric_limits<double>::epsilon());
}
} // namespace mass_normalization_test_utils
TEST_CASE(
"Prepared Mass Normalization Has The Analytic Affine Volume Scaling",
tags::barotrope &tags::prepared &tags::analytic_comparison
) {
using Operator = mean_field::operators::PreparedMassNormalizationOperator;
STATIC_REQUIRE_FALSE(std::is_copy_constructible_v<Operator>);
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<Operator>);
STATIC_REQUIRE_FALSE(std::is_move_constructible_v<Operator>);
STATIC_REQUIRE_FALSE(std::is_move_assignable_v<Operator>);
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 double densityValue = 1.37;
const double targetMass = 0.73;
const double affineScale = 0.086;
const mfem::Vector density = mass_normalization_test_utils::make_constant_density(f, densityValue);
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0;
auto dependencies = mass_normalization_test_utils::make_dependencies();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacement, dependencies);
Operator massOperator(f, *f.domainMapperStateless, gravityContext);
const auto initialReport = massOperator.Prepare({.targetMass = targetMass}, dependencies);
CHECK(initialReport.rebuiltStaticPlan);
CHECK(initialReport.refreshedGeometry);
CHECK(initialReport.refreshedDensity);
CHECK(initialReport.updatedTargetMass);
CHECK(initialReport.assembledResidual);
const double undeformedMass = massOperator.GetCurrentMass();
const mean_field::mapping::COORDINATE_SPACE volumeCoordinates =
f.has_mapping() ? mean_field::mapping::COORDINATE_SPACE::PHYSICAL
: mean_field::mapping::COORDINATE_SPACE::REFERENCE;
const double independentlyIntegratedMass =
densityValue * mean_field::analysis::get_mesh_volume(f, volumeCoordinates, mean_field::utils::DOMAINS::STELLAR);
CHECK(mass_normalization_test_utils::relative_error(undeformedMass, independentlyIntegratedMass) < 1.0e-12);
CHECK(
mass_normalization_test_utils::relative_error(
mass_normalization_test_utils::residual_value(massOperator), undeformedMass - targetMass
) < 2.0e-15
);
displacement = mass_normalization_test_utils::make_affine_displacement(f, affineScale);
++dependencies.displacement.revision;
mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacement, dependencies);
const auto deformedReport = massOperator.Prepare({.targetMass = targetMass}, dependencies);
CHECK_FALSE(deformedReport.rebuiltStaticPlan);
CHECK(deformedReport.refreshedGeometry);
CHECK_FALSE(deformedReport.refreshedDensity);
const double expectedScale = std::pow(1.0 + affineScale, 3);
const double measuredScale = massOperator.GetCurrentMass() / undeformedMass;
INFO("Expected affine mass scale = " << expectedScale);
INFO("Measured affine mass scale = " << measuredScale);
CHECK(mass_normalization_test_utils::relative_error(measuredScale, expectedScale) < 5e-7);
}
TEST_CASE(
"Prepared Mass Normalization Density Jacobian Matches Centered Difference",
tags::barotrope &tags::prepared &tags::jacobian &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());
mfem::Vector density = mass_normalization_test_utils::make_density(f, 0.31);
const mfem::Vector densityDirection = mass_normalization_test_utils::make_density_direction(f, 0.67);
const mfem::Vector displacement = mass_normalization_test_utils::make_displacement_direction(f, 0.43);
auto dependencies = mass_normalization_test_utils::make_dependencies();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacement, dependencies);
mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext);
massOperator.Prepare({.targetMass = 1.23}, dependencies);
mfem::Vector analyticAction;
massOperator.ApplyDensityJacobianAction(densityDirection, analyticAction);
constexpr double epsilon = 1.0e-3;
mfem::Vector densityPlus(density);
densityPlus.Add(epsilon, densityDirection);
++dependencies.density.revision;
mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, densityPlus, displacement, dependencies);
massOperator.Prepare({.targetMass = 1.23}, dependencies);
const double residualPlus = mass_normalization_test_utils::residual_value(massOperator);
mfem::Vector densityMinus(density);
densityMinus.Add(-epsilon, densityDirection);
++dependencies.density.revision;
mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, densityMinus, displacement, dependencies);
massOperator.Prepare({.targetMass = 1.23}, dependencies);
const double residualMinus = mass_normalization_test_utils::residual_value(massOperator);
const double finiteDifference = (residualPlus - residualMinus) / (2.0 * epsilon);
INFO("Density action = " << analyticAction(0));
INFO("Density centered difference = " << finiteDifference);
CHECK(mass_normalization_test_utils::relative_error(analyticAction(0), finiteDifference) < 3.0e-8);
}
TEST_CASE(
"Prepared Mass Normalization Geometry Jacobian Matches Centered Difference",
tags::barotrope &tags::prepared &tags::jacobian &tags::geometry
) {
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 mfem::Vector density = mass_normalization_test_utils::make_density(f, 0.37);
mfem::Vector displacement = mass_normalization_test_utils::make_displacement_direction(f, 0.51);
const mfem::Vector displacementDirection = mass_normalization_test_utils::make_displacement_direction(f, -0.79);
auto dependencies = mass_normalization_test_utils::make_dependencies();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacement, dependencies);
mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext);
massOperator.Prepare({.targetMass = 1.11}, dependencies);
mfem::Vector analyticAction;
massOperator.ApplyDisplacementJacobianAction(displacementDirection, analyticAction);
constexpr double epsilon = 1.0e-6;
mfem::Vector displacementPlus(displacement);
displacementPlus.Add(epsilon, displacementDirection);
++dependencies.displacement.revision;
mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacementPlus, dependencies);
massOperator.Prepare({.targetMass = 1.11}, dependencies);
const double residualPlus = mass_normalization_test_utils::residual_value(massOperator);
mfem::Vector displacementMinus(displacement);
displacementMinus.Add(-epsilon, displacementDirection);
++dependencies.displacement.revision;
mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacementMinus, dependencies);
massOperator.Prepare({.targetMass = 1.11}, dependencies);
const double residualMinus = mass_normalization_test_utils::residual_value(massOperator);
const double finiteDifference = (residualPlus - residualMinus) / (2.0 * epsilon);
INFO("Geometry action = " << analyticAction(0));
INFO("Geometry centered difference = " << finiteDifference);
CHECK(mass_normalization_test_utils::relative_error(analyticAction(0), finiteDifference) < 2.0e-7);
}
TEST_CASE(
"Prepared Mass Normalization Selectively Refreshes Its Cached State",
tags::barotrope &tags::prepared &tags::contexts &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());
mfem::Vector density = mass_normalization_test_utils::make_density(f, 0.29);
mfem::Vector displacement = mass_normalization_test_utils::make_displacement_direction(f, 0.41);
auto dependencies = mass_normalization_test_utils::make_dependencies();
std::uint64_t gravityPotentialRevision = 17;
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
mass_normalization_test_utils::prepare_gravity_context(
gravityContext, f, density, displacement, dependencies, 13, gravityPotentialRevision
);
mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext);
massOperator.Prepare({.targetMass = 1.0}, dependencies);
const std::uint64_t preparationCount = massOperator.GetPreparationCount();
const auto repeated = massOperator.Prepare({.targetMass = 1.0}, dependencies);
CHECK_FALSE(repeated.DidAnyWork());
CHECK(massOperator.GetPreparationCount() == preparationCount);
++gravityPotentialRevision;
mass_normalization_test_utils::prepare_gravity_context(
gravityContext, f, density, displacement, dependencies, 13, gravityPotentialRevision
);
const auto potentialOnly = massOperator.Prepare({.targetMass = 1.0}, dependencies);
CHECK_FALSE(potentialOnly.DidAnyWork());
const double residualBeforeTargetChange = mass_normalization_test_utils::residual_value(massOperator);
const double massBeforeTargetChange = massOperator.GetCurrentMass();
++dependencies.targetMass.revision;
const auto targetOnly = massOperator.Prepare({.targetMass = 1.4}, dependencies);
CHECK(targetOnly.updatedTargetMass);
CHECK(targetOnly.assembledResidual);
CHECK_FALSE(targetOnly.rebuiltStaticPlan);
CHECK_FALSE(targetOnly.refreshedGeometry);
CHECK_FALSE(targetOnly.refreshedDensity);
CHECK(massOperator.GetCurrentMass() == massBeforeTargetChange);
CHECK(
mass_normalization_test_utils::relative_error(
mass_normalization_test_utils::residual_value(massOperator) - residualBeforeTargetChange, -0.4
) < 2.0e-15
);
const double massBeforeDensityChange = massOperator.GetCurrentMass();
density = mass_normalization_test_utils::make_density(f, 0.83);
++dependencies.density.revision;
mass_normalization_test_utils::prepare_gravity_context(
gravityContext, f, density, displacement, dependencies, 13, gravityPotentialRevision
);
const auto densityOnly = massOperator.Prepare({.targetMass = 1.4}, dependencies);
CHECK(densityOnly.refreshedDensity);
CHECK(densityOnly.assembledResidual);
CHECK_FALSE(densityOnly.refreshedGeometry);
CHECK(massOperator.GetCurrentMass() != massBeforeDensityChange);
displacement = mass_normalization_test_utils::make_displacement_direction(f, 0.87);
++dependencies.displacement.revision;
mass_normalization_test_utils::prepare_gravity_context(
gravityContext, f, density, displacement, dependencies, 13, gravityPotentialRevision
);
const auto geometryOnly = massOperator.Prepare({.targetMass = 1.4}, dependencies);
CHECK(geometryOnly.refreshedGeometry);
CHECK(geometryOnly.assembledResidual);
CHECK_FALSE(geometryOnly.refreshedDensity);
}
TEST_CASE(
"Prepared Mass Normalization Complete Action And Coupled Routing Are Exact",
tags::barotrope &tags::prepared &tags::jacobian &tags::mfem_operators
) {
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 mfem::Vector density = mass_normalization_test_utils::make_density(f, 0.47);
const mfem::Vector displacement = mass_normalization_test_utils::make_displacement_direction(f, 0.57);
const mfem::Vector densityDirection = mass_normalization_test_utils::make_density_direction(f, 0.71);
const mfem::Vector displacementDirection = mass_normalization_test_utils::make_displacement_direction(f, -0.63);
const auto dependencies = mass_normalization_test_utils::make_dependencies();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacement, dependencies);
mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext);
massOperator.Prepare({.targetMass = 1.19}, dependencies);
mfem::Vector densityAction;
mfem::Vector displacementAction;
mfem::Vector completeAction;
massOperator.ApplyDensityJacobianAction(densityDirection, densityAction);
massOperator.ApplyDisplacementJacobianAction(displacementDirection, displacementAction);
massOperator.ApplyCompleteJacobianAction(densityDirection, displacementDirection, completeAction);
CHECK(
mass_normalization_test_utils::relative_error(completeAction(0), densityAction(0) + displacementAction(0)) <
2.0e-15
);
const auto layout = mass_normalization_test_utils::make_layout(f);
mean_field::operators::PreparedMassNormalizationJacobianOperator adapter(layout, massOperator);
mfem::Vector direction(layout.value_offsets().Last());
direction = 0.0;
for (int entry = 0; entry < densityDirection.Size(); ++entry) {
direction(layout.offset(mass_normalization_test_utils::densityValue) + entry) = densityDirection(entry);
}
for (int entry = 0; entry < displacementDirection.Size(); ++entry) {
direction(layout.offset(mass_normalization_test_utils::displacementValue) + entry) =
displacementDirection(entry);
}
mfem::Vector coupledAction;
adapter.Mult(direction, coupledAction);
const int massOffset = layout.offset(mass_normalization_test_utils::massResidual);
REQUIRE(coupledAction.Size() == layout.residual_offsets().Last());
CHECK(coupledAction(massOffset) == completeAction(0));
for (int entry = 0; entry < coupledAction.Size(); ++entry) {
if (entry != massOffset) {
CHECK(coupledAction(entry) == 0.0);
}
}
CHECK(&massOperator.GetFEM() == &f);
CHECK(&massOperator.GetGravityContext() == &gravityContext);
CHECK(adapter.GetLayout().residual_offsets().Last() == layout.residual_offsets().Last());
}

View File

@@ -0,0 +1,706 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <memory>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace prepared_pressure_force_test_utils {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form;
struct Maps final {
mean_field::field::FieldDofMap density;
mean_field::field::FieldDofMap displacement;
mean_field::field::FieldDofMap gravityFlux;
mean_field::field::FieldDofMap gravityPotential;
mean_field::field::FieldDofMap enthalpy;
explicit Maps(const mean_field::fem::FEM &f)
: density(
mean_field::field::make_field_dof_map<
mean_field::field::Density,
DomainSchema>(*f.densityFes)
),
displacement(
mean_field::field::make_field_dof_map<
mean_field::field::Displacement,
DomainSchema>(*f.displacementFes)
),
gravityFlux(
mean_field::field::make_field_dof_map<
mean_field::field::Gravity,
DomainSchema>(*f.gravityFluxFes)
),
gravityPotential(
mean_field::field::make_field_dof_map<
mean_field::field::Gravity,
DomainSchema>(*f.gravityPotentialFes)
),
enthalpy(
mean_field::field::make_field_dof_map<
mean_field::field::Enthalpy,
DomainSchema>(*f.enthalpyFes)
) {
}
};
[[nodiscard]]
mfem::Vector make_positive_enthalpy_true(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::Vector enthalpy(f.enthalpyFes->GetTrueVSize());
for (int index = 0; index < enthalpy.Size(); ++index) {
const double position = static_cast<double>(index + 1);
enthalpy(index) =
0.93 + 0.09 * std::sin(0.23 * position + phase) + 0.04 * std::cos(0.17 * position - 0.5 * phase);
}
return enthalpy;
}
[[nodiscard]]
mfem::Vector make_enthalpy_direction_true(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::Vector direction(f.enthalpyFes->GetTrueVSize());
for (int index = 0; index < direction.Size(); ++index) {
const double position = static_cast<double>(index + 1);
direction(index) =
0.27 * std::sin(0.19 * position + phase) + 0.14 * std::cos(0.13 * position - 0.5 * phase);
}
return direction;
}
[[nodiscard]]
mfem::Vector make_displacement_direction_true(
const mean_field::fem::FEM &f,
const double phase
) {
MFEM_VERIFY(
f.mesh->Dimension() == 3, "The prepared pressure-force test requires a "
"three-dimensional mesh."
);
mfem::ParGridFunction directionField(f.displacementFes.get());
mfem::VectorFunctionCoefficient directionCoefficient(
3, [phase](const mfem::Vector &position, mfem::Vector &value) {
const double x = position(0);
const double y = position(1);
const double z = position(2);
value.SetSize(3);
value(0) = 0.019 * x + 0.011 * y * z - 0.006 * z * z + 0.004 * phase * y;
value(1) = -0.016 * y + 0.008 * x * z + 0.005 * x * x - 0.003 * phase * z;
value(2) = 0.013 * z - 0.010 * x * y + 0.006 * y * y + 0.004 * phase * x;
}
);
directionField.ProjectCoefficient(directionCoefficient);
mfem::Vector directionTrue;
directionField.GetTrueDofs(directionTrue);
return directionTrue;
}
[[nodiscard]]
double relative_difference(
const mfem::Vector &left,
const mfem::Vector &right,
const MPI_Comm communicator
) {
MFEM_VERIFY(
left.Size() == right.Size(), "Cannot compare prepared pressure-force vectors with "
"different sizes."
);
mfem::Vector difference(left);
difference -= right;
const double scale = std::max(
{gravity_prepared_test_utils::global_norm(left, communicator),
gravity_prepared_test_utils::global_norm(right, communicator),
100.0 * std::numeric_limits<double>::epsilon()}
);
return gravity_prepared_test_utils::global_norm(difference, communicator) / scale;
}
[[nodiscard]]
mean_field::operators::context::pressure_force::PressureForceDependencies make_dependencies() {
return {
.discretization = {.identity = 1201, .revision = 3},
.enthalpy = {.identity = 1213, .revision = 5},
.displacement = {.identity = 1217, .revision = 7}
};
}
constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto gravityGradientValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term
);
constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term
);
constexpr auto densityResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
[[nodiscard]]
mean_field::operators::BarotropicEquilibriumLayout make_coupled_layout(const Maps &maps) {
const std::array<int, CoupledForm::value_block_count> valueSizes{
maps.density.reduced_size(), maps.displacement.reduced_size(), maps.gravityFlux.reduced_size(),
maps.gravityPotential.reduced_size(), maps.enthalpy.reduced_size(), 1
};
const std::array<int, CoupledForm::residual_block_count> residualSizes{
maps.gravityFlux.reduced_size(), maps.gravityPotential.reduced_size(), maps.density.reduced_size(),
maps.displacement.reduced_size(), maps.enthalpy.reduced_size(), 1
};
return {valueSizes, residualSizes};
}
template <int index>
[[nodiscard]]
mfem::Vector copy_residual_block(
const mfem::Vector &action,
const mean_field::operators::BarotropicEquilibriumLayout &layout,
const mean_field::utils::blocks::residual_block<index> block
) {
mfem::Vector result(layout.size(block));
const int offset = layout.offset(block);
for (int entry = 0; entry < result.Size(); ++entry) {
result(entry) = action(offset + entry);
}
return result;
}
} // namespace prepared_pressure_force_test_utils
TEST_CASE(
"Prepared Pressure Force Uses FieldDof Supported Dimensions And Owns Its Context",
tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::unit
) {
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 prepared_pressure_force_test_utils::Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
REQUIRE(maps.enthalpy.reduced_size() < maps.enthalpy.full_size());
CHECK(maps.displacement.is_identity());
CHECK(preparedOperator.GetEnthalpySize() == maps.enthalpy.reduced_size());
CHECK(preparedOperator.GetDisplacementSize() == maps.displacement.reduced_size());
CHECK(
&preparedOperator.GetContext().GetPreparationStatistics() == &preparedOperator.GetContextPreparationStatistics()
);
}
TEST_CASE(
"Prepared Pressure Force Jacobian Matches Full Stateless Columns Through FieldDof Restriction",
tags::barotrope &tags::pressure &tags::prepared &tags::field &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());
const prepared_pressure_force_test_utils::Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mfem::Vector enthalpy =
maps.enthalpy.gather(prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.47));
const mfem::Vector displacement = maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.69));
const mfem::Vector enthalpyDirection =
maps.enthalpy.gather(prepared_pressure_force_test_utils::make_enthalpy_direction_true(f, 0.73));
const mfem::Vector displacementDirection =
maps.displacement.gather(prepared_pressure_force_test_utils::make_displacement_direction_true(f, 0.83));
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
preparedOperator.Prepare(
{.enthalpy = enthalpy, .displacement = displacement}, prepared_pressure_force_test_utils::make_dependencies()
);
const mfem::Vector enthalpyTrue = maps.enthalpy.scatter(enthalpy);
const mfem::Vector displacementTrue = maps.displacement.scatter(displacement);
const mfem::Vector enthalpyDirectionTrue = maps.enthalpy.scatter(enthalpyDirection);
const mfem::Vector displacementDirectionTrue = maps.displacement.scatter(displacementDirection);
mfem::Vector preparedEnthalpyAction;
mfem::Vector kernelEnthalpyActionTrue;
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyDirection, preparedEnthalpyAction);
mean_field::operators::kernels::apply_pressure_force_enthalpy_action(
f, *f.domainMapperStateless, equationOfState, enthalpyTrue, enthalpyDirectionTrue, displacementTrue,
kernelEnthalpyActionTrue
);
const mfem::Vector kernelEnthalpyAction = maps.displacement.gather(kernelEnthalpyActionTrue);
CHECK(
prepared_pressure_force_test_utils::relative_difference(
preparedEnthalpyAction, kernelEnthalpyAction, f.mesh->GetComm()
) < 2.0e-12
);
mfem::Vector preparedDisplacementAction;
mfem::Vector kernelDisplacementActionTrue;
preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, preparedDisplacementAction);
mean_field::operators::kernels::apply_pressure_force_displacement_action(
f, *f.domainMapperStateless, equationOfState, enthalpyTrue, displacementDirectionTrue, displacementTrue,
kernelDisplacementActionTrue
);
const mfem::Vector kernelDisplacementAction = maps.displacement.gather(kernelDisplacementActionTrue);
CHECK(
prepared_pressure_force_test_utils::relative_difference(
preparedDisplacementAction, kernelDisplacementAction, f.mesh->GetComm()
) < 2.0e-12
);
mfem::Vector fusedAction;
preparedOperator.ApplyCompleteJacobianAction(enthalpyDirection, displacementDirection, fusedAction);
mfem::Vector expectedFusedAction(kernelEnthalpyAction);
expectedFusedAction += kernelDisplacementAction;
CHECK(
prepared_pressure_force_test_utils::relative_difference(fusedAction, expectedFusedAction, f.mesh->GetComm()) <
2.0e-12
);
}
TEST_CASE(
"Prepared Pressure Force MFEM Adapter Routes Reduced Coupled FieldDof Blocks",
tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::integration &tags::jacobian
&tags::mfem_operators &tags::unit
) {
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 prepared_pressure_force_test_utils::Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mfem::Vector enthalpy =
maps.enthalpy.gather(prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.53));
const mfem::Vector displacement = maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.71));
const mfem::Vector enthalpyDirection =
maps.enthalpy.gather(prepared_pressure_force_test_utils::make_enthalpy_direction_true(f, 0.89));
const mfem::Vector displacementDirection =
maps.displacement.gather(prepared_pressure_force_test_utils::make_displacement_direction_true(f, 0.97));
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
preparedOperator.Prepare(
{.enthalpy = enthalpy, .displacement = displacement}, prepared_pressure_force_test_utils::make_dependencies()
);
const mean_field::operators::BarotropicEquilibriumLayout layout =
prepared_pressure_force_test_utils::make_coupled_layout(maps);
mean_field::operators::PreparedPressureForceJacobianOperator adapter(layout, preparedOperator);
CHECK(layout.size(prepared_pressure_force_test_utils::enthalpyValue) == maps.enthalpy.reduced_size());
CHECK(layout.size(prepared_pressure_force_test_utils::densityValue) == maps.density.reduced_size());
mfem::BlockVector direction(layout.value_offsets());
direction = 0.0;
/*
* Populate unrelated columns deliberately.
*/
direction.GetBlock(prepared_pressure_force_test_utils::densityValue) = 0.37;
direction.GetBlock(prepared_pressure_force_test_utils::gravityGradientValue) = -0.41;
direction.GetBlock(prepared_pressure_force_test_utils::gravityPotentialValue) = 0.59;
direction.GetBlock(prepared_pressure_force_test_utils::barotropicConstantValue) = -0.73;
direction.GetBlock(prepared_pressure_force_test_utils::displacementValue) = displacementDirection;
direction.GetBlock(prepared_pressure_force_test_utils::enthalpyValue) = enthalpyDirection;
mfem::Vector expectedDisplacementAction;
preparedOperator.ApplyCompleteJacobianAction(enthalpyDirection, displacementDirection, expectedDisplacementAction);
mfem::Vector action;
adapter.Mult(direction, action);
const mfem::Vector displacementResidualAction = prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::displacementResidual
);
CHECK(
prepared_pressure_force_test_utils::relative_difference(
displacementResidualAction, expectedDisplacementAction, f.mesh->GetComm()
) < 2.0e-14
);
CHECK(
prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::gravityGradientResidual
)
.Norml2() == 0.0
);
CHECK(
prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::gravityPotentialResidual
)
.Norml2() == 0.0
);
CHECK(
prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::densityResidual
)
.Norml2() == 0.0
);
CHECK(
prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::enthalpyResidual
)
.Norml2() == 0.0
);
CHECK(
prepared_pressure_force_test_utils::copy_residual_block(
action, layout, prepared_pressure_force_test_utils::massResidual
)
.Norml2() == 0.0
);
}
TEST_CASE(
"Pressure Force Residual Converges To A Manufactured Analytic Force",
tags::barotrope &tags::pressure &tags::kernels &tags::integration &tags::convergence &tags::h_refinement
&tags::analytic_comparison &tags::accuracy
) {
constexpr int dimension = 3;
constexpr std::array<int, 2> refinementLevels{0, 1};
constexpr double minimumObservedRate = 3.0;
constexpr double finestRelativeTolerance = 2.0e-3;
constexpr double amplitude = 1.0;
constexpr double bumpSharpness = 0.25;
constexpr double supportRadiusFraction = 0.90;
std::array<double, refinementLevels.size()> relativeErrors{};
for (std::size_t levelIndex = 0; levelIndex < refinementLevels.size(); ++levelIndex) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, refinementLevels[levelIndex]);
REQUIRE(f.okay());
REQUIRE(f.mesh->Dimension() == dimension);
REQUIRE(f.mesh->GetNE() > 0);
const MPI_Comm communicator = f.mesh->GetComm();
constexpr double supportRadius = supportRadiusFraction * mean_field::utils::RADIUS;
constexpr double supportRadiusSquared = supportRadius * supportRadius;
auto analyticEnthalpyFunction = [supportRadiusSquared](const mfem::Vector &position) {
const double normalizedRadiusSquared = (position * position) / supportRadiusSquared;
if (normalizedRadiusSquared >= 1.0) {
return 0.0;
}
const double distanceToSupportBoundary = 1.0 - normalizedRadiusSquared;
return amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared / distanceToSupportBoundary);
};
auto analyticPressureForceFunction = [supportRadiusSquared](const mfem::Vector &position, mfem::Vector &force) {
force.SetSize(dimension);
force = 0.0;
const double normalizedRadiusSquared = (position * position) / supportRadiusSquared;
if (normalizedRadiusSquared >= 1.0) {
return;
}
const double distanceToSupportBoundary = 1.0 - normalizedRadiusSquared;
const double enthalpy =
amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared / distanceToSupportBoundary);
const double pressureGradientScale =
-2.0 * bumpSharpness * std::pow(enthalpy, 4.0) /
(supportRadiusSquared * distanceToSupportBoundary * distanceToSupportBoundary);
for (int component = 0; component < dimension; ++component) {
force(component) = pressureGradientScale * position(component);
}
};
mfem::FunctionCoefficient analyticEnthalpyCoefficient(analyticEnthalpyFunction);
mfem::VectorFunctionCoefficient analyticPressureForceCoefficient(dimension, analyticPressureForceFunction);
mfem::ParGridFunction discreteEnthalpyField(f.enthalpyFes.get());
discreteEnthalpyField.ProjectCoefficient(analyticEnthalpyCoefficient);
mfem::Vector discreteEnthalpyTrue;
discreteEnthalpyField.GetTrueDofs(discreteEnthalpyTrue);
mfem::Vector zeroDisplacement(f.displacementFes->GetTrueVSize());
zeroDisplacement = 0.0;
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
mfem::Vector discreteResidual;
mean_field::operators::kernels::apply_pressure_force_residual(
f, *f.domainMapperStateless, barotrope, discreteEnthalpyTrue, zeroDisplacement, discreteResidual
);
REQUIRE(discreteResidual.Size() == f.displacementFes->GetTrueVSize());
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;
}
}
const mfem::Geometry::Type elementGeometry = f.displacementFes->GetFE(0)->GetGeomType();
for (int element = 1; element < f.mesh->GetNE(); ++element) {
REQUIRE(f.displacementFes->GetFE(element)->GetGeomType() == elementGeometry);
}
const int referenceQuadratureOrder = 2 * f.displacementFes->GetMaxElementOrder() + 16;
const mfem::IntegrationRule &referenceQuadrature =
mfem::IntRules.Get(elementGeometry, referenceQuadratureOrder);
auto *analyticForceIntegrator = new mfem::VectorDomainLFIntegrator(analyticPressureForceCoefficient);
analyticForceIntegrator->SetIntRule(&referenceQuadrature);
mfem::ParLinearForm analyticForceLoad(f.displacementFes.get());
analyticForceLoad.AddDomainIntegrator(analyticForceIntegrator, stellarMarker);
analyticForceLoad.Assemble();
std::unique_ptr<mfem::HypreParVector> analyticForceHypreVector(analyticForceLoad.ParallelAssemble());
REQUIRE(analyticForceHypreVector != nullptr);
mfem::Vector analyticForceTrue(*analyticForceHypreVector);
REQUIRE(analyticForceTrue.Size() == discreteResidual.Size());
const double analyticForceNorm = gravity_prepared_test_utils::global_norm(analyticForceTrue, communicator);
REQUIRE(std::isfinite(analyticForceNorm));
REQUIRE(analyticForceNorm > 0.0);
mfem::Vector residualError(discreteResidual);
residualError -= analyticForceTrue;
mfem::ParBilinearForm rieszForm(f.displacementFes.get());
rieszForm.AddDomainIntegrator(new mfem::VectorMassIntegrator());
rieszForm.AddDomainIntegrator(new mfem::VectorDiffusionIntegrator());
rieszForm.Assemble();
rieszForm.Finalize();
std::unique_ptr<mfem::HypreParMatrix> rieszMatrix(rieszForm.ParallelAssemble());
REQUIRE(rieszMatrix != nullptr);
REQUIRE(rieszMatrix->Height() == discreteResidual.Size());
REQUIRE(rieszMatrix->Width() == discreteResidual.Size());
mfem::HypreBoomerAMG rieszPreconditioner(*rieszMatrix);
rieszPreconditioner.SetPrintLevel(0);
mfem::CGSolver rieszSolver(communicator);
rieszSolver.SetOperator(*rieszMatrix);
rieszSolver.SetPreconditioner(rieszPreconditioner);
rieszSolver.SetRelTol(1.0e-13);
rieszSolver.SetAbsTol(1.0e-15);
rieszSolver.SetMaxIter(5000);
rieszSolver.SetPrintLevel(1);
auto calculateDualNorm = [&rieszSolver, communicator](const mfem::Vector &functional) {
mfem::Vector rieszRepresentative(functional.Size());
rieszRepresentative = 0.0;
rieszSolver.Mult(functional, rieszRepresentative);
MFEM_VERIFY(
rieszSolver.GetConverged(), "The pressure-force convergence-test Riesz solve "
"did not converge."
);
const double dualNormSquared =
gravity_prepared_test_utils::global_dot(functional, rieszRepresentative, communicator);
MFEM_VERIFY(std::isfinite(dualNormSquared), "The pressure-force dual norm is not finite.");
MFEM_VERIFY(
dualNormSquared >= -100.0 * std::numeric_limits<double>::epsilon(),
"The pressure-force Riesz operator produced a "
"negative dual norm."
);
return std::sqrt(std::max(dualNormSquared, 0.0));
};
const double errorDualNorm = calculateDualNorm(residualError);
const double analyticDualNorm = calculateDualNorm(analyticForceTrue);
REQUIRE(std::isfinite(errorDualNorm));
REQUIRE(std::isfinite(analyticDualNorm));
REQUIRE(errorDualNorm > 0.0);
REQUIRE(analyticDualNorm > 0.0);
relativeErrors[levelIndex] = errorDualNorm / analyticDualNorm;
INFO("Pressure-force refinement level = " << refinementLevels[levelIndex]);
INFO("Pressure-force true DOFs = " << f.displacementFes->GlobalTrueVSize());
INFO("Pressure-force relative dual error = " << relativeErrors[levelIndex]);
}
for (const double relativeError : relativeErrors) {
REQUIRE(std::isfinite(relativeError));
REQUIRE(relativeError > 0.0);
}
static_assert(refinementLevels.size() == 2, "This reduced convergence test expects exactly two refinement levels.");
const double observedRate = std::log(relativeErrors[0] / relativeErrors[1]) / std::log(2.0);
INFO("Level 0 pressure-force relative dual error = " << relativeErrors[0]);
INFO("Level 1 pressure-force relative dual error = " << relativeErrors[1]);
INFO("Level 0 to 1 pressure-force convergence rate = " << observedRate);
CHECK(relativeErrors[1] < relativeErrors[0]);
CHECK(observedRate > minimumObservedRate);
CHECK(relativeErrors[1] < finestRelativeTolerance);
}

View File

@@ -0,0 +1,609 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace rotational_displacement_force_test_utils {
using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto gravityGradientValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
mean_field::utils::blocks::get_value_block<CoupledForm>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.gradient_term
);
constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::gravity_field.poisson_term
);
constexpr auto densityResidual =
mean_field::utils::blocks::get_residual_block<CoupledForm>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::enthalpy_field.specific_term
);
constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<CoupledForm>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
[[nodiscard]] mean_field::operators::RotationalDisplacementForceLayout make_layout(const mean_field::fem::FEM &f) {
const std::array<int, CoupledForm::value_block_count> valueSizes{
f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(),
f.gravityPotentialFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1
};
const std::array<int, CoupledForm::residual_block_count> residualSizes{
f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize(), f.densityFes->GetTrueVSize(),
f.displacementFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1
};
return {valueSizes, residualSizes};
}
[[nodiscard]] mfem::Vector make_density(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction densityField(f.densityFes.get());
mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) {
return 0.88 + 0.06 * std::sin(0.7 * position(0) + phase) + 0.04 * std::cos(0.6 * position(1) - phase) +
0.025 * position(2) * position(2);
});
densityField.ProjectCoefficient(densityCoefficient);
mfem::Vector densityTrue;
densityField.GetTrueDofs(densityTrue);
return densityTrue;
}
[[nodiscard]] mfem::Vector make_density_direction(
const mean_field::fem::FEM &f,
const double phase
) {
mfem::ParGridFunction densityField(f.densityFes.get());
mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) {
return 0.17 * std::sin(0.9 * position(0) + phase) - 0.12 * std::cos(0.8 * position(1) - phase) +
0.07 * position(2);
});
densityField.ProjectCoefficient(densityCoefficient);
mfem::Vector densityTrue;
densityField.GetTrueDofs(densityTrue);
return densityTrue;
}
[[nodiscard]] mfem::Vector make_displacement_direction(const mean_field::fem::FEM &f) {
mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, 0.91);
const mfem::Vector second = gravity_prepared_test_utils::make_displacement(f, 0.27);
direction -= second;
return direction;
}
[[nodiscard]] mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
mfem::Vector angularVelocity(3);
angularVelocity(0) = scale * 0.17;
angularVelocity(1) = scale * -0.09;
angularVelocity(2) = scale * 0.62;
mfem::Vector center(3);
center(0) = 0.04;
center(1) = -0.03;
center(2) = 0.02;
return mean_field::physics::RigidRotation(angularVelocity, center);
}
[[nodiscard]] mean_field::operators::context::rotational_displacement_force::RotationalDisplacementForceDependencies
make_dependencies() {
return {
.discretization = {.identity = 211, .revision = 3},
.density = {.identity = 223, .revision = 5},
.displacement = {.identity = 227, .revision = 7},
.rotation = {.identity = 229, .revision = 11}
};
}
[[nodiscard]] mfem::Vector make_vacuum_only_density(const mean_field::fem::FEM &f) {
mfem::ParGridFunction densityField(f.densityFes.get());
densityField = 0.0;
const int vacuumAttribute = f.domainMapperStateless->GetVacuumElementAttribute();
mfem::Array<int> densityDofs;
int localVacuumElements = 0;
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId);
REQUIRE(transformation != nullptr);
if (transformation->Attribute != vacuumAttribute) {
continue;
}
f.densityFes->GetElementDofs(elementId, densityDofs);
mfem::Vector elementDensity(densityDofs.Size());
elementDensity = 1.0;
densityField.SetSubVector(densityDofs, elementDensity);
++localVacuumElements;
}
int globalVacuumElements = 0;
MPI_Allreduce(&localVacuumElements, &globalVacuumElements, 1, MPI_INT, MPI_SUM, f.mesh->GetComm());
REQUIRE(globalVacuumElements > 0);
mfem::Vector densityTrue;
densityField.GetTrueDofs(densityTrue);
return densityTrue;
}
[[nodiscard]] double global_norm(
const mfem::Vector &vector,
MPI_Comm communicator
) {
const double localSquaredNorm = vector * vector;
double globalSquaredNorm = 0.0;
MPI_Allreduce(&localSquaredNorm, &globalSquaredNorm, 1, MPI_DOUBLE, MPI_SUM, communicator);
return std::sqrt(globalSquaredNorm);
}
[[nodiscard]] double global_dot(
const mfem::Vector &left,
const mfem::Vector &right,
MPI_Comm communicator
) {
REQUIRE(left.Size() == right.Size());
const double localDot = left * right;
double globalDot = 0.0;
MPI_Allreduce(&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator);
return globalDot;
}
[[nodiscard]] double relative_difference(
const mfem::Vector &computed,
const mfem::Vector &reference,
MPI_Comm communicator
) {
REQUIRE(computed.Size() == reference.Size());
mfem::Vector difference(computed);
difference -= reference;
return global_norm(difference, communicator) /
std::max(global_norm(reference, communicator), std::numeric_limits<double>::epsilon());
}
[[nodiscard]] mfem::Vector centered_difference(
const mean_field::fem::FEM &f,
const mean_field::physics::RigidRotation &rotation,
const mfem::Vector &baseDensity,
const mfem::Vector &densityDirection,
const mfem::Vector &baseDisplacement,
const mfem::Vector &displacementDirection,
const double step
) {
mfem::Vector plusDensity(baseDensity);
plusDensity.Add(step, densityDirection);
mfem::Vector minusDensity(baseDensity);
minusDensity.Add(-step, densityDirection);
mfem::Vector plusDisplacement(baseDisplacement);
plusDisplacement.Add(step, displacementDirection);
mfem::Vector minusDisplacement(baseDisplacement);
minusDisplacement.Add(-step, displacementDirection);
mfem::Vector plusResidual;
mfem::Vector minusResidual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, plusDensity, plusDisplacement, plusResidual
);
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, minusDensity, minusDisplacement, minusResidual
);
plusResidual -= minusResidual;
plusResidual /= 2.0 * step;
return plusResidual;
}
template <int index>
[[nodiscard]] mfem::Vector copy_residual_block(
const mfem::Vector &action,
const mean_field::operators::RotationalDisplacementForceLayout &layout,
const mean_field::utils::blocks::residual_block<index> block
) {
mfem::Vector result(layout.size(block));
const int offset = layout.offset(block);
for (int entry = 0; entry < result.Size(); ++entry) {
result(entry) = action(offset + entry);
}
return result;
}
} // namespace rotational_displacement_force_test_utils
TEST_CASE(
"Rotational Displacement Force Query Includes Density Test And Linear "
"Position",
tags::centrifugal &tags::quadrature &tags::unit
) {
using DisplacementField = mean_field::field::Field<mean_field::field::Displacement>;
constexpr int geometryWeightOrder = 4;
constexpr mean_field::quadrature::Query query =
DisplacementField::make_query<mean_field::field::Displacement::Form::CentrifugalForce>(
mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder, std::array<int, 1>{1},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
/* density: 2, displacement test: 3, position: 1, geometry: 4 */
constexpr int expectedBaseOrder = 2 + 3 + 1 + 4;
STATIC_REQUIRE(query.term == mean_field::quadrature::Term::centrifugal);
STATIC_REQUIRE(query.domain == mean_field::utils::DOMAINS::STELLAR);
STATIC_REQUIRE(query.mapping == mean_field::quadrature::MappingKind::general);
STATIC_REQUIRE(query.base_order.has_value());
STATIC_REQUIRE(*query.base_order == expectedBaseOrder);
}
TEST_CASE(
"Rotational Displacement Force Uses Negative Rotation-Potential "
"Gradient And Excludes Vacuum",
tags::centrifugal &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());
const mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.31);
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0;
const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation();
mfem::Vector residual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, density, displacement, residual
);
mfem::ParGridFunction gradientTestField(f.displacementFes.get());
auto gradientFunction = [&rotation](const mfem::Vector &position, mfem::Vector &value) {
rotation.potential_gradient(position, value);
};
mfem::VectorFunctionCoefficient gradientCoefficient(3, gradientFunction);
gradientTestField.ProjectCoefficient(gradientCoefficient);
mfem::Vector gradientTestDirection;
gradientTestField.GetTrueDofs(gradientTestDirection);
const double signedWork =
rotational_displacement_force_test_utils::global_dot(residual, gradientTestDirection, f.mesh->GetComm());
INFO("Rotation-force work against grad(Psi) = " << signedWork);
CHECK(signedWork < 0.0);
const mfem::Vector vacuumDensity = rotational_displacement_force_test_utils::make_vacuum_only_density(f);
mfem::Vector vacuumResidual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, vacuumDensity, displacement, vacuumResidual
);
CHECK(rotational_displacement_force_test_utils::global_norm(vacuumResidual, f.mesh->GetComm()) == 0.0);
mfem::Vector zeroAngularVelocity(3);
mfem::Vector zeroCenter(3);
zeroAngularVelocity = 0.0;
zeroCenter = 0.0;
const mean_field::physics::RigidRotation zeroRotation(zeroAngularVelocity, zeroCenter);
mfem::Vector zeroRotationResidual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, zeroRotation, density, displacement, zeroRotationResidual
);
CHECK(rotational_displacement_force_test_utils::global_norm(zeroRotationResidual, f.mesh->GetComm()) == 0.0);
}
TEST_CASE(
"Prepared Rotational Displacement Force Reprepares Selectively",
tags::centrifugal &tags::prepared &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());
mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.37);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.53);
mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.81);
auto dependencies = rotational_displacement_force_test_utils::make_dependencies();
mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless);
const auto initialReport =
preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation);
REQUIRE(initialReport.DidAnyWork());
REQUIRE(initialReport.updatedRotation);
REQUIRE(initialReport.preparedResidual);
REQUIRE(preparedOperator.IsPrepared());
mfem::Vector preparedResidual;
mfem::Vector kernelResidual;
preparedOperator.BuildResidual(preparedResidual);
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, density, displacement, kernelResidual
);
CHECK(
rotational_displacement_force_test_utils::relative_difference(
preparedResidual, kernelResidual, f.mesh->GetComm()
) < 2.0e-12
);
CHECK_FALSE(preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation)
.DidAnyWork());
density = rotational_displacement_force_test_utils::make_density(f, 0.79);
++dependencies.density.revision;
const auto densityReport =
preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation);
CHECK(densityReport.preparedResidual);
CHECK_FALSE(densityReport.updatedRotation);
rotation = rotational_displacement_force_test_utils::make_rotation(1.23);
++dependencies.rotation.revision;
const auto rotationReport =
preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation);
CHECK(rotationReport.updatedRotation);
CHECK(rotationReport.preparedResidual);
CHECK(preparedOperator.GetResidualPreparationCount() == 3);
CHECK(preparedOperator.GetResidualApplicationCount() == 1);
}
TEST_CASE(
"Rotational Displacement Force Jacobian Matches Both Columns And "
"Centered Differences",
tags::centrifugal &tags::prepared &tags::jacobian &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());
const mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.43);
const mfem::Vector densityDirection = rotational_displacement_force_test_utils::make_density_direction(f, 0.59);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.61);
const mfem::Vector displacementDirection = rotational_displacement_force_test_utils::make_displacement_direction(f);
const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.93);
mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless);
preparedOperator.Prepare(
{.density = density, .displacement = displacement},
rotational_displacement_force_test_utils::make_dependencies(), rotation
);
mfem::Vector densityAction;
mfem::Vector displacementAction;
mfem::Vector completeAction;
preparedOperator.ApplyDensityJacobianAction(densityDirection, densityAction);
preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, displacementAction);
preparedOperator.ApplyCompleteJacobianAction(densityDirection, displacementDirection, completeAction);
mfem::Vector summedColumns(densityAction);
summedColumns += displacementAction;
CHECK(
rotational_displacement_force_test_utils::relative_difference(
completeAction, summedColumns, f.mesh->GetComm()
) < 2.0e-12
);
mfem::Vector zeroDensity(densityDirection.Size());
mfem::Vector zeroDisplacement(displacementDirection.Size());
zeroDensity = 0.0;
zeroDisplacement = 0.0;
constexpr double step = 1.0e-5;
const mfem::Vector densityDifference = rotational_displacement_force_test_utils::centered_difference(
f, rotation, density, densityDirection, displacement, zeroDisplacement, step
);
const mfem::Vector displacementDifference = rotational_displacement_force_test_utils::centered_difference(
f, rotation, density, zeroDensity, displacement, displacementDirection, step
);
const mfem::Vector completeDifference = rotational_displacement_force_test_utils::centered_difference(
f, rotation, density, densityDirection, displacement, displacementDirection, step
);
const double densityError = rotational_displacement_force_test_utils::relative_difference(
densityAction, densityDifference, f.mesh->GetComm()
);
const double displacementError = rotational_displacement_force_test_utils::relative_difference(
displacementAction, displacementDifference, f.mesh->GetComm()
);
const double completeError = rotational_displacement_force_test_utils::relative_difference(
completeAction, completeDifference, f.mesh->GetComm()
);
INFO("Density-column centered-difference error = " << densityError);
INFO("Displacement-column centered-difference error = " << displacementError);
INFO("Complete centered-difference error = " << completeError);
CHECK(densityError < 2.0e-9);
CHECK(displacementError < 3.0e-8);
CHECK(completeError < 4.0e-8);
}
TEST_CASE(
"Prepared Rotational Displacement Force MFEM Adapter Routes Only R-d",
tags::centrifugal &tags::prepared &tags::mfem_operators &tags::unit
) {
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 mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.47);
const mfem::Vector densityDirection = rotational_displacement_force_test_utils::make_density_direction(f, 0.63);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.57);
const mfem::Vector displacementDirection = rotational_displacement_force_test_utils::make_displacement_direction(f);
const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.87);
mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless);
preparedOperator.Prepare(
{.density = density, .displacement = displacement},
rotational_displacement_force_test_utils::make_dependencies(), rotation
);
const auto layout = rotational_displacement_force_test_utils::make_layout(f);
mean_field::operators::PreparedRotationalDisplacementForceJacobianOperator adapter(layout, preparedOperator);
mfem::BlockVector direction(layout.value_offsets());
direction = 0.0;
direction.GetBlock(rotational_displacement_force_test_utils::densityValue) = densityDirection;
direction.GetBlock(rotational_displacement_force_test_utils::displacementValue) = displacementDirection;
direction.GetBlock(rotational_displacement_force_test_utils::gravityGradientValue) = 0.23;
direction.GetBlock(rotational_displacement_force_test_utils::gravityPotentialValue) = -0.31;
direction.GetBlock(rotational_displacement_force_test_utils::enthalpyValue) = 0.37;
direction.GetBlock(rotational_displacement_force_test_utils::barotropicConstantValue) = -0.41;
mfem::Vector action;
adapter.Mult(direction, action);
mfem::Vector expectedDisplacementAction;
preparedOperator.ApplyCompleteJacobianAction(densityDirection, displacementDirection, expectedDisplacementAction);
const mfem::Vector actualDisplacementAction = rotational_displacement_force_test_utils::copy_residual_block(
action, layout, rotational_displacement_force_test_utils::displacementResidual
);
CHECK(
rotational_displacement_force_test_utils::relative_difference(
actualDisplacementAction, expectedDisplacementAction, f.mesh->GetComm()
) < 2.0e-12
);
const std::array<mfem::Vector, 5> zeroRows{
rotational_displacement_force_test_utils::copy_residual_block(
action, layout, rotational_displacement_force_test_utils::gravityGradientResidual
),
rotational_displacement_force_test_utils::copy_residual_block(
action, layout, rotational_displacement_force_test_utils::gravityPotentialResidual
),
rotational_displacement_force_test_utils::copy_residual_block(
action, layout, rotational_displacement_force_test_utils::densityResidual
),
rotational_displacement_force_test_utils::copy_residual_block(
action, layout, rotational_displacement_force_test_utils::enthalpyResidual
),
rotational_displacement_force_test_utils::copy_residual_block(
action, layout, rotational_displacement_force_test_utils::massResidual
)
};
for (const mfem::Vector &row : zeroRows) {
CHECK(rotational_displacement_force_test_utils::global_norm(row, f.mesh->GetComm()) == 0.0);
}
}

View File

@@ -0,0 +1,303 @@
#include <algorithm>
#include <cmath>
#include <limits>
#include <numbers>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace rotational_displacement_force_affine_deformation_test_utils {
[[nodiscard]] double global_dot(
const mfem::Vector &left,
const mfem::Vector &right,
MPI_Comm communicator
) {
REQUIRE(left.Size() == right.Size());
const double localDot = left * right;
double globalDot = 0.0;
MPI_Allreduce(&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator);
return globalDot;
}
[[nodiscard]] double relative_error(
const double computed,
const double expected
) {
return std::abs(computed - expected) / std::max(std::abs(expected), std::numeric_limits<double>::epsilon());
}
[[nodiscard]] mfem::Vector project_constant_density(
const mean_field::fem::FEM &f,
const double densityValue
) {
mfem::ParGridFunction density(f.densityFes.get());
mfem::ConstantCoefficient coefficient(densityValue);
density.ProjectCoefficient(coefficient);
mfem::Vector densityTrue;
density.GetTrueDofs(densityTrue);
return densityTrue;
}
[[nodiscard]] mfem::Vector project_affine_vector(
const mean_field::fem::FEM &f,
const mfem::DenseMatrix &linearMap,
const mfem::Vector &offset
) {
REQUIRE(linearMap.Height() == 3);
REQUIRE(linearMap.Width() == 3);
REQUIRE(offset.Size() == 3);
auto affineFunction = [&linearMap, &offset](const mfem::Vector &referencePosition, mfem::Vector &value) {
value.SetSize(3);
linearMap.Mult(referencePosition, value);
value += offset;
};
mfem::ParGridFunction field(f.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(3, affineFunction);
field.ProjectCoefficient(coefficient);
mfem::Vector trueDofs;
field.GetTrueDofs(trueDofs);
return trueDofs;
}
[[nodiscard]] mfem::DenseMatrix make_deformation() {
mfem::DenseMatrix deformation(3);
deformation = 0.0;
deformation(0, 0) = 1.08;
deformation(0, 1) = 0.06;
deformation(0, 2) = -0.03;
deformation(1, 1) = 0.96;
deformation(1, 2) = 0.04;
deformation(2, 2) = 1.0 / (1.08 * 0.96);
return deformation;
}
[[nodiscard]] mfem::DenseMatrix make_displacement_gradient(const mfem::DenseMatrix &deformation) {
mfem::DenseMatrix displacementGradient(deformation);
for (int component = 0; component < 3; ++component) {
displacementGradient(component, component) -= 1.0;
}
return displacementGradient;
}
[[nodiscard]] mfem::DenseMatrix make_rotation_potential_hessian(const mfem::Vector &angularVelocity) {
REQUIRE(angularVelocity.Size() == 3);
const double angularSpeedSquared = angularVelocity * angularVelocity;
mfem::DenseMatrix hessian(3);
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column) {
hessian(row, column) =
(row == column ? angularSpeedSquared : 0.0) - angularVelocity(row) * angularVelocity(column);
}
}
return hessian;
}
[[nodiscard]] mfem::DenseMatrix multiply(
const mfem::DenseMatrix &left,
const mfem::DenseMatrix &right
) {
REQUIRE(left.Width() == right.Height());
mfem::DenseMatrix product(left.Height(), right.Width());
product = 0.0;
for (int row = 0; row < product.Height(); ++row) {
for (int column = 0; column < product.Width(); ++column) {
for (int inner = 0; inner < left.Width(); ++inner) {
product(row, column) += left(row, inner) * right(inner, column);
}
}
}
return product;
}
[[nodiscard]] double frobenius_product(
const mfem::DenseMatrix &left,
const mfem::DenseMatrix &right
) {
REQUIRE(left.Height() == right.Height());
REQUIRE(left.Width() == right.Width());
double product = 0.0;
for (int row = 0; row < left.Height(); ++row) {
for (int column = 0; column < left.Width(); ++column) {
product += left(row, column) * right(row, column);
}
}
return product;
}
} // namespace rotational_displacement_force_affine_deformation_test_utils
TEST_CASE(
"Rotational Displacement Force Matches A Nontrivially Deformed "
"Homogeneous Ellipsoid",
tags::centrifugal &tags::analytic_comparison &tags::accuracy &tags::geometry
) {
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 double radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS;
const double referenceVolume = 4.0 * std::numbers::pi * radius * radius * radius / 3.0;
const mfem::DenseMatrix deformation =
rotational_displacement_force_affine_deformation_test_utils::make_deformation();
const double deformationDeterminant = deformation.Det();
REQUIRE(deformationDeterminant > 0.0);
REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14);
mfem::Vector deformationOffset(3);
deformationOffset(0) = 0.031;
deformationOffset(1) = -0.024;
deformationOffset(2) = 0.018;
const mfem::DenseMatrix displacementGradient =
rotational_displacement_force_affine_deformation_test_utils::make_displacement_gradient(deformation);
const mfem::Vector displacement =
rotational_displacement_force_affine_deformation_test_utils::project_affine_vector(
f, displacementGradient, deformationOffset
);
const double densityValue = mass / (deformationDeterminant * referenceVolume);
const mfem::Vector density =
rotational_displacement_force_affine_deformation_test_utils::project_constant_density(f, densityValue);
mfem::Vector angularVelocity(3);
angularVelocity(0) = 0.17;
angularVelocity(1) = -0.12;
angularVelocity(2) = 0.43;
/*
* Put the rotation center at the mapped ellipsoid's center. The affine
* translation is therefore present in the geometry, but cancels from
* x - x_c in the exact centrifugal acceleration.
*/
const mfem::Vector rotationCenter(deformationOffset);
const mean_field::physics::RigidRotation rotation(angularVelocity, rotationCenter);
mfem::Vector residual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, density, displacement, residual
);
const mfem::DenseMatrix rotationPotentialHessian =
rotational_displacement_force_affine_deformation_test_utils::make_rotation_potential_hessian(angularVelocity);
const mfem::DenseMatrix forceMomentTensor =
rotational_displacement_force_affine_deformation_test_utils::multiply(rotationPotentialHessian, deformation);
const double angularSpeedSquared = angularVelocity * angularVelocity;
const double momentScale = mass * radius * radius * angularSpeedSquared / 5.0;
REQUIRE(momentScale > 0.0);
mfem::Vector zeroOffset(3);
zeroOffset = 0.0;
/*
* For X in a homogeneous reference sphere and the affine map x = A X+b,
*
* integral X_i X_j dM = (M R^2 / 5) delta_ij.
*
* With S = |Omega|^2 I - Omega Omega^T and the affine probe
* w = E_(row,column) X, the exact residual action is
*
* R_rot(w) = -(M R^2 / 5) (S A)_(row,column).
*
* Checking all nine probes compares the complete analytic second-moment
* response tensor, including the shear and oblique-axis couplings.
*/
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column) {
mfem::DenseMatrix probe(3);
probe = 0.0;
probe(row, column) = 1.0;
const mfem::Vector probeDirection =
rotational_displacement_force_affine_deformation_test_utils::project_affine_vector(
f, probe, zeroOffset
);
const double computedAction = rotational_displacement_force_affine_deformation_test_utils::global_dot(
residual, probeDirection, f.mesh->GetComm()
);
const double expectedAction = -mass * radius * radius / 5.0 * forceMomentTensor(row, column);
const double normalizedAbsoluteError = std::abs(computedAction - expectedAction) / momentScale;
INFO("Affine probe row = " << row);
INFO("Affine probe column = " << column);
INFO("Computed affine-probe action = " << computedAction);
INFO("Analytic affine-probe action = " << expectedAction);
INFO("Normalized affine-probe absolute error = " << normalizedAbsoluteError);
CHECK(normalizedAbsoluteError < 2.5e-5);
}
}
/*
* The physical dilation about the rotation center is w = x-x_c = A X.
* Its exact work is the deformed rotational virial,
*
* R_rot(x-x_c) = -(M R^2 / 5) tr(A^T S A) = -2T.
*/
const mfem::Vector dilationDirection =
rotational_displacement_force_affine_deformation_test_utils::project_affine_vector(f, deformation, zeroOffset);
const double computedVirial = rotational_displacement_force_affine_deformation_test_utils::global_dot(
residual, dilationDirection, f.mesh->GetComm()
);
const double expectedVirial =
-mass * radius * radius / 5.0 *
rotational_displacement_force_affine_deformation_test_utils::frobenius_product(deformation, forceMomentTensor);
const double virialRelativeError =
rotational_displacement_force_affine_deformation_test_utils::relative_error(computedVirial, expectedVirial);
const double sphericalVirial = -(2.0 / 5.0) * mass * angularSpeedSquared * radius * radius;
const double deformationSignal = std::abs(expectedVirial - sphericalVirial) / std::abs(sphericalVirial);
INFO("Deformation determinant = " << deformationDeterminant);
INFO("Computed deformed rotational virial = " << computedVirial);
INFO("Analytic deformed rotational virial = " << expectedVirial);
INFO("Spherical rotational virial = " << sphericalVirial);
INFO("Relative deformation signal = " << deformationSignal);
INFO("Deformed virial relative error = " << virialRelativeError);
CHECK(computedVirial < 0.0);
CHECK(deformationSignal > 5.0e-2);
CHECK(virialRelativeError < 2.5e-5);
}

View File

@@ -0,0 +1,256 @@
#include <algorithm>
#include <cmath>
#include <functional>
#include <limits>
#include <numbers>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace rotational_displacement_force_analytic_test_utils {
[[nodiscard]] double global_dot(
const mfem::Vector &left,
const mfem::Vector &right,
MPI_Comm communicator
) {
REQUIRE(left.Size() == right.Size());
const double localDot = left * right;
double globalDot = 0.0;
MPI_Allreduce(&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator);
return globalDot;
}
[[nodiscard]] double relative_error(
const double computed,
const double expected
) {
return std::abs(computed - expected) / std::max(std::abs(expected), std::numeric_limits<double>::epsilon());
}
[[nodiscard]] mfem::Vector project_constant_density(
const mean_field::fem::FEM &f,
const double densityValue
) {
mfem::ParGridFunction density(f.densityFes.get());
mfem::ConstantCoefficient densityCoefficient(densityValue);
density.ProjectCoefficient(densityCoefficient);
mfem::Vector densityTrue;
density.GetTrueDofs(densityTrue);
return densityTrue;
}
[[nodiscard]] mfem::Vector project_vector_function(
const mean_field::fem::FEM &f,
const std::function<void(
const mfem::Vector &,
mfem::Vector &
)> &function
) {
mfem::ParGridFunction field(f.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(3, function);
field.ProjectCoefficient(coefficient);
mfem::Vector trueDofs;
field.GetTrueDofs(trueDofs);
return trueDofs;
}
} // namespace rotational_displacement_force_analytic_test_utils
TEST_CASE(
"Rigid Rotation Gradient And Hessian Action Match The Analytic "
"Potential",
tags::centrifugal &tags::unit &tags::accuracy
) {
mfem::Vector angularVelocity(3);
angularVelocity(0) = 0.23;
angularVelocity(1) = -0.31;
angularVelocity(2) = 0.67;
mfem::Vector center(3);
center(0) = 0.11;
center(1) = -0.07;
center(2) = 0.05;
const mean_field::physics::RigidRotation rotation(angularVelocity, center);
mfem::Vector position(3);
position(0) = 0.41;
position(1) = -0.29;
position(2) = 0.37;
mfem::Vector direction(3);
direction(0) = -0.17;
direction(1) = 0.23;
direction(2) = 0.13;
mfem::Vector gradient;
mfem::Vector hessianAction;
rotation.potential_gradient(position, gradient);
rotation.potential_gradient_directional_derivative(direction, hessianAction);
const double directionalDerivative = rotation.potential_directional_derivative(position, direction);
CHECK(
rotational_displacement_force_analytic_test_utils::relative_error(gradient * direction, directionalDerivative) <
2.0e-15
);
constexpr double step = 1.0e-6;
mfem::Vector plusPosition(position);
plusPosition.Add(step, direction);
mfem::Vector minusPosition(position);
minusPosition.Add(-step, direction);
mfem::Vector plusGradient;
mfem::Vector minusGradient;
rotation.potential_gradient(plusPosition, plusGradient);
rotation.potential_gradient(minusPosition, minusGradient);
plusGradient -= minusGradient;
plusGradient /= 2.0 * step;
plusGradient -= hessianAction;
CHECK(plusGradient.Norml2() < 2.0e-10);
}
TEST_CASE(
"Rotational Displacement Force Reproduces The Homogeneous Sphere "
"Rotational Virial",
tags::centrifugal &tags::analytic_comparison &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());
const double radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS;
const double volume = 4.0 * std::numbers::pi * radius * radius * radius / 3.0;
const double densityValue = mass / volume;
const double angularSpeed = 0.73;
const mfem::Vector density =
rotational_displacement_force_analytic_test_utils::project_constant_density(f, densityValue);
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0;
mfem::Vector angularVelocity(3);
mfem::Vector center(3);
angularVelocity = 0.0;
center = 0.0;
angularVelocity(2) = angularSpeed;
const mean_field::physics::RigidRotation rotation(angularVelocity, center);
mfem::Vector residual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, density, displacement, residual
);
const mfem::Vector dilationDirection = rotational_displacement_force_analytic_test_utils::project_vector_function(
f, [](const mfem::Vector &position, mfem::Vector &value) { value = position; }
);
const double computedWork =
rotational_displacement_force_analytic_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm());
const double expectedWork = -(2.0 / 5.0) * mass * angularSpeed * angularSpeed * radius * radius;
const double relativeError =
rotational_displacement_force_analytic_test_utils::relative_error(computedWork, expectedWork);
INFO("Computed rotational virial work = " << computedWork);
INFO("Analytic rotational virial work = " << expectedWork);
INFO("Rotational virial relative error = " << relativeError);
CHECK(computedWork < 0.0);
CHECK(relativeError < 1.0e-5);
}
TEST_CASE(
"Rotational Displacement Force Matches The Analytic Off-Axis "
"Resultant",
tags::centrifugal &tags::analytic_comparison &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());
const double radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS;
const double volume = 4.0 * std::numbers::pi * radius * radius * radius / 3.0;
const double densityValue = mass / volume;
const double angularSpeed = 0.61;
const mfem::Vector density =
rotational_displacement_force_analytic_test_utils::project_constant_density(f, densityValue);
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0;
mfem::Vector angularVelocity(3);
mfem::Vector center(3);
angularVelocity = 0.0;
center = 0.0;
angularVelocity(2) = angularSpeed;
center(0) = 0.13;
center(1) = -0.08;
const mean_field::physics::RigidRotation rotation(angularVelocity, center);
mfem::Vector residual;
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, density, displacement, residual
);
for (int component = 0; component < 3; ++component) {
const mfem::Vector translationDirection =
rotational_displacement_force_analytic_test_utils::project_vector_function(
f, [component](const mfem::Vector &, mfem::Vector &value) {
value.SetSize(3);
value = 0.0;
value(component) = 1.0;
}
);
const double computedResultant = rotational_displacement_force_analytic_test_utils::global_dot(
residual, translationDirection, f.mesh->GetComm()
);
const double expectedResultant = component < 2 ? mass * angularSpeed * angularSpeed * center(component) : 0.0;
INFO("Resultant component = " << component);
INFO("Computed resultant = " << computedResultant);
INFO("Expected resultant = " << expectedResultant);
if (component < 2) {
CHECK(
rotational_displacement_force_analytic_test_utils::relative_error(
computedResultant, expectedResultant
) < 1.0e-5
);
} else {
CHECK(std::abs(computedResultant) < 1.0e-10);
}
}
}

File diff suppressed because it is too large Load Diff