feat(libmeanfield): variadic refactor
also added normaliztion operator
This commit is contained in:
531
tests/operators/prepared_angular_momentum.cpp
Normal file
531
tests/operators/prepared_angular_momentum.cpp
Normal file
@@ -0,0 +1,531 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace angular_momentum_test_utils {
|
||||
[[nodiscard]] mean_field::operators::AngularMomentumDependencies makeDependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 15013, .revision = 3},
|
||||
.density = {.identity = 15017, .revision = 5},
|
||||
.displacement = {.identity = 15031, .revision = 7},
|
||||
.rotation = {.identity = 15053, .revision = 11}
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions makeGravityRevisions(
|
||||
const mean_field::operators::AngularMomentumDependencies &dependencies,
|
||||
const std::uint64_t gravityGradientRevision = 13,
|
||||
const std::uint64_t gravityPotentialRevision = 17
|
||||
) {
|
||||
return {
|
||||
.discretization = {.value = dependencies.discretization.revision},
|
||||
.displacement = {.value = dependencies.displacement.revision},
|
||||
.density = {.value = dependencies.density.revision},
|
||||
.gravity_gradient = {.value = gravityGradientRevision},
|
||||
.gravity_potential = {.value = gravityPotentialRevision}
|
||||
};
|
||||
}
|
||||
|
||||
void prepareGravityContext(
|
||||
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &context,
|
||||
const mean_field::fem::FEM &finiteElements,
|
||||
const mfem::Vector &density,
|
||||
const mfem::Vector &displacement,
|
||||
const mean_field::operators::AngularMomentumDependencies &dependencies,
|
||||
const std::uint64_t gravityGradientRevision = 13,
|
||||
const std::uint64_t gravityPotentialRevision = 17
|
||||
) {
|
||||
mfem::Vector gravityGradient(finiteElements.gravityFluxFes->GetTrueVSize());
|
||||
mfem::Vector gravityPotential(finiteElements.gravityPotentialFes->GetTrueVSize());
|
||||
gravityGradient = 0.0;
|
||||
gravityPotential = 0.0;
|
||||
context.Prepare(
|
||||
{.density = context.GetDensityMap().gather(density),
|
||||
.displacement = context.GetDisplacementMap().gather(displacement),
|
||||
.gravity_gradient = context.GetGravityGradientMap().gather(gravityGradient),
|
||||
.gravity_potential = context.GetGravityPotentialMap().gather(gravityPotential)},
|
||||
makeGravityRevisions(dependencies, gravityGradientRevision, gravityPotentialRevision)
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector projectDensity(
|
||||
const mean_field::fem::FEM &finiteElements,
|
||||
const double phase
|
||||
) {
|
||||
mfem::ParGridFunction field(finiteElements.densityFes.get());
|
||||
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
|
||||
return 0.94 + 0.08 * std::sin(0.71 * position(0) + phase) +
|
||||
0.05 * std::cos(0.63 * position(1) - phase) + 0.03 * position(2) * position(2);
|
||||
});
|
||||
field.ProjectCoefficient(coefficient);
|
||||
mfem::Vector result;
|
||||
field.GetTrueDofs(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector projectDensityDirection(
|
||||
const mean_field::fem::FEM &finiteElements,
|
||||
const double phase
|
||||
) {
|
||||
mfem::ParGridFunction field(finiteElements.densityFes.get());
|
||||
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
|
||||
return 0.17 * std::sin(0.83 * position(0) + phase) -
|
||||
0.12 * std::cos(0.79 * position(1) - phase) + 0.06 * position(2);
|
||||
});
|
||||
field.ProjectCoefficient(coefficient);
|
||||
mfem::Vector result;
|
||||
field.GetTrueDofs(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector projectConstantDensity(
|
||||
const mean_field::fem::FEM &finiteElements,
|
||||
const double value,
|
||||
mfem::ParGridFunction *fieldOutput = nullptr
|
||||
) {
|
||||
mfem::ParGridFunction field(finiteElements.densityFes.get());
|
||||
mfem::ConstantCoefficient coefficient(value);
|
||||
field.ProjectCoefficient(coefficient);
|
||||
if (fieldOutput != nullptr) {
|
||||
*fieldOutput = field;
|
||||
}
|
||||
mfem::Vector result;
|
||||
field.GetTrueDofs(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector projectAffineDisplacement(
|
||||
const mean_field::fem::FEM &finiteElements,
|
||||
const double scale
|
||||
) {
|
||||
mfem::ParGridFunction field(finiteElements.displacementFes.get());
|
||||
mfem::VectorFunctionCoefficient coefficient(
|
||||
finiteElements.mesh->Dimension(),
|
||||
[scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(position.Size());
|
||||
for (int component = 0; component < position.Size(); ++component) {
|
||||
value(component) = scale * position(component);
|
||||
}
|
||||
}
|
||||
);
|
||||
field.ProjectCoefficient(coefficient);
|
||||
mfem::Vector result;
|
||||
field.GetTrueDofs(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector projectDisplacementDirection(
|
||||
const mean_field::fem::FEM &finiteElements,
|
||||
const double scale
|
||||
) {
|
||||
mfem::ParGridFunction field(finiteElements.displacementFes.get());
|
||||
mfem::VectorFunctionCoefficient coefficient(
|
||||
finiteElements.mesh->Dimension(),
|
||||
[scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value(0) = scale * (0.07 * position(0) + 0.018 * position(1) * position(2));
|
||||
value(1) = scale * (-0.05 * position(1) + 0.013 * position(0) * position(2));
|
||||
value(2) = scale * (0.04 * position(2) - 0.011 * position(0) * position(1));
|
||||
}
|
||||
);
|
||||
field.ProjectCoefficient(coefficient);
|
||||
mfem::Vector result;
|
||||
field.GetTrueDofs(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] double residual(const mean_field::operators::PreparedAngularMomentumOperator &operation) {
|
||||
mfem::Vector value;
|
||||
operation.BuildResidual(value);
|
||||
REQUIRE(value.Size() == 1);
|
||||
return value(0);
|
||||
}
|
||||
|
||||
[[nodiscard]] double relativeError(const double actual, const double expected) {
|
||||
return std::abs(actual - expected) /
|
||||
std::max({std::abs(actual), std::abs(expected), 100.0 * std::numeric_limits<double>::epsilon()});
|
||||
}
|
||||
} // namespace angular_momentum_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Angular Momentum Satisfies Moment Scaling And The Parallel Axis Theorem",
|
||||
"[fixed-angular-momentum][physics][analytic]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
using Catch::Approx;
|
||||
using Operator = operators::PreparedAngularMomentumOperator;
|
||||
|
||||
STATIC_CHECK_FALSE(std::is_copy_constructible_v<Operator>);
|
||||
STATIC_CHECK_FALSE(std::is_move_constructible_v<Operator>);
|
||||
|
||||
utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
REQUIRE(finiteElements.okay());
|
||||
|
||||
constexpr double densityValue = 1.37;
|
||||
constexpr double angularVelocity = 0.73;
|
||||
constexpr double targetAngularMomentum = 0.41;
|
||||
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
|
||||
const mfem::Vector density = angular_momentum_test_utils::projectConstantDensity(
|
||||
finiteElements,
|
||||
densityValue,
|
||||
&densityField
|
||||
);
|
||||
mfem::Vector displacement(finiteElements.displacementFes->GetTrueVSize());
|
||||
displacement = 0.0;
|
||||
finiteElements.displacement->SetFromTrueDofs(displacement);
|
||||
|
||||
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless
|
||||
);
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacement,
|
||||
dependencies
|
||||
);
|
||||
|
||||
const models::CompiledFixedAngularMomentum originConstraint = models::compileConstraint(
|
||||
integral::FixedAngularMomentum({
|
||||
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
|
||||
.axis = {0.0, 0.0, 4.0}
|
||||
})
|
||||
);
|
||||
Operator origin(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless,
|
||||
gravityContext,
|
||||
originConstraint
|
||||
);
|
||||
const auto initial = origin.Prepare(angularVelocity, dependencies);
|
||||
CHECK(initial.rebuiltStaticPlan);
|
||||
CHECK(initial.refreshedGeometry);
|
||||
CHECK(initial.refreshedDensity);
|
||||
CHECK(initial.updatedAngularVelocity);
|
||||
CHECK(initial.assembledResidual);
|
||||
|
||||
const double independentMoment = analysis::get_moment_of_inertia(finiteElements, densityField);
|
||||
CHECK(angular_momentum_test_utils::relativeError(origin.GetMomentOfInertia(), independentMoment) < 2.0e-13);
|
||||
CHECK(origin.GetCurrentAngularMomentum() ==
|
||||
Approx(angularVelocity * origin.GetMomentOfInertia()).epsilon(2.0e-15));
|
||||
CHECK(angular_momentum_test_utils::residual(origin) ==
|
||||
Approx(angularVelocity * origin.GetMomentOfInertia() - targetAngularMomentum).epsilon(2.0e-15));
|
||||
|
||||
const auto report = origin.GetConstraintReport();
|
||||
CHECK(report.targetAngularMomentum == targetAngularMomentum);
|
||||
CHECK(report.achievedAngularMomentum == origin.GetCurrentAngularMomentum());
|
||||
CHECK(report.momentOfInertia == origin.GetMomentOfInertia());
|
||||
CHECK(report.angularVelocity == angularVelocity);
|
||||
const physics::RigidRotation rotation = origin.GetRotation();
|
||||
CHECK(rotation.angular_velocity()(0) == 0.0);
|
||||
CHECK(rotation.angular_velocity()(1) == 0.0);
|
||||
CHECK(rotation.angular_velocity()(2) == angularVelocity);
|
||||
|
||||
constexpr double affineScale = 0.086;
|
||||
const mfem::Vector affineDisplacement =
|
||||
angular_momentum_test_utils::projectAffineDisplacement(finiteElements, affineScale);
|
||||
++dependencies.displacement.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
affineDisplacement,
|
||||
dependencies
|
||||
);
|
||||
const auto affine = origin.Prepare(angularVelocity, dependencies);
|
||||
CHECK(affine.refreshedGeometry);
|
||||
CHECK_FALSE(affine.refreshedDensity);
|
||||
const double expectedAffineRatio = std::pow(1.0 + affineScale, 5);
|
||||
const double measuredAffineRatio = origin.GetMomentOfInertia() / independentMoment;
|
||||
INFO("Expected homothetic I ratio = " << expectedAffineRatio);
|
||||
INFO("Measured homothetic I ratio = " << measuredAffineRatio);
|
||||
CHECK(angular_momentum_test_utils::relativeError(measuredAffineRatio, expectedAffineRatio) < 7.0e-7);
|
||||
|
||||
displacement = 0.0;
|
||||
++dependencies.displacement.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacement,
|
||||
dependencies
|
||||
);
|
||||
origin.Prepare(angularVelocity, dependencies);
|
||||
constexpr std::array<double, 3> shiftedCenter{0.27, -0.19, 0.31};
|
||||
Operator shifted(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless,
|
||||
gravityContext,
|
||||
models::compileConstraint(integral::FixedAngularMomentum({
|
||||
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
|
||||
.axis = {0.0, 0.0, 1.0},
|
||||
.center = shiftedCenter
|
||||
}))
|
||||
);
|
||||
shifted.Prepare(angularVelocity, dependencies);
|
||||
|
||||
const double mass = analysis::domain_integrate_grid_function(
|
||||
finiteElements,
|
||||
densityField,
|
||||
utils::DOMAINS::STELLAR,
|
||||
mapping::COORDINATE_SPACE::PHYSICAL
|
||||
);
|
||||
const mfem::Vector centerOfMass = analysis::get_com(finiteElements, densityField);
|
||||
const double expectedShiftedMoment = origin.GetMomentOfInertia() +
|
||||
mass * (shiftedCenter[0] * shiftedCenter[0] +
|
||||
shiftedCenter[1] * shiftedCenter[1]) -
|
||||
2.0 * mass * (shiftedCenter[0] * centerOfMass(0) +
|
||||
shiftedCenter[1] * centerOfMass(1));
|
||||
CHECK(angular_momentum_test_utils::relativeError(shifted.GetMomentOfInertia(), expectedShiftedMoment) < 3.0e-13);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Angular Momentum Jacobian Matches Density Geometry And Angular Velocity Differences",
|
||||
"[fixed-angular-momentum][jacobian][accuracy]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
REQUIRE(finiteElements.okay());
|
||||
|
||||
const mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.31);
|
||||
const mfem::Vector densityDirection =
|
||||
angular_momentum_test_utils::projectDensityDirection(finiteElements, 0.67);
|
||||
const mfem::Vector displacement =
|
||||
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.43);
|
||||
const mfem::Vector displacementDirection =
|
||||
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, -0.79);
|
||||
constexpr double angularVelocity = 0.63;
|
||||
constexpr double angularVelocityDirection = -0.37;
|
||||
|
||||
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless
|
||||
);
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacement,
|
||||
dependencies
|
||||
);
|
||||
operators::PreparedAngularMomentumOperator operation(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless,
|
||||
gravityContext,
|
||||
models::compileConstraint(
|
||||
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.81}})
|
||||
)
|
||||
);
|
||||
operation.Prepare(angularVelocity, dependencies);
|
||||
|
||||
const mfem::Vector reducedDensityDirection = gravityContext.GetDensityMap().gather(densityDirection);
|
||||
const mfem::Vector reducedDisplacementDirection =
|
||||
gravityContext.GetDisplacementMap().gather(displacementDirection);
|
||||
mfem::Vector densityAction;
|
||||
mfem::Vector geometryAction;
|
||||
mfem::Vector angularVelocityAction;
|
||||
mfem::Vector completeAction;
|
||||
operation.ApplyDensityJacobianAction(reducedDensityDirection, densityAction);
|
||||
operation.ApplyDisplacementJacobianAction(reducedDisplacementDirection, geometryAction);
|
||||
operation.ApplyAngularVelocityJacobianAction(angularVelocityDirection, angularVelocityAction);
|
||||
operation.ApplyCompleteJacobianAction(
|
||||
reducedDensityDirection,
|
||||
reducedDisplacementDirection,
|
||||
angularVelocityDirection,
|
||||
completeAction
|
||||
);
|
||||
CHECK(angular_momentum_test_utils::relativeError(
|
||||
completeAction(0),
|
||||
densityAction(0) + geometryAction(0) + angularVelocityAction(0)
|
||||
) < 3.0e-15);
|
||||
CHECK(angularVelocityAction(0) ==
|
||||
Catch::Approx(operation.GetMomentOfInertia() * angularVelocityDirection).epsilon(2.0e-15));
|
||||
|
||||
constexpr double angularStep = 1.0e-6;
|
||||
++dependencies.rotation.revision;
|
||||
operation.Prepare(angularVelocity + angularStep * angularVelocityDirection, dependencies);
|
||||
const double angularPlus = angular_momentum_test_utils::residual(operation);
|
||||
++dependencies.rotation.revision;
|
||||
operation.Prepare(angularVelocity - angularStep * angularVelocityDirection, dependencies);
|
||||
const double angularMinus = angular_momentum_test_utils::residual(operation);
|
||||
const double angularDifference = (angularPlus - angularMinus) / (2.0 * angularStep);
|
||||
CHECK(angular_momentum_test_utils::relativeError(angularVelocityAction(0), angularDifference) < 2.0e-10);
|
||||
|
||||
constexpr double densityStep = 1.0e-3;
|
||||
mfem::Vector densityPlus(density);
|
||||
densityPlus.Add(densityStep, densityDirection);
|
||||
++dependencies.density.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
densityPlus,
|
||||
displacement,
|
||||
dependencies
|
||||
);
|
||||
operation.Prepare(angularVelocity, dependencies);
|
||||
const double densityPlusResidual = angular_momentum_test_utils::residual(operation);
|
||||
mfem::Vector densityMinus(density);
|
||||
densityMinus.Add(-densityStep, densityDirection);
|
||||
++dependencies.density.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
densityMinus,
|
||||
displacement,
|
||||
dependencies
|
||||
);
|
||||
operation.Prepare(angularVelocity, dependencies);
|
||||
const double densityMinusResidual = angular_momentum_test_utils::residual(operation);
|
||||
const double densityDifference = (densityPlusResidual - densityMinusResidual) / (2.0 * densityStep);
|
||||
CHECK(angular_momentum_test_utils::relativeError(densityAction(0), densityDifference) < 4.0e-8);
|
||||
|
||||
constexpr double geometryStep = 1.0e-6;
|
||||
mfem::Vector displacementPlus(displacement);
|
||||
displacementPlus.Add(geometryStep, displacementDirection);
|
||||
++dependencies.density.revision;
|
||||
++dependencies.displacement.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacementPlus,
|
||||
dependencies
|
||||
);
|
||||
operation.Prepare(angularVelocity, dependencies);
|
||||
const double geometryPlusResidual = angular_momentum_test_utils::residual(operation);
|
||||
mfem::Vector displacementMinus(displacement);
|
||||
displacementMinus.Add(-geometryStep, displacementDirection);
|
||||
++dependencies.displacement.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacementMinus,
|
||||
dependencies
|
||||
);
|
||||
operation.Prepare(angularVelocity, dependencies);
|
||||
const double geometryMinusResidual = angular_momentum_test_utils::residual(operation);
|
||||
const double geometryDifference = (geometryPlusResidual - geometryMinusResidual) / (2.0 * geometryStep);
|
||||
INFO("Density angular-momentum derivative error = " <<
|
||||
angular_momentum_test_utils::relativeError(densityAction(0), densityDifference));
|
||||
INFO("Geometry angular-momentum derivative error = " <<
|
||||
angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference));
|
||||
CHECK(angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference) < 4.0e-7);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Angular Momentum Refreshes Only Changed Runtime Data",
|
||||
"[fixed-angular-momentum][prepared][lifecycle]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
REQUIRE(finiteElements.okay());
|
||||
mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.29);
|
||||
mfem::Vector displacement =
|
||||
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.41);
|
||||
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
||||
std::uint64_t gravityPotentialRevision = 17;
|
||||
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless
|
||||
);
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacement,
|
||||
dependencies,
|
||||
13,
|
||||
gravityPotentialRevision
|
||||
);
|
||||
operators::PreparedAngularMomentumOperator operation(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless,
|
||||
gravityContext,
|
||||
models::compileConstraint(
|
||||
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.71}})
|
||||
)
|
||||
);
|
||||
operation.Prepare(0.52, dependencies);
|
||||
const auto preparationCount = operation.GetPreparationCount();
|
||||
const double moment = operation.GetMomentOfInertia();
|
||||
|
||||
const auto repeated = operation.Prepare(0.52, dependencies);
|
||||
CHECK_FALSE(repeated.DidAnyWork());
|
||||
CHECK(operation.GetPreparationCount() == preparationCount);
|
||||
|
||||
++gravityPotentialRevision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacement,
|
||||
dependencies,
|
||||
13,
|
||||
gravityPotentialRevision
|
||||
);
|
||||
const auto unrelatedPotential = operation.Prepare(0.52, dependencies);
|
||||
CHECK_FALSE(unrelatedPotential.DidAnyWork());
|
||||
|
||||
const double residualBeforeRotation = angular_momentum_test_utils::residual(operation);
|
||||
++dependencies.rotation.revision;
|
||||
const auto rotationOnly = operation.Prepare(0.81, dependencies);
|
||||
CHECK(rotationOnly.updatedAngularVelocity);
|
||||
CHECK(rotationOnly.assembledResidual);
|
||||
CHECK_FALSE(rotationOnly.refreshedDensity);
|
||||
CHECK_FALSE(rotationOnly.refreshedGeometry);
|
||||
CHECK(operation.GetMomentOfInertia() == moment);
|
||||
CHECK(angular_momentum_test_utils::residual(operation) - residualBeforeRotation ==
|
||||
Catch::Approx((0.81 - 0.52) * moment).epsilon(3.0e-15));
|
||||
|
||||
density = angular_momentum_test_utils::projectDensity(finiteElements, 0.83);
|
||||
++dependencies.density.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacement,
|
||||
dependencies,
|
||||
13,
|
||||
gravityPotentialRevision
|
||||
);
|
||||
const auto densityOnly = operation.Prepare(0.81, dependencies);
|
||||
CHECK(densityOnly.refreshedDensity);
|
||||
CHECK_FALSE(densityOnly.refreshedGeometry);
|
||||
CHECK_FALSE(densityOnly.updatedAngularVelocity);
|
||||
|
||||
displacement = angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.87);
|
||||
++dependencies.displacement.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacement,
|
||||
dependencies,
|
||||
13,
|
||||
gravityPotentialRevision
|
||||
);
|
||||
const auto geometryOnly = operation.Prepare(0.81, dependencies);
|
||||
CHECK(geometryOnly.refreshedGeometry);
|
||||
CHECK_FALSE(geometryOnly.refreshedDensity);
|
||||
CHECK_FALSE(geometryOnly.updatedAngularVelocity);
|
||||
}
|
||||
@@ -44,20 +44,11 @@ namespace {
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Central Density Bordered Root Preserves The Physical Operator Prefix",
|
||||
"Central Density Contribution Composes Through The Variadic Root",
|
||||
tags::central_density_phase_integration
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
STATIC_CHECK_FALSE(
|
||||
std::same_as<
|
||||
operators::PreparedStellarEquilibriumOperator, operators::PreparedCentralDensityStellarEquilibriumOperator>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
operators::CentralDensityStellarEquilibriumSpecificationModel::compilationClass ==
|
||||
models::ModelCompilationClass::isolated_root
|
||||
);
|
||||
|
||||
utils::Args args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(f.okay());
|
||||
@@ -80,7 +71,8 @@ TEST_CASE(
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
typename std::remove_cvref_t<decltype(equilibriumProblem)>::PreparedOperatorType,
|
||||
operators::PreparedCentralDensityStellarEquilibriumOperator>
|
||||
operators::PreparedVariadicStellarEquilibriumOperator<
|
||||
typename std::remove_cvref_t<decltype(equilibriumProblem)>::ModelType>>
|
||||
);
|
||||
CHECK(
|
||||
equilibriumProblem.GetStellarModel().specification<constraint::FixedCentralDensity>().targetDensity() ==
|
||||
@@ -95,19 +87,21 @@ TEST_CASE(
|
||||
CHECK(borderedOperator.GetRootManifest().constraints().size() == 3);
|
||||
CHECK(borderedOperator.GetRootManifest().specificationDescriptors().size() == 4);
|
||||
|
||||
const auto constraints = borderedOperator.GetRootManifest().constraints();
|
||||
CHECK(constraints[2].stableId == "FixedCentralDensity");
|
||||
CHECK(constraints[2].role == models::SpecificationRole::phase_condition);
|
||||
CHECK(constraints[2].columnPolicy == operators::RootColumnPolicy::solver_border);
|
||||
CHECK(constraints[2].target == 1.0);
|
||||
REQUIRE(constraints[2].carrierTarget.has_value());
|
||||
CHECK(*constraints[2].carrierTarget == 1.0);
|
||||
CHECK(constraints[2].targetUnits == "density");
|
||||
CHECK(constraints[2].residualUnits == "specific_enthalpy");
|
||||
const auto ¢ralDescriptor =
|
||||
borderedOperator.GetRootManifest().specification<constraint::FixedCentralDensity>();
|
||||
CHECK(centralDescriptor.stableId == "FixedCentralDensity");
|
||||
CHECK(centralDescriptor.role == models::SpecificationRole::phase_condition);
|
||||
CHECK(centralDescriptor.columnPolicy == operators::RootColumnPolicy::solver_border);
|
||||
CHECK(centralDescriptor.target == 1.0);
|
||||
REQUIRE(centralDescriptor.carrierTarget.has_value());
|
||||
CHECK(*centralDescriptor.carrierTarget == 1.0);
|
||||
CHECK(centralDescriptor.targetUnits == "density");
|
||||
CHECK(centralDescriptor.residualUnits == "specific_enthalpy");
|
||||
|
||||
mfem::Vector physicalState(physicalOperator.Width());
|
||||
physicalState = 0.0;
|
||||
const auto physicalStateView = physicalOperator.GetRootStateView(physicalState);
|
||||
const auto physicalStateView =
|
||||
physicalOperator.GetRootManifest().stateView(physicalState);
|
||||
physicalStateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||
physicalStateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||
|
||||
@@ -118,10 +112,9 @@ TEST_CASE(
|
||||
const operators::StellarEquilibriumDependencies dependencies = make_dependencies();
|
||||
const physics::RigidRotation rotation = make_zero_rotation();
|
||||
physicalOperator.Prepare(physicalState, dependencies, rotation);
|
||||
const operators::PreparedCentralDensityStellarEquilibriumReport initialReport =
|
||||
equilibriumProblem.Prepare(borderedState, dependencies, rotation);
|
||||
const auto initialReport = equilibriumProblem.Prepare(borderedState, dependencies, rotation);
|
||||
CHECK(initialReport.physical.assembledResidual);
|
||||
CHECK(initialReport.phase.assembledResidual);
|
||||
CHECK(initialReport.specification<constraint::FixedCentralDensity>().constraint.assembledResidual);
|
||||
CHECK(initialReport.assembledResidual);
|
||||
|
||||
mfem::Vector physicalResidual;
|
||||
@@ -164,13 +157,13 @@ TEST_CASE(
|
||||
|
||||
const auto repeatedReport = borderedOperator.Prepare(borderedState, dependencies, rotation);
|
||||
CHECK_FALSE(repeatedReport.physical.DidAnyWork());
|
||||
CHECK_FALSE(repeatedReport.phase.DidAnyWork());
|
||||
CHECK_FALSE(repeatedReport.assembledResidual);
|
||||
CHECK_FALSE(repeatedReport.specification<constraint::FixedCentralDensity>().DidAnyWork());
|
||||
CHECK(repeatedReport.assembledResidual);
|
||||
|
||||
borderedState(borderedState.Size() - 1) = 0.375;
|
||||
const auto borderReport = borderedOperator.Prepare(borderedState, dependencies, rotation);
|
||||
CHECK_FALSE(borderReport.physical.DidAnyWork());
|
||||
CHECK(borderReport.phase.refreshedBorder);
|
||||
CHECK(borderReport.specification<constraint::FixedCentralDensity>().constraint.refreshedBorder);
|
||||
CHECK(borderReport.assembledResidual);
|
||||
|
||||
mfem::Vector borderOnlyDirection(borderedOperator.Width());
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
2409
tests/operators/stellar_equilibrium_compiler.cpp
Normal file
2409
tests/operators/stellar_equilibrium_compiler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <numbers>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
@@ -10,6 +11,83 @@
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace outer_manifest_report_test {
|
||||
template <mean_field::model::StellarModelType Model>
|
||||
class PreparedEarlierMultiplier;
|
||||
|
||||
class EarlierMultiplier final {
|
||||
public:
|
||||
struct Parameters final {
|
||||
mean_field::dimensions::SpecificEnergyValue target;
|
||||
};
|
||||
|
||||
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
|
||||
EarlierMultiplier,
|
||||
"AardvarkOuterManifestMultiplier",
|
||||
mean_field::models::DependsOn<mean_field::models::stellar::state::Density>,
|
||||
mean_field::models::Affects<mean_field::models::stellar::equation::HydrostaticBalance>,
|
||||
mean_field::models::GlobalScalarNormalization<
|
||||
mean_field::models::PhysicalScaleLaw::specific_energy,
|
||||
mean_field::models::PhysicalScaleLaw::specific_energy>,
|
||||
mean_field::models::GeneratedManifest<
|
||||
"aardvark_outer_manifest.value",
|
||||
"a",
|
||||
"aardvark_outer_manifest.residual",
|
||||
"R_a",
|
||||
"specific_energy",
|
||||
"specific_energy">>;
|
||||
using EquilibriumPhysics =
|
||||
mean_field::operators::SpecificationEquilibriumPhysics<
|
||||
PreparedEarlierMultiplier>;
|
||||
|
||||
explicit EarlierMultiplier(const Parameters parameters) noexcept
|
||||
: m_target(parameters.target) {
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::dimensions::SpecificEnergyValue target() const noexcept {
|
||||
return m_target;
|
||||
}
|
||||
|
||||
private:
|
||||
mean_field::dimensions::SpecificEnergyValue m_target;
|
||||
};
|
||||
|
||||
template <mean_field::model::StellarModelType Model>
|
||||
class PreparedEarlierMultiplier final {
|
||||
public:
|
||||
using Report = mean_field::operators::EmptySpecificationPreparationReport;
|
||||
|
||||
explicit PreparedEarlierMultiplier(const EarlierMultiplier &) noexcept {
|
||||
}
|
||||
|
||||
template <typename StateView>
|
||||
[[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename Equation, typename Row>
|
||||
[[nodiscard]] mean_field::stellar::StructuralZero AddResidual(
|
||||
Equation,
|
||||
Row &
|
||||
) const noexcept {
|
||||
return mean_field::stellar::structuralZero;
|
||||
}
|
||||
|
||||
template <typename Equation, typename State, typename Direction, typename Row>
|
||||
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||
mean_field::stellar::Derivative<Equation, State>,
|
||||
const Direction &,
|
||||
Row &
|
||||
) const noexcept {
|
||||
return mean_field::stellar::zeroDerivative;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
} // namespace outer_manifest_report_test
|
||||
|
||||
namespace {
|
||||
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||
mean_field::eos::Polytrope,
|
||||
@@ -22,9 +100,30 @@ namespace {
|
||||
mean_field::integral::FixedTotalMass,
|
||||
mean_field::constraint::FixedCentralDensity>>;
|
||||
|
||||
using AngularMomentumModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||
mean_field::eos::Polytrope,
|
||||
mean_field::surface::Isobaric,
|
||||
mean_field::integral::FixedTotalMass,
|
||||
mean_field::integral::FixedAngularMomentum>>;
|
||||
|
||||
using AngularMomentumCentralDensityModel =
|
||||
mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||
mean_field::eos::Polytrope,
|
||||
mean_field::surface::Isobaric,
|
||||
mean_field::integral::FixedTotalMass,
|
||||
mean_field::integral::FixedAngularMomentum,
|
||||
mean_field::constraint::FixedCentralDensity>>;
|
||||
|
||||
using IncompleteModel =
|
||||
mean_field::model::StellarModel<mean_field::models::SpecificationSet<mean_field::eos::Polytrope>>;
|
||||
|
||||
using EarlierMultiplierModel =
|
||||
mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||
mean_field::eos::Polytrope,
|
||||
mean_field::surface::Isobaric,
|
||||
outer_manifest_report_test::EarlierMultiplier,
|
||||
mean_field::integral::FixedTotalMass>>;
|
||||
|
||||
template <typename Candidate>
|
||||
concept HasLegacyNumericalModelAdapter = requires { typename Candidate::NumericalModelAdapter; };
|
||||
|
||||
@@ -58,6 +157,7 @@ namespace {
|
||||
difference -= right;
|
||||
return difference.Norml2() / std::max({1.0, left.Norml2(), right.Norml2()});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
@@ -68,26 +168,65 @@ TEST_CASE(
|
||||
|
||||
using BaseProblem = equilibrium::StellarEquilibriumProblem<BaseModel>;
|
||||
using CentralDensityProblem = equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
|
||||
using AngularMomentumProblem = equilibrium::StellarEquilibriumProblem<AngularMomentumModel>;
|
||||
using AngularMomentumCentralDensityProblem =
|
||||
equilibrium::StellarEquilibriumProblem<AngularMomentumCentralDensityModel>;
|
||||
|
||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<BaseModel>);
|
||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<CentralDensityModel>);
|
||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<AngularMomentumModel>);
|
||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<AngularMomentumCentralDensityModel>);
|
||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<EarlierMultiplierModel>);
|
||||
STATIC_CHECK_FALSE(equilibrium::StellarEquilibriumModel<IncompleteModel>);
|
||||
STATIC_CHECK_FALSE(operators::StellarEquilibriumRuntimeContribution<
|
||||
outer_manifest_report_test::EarlierMultiplier>::registered);
|
||||
STATIC_CHECK_FALSE(operators::stellarEquilibriumBackendRuntimeAuthorized<
|
||||
outer_manifest_report_test::EarlierMultiplier,
|
||||
EarlierMultiplierModel>);
|
||||
STATIC_CHECK(operators::StellarEquilibriumPhysicsAvailableFor<
|
||||
outer_manifest_report_test::EarlierMultiplier,
|
||||
EarlierMultiplierModel>);
|
||||
STATIC_CHECK_FALSE(std::same_as<BaseProblem, CentralDensityProblem>);
|
||||
STATIC_CHECK(BaseProblem::symbolicallySquare);
|
||||
STATIC_CHECK(CentralDensityProblem::symbolicallySquare);
|
||||
STATIC_CHECK_FALSE(BaseProblem::hasFixedCentralDensity);
|
||||
STATIC_CHECK(CentralDensityProblem::hasFixedCentralDensity);
|
||||
STATIC_CHECK(AngularMomentumProblem::hasFixedAngularMomentum);
|
||||
STATIC_CHECK_FALSE(AngularMomentumProblem::hasFixedCentralDensity);
|
||||
STATIC_CHECK(AngularMomentumCentralDensityProblem::hasFixedAngularMomentum);
|
||||
STATIC_CHECK(AngularMomentumCentralDensityProblem::hasFixedCentralDensity);
|
||||
STATIC_CHECK_FALSE(HasLegacyNumericalModelAdapter<BaseProblem>);
|
||||
STATIC_CHECK_FALSE(HasLegacyNumericalModelAdapter<CentralDensityProblem>);
|
||||
STATIC_CHECK(std::same_as<BaseProblem, equilibrium::StellarEquilibriumSystem<BaseModel>>);
|
||||
STATIC_CHECK(
|
||||
std::same_as<typename BaseProblem::PreparedOperatorType, operators::PreparedStellarEquilibriumOperator>
|
||||
std::same_as<
|
||||
typename BaseProblem::PreparedOperatorType,
|
||||
operators::PreparedVariadicStellarEquilibriumOperator<BaseModel>>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
typename CentralDensityProblem::PreparedOperatorType,
|
||||
operators::PreparedCentralDensityStellarEquilibriumOperator>
|
||||
operators::PreparedVariadicStellarEquilibriumOperator<CentralDensityModel>>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
typename AngularMomentumProblem::PreparedOperatorType,
|
||||
operators::PreparedVariadicStellarEquilibriumOperator<AngularMomentumModel>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(std::same_as<
|
||||
typename BaseProblem::PreparedOperatorType,
|
||||
typename CentralDensityProblem::PreparedOperatorType>);
|
||||
STATIC_CHECK_FALSE(std::same_as<
|
||||
typename AngularMomentumProblem::PreparedOperatorType,
|
||||
typename AngularMomentumCentralDensityProblem::PreparedOperatorType>);
|
||||
STATIC_CHECK(AngularMomentumProblem::FormType::value_block_count == 7);
|
||||
STATIC_CHECK(AngularMomentumCentralDensityProblem::FormType::value_block_count == 8);
|
||||
STATIC_CHECK(std::same_as<
|
||||
typename BaseProblem::FormType,
|
||||
utils::blocks::surface_deformed_stellar_equilibrium_form>);
|
||||
STATIC_CHECK(std::same_as<
|
||||
typename CentralDensityProblem::FormType,
|
||||
utils::blocks::central_density_bordered_stellar_equilibrium_form>);
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
typename BaseProblem::CompiledSurfaceConstraintType,
|
||||
@@ -97,6 +236,183 @@ TEST_CASE(
|
||||
STATIC_CHECK(material::CompiledThermodynamicEquations<typename BaseProblem::ThermodynamicEquationsType>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Fixed Angular Momentum Root Uses Its Generated Angular Velocity In Every Physical Row",
|
||||
"[fixed-angular-momentum][stellar-equilibrium][jacobian][integration]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
REQUIRE(finiteElements.okay());
|
||||
|
||||
constexpr double radius = utils::RADIUS;
|
||||
constexpr double mass = utils::MASS;
|
||||
constexpr double targetAngularMomentum = 0.1;
|
||||
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
||||
const double seedCentralDensity =
|
||||
std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
||||
auto model = model::StellarModel(
|
||||
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||
integral::FixedAngularMomentum({
|
||||
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
|
||||
.axis = {0.0, 0.0, 3.0}
|
||||
})
|
||||
);
|
||||
auto problem = equilibrium::discretize(model, finiteElements);
|
||||
auto projected = seed::makeProjectedEquilibriumState(
|
||||
problem,
|
||||
seed::LaneEmden({
|
||||
.centralDensity = dimensions::DensityValue{seedCentralDensity},
|
||||
.radialSampleCount = 1024
|
||||
})
|
||||
);
|
||||
auto dependencies = make_dependencies();
|
||||
|
||||
const auto preparation = problem.Prepare(projected.values, dependencies);
|
||||
CHECK(preparation.generatedPhysicalControl);
|
||||
CHECK(preparation.physical.DidAnyWork());
|
||||
CHECK(preparation.template specification<models::FixedAngularMomentum>().constraint.DidAnyWork());
|
||||
CHECK(preparation.template specification<models::FixedAngularMomentum>().generatedRotation);
|
||||
CHECK(problem.IsPrepared());
|
||||
const auto angularReport = problem.GetPreparedOperator().GetAngularMomentumReport();
|
||||
CHECK(angularReport.targetAngularMomentum == targetAngularMomentum);
|
||||
CHECK(angularReport.angularVelocity > 0.0);
|
||||
CHECK(angularReport.momentOfInertia > 0.0);
|
||||
CHECK(std::abs(angularReport.scaledResidual) < 7.0e-4);
|
||||
|
||||
mfem::Vector direction(problem.StateSize());
|
||||
direction = 0.0;
|
||||
mfem::Vector angularVelocityDirection = problem.GetManifest().stateView(direction).block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
);
|
||||
REQUIRE(angularVelocityDirection.Size() == 1);
|
||||
angularVelocityDirection(0) = -0.37;
|
||||
angularVelocityDirection.SyncAliasMemory(direction);
|
||||
|
||||
mfem::Vector analyticAction;
|
||||
problem.ApplyLinearization(direction, analyticAction);
|
||||
|
||||
constexpr double step = 1.0e-5;
|
||||
mfem::Vector plusState(projected.values);
|
||||
plusState.Add(step, direction);
|
||||
problem.Prepare(plusState, dependencies);
|
||||
mfem::Vector plusResidual;
|
||||
problem.BuildResidual(plusResidual);
|
||||
mfem::Vector minusState(projected.values);
|
||||
minusState.Add(-step, direction);
|
||||
problem.Prepare(minusState, dependencies);
|
||||
mfem::Vector minusResidual;
|
||||
problem.BuildResidual(minusResidual);
|
||||
plusResidual -= minusResidual;
|
||||
plusResidual /= 2.0 * step;
|
||||
|
||||
auto analyticView = problem.GetManifest().residualView(analyticAction);
|
||||
auto differenceView = problem.GetManifest().residualView(plusResidual);
|
||||
const auto blockError = [&](const auto &term) {
|
||||
const mfem::Vector analytic = analyticView.block(term);
|
||||
const mfem::Vector difference = differenceView.block(term);
|
||||
return relative_difference(analytic, difference);
|
||||
};
|
||||
|
||||
const double surfaceError = blockError(utils::blocks::surface_deformation_field.shape_equilibrium_term);
|
||||
const double enthalpyError = blockError(utils::blocks::enthalpy_field.specific_term);
|
||||
const double angularMomentumError =
|
||||
blockError(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term);
|
||||
INFO("Generated-Omega surface-row centered-difference error = " << surfaceError);
|
||||
INFO("Generated-Omega hydrostatic-row centered-difference error = " << enthalpyError);
|
||||
INFO("Generated-Omega invariant-row centered-difference error = " << angularMomentumError);
|
||||
CHECK(surfaceError < 3.0e-7);
|
||||
CHECK(enthalpyError < 3.0e-7);
|
||||
CHECK(angularMomentumError < 3.0e-10);
|
||||
CHECK(analyticView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term).Norml2() > 0.0);
|
||||
CHECK(analyticView.block(utils::blocks::enthalpy_field.specific_term).Norml2() > 0.0);
|
||||
CHECK(analyticView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term).Norml2() > 0.0);
|
||||
CHECK(analyticView.block(utils::blocks::gravity_field.gradient_term).Norml2() == 0.0);
|
||||
CHECK(analyticView.block(utils::blocks::gravity_field.poisson_term).Norml2() == 0.0);
|
||||
CHECK(analyticView.block(utils::blocks::density_field.mass_term).Norml2() == 0.0);
|
||||
CHECK(analyticView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term).Norml2() == 0.0);
|
||||
|
||||
auto zeroModel = model::StellarModel(
|
||||
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.0}})
|
||||
);
|
||||
auto zeroProblem = equilibrium::discretize(zeroModel, finiteElements);
|
||||
mfem::Vector zeroState(projected.values);
|
||||
zeroProblem.GetManifest().stateView(zeroState).block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
) = 0.0;
|
||||
zeroProblem.Prepare(zeroState, dependencies);
|
||||
mfem::Vector zeroAction;
|
||||
zeroProblem.ApplyLinearization(direction, zeroAction);
|
||||
auto zeroView = zeroProblem.GetManifest().residualView(zeroAction);
|
||||
CHECK(zeroView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term).Norml2() == 0.0);
|
||||
CHECK(zeroView.block(utils::blocks::enthalpy_field.specific_term).Norml2() == 0.0);
|
||||
CHECK(zeroView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term).Norml2() > 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Fixed Mass Reports Use The Inferred Outer Manifest Indices",
|
||||
"[stellar-equilibrium][manifest][runtime][ordering]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
using Form = operators::CompiledStellarEquilibriumForm<EarlierMultiplierModel>;
|
||||
using EarlierValue = utils::blocks::generated_value_block<
|
||||
models::MultiplierFor<outer_manifest_report_test::EarlierMultiplier>>;
|
||||
using MassValue = utils::blocks::fixed_total_mass::mass_normalization::value;
|
||||
|
||||
STATIC_CHECK(utils::blocks::type_index_v<
|
||||
EarlierValue,
|
||||
typename Form::value_blocks> == 5);
|
||||
STATIC_CHECK(utils::blocks::type_index_v<
|
||||
MassValue,
|
||||
typename Form::value_blocks> == 6);
|
||||
|
||||
utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
REQUIRE(finiteElements.okay());
|
||||
|
||||
auto model = model::StellarModel(
|
||||
eos::Polytrope({.n = 1.0, .K = 0.25}),
|
||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||
outer_manifest_report_test::EarlierMultiplier({
|
||||
.target = dimensions::SpecificEnergyValue{0.75}}),
|
||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.25}})
|
||||
);
|
||||
auto problem = equilibrium::discretize(model, finiteElements);
|
||||
|
||||
mfem::Vector state(problem.StateSize());
|
||||
state = 0.0;
|
||||
const auto stateView = problem.GetManifest().stateView(state);
|
||||
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term) = 0.25;
|
||||
|
||||
const auto preparation = problem.Prepare(
|
||||
state,
|
||||
make_dependencies(),
|
||||
make_zero_rotation()
|
||||
);
|
||||
REQUIRE(preparation.physical.DidAnyWork());
|
||||
|
||||
const auto report = problem.GetPreparedOperator().GetFixedMassReport();
|
||||
const auto &outerDescriptor =
|
||||
problem.GetManifest().template specification<models::FixedTotalMass>();
|
||||
CHECK(report.descriptor.stableId == outerDescriptor.stableId);
|
||||
CHECK(report.descriptor.valueBlock == outerDescriptor.valueBlock);
|
||||
CHECK(report.descriptor.residualBlock == outerDescriptor.residualBlock);
|
||||
CHECK(report.descriptor.valueBlock == 6);
|
||||
CHECK(report.descriptor.residualBlock == 6);
|
||||
CHECK(report.descriptor.target == 1.25);
|
||||
CHECK(report.dimensionalResidual == report.achieved - report.descriptor.target);
|
||||
CHECK(report.scaledResidual ==
|
||||
report.dimensionalResidual / report.descriptor.residualScale);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Discretized Stellar Equilibrium Problem Is Exactly Equivalent To The Legacy Construction Path",
|
||||
tags::stellar_equilibrium_problem_integration
|
||||
@@ -122,6 +438,7 @@ TEST_CASE(
|
||||
discretization
|
||||
);
|
||||
auto &modelDrivenOperator = equilibriumProblem.GetPreparedOperator();
|
||||
const auto &physicalOperator = equilibriumProblem.GetPhysicalOperator();
|
||||
|
||||
CHECK(equilibriumProblem.StateSize() == legacyOperator.Width());
|
||||
CHECK(equilibriumProblem.EquationSize() == legacyOperator.Height());
|
||||
@@ -129,16 +446,16 @@ TEST_CASE(
|
||||
CHECK(&equilibriumProblem.GetDiscretization().finiteElementModel() == &f);
|
||||
CHECK(&equilibriumProblem.GetDiscretization().domainMapper() == f.domainMapperStateless.get());
|
||||
CHECK(equilibriumProblem.GetDiscretization().isCurrent());
|
||||
CHECK(modelDrivenOperator.GetTargetMass() == 1.25);
|
||||
CHECK(modelDrivenOperator.GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure == 0.0);
|
||||
CHECK(physicalOperator.GetTargetMass() == 1.25);
|
||||
CHECK(physicalOperator.GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure == 0.0);
|
||||
CHECK(equilibriumProblem.GetCompiledSurfaceConstraint().targetPressure() == dimensions::PressureValue{0.0});
|
||||
CHECK(modelDrivenOperator.GetDomainDeformation().matchesCurrentDiscretization());
|
||||
CHECK(physicalOperator.GetDomainDeformation().matchesCurrentDiscretization());
|
||||
CHECK(&equilibriumProblem.GetLinearizationOperator() == &modelDrivenOperator);
|
||||
CHECK(equilibriumProblem.GetManifest().constraints()[0].target == 1.25);
|
||||
CHECK(equilibriumProblem.GetManifest().template specification<models::FixedTotalMass>().target == 1.25);
|
||||
|
||||
mfem::Vector state(legacyOperator.Width());
|
||||
state = 0.0;
|
||||
const auto stateView = legacyOperator.GetRootStateView(state);
|
||||
const auto stateView = legacyOperator.GetRootManifest().stateView(state);
|
||||
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||
|
||||
@@ -163,3 +480,65 @@ TEST_CASE(
|
||||
equilibriumProblem.ApplyLinearization(direction, modelDrivenAction);
|
||||
CHECK(relative_difference(modelDrivenAction, legacyAction) < 2.0e-15);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Fixed Angular Momentum Composes With The Optional Central Density Phase At Runtime",
|
||||
"[fixed-angular-momentum][central-density][stellar-equilibrium][integration]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
REQUIRE(finiteElements.okay());
|
||||
auto model = model::StellarModel(
|
||||
eos::Polytrope({.n = 1.0, .K = 0.25}),
|
||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
||||
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.2}}),
|
||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
||||
);
|
||||
auto problem = equilibrium::discretize(model, finiteElements);
|
||||
using Problem = std::remove_cvref_t<decltype(problem)>;
|
||||
STATIC_CHECK(Problem::FormType::value_block_count == 8);
|
||||
STATIC_CHECK(Problem::FormType::residual_block_count == 8);
|
||||
|
||||
mfem::Vector state(problem.StateSize());
|
||||
state = 0.0;
|
||||
const auto stateView = problem.GetManifest().stateView(state);
|
||||
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term) = 0.25;
|
||||
stateView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term) = 0.4;
|
||||
stateView.block(utils::blocks::fixed_central_density_phase.central_value_term) = 0.03;
|
||||
|
||||
const auto report = problem.Prepare(state, make_dependencies());
|
||||
CHECK(report.template specification<models::FixedCentralDensity>().constraint.DidAnyWork());
|
||||
CHECK(report.template specification<models::FixedAngularMomentum>().constraint.DidAnyWork());
|
||||
CHECK(problem.IsPrepared());
|
||||
CHECK(problem.StateSize() == problem.GetPhysicalOperator().Width() + 2);
|
||||
REQUIRE(problem.GetManifest().constraints().size() == 4);
|
||||
CHECK(problem.GetManifest().template specification<models::FixedAngularMomentum>().stableId ==
|
||||
"FixedAngularMomentum");
|
||||
CHECK(problem.GetManifest().template specification<models::FixedCentralDensity>().stableId ==
|
||||
"FixedCentralDensity");
|
||||
|
||||
mfem::Vector residual;
|
||||
problem.BuildResidual(residual);
|
||||
REQUIRE(residual.Size() == problem.EquationSize());
|
||||
const auto residualView = problem.GetManifest().residualView(residual);
|
||||
CHECK(std::isfinite(
|
||||
residualView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term)(0)
|
||||
));
|
||||
CHECK(std::isfinite(residualView.block(utils::blocks::fixed_central_density_phase.central_value_term)(0)));
|
||||
|
||||
mfem::Vector direction(problem.StateSize());
|
||||
for (int index = 0; index < direction.Size(); ++index) {
|
||||
direction(index) = 0.01 * std::sin(0.17 * static_cast<double>(index + 1));
|
||||
}
|
||||
mfem::Vector action;
|
||||
problem.ApplyLinearization(direction, action);
|
||||
REQUIRE(action.Size() == problem.EquationSize());
|
||||
for (int index = 0; index < action.Size(); ++index) {
|
||||
CHECK(std::isfinite(action(index)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user