710 lines
26 KiB
C++
710 lines
26 KiB
C++
#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 = field_dof_test_utils::vacuum_material_attribute;
|
|
|
|
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(0);
|
|
|
|
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);
|
|
}
|