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:
706
tests/operators/prepared_pressure_force.cpp
Normal file
706
tests/operators/prepared_pressure_force.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user