856 lines
32 KiB
C++
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 = 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) {
|
|
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::DomainMapper &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::DomainMapper &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);
|
|
} |