Files
MeanField/tests/operators/contexts/barotropic_closure_linearization_context.cpp
Emily Boudreaux 0f3ca8050b feat(field-support): added field support system, mid migration
currently the barotope and the pressure force operator are migrated to the new support system
2026-08-23 10:13:53 -04:00

417 lines
15 KiB
C++

#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 {
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
) {
return {.density = density, .enthalpy = enthalpy, .displacement = displacement};
}
[[nodiscard]] mfem::Vector reduce(
const field::FieldDofMap &map,
const mfem::Vector &full
) {
return map.gather(full);
}
[[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;
}
[[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(
"Prepared Barotropic Closure Owns Its Linearization Context",
tags::barotrope &tags::closure &tags::contexts &tags::prepared &tags::field &tags::unit
) {
using Operator = mean_field::operators::PreparedBarotropicClosureOperator;
using Context = mean_field::operators::context::barotropic::BarotropicClosureLinearizationContext;
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>);
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 mean_field::eos::Polytrope equationOfState(3.0, 1.5);
Operator preparedOperator(f, *f.domainMapperStateless, equationOfState);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_FALSE(preparedOperator.GetContext().IsPrepared());
CHECK(&preparedOperator.GetContext() == &preparedOperator.GetContext());
const auto statistics = preparedOperator.GetContextPreparationStatistics();
CHECK(statistics.staticPreparations == 0);
CHECK(statistics.geometryPreparations == 0);
CHECK(statistics.baseStatePreparations == 0);
}
TEST_CASE(
"Barotropic Closure Context Applies Selective Invalidation In Reduced Field Coordinates",
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
);
mfem::Vector density = reduce(maps.density, make_density(f, 0.17));
mfem::Vector enthalpy = reduce(maps.enthalpy, make_enthalpy(f, 0.29));
const mfem::Vector initialDisplacement =
reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.35));
/*
* 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 displacement(initialDisplacement);
auto dependencies = make_dependencies();
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(relative_error(context.GetBaseDensity(), frozenDensity, communicator) == 0.0);
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
);
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));
auto dependencies = make_dependencies();
preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
const mfem::Vector frozenDensity = preparedOperator.GetContext().GetBaseDensity();
density(0) += 0.19;
const auto sameStampReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
CHECK_FALSE(sameStampReport.DidAnyWork());
CHECK(preparedOperator.GetContext().GetBaseDensity()(0) == frozenDensity(0));
++dependencies.density.identity;
++dependencies.density.revision;
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));
++dependencies.displacement.identity;
++dependencies.displacement.revision;
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);
++dependencies.discretization.identity;
++dependencies.discretization.revision;
const auto discretizationIdentityReport =
preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies);
CHECK(discretizationIdentityReport.contextReport.preparedStaticDependencies);
CHECK(discretizationIdentityReport.contextReport.preparedGeometryState);
CHECK(discretizationIdentityReport.contextReport.preparedBaseState);
}