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

856 lines
32 KiB
C++

#include <algorithm>
#include <cmath>
#include <limits>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace hydrostatic_kernel_test_utils {
mfem::Vector project_scalar(
mfem::ParFiniteElementSpace &finiteElementSpace,
mfem::Coefficient &coefficient
) {
mfem::ParGridFunction field(&finiteElementSpace);
field.ProjectCoefficient(coefficient);
mfem::Vector trueVector;
field.GetTrueDofs(trueVector);
return trueVector;
}
mfem::Vector make_constant_field(
mfem::ParFiniteElementSpace &finiteElementSpace,
const double value
) {
mfem::ConstantCoefficient coefficient(value);
return project_scalar(finiteElementSpace, coefficient);
}
mfem::Vector make_enthalpy(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
return 1.10 + 0.035 * position(0) - 0.021 * position(1) + 0.014 * position(2);
});
return project_scalar(*f.enthalpyFes, coefficient);
}
mfem::Vector make_potential(const mean_field::fem::FEM &f) {
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
return -0.72 + 0.018 * position(0) + 0.011 * position(1) - 0.025 * position(2);
});
return project_scalar(*f.gravityPotentialFes, coefficient);
}
mean_field::physics::RigidRotation make_rotation() {
mfem::Vector angularVelocity(3);
angularVelocity(0) = 0.21;
angularVelocity(1) = -0.13;
angularVelocity(2) = 0.48;
mfem::Vector center(3);
center(0) = 0.04;
center(1) = -0.03;
center(2) = 0.02;
return mean_field::physics::RigidRotation(angularVelocity, center);
}
mean_field::physics::RigidRotation make_zero_rotation() {
mfem::Vector angularVelocity(3);
mfem::Vector center(3);
angularVelocity = 0.0;
center = 0.0;
return mean_field::physics::RigidRotation(angularVelocity, center);
}
mfem::Vector centered_difference(
const mfem::Vector &plusResidual,
const mfem::Vector &minusResidual,
const double epsilon
) {
mfem::Vector difference(plusResidual);
difference -= minusResidual;
difference *= 1.0 / (2.0 * epsilon);
return difference;
}
double sum_normalized_error(
const mfem::Vector &computed,
const mfem::Vector &reference,
const double normalization,
const MPI_Comm communicator
) {
mfem::Vector difference(computed);
difference -= reference;
return gravity_prepared_test_utils::global_norm(difference, communicator) /
std::max(normalization, std::numeric_limits<double>::epsilon());
}
mfem::Vector make_vacuum_supported_potential(const mean_field::fem::FEM &f) {
mfem::Vector attributeValues(f.mesh->attributes.Max());
attributeValues = 0.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) {
attributeValues(attribute - 1) = 1.0;
}
}
mfem::PWConstCoefficient coefficient(attributeValues);
return project_scalar(*f.gravityPotentialFes, coefficient);
}
class HydrostaticEnthalpyMassOperator final : public mfem::Operator {
public:
HydrostaticEnthalpyMassOperator(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &displacementTrue
)
: mfem::Operator(f.enthalpyFes->GetTrueVSize()),
f_(f),
domainMapper_(domainMapper),
displacementTrue_(displacementTrue) {
}
void Mult(
const mfem::Vector &input,
mfem::Vector &output
) const override {
mean_field::operators::kernels::apply_hydrostatic_equilibrium_enthalpy_action(
f_, domainMapper_, input, displacementTrue_, output
);
}
private:
const mean_field::fem::FEM &f_;
const mean_field::mapping::DomainMapperStateless &domainMapper_;
const mfem::Vector &displacementTrue_;
};
} // namespace hydrostatic_kernel_test_utils
TEST_CASE(
"Rigid Rotation Potential Derivative Matches Centered Differences",
tags::barotrope &tags::hydro &tags::jacobian &tags::physics &tags::unit &tags::kernels
) {
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
mfem::Vector position(3);
mfem::Vector direction(3);
position(0) = 0.71;
position(1) = -0.42;
position(2) = 0.36;
direction(0) = -0.17;
direction(1) = 0.29;
direction(2) = 0.11;
constexpr double epsilon = 1.0e-7;
mfem::Vector plusPosition(position);
mfem::Vector minusPosition(position);
plusPosition.Add(epsilon, direction);
minusPosition.Add(-epsilon, direction);
const double centeredDerivative =
(rotation.potential(plusPosition) - rotation.potential(minusPosition)) / (2.0 * epsilon);
const double analyticDerivative = rotation.potential_directional_derivative(position, direction);
const double relativeError = std::abs(centeredDerivative - analyticDerivative) /
std::max(std::abs(analyticDerivative), std::numeric_limits<double>::epsilon());
INFO("Rigid-rotation derivative error = " << relativeError);
CHECK(relativeError < 2.0e-9);
}
TEST_CASE(
"Hydrostatic Residual Vanishes For A Manufactured Rotating State",
tags::barotrope &tags::hydro &tags::integration &tags::kernels &tags::physics &tags::residuals
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
constexpr double bernoulliConstant = 0.73;
constexpr double potentialValue = -0.21;
constexpr double constantOffset = 0.40;
mfem::FunctionCoefficient enthalpyCoefficient([&rotation](const mfem::Vector &position) {
return bernoulliConstant - potentialValue + rotation.potential(position);
});
const mfem::Vector interpolatedEnthalpy =
hydrostatic_kernel_test_utils::project_scalar(*f.enthalpyFes, enthalpyCoefficient);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, potentialValue);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.0);
const MPI_Comm communicator = f.mesh->GetComm();
/*
* First measure the residual of the nodally interpolated
* analytic equilibrium. Because the order-3 enthalpy space
* cannot exactly represent the quadratic rotation potential
* on an order-4 curved mesh, this measures the representation
* floor rather than an algebraic residual.
*/
mfem::Vector interpolatedResidual;
mfem::Vector interpolatedReferenceResidual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential, displacement, bernoulliConstant,
interpolatedResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential, displacement,
bernoulliConstant + constantOffset, interpolatedReferenceResidual
);
const double interpolatedResidualNorm =
gravity_prepared_test_utils::global_norm(interpolatedResidual, communicator);
const double interpolatedReferenceNorm =
gravity_prepared_test_utils::global_norm(interpolatedReferenceResidual, communicator);
REQUIRE(interpolatedReferenceNorm > 1.0e-12);
const double representationFloor = interpolatedResidualNorm / interpolatedReferenceNorm;
INFO("Interpolated rotating-state residual norm = " << interpolatedResidualNorm);
INFO(
"Interpolated rotating-state relative "
"representation floor = "
<< representationFloor
);
/*
* This remains an independent physical/sign check. A wrong
* rotation sign or coordinate convention would produce an
* order-unity error rather than the observed projection floor.
*/
CHECK(representationFloor < 2.0e-5);
/*
* Construct the weakly manufactured discrete equilibrium.
*
* If r_I is the residual of the nodal interpolant, solve
*
* M_h delta_h = -r_I,
*
* where M_h is exactly the stellar-domain enthalpy action.
* Then h_I + delta_h satisfies the discrete weak equilibrium.
*
* Vacuum-only enthalpy DOFs form a nullspace, but the right-hand
* side is in the range of M_h. Starting CG from zero keeps the
* iteration in the active stellar subspace.
*/
hydrostatic_kernel_test_utils::HydrostaticEnthalpyMassOperator enthalpyMassOperator(
f, *f.domainMapperStateless, displacement
);
mfem::Vector correctionRightHandSide(interpolatedResidual);
correctionRightHandSide *= -1.0;
mfem::Vector enthalpyCorrection(f.enthalpyFes->GetTrueVSize());
enthalpyCorrection = 0.0;
mfem::CGSolver projectionSolver(communicator);
projectionSolver.SetOperator(enthalpyMassOperator);
projectionSolver.SetRelTol(1.0e-12);
projectionSolver.SetAbsTol(1.0e-15);
projectionSolver.SetMaxIter(1000);
projectionSolver.SetPrintLevel(0);
projectionSolver.Mult(correctionRightHandSide, enthalpyCorrection);
INFO("Discrete-equilibrium projection converged = " << projectionSolver.GetConverged());
INFO("Discrete-equilibrium projection iterations = " << projectionSolver.GetNumIterations());
INFO("Discrete-equilibrium projection final norm = " << projectionSolver.GetFinalNorm());
REQUIRE(projectionSolver.GetConverged());
mfem::Vector correctionEquationResidual;
enthalpyMassOperator.Mult(enthalpyCorrection, correctionEquationResidual);
correctionEquationResidual -= correctionRightHandSide;
const double correctionEquationNorm =
gravity_prepared_test_utils::global_norm(correctionEquationResidual, communicator);
INFO(
"Discrete-equilibrium correction-equation "
"residual norm = "
<< correctionEquationNorm
);
CHECK(correctionEquationNorm <= std::max(5.0e-12 * interpolatedResidualNorm, 5.0e-15));
mfem::Vector discreteEnthalpy(interpolatedEnthalpy);
discreteEnthalpy += enthalpyCorrection;
mfem::Vector exactResidual;
mfem::Vector referenceResidual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential, displacement, bernoulliConstant,
exactResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential, displacement,
bernoulliConstant + constantOffset, referenceResidual
);
const double exactNorm = gravity_prepared_test_utils::global_norm(exactResidual, communicator);
const double referenceNorm = gravity_prepared_test_utils::global_norm(referenceResidual, communicator);
const double correctionNorm = gravity_prepared_test_utils::global_norm(enthalpyCorrection, communicator);
INFO("Enthalpy representation correction norm = " << correctionNorm);
INFO("Discrete manufactured residual norm = " << exactNorm);
INFO("Discrete reference residual norm = " << referenceNorm);
REQUIRE(referenceNorm > 1.0e-12);
CHECK(exactNorm <= 5.0e-12 * referenceNorm);
}
TEST_CASE(
"Exact Constant Hydrostatic Equilibrium Remains Zero Under Deformation",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::kernels &tags::mapping &tags::physics
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_zero_rotation();
constexpr double enthalpyValue = 1.20;
constexpr double potentialValue = -0.35;
constexpr double bernoulliConstant = enthalpyValue + potentialValue;
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_constant_field(*f.enthalpyFes, enthalpyValue);
const mfem::Vector potential =
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, potentialValue);
const mfem::Vector displacementVariation = gravity_prepared_test_utils::make_displacement(f, 0.67);
const MPI_Comm communicator = f.mesh->GetComm();
for (const double deformationScale : {0.0, 0.5, 1.0}) {
DYNAMIC_SECTION("Deformation scale = " << deformationScale) {
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, deformationScale);
mfem::Vector exactResidual;
mfem::Vector referenceResidual;
mfem::Vector exactGeometryAction;
mfem::Vector referenceGeometryAction;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant,
exactResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant + 0.50,
referenceResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant,
displacementVariation, exactGeometryAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant + 0.50,
displacementVariation, referenceGeometryAction
);
const double exactResidualNorm = gravity_prepared_test_utils::global_norm(exactResidual, communicator);
const double referenceResidualNorm =
gravity_prepared_test_utils::global_norm(referenceResidual, communicator);
const double exactGeometryNorm =
gravity_prepared_test_utils::global_norm(exactGeometryAction, communicator);
const double referenceGeometryNorm =
gravity_prepared_test_utils::global_norm(referenceGeometryAction, communicator);
REQUIRE(referenceResidualNorm > 1.0e-12);
REQUIRE(referenceGeometryNorm > 1.0e-14);
CHECK(exactResidualNorm <= 5.0e-12 * referenceResidualNorm);
CHECK(exactGeometryNorm <= 5.0e-12 * referenceGeometryNorm);
}
}
}
TEST_CASE(
"Hydrostatic Equilibrium Excludes Vacuum Elements",
tags::barotrope &tags::hydro &tags::kernels &tags::mapping &tags::physics &tags::unit
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_zero_rotation();
const mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize());
mfem::Vector enthalpy(zeroEnthalpy);
enthalpy = 0.0;
const mfem::Vector vacuumPotential = hydrostatic_kernel_test_utils::make_vacuum_supported_potential(f);
const mfem::Vector stellarPotential =
hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, 1.0);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
mfem::Vector residual;
mfem::Vector vacuumAction;
mfem::Vector stellarAction;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, enthalpy, vacuumPotential, displacement, 0.0, residual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, vacuumPotential, displacement, vacuumAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, stellarPotential, displacement, stellarAction
);
const MPI_Comm communicator = f.mesh->GetComm();
const double residualNorm = gravity_prepared_test_utils::global_norm(residual, communicator);
const double vacuumActionNorm = gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
const double stellarActionNorm = gravity_prepared_test_utils::global_norm(stellarAction, communicator);
REQUIRE(stellarActionNorm > 1.0e-12);
CHECK(residualNorm <= 5.0e-13 * stellarActionNorm);
CHECK(vacuumActionNorm <= 5.0e-13 * stellarActionNorm);
}
TEST_CASE(
"Hydrostatic Jacobian Matches Blocks And Centered Differences",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::kernels &tags::mapping &tags::physics
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector enthalpyVariation =
gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.23);
const mfem::Vector potentialVariation =
gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.47);
const mfem::Vector displacementVariation =
gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.71);
constexpr double bernoulliConstant = 0.41;
constexpr double constantVariation = -0.37;
constexpr double epsilon = 1.0e-7;
mfem::Vector enthalpyAction;
mfem::Vector potentialAction;
mfem::Vector constantAction;
mfem::Vector displacementAction;
mfem::Vector completeAction;
mean_field::operators::kernels::apply_hydrostatic_equilibrium_enthalpy_action(
f, *f.domainMapperStateless, enthalpyVariation, displacement, enthalpyAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action(
f, *f.domainMapperStateless, potentialVariation, displacement, potentialAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_constant_action(
f, *f.domainMapperStateless, constantVariation, displacement, constantAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant,
displacementVariation, displacementAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, enthalpyVariation,
potentialVariation, constantVariation, displacementVariation, completeAction
);
mfem::Vector blockAction(enthalpyAction);
blockAction += potentialAction;
blockAction += constantAction;
blockAction += displacementAction;
const MPI_Comm communicator = f.mesh->GetComm();
const double blockError = gravity_prepared_test_utils::relative_error(completeAction, blockAction, communicator);
INFO("Hydrostatic block reconstruction error = " << blockError);
CHECK(blockError < 5.0e-13);
auto evaluate_residual = [&f, &rotation](
const mfem::Vector &trialEnthalpy, const mfem::Vector &trialPotential,
const mfem::Vector &trialDisplacement, const double trialConstant
) {
mfem::Vector residual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, rotation, trialEnthalpy, trialPotential, trialDisplacement, trialConstant,
residual
);
return residual;
};
mfem::Vector plusEnthalpy(enthalpy);
mfem::Vector minusEnthalpy(enthalpy);
plusEnthalpy.Add(epsilon, enthalpyVariation);
minusEnthalpy.Add(-epsilon, enthalpyVariation);
const mfem::Vector enthalpyDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(plusEnthalpy, potential, displacement, bernoulliConstant),
evaluate_residual(minusEnthalpy, potential, displacement, bernoulliConstant), epsilon
);
mfem::Vector plusPotential(potential);
mfem::Vector minusPotential(potential);
plusPotential.Add(epsilon, potentialVariation);
minusPotential.Add(-epsilon, potentialVariation);
const mfem::Vector potentialDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, plusPotential, displacement, bernoulliConstant),
evaluate_residual(enthalpy, minusPotential, displacement, bernoulliConstant), epsilon
);
const mfem::Vector constantDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, potential, displacement, bernoulliConstant + epsilon * constantVariation),
evaluate_residual(enthalpy, potential, displacement, bernoulliConstant - epsilon * constantVariation), epsilon
);
mfem::Vector plusDisplacement(displacement);
mfem::Vector minusDisplacement(displacement);
plusDisplacement.Add(epsilon, displacementVariation);
minusDisplacement.Add(-epsilon, displacementVariation);
const mfem::Vector displacementDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(enthalpy, potential, plusDisplacement, bernoulliConstant),
evaluate_residual(enthalpy, potential, minusDisplacement, bernoulliConstant), epsilon
);
const double enthalpyError =
gravity_prepared_test_utils::relative_error(enthalpyAction, enthalpyDifference, communicator);
const double potentialError =
gravity_prepared_test_utils::relative_error(potentialAction, potentialDifference, communicator);
const double constantError =
gravity_prepared_test_utils::relative_error(constantAction, constantDifference, communicator);
const double displacementError =
gravity_prepared_test_utils::relative_error(displacementAction, displacementDifference, communicator);
INFO("Hydrostatic enthalpy-block error = " << enthalpyError);
INFO("Hydrostatic potential-block error = " << potentialError);
INFO("Hydrostatic constant-block error = " << constantError);
INFO("Hydrostatic displacement-block error = " << displacementError);
CHECK(enthalpyError < 2.0e-8);
CHECK(potentialError < 2.0e-8);
CHECK(constantError < 2.0e-8);
CHECK(displacementError < 2.0e-7);
mfem::Vector combinedPlusEnthalpy(enthalpy);
mfem::Vector combinedMinusEnthalpy(enthalpy);
mfem::Vector combinedPlusPotential(potential);
mfem::Vector combinedMinusPotential(potential);
mfem::Vector combinedPlusDisplacement(displacement);
mfem::Vector combinedMinusDisplacement(displacement);
combinedPlusEnthalpy.Add(epsilon, enthalpyVariation);
combinedMinusEnthalpy.Add(-epsilon, enthalpyVariation);
combinedPlusPotential.Add(epsilon, potentialVariation);
combinedMinusPotential.Add(-epsilon, potentialVariation);
combinedPlusDisplacement.Add(epsilon, displacementVariation);
combinedMinusDisplacement.Add(-epsilon, displacementVariation);
const mfem::Vector combinedDifference = hydrostatic_kernel_test_utils::centered_difference(
evaluate_residual(
combinedPlusEnthalpy, combinedPlusPotential, combinedPlusDisplacement,
bernoulliConstant + epsilon * constantVariation
),
evaluate_residual(
combinedMinusEnthalpy, combinedMinusPotential, combinedMinusDisplacement,
bernoulliConstant - epsilon * constantVariation
),
epsilon
);
const double blockNormSum = gravity_prepared_test_utils::global_norm(enthalpyAction, communicator) +
gravity_prepared_test_utils::global_norm(potentialAction, communicator) +
gravity_prepared_test_utils::global_norm(constantAction, communicator) +
gravity_prepared_test_utils::global_norm(displacementAction, communicator);
const double simultaneousError = hydrostatic_kernel_test_utils::sum_normalized_error(
completeAction, combinedDifference, blockNormSum, communicator
);
INFO("Hydrostatic simultaneous Jacobian error = " << simultaneousError);
CHECK(simultaneousError < 2.0e-7);
}
TEST_CASE(
"Hydrostatic Displacement Action Is Linear In Its Direction",
tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::mapping &tags::physics &tags::unit
&tags::kernels
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation();
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
const mfem::Vector firstDirection =
gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.31);
const mfem::Vector secondDirection =
gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.83);
constexpr double firstScale = 0.43;
constexpr double secondScale = -0.29;
constexpr double bernoulliConstant = 0.41;
const mfem::Vector combinedDirection =
gravity_prepared_test_utils::linear_combination(firstDirection, firstScale, secondDirection, secondScale);
mfem::Vector firstAction;
mfem::Vector secondAction;
mfem::Vector combinedAction;
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, firstDirection,
firstAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, secondDirection,
secondAction
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action(
f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, combinedDirection,
combinedAction
);
const mfem::Vector expectedAction =
gravity_prepared_test_utils::linear_combination(firstAction, firstScale, secondAction, secondScale);
const double linearityError =
gravity_prepared_test_utils::relative_error(combinedAction, expectedAction, f.mesh->GetComm());
INFO("Hydrostatic displacement-linearity error = " << linearityError);
CHECK(linearityError < 5.0e-12);
}
TEST_CASE(
"Hydrostatic Residual Is Translationally Invariant On Deformed Geometry",
tags::barotrope &tags::hydro &tags::integration &tags::kernels &tags::mapping &tags::physics &tags::residuals
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
mfem::Vector angularVelocity(3);
angularVelocity(0) = 0.21;
angularVelocity(1) = -0.13;
angularVelocity(2) = 0.48;
mfem::Vector center(3);
center(0) = 0.04;
center(1) = -0.03;
center(2) = 0.02;
mfem::Vector translation(3);
translation(0) = 0.071;
translation(1) = -0.053;
translation(2) = 0.037;
mfem::Vector translatedCenter(center);
translatedCenter += translation;
const mean_field::physics::RigidRotation baseRotation(angularVelocity, center);
const mean_field::physics::RigidRotation translatedRotation(angularVelocity, translatedCenter);
const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f);
const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f);
/*
* Use a nontrivially deformed base state so this checks rotation
* and mapped geometry simultaneously. The comparison state adds
* an exactly representable rigid translation to that deformation.
*/
const mfem::Vector baseDisplacement = gravity_prepared_test_utils::make_displacement(f, 0.73);
mfem::ParGridFunction translationField(f.displacementFes.get());
mfem::VectorFunctionCoefficient translationCoefficient(
f.mesh->Dimension(), [&translation](const mfem::Vector &, mfem::Vector &value) {
value.SetSize(translation.Size());
value = translation;
}
);
translationField.ProjectCoefficient(translationCoefficient);
mfem::Vector translationTrue;
translationField.GetTrueDofs(translationTrue);
mfem::Vector translatedDisplacement(baseDisplacement);
translatedDisplacement += translationTrue;
constexpr double bernoulliConstant = 0.41;
mfem::Vector baseResidual;
mfem::Vector translatedResidual;
mfem::Vector untranslatedCenterResidual;
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, baseRotation, enthalpy, potential, baseDisplacement, bernoulliConstant,
baseResidual
);
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, translatedRotation, enthalpy, potential, translatedDisplacement, bernoulliConstant,
translatedResidual
);
/*
* Negative control: translate the geometry but leave the rotation
* center fixed. This must not agree with the covariant result.
*/
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
f, *f.domainMapperStateless, baseRotation, enthalpy, potential, translatedDisplacement, bernoulliConstant,
untranslatedCenterResidual
);
const MPI_Comm communicator = f.mesh->GetComm();
const double baseResidualNorm = gravity_prepared_test_utils::global_norm(baseResidual, communicator);
const double translatedResidualNorm = gravity_prepared_test_utils::global_norm(translatedResidual, communicator);
const double translationInvarianceError =
gravity_prepared_test_utils::relative_error(translatedResidual, baseResidual, communicator);
const double fixedCenterDifference =
gravity_prepared_test_utils::relative_error(untranslatedCenterResidual, translatedResidual, communicator);
INFO("Base deformed hydrostatic residual norm = " << baseResidualNorm);
INFO("Translated hydrostatic residual norm = " << translatedResidualNorm);
INFO("Mapped-rotation translation invariance error = " << translationInvarianceError);
INFO("Relative change with untranslated rotation center = " << fixedCenterDifference);
REQUIRE(baseResidualNorm > 1.0e-12);
REQUIRE(translatedResidualNorm > 1.0e-12);
REQUIRE(fixedCenterDifference > 1.0e-5);
CHECK(translationInvarianceError < 5.0e-12);
}