1054 lines
34 KiB
C++
1054 lines
34 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
|
|
) {
|
|
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
|
|
) {
|
|
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);
|
|
} |