Files
MeanField/tests/operators/prepared_barotropic_closure.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

852 lines
40 KiB
C++

#include <algorithm>
#include <array>
#include <catch2/catch_test_macros.hpp>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <mfem.hpp>
#include <type_traits>
import mean_field;
import test_helpers;
namespace prepared_barotropic_closure_test_utils {
namespace field = mean_field::field;
namespace domain = mean_field::utils::domain;
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)
) {
}
};
using ClosureDependencies = mean_field::operators::context::barotropic::BarotropicClosureDependencies;
[[nodiscard]] ClosureDependencies make_dependencies(const std::uint64_t revisionOffset = 0) {
return {
.discretization = {.identity = 201, .revision = 3 + revisionOffset},
.density = {.identity = 211, .revision = 5 + revisionOffset},
.enthalpy = {.identity = 223, .revision = 7 + revisionOffset},
.displacement = {.identity = 227, .revision = 11 + revisionOffset}
};
}
[[nodiscard]] mean_field::operators::context::barotropic::BarotropicClosureStateView make_state_view(
const mfem::Vector &density,
const mfem::Vector &enthalpy,
const mfem::Vector &displacement
) {
return {.density = density, .enthalpy = enthalpy, .displacement = displacement};
}
[[nodiscard]] mfem::Vector project_scalar(
mfem::ParFiniteElementSpace &finiteElementSpace,
mfem::Coefficient &coefficient
) {
mfem::ParGridFunction fieldValue(&finiteElementSpace);
fieldValue.ProjectCoefficient(coefficient);
mfem::Vector trueVector;
fieldValue.GetTrueDofs(trueVector);
return trueVector;
}
[[nodiscard]] mfem::Vector make_base_density(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
return 0.42 + 0.025 * position(0) - 0.012 * position(1) + 0.007 * position(2);
});
return project_scalar(*f.densityFes, coefficient);
}
[[nodiscard]] mfem::Vector make_base_enthalpy(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
return 0.92 + 0.018 * position(0) - 0.011 * position(1) + 0.006 * position(2);
});
return project_scalar(*f.enthalpyFes, coefficient);
}
[[nodiscard]] mfem::Vector make_enthalpy_variation(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
return 0.065 + 0.014 * position(0) + 0.009 * position(2);
});
return project_scalar(*f.enthalpyFes, coefficient);
}
[[nodiscard]] mfem::Vector make_constant_field(
mfem::ParFiniteElementSpace &finiteElementSpace,
const double value
) {
mfem::ConstantCoefficient coefficient(value);
return project_scalar(finiteElementSpace, coefficient);
}
[[nodiscard]] mfem::Vector reduce(
const field::FieldDofMap &map,
const mfem::Vector &full
) {
return map.gather(full);
}
[[nodiscard]] mfem::Vector expand(
const field::FieldDofMap &map,
const mfem::Vector &reduced
) {
return map.scatter(reduced);
}
[[nodiscard]] mfem::Vector make_combined_variation(
const mfem::Vector &densityVariation,
const mfem::Vector &enthalpyVariation,
const mfem::Vector &displacementVariation
) {
mfem::Vector combined(densityVariation.Size() + enthalpyVariation.Size() + displacementVariation.Size());
int offset = 0;
for (int index = 0; index < densityVariation.Size(); ++index) {
combined(offset + index) = densityVariation(index);
}
offset += densityVariation.Size();
for (int index = 0; index < enthalpyVariation.Size(); ++index) {
combined(offset + index) = enthalpyVariation(index);
}
offset += enthalpyVariation.Size();
for (int index = 0; index < displacementVariation.Size(); ++index) {
combined(offset + index) = displacementVariation(index);
}
return combined;
}
[[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);
}
[[nodiscard]] double global_norm(
const mfem::Vector &vector,
const MPI_Comm communicator
) {
return gravity_prepared_test_utils::global_norm(vector, communicator);
}
[[nodiscard]] long long global_sum(
const int localValue,
const MPI_Comm communicator
) {
const long long local = static_cast<long long>(localValue);
long long global = 0;
MPI_Allreduce(&local, &global, 1, MPI_LONG_LONG, MPI_SUM, communicator);
return global;
}
[[nodiscard]] mfem::Vector gather_reference(
const field::FieldDofMap &densityMap,
const mfem::Vector &fullReference
) {
return densityMap.gather(fullReference);
}
struct ClosureCondition final {
const char *name;
double polytropicIndex;
double polytropicConstant;
double enthalpyOffset;
double enthalpyGradient;
double densityFactor;
double densityOffset;
double densityGradient;
double deformationScale;
double directionPhase;
};
inline constexpr std::array<ClosureCondition, 3> conditions{
{{.name = "Linear polytrope on identity geometry",
.polytropicIndex = 1.0,
.polytropicConstant = 0.8,
.enthalpyOffset = 0.65,
.enthalpyGradient = 0.06,
.densityFactor = 0.80,
.densityOffset = 0.015,
.densityGradient = 0.004,
.deformationScale = 0.0,
.directionPhase = 0.31},
{.name = "Fractional polytrope on moderate deformation",
.polytropicIndex = 1.5,
.polytropicConstant = 1.2,
.enthalpyOffset = 0.90,
.enthalpyGradient = 0.09,
.densityFactor = 1.15,
.densityOffset = -0.003,
.densityGradient = 0.003,
.deformationScale = 0.45,
.directionPhase = 0.53},
{.name = "Target n=3 polytrope on strong deformation",
.polytropicIndex = 3.0,
.polytropicConstant = 1.5,
.enthalpyOffset = 1.20,
.enthalpyGradient = 0.12,
.densityFactor = 1.40,
.densityOffset = 0.006,
.densityGradient = 0.002,
.deformationScale = 1.0,
.directionPhase = 0.79}}
};
[[nodiscard]] double evaluate_enthalpy(
const mfem::Vector &position,
const ClosureCondition &condition
) {
return condition.enthalpyOffset +
condition.enthalpyGradient * (0.50 * position(0) - 0.30 * position(1) + 0.20 * position(2));
}
[[nodiscard]] mfem::Vector make_enthalpy(
const mean_field::fem::FEM &f,
const ClosureCondition &condition
) {
mfem::FunctionCoefficient coefficient([condition](const mfem::Vector &position) {
return evaluate_enthalpy(position, condition);
});
return project_scalar(*f.enthalpyFes, coefficient);
}
[[nodiscard]] mfem::Vector make_density(
const mean_field::fem::FEM &f,
const mean_field::eos::Polytrope &equationOfState,
const ClosureCondition &condition
) {
mfem::FunctionCoefficient coefficient([&equationOfState, condition](const mfem::Vector &position) {
const double enthalpy = evaluate_enthalpy(position, condition);
return condition.densityFactor * equationOfState.density_from_enthalpy(enthalpy) + condition.densityOffset +
condition.densityGradient * (0.40 * position(0) + 0.25 * position(1) - 0.15 * position(2));
});
return project_scalar(*f.densityFes, coefficient);
}
TEST_CASE(
"Prepared Barotropic Closure Uses FieldDof Supported Dimensions",
tags::barotrope &tags::closure &tags::prepared &tags::field &tags::unit
) {
using Operator = mean_field::operators::PreparedBarotropicClosureOperator;
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>);
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 prepared_barotropic_closure_test_utils::Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 1.5);
Operator preparedOperator(f, *f.domainMapperStateless, equationOfState);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK(preparedOperator.GetPreparationCount() == 0);
CHECK(preparedOperator.GetDensitySize() == maps.density.reduced_size());
CHECK(preparedOperator.GetEnthalpySize() == maps.enthalpy.reduced_size());
CHECK(preparedOperator.GetDisplacementSize() == maps.displacement.reduced_size());
CHECK(preparedOperator.Height() == maps.density.reduced_size());
CHECK(
preparedOperator.Width() ==
maps.density.reduced_size() + maps.enthalpy.reduced_size() + maps.displacement.reduced_size()
);
CHECK(maps.displacement.is_identity());
const MPI_Comm communicator = f.mesh->GetComm();
const long long globalDensityFull =
prepared_barotropic_closure_test_utils::global_sum(maps.density.full_size(), communicator);
const long long globalDensityReduced =
prepared_barotropic_closure_test_utils::global_sum(maps.density.reduced_size(), communicator);
const long long globalEnthalpyFull =
prepared_barotropic_closure_test_utils::global_sum(maps.enthalpy.full_size(), communicator);
const long long globalEnthalpyReduced =
prepared_barotropic_closure_test_utils::global_sum(maps.enthalpy.reduced_size(), communicator);
REQUIRE(globalDensityReduced > 0);
REQUIRE(globalEnthalpyReduced > 0);
CHECK(globalDensityReduced < globalDensityFull);
CHECK(globalEnthalpyReduced < globalEnthalpyFull);
}
TEST_CASE(
"Prepared Barotropic Closure Matches Full Stateless Kernels Through FieldDof Restriction",
tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::field &tags::integration
) {
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
);
const mfem::Vector fullBaseDensity = make_base_density(f);
const mfem::Vector fullBaseEnthalpy = make_base_enthalpy(f);
const mfem::Vector fullDisplacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector density = reduce(maps.density, fullBaseDensity);
const mfem::Vector enthalpy = reduce(maps.enthalpy, fullBaseEnthalpy);
const mfem::Vector displacement = reduce(maps.displacement, fullDisplacement);
const mfem::Vector rawDensityVariation =
gravity_prepared_test_utils::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.43);
const mfem::Vector rawEnthalpyVariation = make_enthalpy_variation(f);
const mfem::Vector rawDisplacementVariation = gravity_prepared_test_utils::make_displacement(f, 0.63);
const mfem::Vector densityVariation = reduce(maps.density, rawDensityVariation);
const mfem::Vector enthalpyVariation = reduce(maps.enthalpy, rawEnthalpyVariation);
const mfem::Vector displacementVariation = reduce(maps.displacement, rawDisplacementVariation);
const mfem::Vector fullDensityVariation = expand(maps.density, densityVariation);
const mfem::Vector fullEnthalpyVariation = expand(maps.enthalpy, enthalpyVariation);
const mfem::Vector fullDisplacementVariation = expand(maps.displacement, displacementVariation);
mfem::Vector zeroDensity(maps.density.reduced_size());
mfem::Vector zeroEnthalpy(maps.enthalpy.reduced_size());
mfem::Vector zeroDisplacement(maps.displacement.reduced_size());
zeroDensity = 0.0;
zeroEnthalpy = 0.0;
zeroDisplacement = 0.0;
preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), make_dependencies());
mfem::Vector preparedResidual;
mfem::Vector preparedDensityAction;
mfem::Vector preparedEnthalpyAction;
mfem::Vector preparedDisplacementAction;
mfem::Vector preparedCompleteAction;
mfem::Vector preparedPackedAction;
preparedOperator.BuildResidual(preparedResidual);
preparedOperator.Mult(densityVariation, zeroEnthalpy, zeroDisplacement, preparedDensityAction);
preparedOperator.Mult(zeroDensity, enthalpyVariation, zeroDisplacement, preparedEnthalpyAction);
preparedOperator.Mult(zeroDensity, zeroEnthalpy, displacementVariation, preparedDisplacementAction);
preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, preparedCompleteAction);
const mfem::Vector packedDirection =
make_combined_variation(densityVariation, enthalpyVariation, displacementVariation);
preparedOperator.Mult(packedDirection, preparedPackedAction);
mfem::Vector fullReferenceResidual;
mfem::Vector fullReferenceDensityAction;
mfem::Vector fullReferenceEnthalpyAction;
mfem::Vector fullReferenceDisplacementAction;
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, equationOfState, expand(maps.density, density),
expand(maps.enthalpy, enthalpy), expand(maps.displacement, displacement), fullReferenceResidual
);
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, equationOfState, fullDensityVariation, expand(maps.displacement, displacement),
fullReferenceDensityAction
);
mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action(
f, *f.domainMapperStateless, equationOfState, expand(maps.enthalpy, enthalpy), fullEnthalpyVariation,
expand(maps.displacement, displacement), fullReferenceEnthalpyAction
);
mean_field::operators::kernels::apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, equationOfState, expand(maps.density, density),
expand(maps.enthalpy, enthalpy), expand(maps.displacement, displacement), fullDisplacementVariation,
fullReferenceDisplacementAction
);
const mfem::Vector referenceResidual = gather_reference(maps.density, fullReferenceResidual);
const mfem::Vector referenceDensityAction = gather_reference(maps.density, fullReferenceDensityAction);
const mfem::Vector referenceEnthalpyAction = gather_reference(maps.density, fullReferenceEnthalpyAction);
const mfem::Vector referenceDisplacementAction =
gather_reference(maps.density, fullReferenceDisplacementAction);
mfem::Vector referenceCompleteAction(referenceDensityAction);
referenceCompleteAction += referenceEnthalpyAction;
referenceCompleteAction += referenceDisplacementAction;
const MPI_Comm communicator = f.mesh->GetComm();
CHECK(relative_error(preparedResidual, referenceResidual, communicator) < 2.0e-12);
CHECK(relative_error(preparedDensityAction, referenceDensityAction, communicator) < 2.0e-12);
CHECK(relative_error(preparedEnthalpyAction, referenceEnthalpyAction, communicator) < 2.0e-12);
CHECK(relative_error(preparedDisplacementAction, referenceDisplacementAction, communicator) < 2.0e-12);
CHECK(relative_error(preparedCompleteAction, referenceCompleteAction, communicator) < 2.0e-12);
CHECK(relative_error(preparedPackedAction, referenceCompleteAction, communicator) < 2.0e-12);
CHECK(preparedOperator.GetPreparationCount() == 1);
}
TEST_CASE(
"Prepared Barotropic Closure Jacobian Matches A Reduced Coordinate Centered Difference",
tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::field &tags::jacobian &tags::accuracy
) {
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);
const mfem::Vector density = reduce(maps.density, make_base_density(f));
const mfem::Vector enthalpy = reduce(maps.enthalpy, make_base_enthalpy(f));
const mfem::Vector displacement =
reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.8));
const mfem::Vector densityVariation = reduce(
maps.density, gravity_prepared_test_utils::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.71)
);
const mfem::Vector enthalpyVariation = reduce(maps.enthalpy, make_enthalpy_variation(f));
const mfem::Vector displacementVariation =
reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.63));
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState
);
preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), make_dependencies());
mfem::Vector analyticAction;
preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, analyticAction);
constexpr double differenceStep = 1.0e-5;
mfem::Vector plusDensity(density);
mfem::Vector minusDensity(density);
mfem::Vector plusEnthalpy(enthalpy);
mfem::Vector minusEnthalpy(enthalpy);
mfem::Vector plusDisplacement(displacement);
mfem::Vector minusDisplacement(displacement);
plusDensity.Add(differenceStep, densityVariation);
minusDensity.Add(-differenceStep, densityVariation);
plusEnthalpy.Add(differenceStep, enthalpyVariation);
minusEnthalpy.Add(-differenceStep, enthalpyVariation);
plusDisplacement.Add(differenceStep, displacementVariation);
minusDisplacement.Add(-differenceStep, displacementVariation);
mfem::Vector plusFullResidual;
mfem::Vector minusFullResidual;
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, equationOfState, expand(maps.density, plusDensity),
expand(maps.enthalpy, plusEnthalpy), expand(maps.displacement, plusDisplacement), plusFullResidual
);
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, equationOfState, expand(maps.density, minusDensity),
expand(maps.enthalpy, minusEnthalpy), expand(maps.displacement, minusDisplacement), minusFullResidual
);
mfem::Vector finiteDifference = maps.density.gather(plusFullResidual);
mfem::Vector minusReduced = maps.density.gather(minusFullResidual);
finiteDifference -= minusReduced;
finiteDifference /= 2.0 * differenceStep;
const double error = relative_error(analyticAction, finiteDifference, f.mesh->GetComm());
INFO("Reduced closure centered-difference error = " << error);
CHECK(error < 2.0e-7);
}
TEST_CASE(
"Prepared Barotropic Closure Reuses Its Frozen Expanded State",
tags::barotrope &tags::closure &tags::prepared &tags::field &tags::unit
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.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_base_density(f));
mfem::Vector enthalpy = reduce(maps.enthalpy, make_base_enthalpy(f));
mfem::Vector displacement = reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 1.0));
const mfem::Vector densityVariation = reduce(
maps.density, gravity_prepared_test_utils::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.31)
);
const mfem::Vector enthalpyVariation = reduce(maps.enthalpy, make_enthalpy_variation(f));
const mfem::Vector displacementVariation =
reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.63));
preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), make_dependencies());
REQUIRE(preparedOperator.GetPreparationCount() == 1);
mfem::Vector firstResidual;
mfem::Vector firstAction;
preparedOperator.BuildResidual(firstResidual);
preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, firstAction);
density = 7.0;
enthalpy = 3.0;
displacement *= -4.0;
const std::uint64_t preparationCount = preparedOperator.GetPreparationCount();
mfem::Vector repeatedResidual;
mfem::Vector repeatedAction;
preparedOperator.BuildResidual(repeatedResidual);
preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, repeatedAction);
const MPI_Comm communicator = f.mesh->GetComm();
CHECK(relative_error(repeatedResidual, firstResidual, communicator) < 2.0e-14);
CHECK(relative_error(repeatedAction, firstAction, communicator) < 2.0e-14);
CHECK(preparedOperator.GetPreparationCount() == preparationCount);
}
TEST_CASE(
"Prepared Barotropic Closure Reprepares Correctly For New Geometry",
tags::barotrope &tags::closure &tags::hydro &tags::mapping &tags::prepared &tags::field &tags::integration
) {
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);
const mfem::Vector density = reduce(maps.density, make_base_density(f));
const mfem::Vector enthalpy = reduce(maps.enthalpy, make_base_enthalpy(f));
const mfem::Vector densityVariation = reduce(
maps.density, gravity_prepared_test_utils::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.59)
);
mfem::Vector zeroEnthalpy(maps.enthalpy.reduced_size());
mfem::Vector zeroDisplacementVariation(maps.displacement.reduced_size());
zeroEnthalpy = 0.0;
zeroDisplacementVariation = 0.0;
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState
);
mfem::Vector identityAction;
mfem::Vector deformedAction;
ClosureDependencies dependencies = make_dependencies();
for (const double deformationScale : {0.0, 1.0}) {
CAPTURE(deformationScale);
const mfem::Vector displacement =
reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, deformationScale));
preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies);
mfem::Vector preparedResidual;
mfem::Vector preparedAction;
preparedOperator.BuildResidual(preparedResidual);
preparedOperator.Mult(densityVariation, zeroEnthalpy, zeroDisplacementVariation, preparedAction);
mfem::Vector fullReferenceResidual;
mfem::Vector fullReferenceAction;
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, equationOfState, expand(maps.density, density),
expand(maps.enthalpy, enthalpy), expand(maps.displacement, displacement), fullReferenceResidual
);
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, equationOfState, expand(maps.density, densityVariation),
expand(maps.displacement, displacement), fullReferenceAction
);
const mfem::Vector referenceResidual = maps.density.gather(fullReferenceResidual);
const mfem::Vector referenceAction = maps.density.gather(fullReferenceAction);
const MPI_Comm communicator = f.mesh->GetComm();
CHECK(relative_error(preparedResidual, referenceResidual, communicator) < 2.0e-12);
CHECK(relative_error(preparedAction, referenceAction, communicator) < 2.0e-12);
if (deformationScale == 0.0) {
identityAction = preparedAction;
} else {
deformedAction = preparedAction;
}
++dependencies.displacement.revision;
}
const double geometryChange = relative_error(deformedAction, identityAction, f.mesh->GetComm());
INFO("Prepared closure geometry change = " << geometryChange);
CHECK(preparedOperator.GetPreparationCount() == 2);
CHECK(geometryChange > 1.0e-5);
}
TEST_CASE(
"Prepared Barotropic Closure Covers Multiple EOS And Geometry Conditions In Reduced Coordinates",
tags::barotrope &tags::closure &tags::hydro &tags::integration &tags::jacobian &tags::mapping &tags::physics
&tags::prepared &tags::field
) {
auto 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 Maps maps(f);
const MPI_Comm communicator = f.mesh->GetComm();
constexpr double differenceStep = 1.0e-5;
for (std::size_t conditionIndex = 0; conditionIndex < conditions.size(); ++conditionIndex) {
const ClosureCondition &condition = conditions[conditionIndex];
DYNAMIC_SECTION(condition.name) {
const mean_field::eos::Polytrope equationOfState(
condition.polytropicIndex, condition.polytropicConstant
);
const mfem::Vector fullBaseDensityRaw = make_density(f, equationOfState, condition);
const mfem::Vector fullBaseEnthalpyRaw = make_enthalpy(f, condition);
const mfem::Vector fullBaseDisplacementRaw =
gravity_prepared_test_utils::make_displacement(f, condition.deformationScale);
const mfem::Vector baseDensity = reduce(maps.density, fullBaseDensityRaw);
const mfem::Vector baseEnthalpy = reduce(maps.enthalpy, fullBaseEnthalpyRaw);
const mfem::Vector baseDisplacement = reduce(maps.displacement, fullBaseDisplacementRaw);
mfem::Vector densityVariation = reduce(
maps.density, gravity_prepared_test_utils::make_deterministic_vector(
f.densityFes->GetTrueVSize(), condition.directionPhase
)
);
mfem::Vector enthalpyVariation = reduce(maps.enthalpy, make_enthalpy_variation(f));
mfem::Vector displacementVariation = reduce(
maps.displacement,
gravity_prepared_test_utils::make_displacement(f, 0.55 + 0.1 * condition.directionPhase)
);
mfem::Vector fullDensityAction;
mfem::Vector fullEnthalpyAction;
mfem::Vector fullDisplacementAction;
mean_field::operators::kernels::apply_barotropic_closure_density_action(
f, *f.domainMapperStateless, equationOfState, expand(maps.density, densityVariation),
expand(maps.displacement, baseDisplacement), fullDensityAction
);
mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action(
f, *f.domainMapperStateless, equationOfState, expand(maps.enthalpy, baseEnthalpy),
expand(maps.enthalpy, enthalpyVariation), expand(maps.displacement, baseDisplacement),
fullEnthalpyAction
);
mean_field::operators::kernels::apply_barotropic_closure_displacement_action(
f, *f.domainMapperStateless, equationOfState, expand(maps.density, baseDensity),
expand(maps.enthalpy, baseEnthalpy), expand(maps.displacement, baseDisplacement),
expand(maps.displacement, displacementVariation), fullDisplacementAction
);
mfem::Vector densityAction = maps.density.gather(fullDensityAction);
mfem::Vector enthalpyAction = maps.density.gather(fullEnthalpyAction);
mfem::Vector displacementAction = maps.density.gather(fullDisplacementAction);
double densityNorm = global_norm(densityAction, communicator);
double enthalpyNorm = global_norm(enthalpyAction, communicator);
double displacementNorm = global_norm(displacementAction, communicator);
REQUIRE(densityNorm > 1.0e-12);
REQUIRE(enthalpyNorm > 1.0e-12);
REQUIRE(displacementNorm > 1.0e-12);
const double targetNorm = std::min({densityNorm, enthalpyNorm, displacementNorm});
const double densityScale = targetNorm / densityNorm;
const double enthalpyScale = targetNorm / enthalpyNorm;
const double displacementScale = targetNorm / displacementNorm;
densityVariation *= densityScale;
enthalpyVariation *= enthalpyScale;
displacementVariation *= displacementScale;
densityAction *= densityScale;
enthalpyAction *= enthalpyScale;
displacementAction *= displacementScale;
densityNorm = global_norm(densityAction, communicator);
enthalpyNorm = global_norm(enthalpyAction, communicator);
displacementNorm = global_norm(displacementAction, communicator);
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState
);
preparedOperator.Prepare(
make_state_view(baseDensity, baseEnthalpy, baseDisplacement), make_dependencies()
);
mfem::Vector preparedResidual;
mfem::Vector preparedAction;
preparedOperator.BuildResidual(preparedResidual);
preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, preparedAction);
mfem::Vector fullReferenceResidual;
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, equationOfState, expand(maps.density, baseDensity),
expand(maps.enthalpy, baseEnthalpy), expand(maps.displacement, baseDisplacement),
fullReferenceResidual
);
const mfem::Vector referenceResidual = maps.density.gather(fullReferenceResidual);
mfem::Vector blockSum(densityAction);
blockSum += enthalpyAction;
blockSum += displacementAction;
const double residualEvaluationError =
relative_error(preparedResidual, referenceResidual, communicator);
const double blockAssemblyError = relative_error(preparedAction, blockSum, communicator);
mfem::Vector plusDensity(baseDensity);
mfem::Vector minusDensity(baseDensity);
mfem::Vector plusEnthalpy(baseEnthalpy);
mfem::Vector minusEnthalpy(baseEnthalpy);
mfem::Vector plusDisplacement(baseDisplacement);
mfem::Vector minusDisplacement(baseDisplacement);
plusDensity.Add(differenceStep, densityVariation);
minusDensity.Add(-differenceStep, densityVariation);
plusEnthalpy.Add(differenceStep, enthalpyVariation);
minusEnthalpy.Add(-differenceStep, enthalpyVariation);
plusDisplacement.Add(differenceStep, displacementVariation);
minusDisplacement.Add(-differenceStep, displacementVariation);
mfem::Vector fullPlusResidual;
mfem::Vector fullMinusResidual;
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, equationOfState, expand(maps.density, plusDensity),
expand(maps.enthalpy, plusEnthalpy), expand(maps.displacement, plusDisplacement), fullPlusResidual
);
mean_field::operators::kernels::apply_barotropic_closure(
f, *f.domainMapperStateless, equationOfState, expand(maps.density, minusDensity),
expand(maps.enthalpy, minusEnthalpy), expand(maps.displacement, minusDisplacement),
fullMinusResidual
);
mfem::Vector finiteDifference = maps.density.gather(fullPlusResidual);
const mfem::Vector minusReduced = maps.density.gather(fullMinusResidual);
finiteDifference -= minusReduced;
finiteDifference /= 2.0 * differenceStep;
mfem::Vector finiteDifferenceError(preparedAction);
finiteDifferenceError -= finiteDifference;
const double finiteDifferenceErrorNorm = global_norm(finiteDifferenceError, communicator);
const double blockNormSum = densityNorm + enthalpyNorm + displacementNorm;
const double blockScaledDifferenceError = finiteDifferenceErrorNorm / blockNormSum;
const double completeRelativeError = relative_error(preparedAction, finiteDifference, communicator);
INFO("Condition = " << condition.name);
INFO("Polytropic index = " << condition.polytropicIndex);
INFO("Deformation scale = " << condition.deformationScale);
INFO("Prepared residual error = " << residualEvaluationError);
INFO("Complete block-assembly error = " << blockAssemblyError);
INFO("Complete centered-difference relative error = " << completeRelativeError);
INFO("Block-scaled centered-difference error = " << blockScaledDifferenceError);
CHECK(preparedOperator.GetPreparationCount() == 1);
CHECK(residualEvaluationError < 5.0e-12);
CHECK(blockAssemblyError < 5.0e-12);
CHECK(blockScaledDifferenceError < 2.0e-7);
}
}
}
TEST_CASE(
"Exact Constant Prepared Barotropic Closure Remains Zero Under Deformation",
tags::barotrope &tags::closure &tags::hydro &tags::integration &tags::jacobian &tags::mapping &tags::physics
&tags::prepared &tags::field
) {
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);
constexpr double enthalpyValue = 1.20;
const double equilibriumDensityValue = equationOfState.density_from_enthalpy(enthalpyValue);
const mfem::Vector enthalpy = reduce(maps.enthalpy, make_constant_field(*f.enthalpyFes, enthalpyValue));
const mfem::Vector equilibriumDensity =
reduce(maps.density, make_constant_field(*f.densityFes, equilibriumDensityValue));
const mfem::Vector referenceDensity =
reduce(maps.density, make_constant_field(*f.densityFes, equilibriumDensityValue + 1.0));
const mfem::Vector displacementVariation =
reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.67));
mfem::Vector zeroDensity(maps.density.reduced_size());
mfem::Vector zeroEnthalpy(maps.enthalpy.reduced_size());
zeroDensity = 0.0;
zeroEnthalpy = 0.0;
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 =
reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, deformationScale));
mean_field::operators::PreparedBarotropicClosureOperator exactOperator(
f, *f.domainMapperStateless, equationOfState
);
mean_field::operators::PreparedBarotropicClosureOperator referenceOperator(
f, *f.domainMapperStateless, equationOfState
);
exactOperator.Prepare(make_state_view(equilibriumDensity, enthalpy, displacement), make_dependencies());
referenceOperator.Prepare(
make_state_view(referenceDensity, enthalpy, displacement), make_dependencies()
);
mfem::Vector exactResidual;
mfem::Vector referenceResidual;
mfem::Vector exactGeometryAction;
mfem::Vector referenceGeometryAction;
exactOperator.BuildResidual(exactResidual);
referenceOperator.BuildResidual(referenceResidual);
exactOperator.Mult(zeroDensity, zeroEnthalpy, displacementVariation, exactGeometryAction);
referenceOperator.Mult(zeroDensity, zeroEnthalpy, displacementVariation, referenceGeometryAction);
const double exactResidualNorm = global_norm(exactResidual, communicator);
const double referenceResidualNorm = global_norm(referenceResidual, communicator);
const double exactGeometryNorm = global_norm(exactGeometryAction, communicator);
const double referenceGeometryNorm = global_norm(referenceGeometryAction, communicator);
INFO("Deformation scale = " << deformationScale);
INFO("Exact reduced closure residual norm = " << exactResidualNorm);
INFO("Reference reduced residual norm = " << referenceResidualNorm);
INFO("Exact reduced geometry-action norm = " << exactGeometryNorm);
INFO("Reference reduced geometry-action norm = " << referenceGeometryNorm);
REQUIRE(referenceResidualNorm > 1.0e-12);
REQUIRE(referenceGeometryNorm > 1.0e-14);
CHECK(exactResidualNorm <= 5.0e-12 * referenceResidualNorm);
CHECK(exactGeometryNorm <= 5.0e-12 * referenceGeometryNorm);
}
}
}
} // namespace prepared_barotropic_closure_test_utils