feat(libmeanfield): variadic refactor
also added normaliztion operator
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
#include <algorithm>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <mfem.hpp>
|
||||
#include <numbers>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
import mean_field;
|
||||
@@ -30,12 +34,41 @@ namespace {
|
||||
return vector;
|
||||
}
|
||||
|
||||
void require_all_ranks(
|
||||
const bool localCondition,
|
||||
const MPI_Comm communicator,
|
||||
const char *description
|
||||
) {
|
||||
int rank = 0;
|
||||
int size = 0;
|
||||
MPI_Comm_rank(communicator, &rank);
|
||||
MPI_Comm_size(communicator, &size);
|
||||
|
||||
const int localFailure = localCondition ? size : rank;
|
||||
int firstFailure = size;
|
||||
const int result = MPI_Allreduce(
|
||||
&localFailure,
|
||||
&firstFailure,
|
||||
1,
|
||||
MPI_INT,
|
||||
MPI_MIN,
|
||||
communicator
|
||||
);
|
||||
REQUIRE(result == MPI_SUCCESS);
|
||||
CAPTURE(description, localCondition, firstFailure);
|
||||
REQUIRE(firstFailure == size);
|
||||
}
|
||||
|
||||
double global_dot(
|
||||
const mfem::Vector &left,
|
||||
const mfem::Vector &right,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
REQUIRE(left.Size() == right.Size());
|
||||
require_all_ranks(
|
||||
left.Size() == right.Size(),
|
||||
communicator,
|
||||
"global dot-product vector sizes"
|
||||
);
|
||||
const double local = left * right;
|
||||
double global = 0.0;
|
||||
REQUIRE(MPI_Allreduce(&local, &global, 1, MPI_DOUBLE, MPI_SUM, communicator) == MPI_SUCCESS);
|
||||
@@ -48,6 +81,39 @@ namespace {
|
||||
) {
|
||||
return std::sqrt(global_dot(vector, vector, communicator));
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies
|
||||
make_stellar_dependencies(const std::uint64_t revision = 1) {
|
||||
return {
|
||||
.discretization = {.identity = 16101, .revision = 1},
|
||||
.density = {.identity = 16103, .revision = revision},
|
||||
.surfaceDeformation = {.identity = 16111, .revision = revision},
|
||||
.gravityGradient = {.identity = 16127, .revision = revision},
|
||||
.gravityPotential = {.identity = 16139, .revision = revision},
|
||||
.enthalpy = {.identity = 16141, .revision = revision},
|
||||
.bernoulliConstant = {.identity = 16183, .revision = revision},
|
||||
.rotation = {.identity = 16187, .revision = revision},
|
||||
.targetMass = {.identity = 16189, .revision = 1}
|
||||
};
|
||||
}
|
||||
|
||||
void check_rank_consistent_scalar(
|
||||
const double value,
|
||||
const MPI_Comm communicator,
|
||||
const double relativeTolerance = 2.0e-13
|
||||
) {
|
||||
double minimum = 0.0;
|
||||
double maximum = 0.0;
|
||||
REQUIRE(MPI_Allreduce(&value, &minimum, 1, MPI_DOUBLE, MPI_MIN, communicator) == MPI_SUCCESS);
|
||||
REQUIRE(MPI_Allreduce(&value, &maximum, 1, MPI_DOUBLE, MPI_MAX, communicator) == MPI_SUCCESS);
|
||||
CAPTURE(value, minimum, maximum);
|
||||
CHECK(std::isfinite(minimum));
|
||||
CHECK(std::isfinite(maximum));
|
||||
CHECK(
|
||||
std::abs(maximum - minimum) <=
|
||||
relativeTolerance * std::max({1.0, std::abs(minimum), std::abs(maximum)})
|
||||
);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
@@ -97,6 +163,750 @@ TEST_CASE(
|
||||
CHECK(f.logicalReferenceMesh->GetNE() == f.mesh->GetNE());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"MPI Fixed Angular Momentum Produces One Consistent Global Invariant Row",
|
||||
"[mpi][distributed][fixed-angular-momentum][physics][jacobian]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
const auto args = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(args.mesh_file, args, 0);
|
||||
require_all_ranks(
|
||||
finiteElements.okay(),
|
||||
MPI_COMM_WORLD,
|
||||
"fixed-angular-momentum FEM setup"
|
||||
);
|
||||
const MPI_Comm communicator = finiteElements.mesh->GetComm();
|
||||
|
||||
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
|
||||
mfem::ConstantCoefficient densityCoefficient(1.23);
|
||||
densityField.ProjectCoefficient(densityCoefficient);
|
||||
mfem::Vector densityTrue;
|
||||
densityField.GetTrueDofs(densityTrue);
|
||||
|
||||
mfem::Vector displacementTrue(finiteElements.displacementFes->GetTrueVSize());
|
||||
displacementTrue = 0.0;
|
||||
finiteElements.displacement->SetFromTrueDofs(displacementTrue);
|
||||
mfem::Vector gravityGradientTrue(finiteElements.gravityFluxFes->GetTrueVSize());
|
||||
mfem::Vector gravityPotentialTrue(finiteElements.gravityPotentialFes->GetTrueVSize());
|
||||
gravityGradientTrue = 0.0;
|
||||
gravityPotentialTrue = 0.0;
|
||||
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless
|
||||
);
|
||||
gravityContext.Prepare(
|
||||
{.density = gravityContext.GetDensityMap().gather(densityTrue),
|
||||
.displacement = gravityContext.GetDisplacementMap().gather(displacementTrue),
|
||||
.gravity_gradient = gravityContext.GetGravityGradientMap().gather(gravityGradientTrue),
|
||||
.gravity_potential = gravityContext.GetGravityPotentialMap().gather(gravityPotentialTrue)},
|
||||
{.discretization = {.value = 2},
|
||||
.displacement = {.value = 3},
|
||||
.density = {.value = 5},
|
||||
.gravity_gradient = {.value = 7},
|
||||
.gravity_potential = {.value = 11}}
|
||||
);
|
||||
|
||||
constexpr double targetAngularMomentum = 0.37;
|
||||
constexpr double angularVelocity = 0.61;
|
||||
operators::PreparedAngularMomentumOperator invariant(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless,
|
||||
gravityContext,
|
||||
models::compileConstraint(
|
||||
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum}})
|
||||
)
|
||||
);
|
||||
const operators::AngularMomentumDependencies dependencies{
|
||||
.discretization = {.identity = 101, .revision = 2},
|
||||
.density = {.identity = 103, .revision = 5},
|
||||
.displacement = {.identity = 107, .revision = 3},
|
||||
.rotation = {.identity = 109, .revision = 13}
|
||||
};
|
||||
const auto preparation = invariant.Prepare(angularVelocity, dependencies);
|
||||
CHECK(preparation.rebuiltStaticPlan);
|
||||
CHECK(preparation.refreshedGeometry);
|
||||
CHECK(preparation.refreshedDensity);
|
||||
CHECK(preparation.updatedAngularVelocity);
|
||||
CHECK(preparation.assembledResidual);
|
||||
|
||||
const double independentMoment = analysis::get_moment_of_inertia(finiteElements, densityField);
|
||||
const double preparedMoment = invariant.GetMomentOfInertia();
|
||||
const double comparisonScale = std::max({std::abs(independentMoment), std::abs(preparedMoment), 1.0e-300});
|
||||
CHECK(std::abs(preparedMoment - independentMoment) / comparisonScale <= 3.0e-13);
|
||||
|
||||
double minimumMoment = 0.0;
|
||||
double maximumMoment = 0.0;
|
||||
MPI_Allreduce(&preparedMoment, &minimumMoment, 1, MPI_DOUBLE, MPI_MIN, finiteElements.mesh->GetComm());
|
||||
MPI_Allreduce(&preparedMoment, &maximumMoment, 1, MPI_DOUBLE, MPI_MAX, finiteElements.mesh->GetComm());
|
||||
CHECK(std::abs(maximumMoment - minimumMoment) / comparisonScale <= 2.0e-15);
|
||||
|
||||
mfem::Vector residual;
|
||||
invariant.BuildResidual(residual);
|
||||
require_all_ranks(
|
||||
residual.Size() == 1,
|
||||
communicator,
|
||||
"fixed-angular-momentum residual size"
|
||||
);
|
||||
CHECK(
|
||||
std::abs(residual(0) - (angularVelocity * independentMoment - targetAngularMomentum)) /
|
||||
std::max({std::abs(residual(0)), std::abs(angularVelocity * independentMoment), 1.0}) <=
|
||||
3.0e-13
|
||||
);
|
||||
|
||||
mfem::Vector densityAction;
|
||||
invariant.ApplyDensityJacobianAction(gravityContext.GetDensityMap().gather(densityTrue), densityAction);
|
||||
require_all_ranks(
|
||||
densityAction.Size() == 1,
|
||||
communicator,
|
||||
"fixed-angular-momentum density action size"
|
||||
);
|
||||
CHECK(std::abs(densityAction(0) - angularVelocity * preparedMoment) / comparisonScale <= 3.0e-13);
|
||||
|
||||
constexpr double angularVelocityVariation = -0.29;
|
||||
mfem::Vector angularVelocityAction;
|
||||
invariant.ApplyAngularVelocityJacobianAction(angularVelocityVariation, angularVelocityAction);
|
||||
require_all_ranks(
|
||||
angularVelocityAction.Size() == 1,
|
||||
communicator,
|
||||
"fixed-angular-momentum rotation action size"
|
||||
);
|
||||
CHECK(
|
||||
std::abs(angularVelocityAction(0) - angularVelocityVariation * preparedMoment) / comparisonScale <=
|
||||
2.0e-15
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"MPI Density Volume Context Forwards The Prepared Global Mass Integral",
|
||||
"[mpi][distributed][integral-context][physics-extension]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
const utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
require_all_ranks(
|
||||
finiteElements.okay(),
|
||||
MPI_COMM_WORLD,
|
||||
"density-volume context FEM setup"
|
||||
);
|
||||
const MPI_Comm communicator = finiteElements.mesh->GetComm();
|
||||
|
||||
constexpr double densityValue = 1.23;
|
||||
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
|
||||
mfem::ConstantCoefficient densityCoefficient(densityValue);
|
||||
densityField.ProjectCoefficient(densityCoefficient);
|
||||
mfem::Vector densityTrue;
|
||||
densityField.GetTrueDofs(densityTrue);
|
||||
|
||||
mfem::Vector displacementTrue(finiteElements.displacementFes->GetTrueVSize());
|
||||
displacementTrue = 0.0;
|
||||
finiteElements.displacement->SetFromTrueDofs(displacementTrue);
|
||||
mfem::Vector gravityGradientTrue(finiteElements.gravityFluxFes->GetTrueVSize());
|
||||
mfem::Vector gravityPotentialTrue(finiteElements.gravityPotentialFes->GetTrueVSize());
|
||||
gravityGradientTrue = 0.0;
|
||||
gravityPotentialTrue = 0.0;
|
||||
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless
|
||||
);
|
||||
const mfem::Vector reducedDensity = gravityContext.GetDensityMap().gather(
|
||||
densityTrue
|
||||
);
|
||||
gravityContext.Prepare(
|
||||
{.density = reducedDensity,
|
||||
.displacement = gravityContext.GetDisplacementMap().gather(displacementTrue),
|
||||
.gravity_gradient = gravityContext.GetGravityGradientMap().gather(gravityGradientTrue),
|
||||
.gravity_potential = gravityContext.GetGravityPotentialMap().gather(gravityPotentialTrue)},
|
||||
{.discretization = {.value = 2},
|
||||
.displacement = {.value = 3},
|
||||
.density = {.value = 5},
|
||||
.gravity_gradient = {.value = 7},
|
||||
.gravity_potential = {.value = 11}}
|
||||
);
|
||||
|
||||
operators::PreparedMassNormalizationOperator massIntegral(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless,
|
||||
gravityContext
|
||||
);
|
||||
massIntegral.Prepare(
|
||||
models::compileConstraint(
|
||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}})
|
||||
),
|
||||
{.discretization = {.identity = 101, .revision = 2},
|
||||
.density = {.identity = 103, .revision = 5},
|
||||
.displacement = {.identity = 107, .revision = 3},
|
||||
.targetMass = {.identity = 109, .revision = 1}}
|
||||
);
|
||||
|
||||
class DistributedMassIntegralCore final {
|
||||
public:
|
||||
explicit DistributedMassIntegralCore(
|
||||
const operators::PreparedMassNormalizationOperator &mass
|
||||
) noexcept
|
||||
: m_mass(&mass) {
|
||||
}
|
||||
|
||||
[[nodiscard]] double ApplyDensityVolumeIntegralDensityAction(
|
||||
const mfem::Vector &direction
|
||||
) const {
|
||||
mfem::Vector action;
|
||||
m_mass->ApplyDensityJacobianAction(direction, action);
|
||||
return action(0);
|
||||
}
|
||||
|
||||
[[nodiscard]] double ApplyDensityVolumeIntegralSurfaceShapeAction(
|
||||
const mfem::Vector &
|
||||
) const noexcept {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private:
|
||||
const operators::PreparedMassNormalizationOperator *m_mass;
|
||||
};
|
||||
|
||||
const DistributedMassIntegralCore testCore{massIntegral};
|
||||
|
||||
const stellar::DensityVolumeIntegralContext<integral::FixedTotalMass>
|
||||
densityIntegral{testCore};
|
||||
const double integratedMass =
|
||||
densityIntegral.integrateDensity(reducedDensity).value();
|
||||
const double linearizedDensityMass =
|
||||
densityIntegral.linearizeDensityIntegral(reducedDensity).value();
|
||||
const double preparedMass = massIntegral.GetCurrentMass();
|
||||
const double independentMass =
|
||||
densityValue * analysis::get_mesh_volume(finiteElements);
|
||||
|
||||
check_rank_consistent_scalar(integratedMass, communicator);
|
||||
check_rank_consistent_scalar(linearizedDensityMass, communicator);
|
||||
check_rank_consistent_scalar(preparedMass, communicator);
|
||||
check_rank_consistent_scalar(independentMass, communicator);
|
||||
const double massScale = std::max(
|
||||
{1.0, std::abs(integratedMass), std::abs(independentMass)}
|
||||
);
|
||||
CHECK(std::abs(integratedMass - independentMass) <= 3.0e-12 * massScale);
|
||||
CHECK(std::abs(integratedMass - preparedMass) <= 3.0e-13 * massScale);
|
||||
CHECK(std::abs(linearizedDensityMass - integratedMass) <=
|
||||
3.0e-13 * massScale);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"MPI Assembled Variadic Stellar Root Normalizes And Applies Its Inferred Preconditioner",
|
||||
"[mpi][distributed][stellar-equilibrium][normalization][preconditioning][integration]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
const utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
require_all_ranks(
|
||||
finiteElements.okay(),
|
||||
MPI_COMM_WORLD,
|
||||
"variadic stellar-root FEM setup"
|
||||
);
|
||||
const MPI_Comm communicator = finiteElements.mesh->GetComm();
|
||||
|
||||
constexpr double radius = utils::RADIUS;
|
||||
constexpr double mass = utils::MASS;
|
||||
const double polytropicConstant =
|
||||
2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
||||
const double centralDensity =
|
||||
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{0.05},
|
||||
.axis = {0.0, 0.0, 1.0}
|
||||
}),
|
||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||
);
|
||||
auto problem = equilibrium::discretize(
|
||||
model,
|
||||
equilibrium::makeStellarDiscretization(
|
||||
finiteElements,
|
||||
normalization::PhysicalRieszDiagonal{
|
||||
dimensions::LengthValue{radius},
|
||||
utils::G
|
||||
}
|
||||
)
|
||||
);
|
||||
auto projected = seed::makeProjectedEquilibriumState(
|
||||
problem,
|
||||
seed::LaneEmden({
|
||||
.centralDensity = dimensions::DensityValue{centralDensity},
|
||||
.radialSampleCount = 512
|
||||
})
|
||||
);
|
||||
auto normalized = normalization::makeNormalizedStellarEquilibriumOperator(problem);
|
||||
using Problem = std::remove_cvref_t<decltype(problem)>;
|
||||
using Form = typename Problem::FormType;
|
||||
constexpr auto massResidualBlock = utils::blocks::get_residual_block<Form>(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||
);
|
||||
constexpr auto angularMomentumResidualBlock = utils::blocks::get_residual_block<Form>(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
);
|
||||
constexpr auto centralDensityResidualBlock = utils::blocks::get_residual_block<Form>(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term
|
||||
);
|
||||
require_all_ranks(
|
||||
problem.StateSize() == problem.EquationSize(),
|
||||
communicator,
|
||||
"variadic stellar-root square layout"
|
||||
);
|
||||
|
||||
mfem::Vector normalizedState;
|
||||
normalized.NormalizeState(projected.values, normalizedState);
|
||||
const auto preparation = normalized.Prepare(
|
||||
normalizedState,
|
||||
make_stellar_dependencies(1)
|
||||
);
|
||||
require_all_ranks(
|
||||
preparation.DidAnyWork() &&
|
||||
preparation.generatedPhysicalControl &&
|
||||
preparation.template specification<models::FixedAngularMomentum>().generatedRotation &&
|
||||
problem.IsPrepared() &&
|
||||
normalized.IsPrepared(),
|
||||
communicator,
|
||||
"initial variadic stellar-root preparation"
|
||||
);
|
||||
|
||||
// The astronomy-facing integral handle must delegate to the same mapped
|
||||
// physical-volume quadrature and collective reduction as the prepared
|
||||
// mass equation, without exposing the FEM/core objects to extension
|
||||
// physics. Linearity in density gives an independent check of both
|
||||
// public operations on every rank.
|
||||
const auto physicalState = problem.GetManifest().stateView(projected.values);
|
||||
const auto physicalDensity = physicalState.block(
|
||||
utils::blocks::density_field.mass_term
|
||||
);
|
||||
const stellar::DensityVolumeIntegralContext<integral::FixedTotalMass>
|
||||
densityIntegral{problem.GetPhysicalOperator()};
|
||||
const double integratedMass = densityIntegral.integrateDensity(
|
||||
physicalDensity
|
||||
).value();
|
||||
const double linearizedMass = densityIntegral.linearizeDensityIntegral(
|
||||
physicalDensity
|
||||
).value();
|
||||
const double preparedMass = problem.GetPhysicalOperator()
|
||||
.GetFixedMassReport()
|
||||
.achieved;
|
||||
check_rank_consistent_scalar(integratedMass, communicator);
|
||||
check_rank_consistent_scalar(linearizedMass, communicator);
|
||||
const double massComparisonScale = std::max(
|
||||
{std::abs(integratedMass), std::abs(preparedMass), 1.0e-300}
|
||||
);
|
||||
CHECK(std::abs(integratedMass - preparedMass) / massComparisonScale <=
|
||||
3.0e-13);
|
||||
CHECK(std::abs(linearizedMass - preparedMass) / massComparisonScale <=
|
||||
3.0e-13);
|
||||
|
||||
mfem::Vector normalizedResidual;
|
||||
normalized.BuildResidual(normalizedResidual);
|
||||
require_all_ranks(
|
||||
normalizedResidual.Size() == problem.EquationSize(),
|
||||
communicator,
|
||||
"normalized variadic residual size"
|
||||
);
|
||||
|
||||
auto residualView = problem.GetManifest().residualView(normalizedResidual);
|
||||
const auto &layout = problem.GetManifest().layout();
|
||||
const auto &residualFactors = normalized.GetNormalization().ResidualFactors();
|
||||
const auto massReport = problem.GetPreparedOperator().GetFixedMassReport();
|
||||
const auto angularMomentumReport =
|
||||
problem.GetPreparedOperator().GetAngularMomentumReport();
|
||||
const auto centralDensityReport =
|
||||
problem.GetPreparedOperator().GetCentralDensityReport();
|
||||
const auto checkReportedScalarResidual = [&](const mfem::Vector &block,
|
||||
const double expected) {
|
||||
require_all_ranks(
|
||||
block.Size() == 1,
|
||||
communicator,
|
||||
"reported scalar residual block size"
|
||||
);
|
||||
const double actual = block(0);
|
||||
CAPTURE(actual, expected);
|
||||
CHECK(std::isfinite(expected));
|
||||
CHECK(
|
||||
std::abs(actual - expected) <=
|
||||
5.0e-13 * std::max({1.0, std::abs(actual), std::abs(expected)})
|
||||
);
|
||||
check_rank_consistent_scalar(actual, finiteElements.mesh->GetComm());
|
||||
};
|
||||
checkReportedScalarResidual(
|
||||
residualView.block(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||
),
|
||||
massReport.dimensionalResidual *
|
||||
residualFactors(layout.offset(massResidualBlock))
|
||||
);
|
||||
checkReportedScalarResidual(
|
||||
residualView.block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
),
|
||||
angularMomentumReport.dimensionalResidual *
|
||||
residualFactors(layout.offset(angularMomentumResidualBlock))
|
||||
);
|
||||
checkReportedScalarResidual(
|
||||
residualView.block(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term
|
||||
),
|
||||
centralDensityReport.enthalpyResidual *
|
||||
residualFactors(layout.offset(centralDensityResidualBlock))
|
||||
);
|
||||
|
||||
mfem::Vector normalizedDirection = make_deterministic_vector(problem.StateSize(), 0.37);
|
||||
const auto directionState = problem.GetManifest().stateView(normalizedDirection);
|
||||
mfem::Vector massDirection = directionState.block(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||
);
|
||||
mfem::Vector angularVelocityDirection = directionState.block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
);
|
||||
mfem::Vector phaseDirection = directionState.block(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term
|
||||
);
|
||||
require_all_ranks(
|
||||
massDirection.Size() == 1 &&
|
||||
angularVelocityDirection.Size() == 1 &&
|
||||
phaseDirection.Size() == 1,
|
||||
communicator,
|
||||
"generated scalar direction block sizes"
|
||||
);
|
||||
massDirection(0) = 0.17;
|
||||
angularVelocityDirection(0) = -0.23;
|
||||
phaseDirection(0) = 0.31;
|
||||
massDirection.SyncAliasMemory(normalizedDirection);
|
||||
angularVelocityDirection.SyncAliasMemory(normalizedDirection);
|
||||
phaseDirection.SyncAliasMemory(normalizedDirection);
|
||||
normalizedDirection /= global_norm(normalizedDirection, finiteElements.mesh->GetComm());
|
||||
|
||||
mfem::Vector normalizedAction;
|
||||
normalized.Mult(normalizedDirection, normalizedAction);
|
||||
require_all_ranks(
|
||||
normalizedAction.Size() == problem.EquationSize(),
|
||||
communicator,
|
||||
"normalized variadic Jacobian-action size"
|
||||
);
|
||||
|
||||
/* Different dependency revisions are intentional: the state changes in
|
||||
* each difference evaluation, so the distributed physical contexts must
|
||||
* be rebuilt even though all persistent identities remain the same. */
|
||||
constexpr double differenceStep = 1.0e-5;
|
||||
mfem::Vector plusState(normalizedState);
|
||||
plusState.Add(differenceStep, normalizedDirection);
|
||||
const auto plusPreparation = normalized.Prepare(
|
||||
plusState,
|
||||
make_stellar_dependencies(2)
|
||||
);
|
||||
require_all_ranks(
|
||||
plusPreparation.DidAnyWork(),
|
||||
communicator,
|
||||
"positive finite-difference preparation"
|
||||
);
|
||||
mfem::Vector plusResidual;
|
||||
normalized.BuildResidual(plusResidual);
|
||||
|
||||
mfem::Vector minusState(normalizedState);
|
||||
minusState.Add(-differenceStep, normalizedDirection);
|
||||
const auto minusPreparation = normalized.Prepare(
|
||||
minusState,
|
||||
make_stellar_dependencies(3)
|
||||
);
|
||||
require_all_ranks(
|
||||
minusPreparation.DidAnyWork(),
|
||||
communicator,
|
||||
"negative finite-difference preparation"
|
||||
);
|
||||
mfem::Vector minusResidual;
|
||||
normalized.BuildResidual(minusResidual);
|
||||
|
||||
mfem::Vector finiteDifference(plusResidual);
|
||||
finiteDifference -= minusResidual;
|
||||
finiteDifference /= 2.0 * differenceStep;
|
||||
const auto restoredPreparation = normalized.Prepare(
|
||||
normalizedState,
|
||||
make_stellar_dependencies(4)
|
||||
);
|
||||
require_all_ranks(
|
||||
restoredPreparation.DidAnyWork() && normalized.IsPrepared(),
|
||||
communicator,
|
||||
"restored finite-difference preparation"
|
||||
);
|
||||
mfem::Vector restoredResidual;
|
||||
normalized.BuildResidual(restoredResidual);
|
||||
mfem::Vector restoredResidualDifference(restoredResidual);
|
||||
restoredResidualDifference -= normalizedResidual;
|
||||
const double restoredResidualError = global_norm(
|
||||
restoredResidualDifference,
|
||||
finiteElements.mesh->GetComm()
|
||||
) / std::max({
|
||||
global_norm(restoredResidual, finiteElements.mesh->GetComm()),
|
||||
global_norm(normalizedResidual, finiteElements.mesh->GetComm()),
|
||||
std::numeric_limits<double>::epsilon()
|
||||
});
|
||||
CAPTURE(restoredResidualError);
|
||||
CHECK(restoredResidualError <= 2.0e-12);
|
||||
|
||||
mfem::Vector finiteDifferenceError(normalizedAction);
|
||||
finiteDifferenceError -= finiteDifference;
|
||||
const double actionNorm = global_norm(
|
||||
normalizedAction,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
const double finiteDifferenceNorm = global_norm(
|
||||
finiteDifference,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
const double completeDifferenceError = global_norm(
|
||||
finiteDifferenceError,
|
||||
finiteElements.mesh->GetComm()
|
||||
) / std::max({
|
||||
actionNorm,
|
||||
finiteDifferenceNorm,
|
||||
std::numeric_limits<double>::epsilon()
|
||||
});
|
||||
CAPTURE(actionNorm, finiteDifferenceNorm, completeDifferenceError);
|
||||
CHECK(actionNorm > std::numeric_limits<double>::min());
|
||||
CHECK(finiteDifferenceNorm > std::numeric_limits<double>::min());
|
||||
CHECK(completeDifferenceError <= 8.0e-5);
|
||||
|
||||
const int locallyFinite =
|
||||
vector_is_finite(normalizedResidual) &&
|
||||
vector_is_finite(normalizedAction) &&
|
||||
vector_is_finite(finiteDifference) ? 1 : 0;
|
||||
int globallyFinite = 0;
|
||||
REQUIRE(MPI_Allreduce(
|
||||
&locallyFinite,
|
||||
&globallyFinite,
|
||||
1,
|
||||
MPI_INT,
|
||||
MPI_MIN,
|
||||
finiteElements.mesh->GetComm()
|
||||
) == MPI_SUCCESS);
|
||||
CHECK(globallyFinite == 1);
|
||||
CHECK(global_norm(normalizedResidual, finiteElements.mesh->GetComm()) > 0.0);
|
||||
CHECK(global_norm(normalizedAction, finiteElements.mesh->GetComm()) > 0.0);
|
||||
|
||||
auto actionView = problem.GetManifest().residualView(normalizedAction);
|
||||
auto finiteDifferenceView = problem.GetManifest().residualView(finiteDifference);
|
||||
const auto checkGlobalRow = [&](const auto &term, const char *rowName) {
|
||||
const mfem::Vector residualBlock = residualView.block(term);
|
||||
const mfem::Vector actionBlock = actionView.block(term);
|
||||
const mfem::Vector differenceBlock = finiteDifferenceView.block(term);
|
||||
require_all_ranks(
|
||||
residualBlock.Size() == 1 &&
|
||||
actionBlock.Size() == 1 &&
|
||||
differenceBlock.Size() == 1,
|
||||
communicator,
|
||||
"global scalar residual/Jacobian block sizes"
|
||||
);
|
||||
check_rank_consistent_scalar(residualBlock(0), finiteElements.mesh->GetComm());
|
||||
check_rank_consistent_scalar(actionBlock(0), finiteElements.mesh->GetComm());
|
||||
check_rank_consistent_scalar(differenceBlock(0), finiteElements.mesh->GetComm());
|
||||
const double rowMagnitude = std::max(
|
||||
std::abs(actionBlock(0)),
|
||||
std::abs(differenceBlock(0))
|
||||
);
|
||||
const double rowDifferenceError =
|
||||
std::abs(actionBlock(0) - differenceBlock(0)) /
|
||||
std::max(rowMagnitude, std::numeric_limits<double>::epsilon());
|
||||
CAPTURE(rowName, actionBlock(0), differenceBlock(0), rowMagnitude,
|
||||
rowDifferenceError);
|
||||
CHECK(rowMagnitude > 1.0e-10);
|
||||
CHECK(rowDifferenceError <= 8.0e-5);
|
||||
};
|
||||
checkGlobalRow(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term,
|
||||
"fixed-total-mass"
|
||||
);
|
||||
checkGlobalRow(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term,
|
||||
"fixed-angular-momentum"
|
||||
);
|
||||
checkGlobalRow(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term,
|
||||
"fixed-central-density phase"
|
||||
);
|
||||
|
||||
auto component = preconditioning::makePreconditioner(problem);
|
||||
STATIC_CHECK(decltype(component)::borderValueArity == 3);
|
||||
STATIC_CHECK(decltype(component)::borderResidualArity == 3);
|
||||
auto physicalInverse = preconditioning::prepare(problem, component);
|
||||
require_all_ranks(
|
||||
physicalInverse.IsCurrent(),
|
||||
communicator,
|
||||
"prepared physical preconditioner currentness"
|
||||
);
|
||||
auto scaledInverse = normalized.MakeScaledPreconditioner(physicalInverse);
|
||||
require_all_ranks(
|
||||
scaledInverse.IsCurrent(),
|
||||
communicator,
|
||||
"prepared normalized preconditioner currentness"
|
||||
);
|
||||
|
||||
mfem::Vector normalizedRightHandSide =
|
||||
make_deterministic_vector(problem.EquationSize(), 0.73);
|
||||
auto rightHandSideView = problem.GetManifest().residualView(normalizedRightHandSide);
|
||||
mfem::Vector massRightHandSide = rightHandSideView.block(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||
);
|
||||
mfem::Vector angularMomentumRightHandSide = rightHandSideView.block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
);
|
||||
mfem::Vector phaseRightHandSide = rightHandSideView.block(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term
|
||||
);
|
||||
massRightHandSide(0) = 0.11;
|
||||
angularMomentumRightHandSide(0) = -0.19;
|
||||
phaseRightHandSide(0) = 0.29;
|
||||
massRightHandSide.SyncAliasMemory(normalizedRightHandSide);
|
||||
angularMomentumRightHandSide.SyncAliasMemory(normalizedRightHandSide);
|
||||
phaseRightHandSide.SyncAliasMemory(normalizedRightHandSide);
|
||||
normalizedRightHandSide /= global_norm(
|
||||
normalizedRightHandSide,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
|
||||
mfem::Vector firstCorrection(scaledInverse.Height());
|
||||
mfem::Vector repeatedCorrection(scaledInverse.Height());
|
||||
firstCorrection = 0.0;
|
||||
repeatedCorrection = 0.0;
|
||||
scaledInverse.Mult(normalizedRightHandSide, firstCorrection);
|
||||
scaledInverse.Mult(normalizedRightHandSide, repeatedCorrection);
|
||||
|
||||
mfem::Vector repeatDifference(repeatedCorrection);
|
||||
repeatDifference -= firstCorrection;
|
||||
const double correctionNorm = global_norm(firstCorrection, finiteElements.mesh->GetComm());
|
||||
const double repeatError = global_norm(repeatDifference, finiteElements.mesh->GetComm()) /
|
||||
std::max(correctionNorm, std::numeric_limits<double>::epsilon());
|
||||
CAPTURE(correctionNorm, repeatError);
|
||||
const int locallyFiniteCorrections =
|
||||
vector_is_finite(firstCorrection) && vector_is_finite(repeatedCorrection) ? 1 : 0;
|
||||
int globallyFiniteCorrections = 0;
|
||||
REQUIRE(MPI_Allreduce(
|
||||
&locallyFiniteCorrections,
|
||||
&globallyFiniteCorrections,
|
||||
1,
|
||||
MPI_INT,
|
||||
MPI_MIN,
|
||||
finiteElements.mesh->GetComm()
|
||||
) == MPI_SUCCESS);
|
||||
CHECK(globallyFiniteCorrections == 1);
|
||||
CHECK(std::isfinite(correctionNorm));
|
||||
CHECK(correctionNorm > 0.0);
|
||||
CHECK(repeatError <= 2.0e-12);
|
||||
require_all_ranks(
|
||||
physicalInverse.IsCurrent() && scaledInverse.IsCurrent(),
|
||||
communicator,
|
||||
"preconditioner currentness after repeated application"
|
||||
);
|
||||
|
||||
const mfem::Vector &readOnlyCorrection = firstCorrection;
|
||||
const auto correctionView = problem.GetManifest().stateView(readOnlyCorrection);
|
||||
const double massCorrection = correctionView.block(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||
)(0);
|
||||
const double angularVelocityCorrection = correctionView.block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
)(0);
|
||||
const double phaseCorrection = correctionView.block(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term
|
||||
)(0);
|
||||
CAPTURE(massCorrection, angularVelocityCorrection, phaseCorrection);
|
||||
check_rank_consistent_scalar(
|
||||
massCorrection,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
check_rank_consistent_scalar(
|
||||
angularVelocityCorrection,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
check_rank_consistent_scalar(
|
||||
phaseCorrection,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
|
||||
/* A Newton iteration reparses the same normalized state under fresh
|
||||
* dependency revisions. Every rank must observe the stale inverse, and
|
||||
* refresh must reconstruct the inferred border actions and Schur data. */
|
||||
const auto secondPreparation = normalized.Prepare(
|
||||
normalizedState,
|
||||
make_stellar_dependencies(5)
|
||||
);
|
||||
require_all_ranks(
|
||||
secondPreparation.DidAnyWork() && normalized.IsPrepared(),
|
||||
communicator,
|
||||
"second variadic stellar-root preparation"
|
||||
);
|
||||
mfem::Vector secondPreparedResidual;
|
||||
normalized.BuildResidual(secondPreparedResidual);
|
||||
mfem::Vector secondPreparedResidualDifference(secondPreparedResidual);
|
||||
secondPreparedResidualDifference -= normalizedResidual;
|
||||
const double secondPreparedResidualError = global_norm(
|
||||
secondPreparedResidualDifference,
|
||||
finiteElements.mesh->GetComm()
|
||||
) / std::max({
|
||||
global_norm(secondPreparedResidual, finiteElements.mesh->GetComm()),
|
||||
global_norm(normalizedResidual, finiteElements.mesh->GetComm()),
|
||||
std::numeric_limits<double>::epsilon()
|
||||
});
|
||||
CAPTURE(secondPreparedResidualError);
|
||||
CHECK(secondPreparedResidualError <= 2.0e-12);
|
||||
require_all_ranks(
|
||||
!physicalInverse.IsCurrent() && !scaledInverse.IsCurrent(),
|
||||
communicator,
|
||||
"preconditioners become stale together"
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
scaledInverse.Mult(normalizedRightHandSide, repeatedCorrection),
|
||||
std::logic_error
|
||||
);
|
||||
|
||||
const auto refresh = physicalInverse.Refresh();
|
||||
CHECK(refresh.specificationActionsRefreshed);
|
||||
CHECK(refresh.rebuiltSchurComplement);
|
||||
CHECK(refresh.DidAnyWork());
|
||||
require_all_ranks(
|
||||
physicalInverse.IsCurrent() && scaledInverse.IsCurrent(),
|
||||
communicator,
|
||||
"refreshed preconditioner currentness"
|
||||
);
|
||||
const auto noOpRefresh = physicalInverse.Refresh();
|
||||
CHECK_FALSE(noOpRefresh.DidAnyWork());
|
||||
|
||||
mfem::Vector refreshedCorrection(scaledInverse.Height());
|
||||
refreshedCorrection = 0.0;
|
||||
scaledInverse.Mult(normalizedRightHandSide, refreshedCorrection);
|
||||
mfem::Vector refreshDifference(refreshedCorrection);
|
||||
refreshDifference -= firstCorrection;
|
||||
const double refreshError = global_norm(
|
||||
refreshDifference,
|
||||
finiteElements.mesh->GetComm()
|
||||
) / std::max(
|
||||
correctionNorm,
|
||||
std::numeric_limits<double>::epsilon()
|
||||
);
|
||||
const int locallyFiniteRefresh = vector_is_finite(refreshedCorrection) ? 1 : 0;
|
||||
int globallyFiniteRefresh = 0;
|
||||
REQUIRE(MPI_Allreduce(
|
||||
&locallyFiniteRefresh,
|
||||
&globallyFiniteRefresh,
|
||||
1,
|
||||
MPI_INT,
|
||||
MPI_MIN,
|
||||
finiteElements.mesh->GetComm()
|
||||
) == MPI_SUCCESS);
|
||||
CAPTURE(refreshError);
|
||||
CHECK(globallyFiniteRefresh == 1);
|
||||
CHECK(refreshError <= 2.0e-10);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"MPI Prepared Gravity Operators Preserve Global Algebraic Identities",
|
||||
"[mpi][distributed][gravity][operators][unit]"
|
||||
|
||||
Reference in New Issue
Block a user