Files
MeanField/tests/preconditioning/material_surface.cpp
2026-09-04 07:54:10 -04:00

615 lines
31 KiB
C++

#include <algorithm>
#include <array>
#include <cmath>
#include <concepts>
#include <numbers>
#include <stdexcept>
#include <type_traits>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
namespace {
namespace backend = mean_field::preconditioning::backend;
namespace blocks = mean_field::utils::blocks;
namespace preconditioning = mean_field::preconditioning;
using PolytropicModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::integral::FixedTotalMass,
mean_field::constraint::FixedCentralDensity>>;
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
using MaterialSurfaceDiagonal = preconditioning::MaterialSurfaceBlock<
PolytropicMaterialSurfaceDescriptor,
backend::Diagonal,
backend::Diagonal,
preconditioning::SurfaceThenMaterialTriangular>;
using FixedCycleAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
using MaterialSurfaceH1 = preconditioning::MaterialSurfaceBlock<
PolytropicMaterialSurfaceDescriptor,
backend::Diagonal,
FixedCycleAMG,
preconditioning::ApproximateMaterialSurfaceLDU,
preconditioning::SurfaceH1MassStiffness>;
using PreparedMaterialSurfaceDiagonal = preconditioning::PreparedMaterialSurfaceBlock<
PolytropicMaterialSurfaceDescriptor,
preconditioning::SurfaceThenMaterialTriangular>;
using PreparedMaterialSurfaceH1 = preconditioning::PreparedH1MaterialSurfaceBlock<
PolytropicMaterialSurfaceDescriptor,
preconditioning::ApproximateMaterialSurfaceLDU,
backend::FixedCycles>;
class KnownCouplings final {
public:
KnownCouplings() : m_offsets(4) {
m_offsets[0] = 0;
m_offsets[1] = 1;
m_offsets[2] = 2;
m_offsets[3] = 3;
}
[[nodiscard]] int Height() const noexcept {
return 3;
}
[[nodiscard]] const mfem::Array<int> &GetOffsets() const noexcept {
return m_offsets;
}
void ApplyEnthalpyToDensity(
const mfem::Vector &enthalpy,
mfem::Vector &density
) const {
density(0) = 4.0 * enthalpy(0);
}
void ApplySurfaceToMaterial(
const mfem::Vector &surface,
mfem::Vector &density,
mfem::Vector &enthalpy
) const {
density(0) = 3.0 * surface(0);
enthalpy(0) = 8.0 * surface(0);
}
void ApplyMaterialToSurface(
const mfem::Vector &density,
const mfem::Vector &enthalpy,
mfem::Vector &surface
) const {
surface(0) = 5.0 * density(0) + 7.0 * enthalpy(0);
}
private:
mfem::Array<int> m_offsets;
};
template <typename Policy>
[[nodiscard]] mfem::Vector applyKnownFactorization(
Policy policy,
const mfem::Vector &rightHandSide,
const double surfaceEntry = 6.0
) {
mfem::Vector densityDiagonal(1);
mfem::Vector surfaceDiagonal(1);
mfem::Vector enthalpyDiagonal(1);
densityDiagonal(0) = 2.0;
surfaceDiagonal(0) = surfaceEntry;
enthalpyDiagonal(0) = 9.0;
const auto densityInverse = backend::prepare(backend::Diagonal{}, densityDiagonal);
const auto surfaceInverse = backend::prepare(backend::Diagonal{}, surfaceDiagonal);
const auto enthalpyInverse = backend::prepare(backend::Diagonal{}, enthalpyDiagonal);
const KnownCouplings couplings;
preconditioning::MaterialSurfaceFactorizationOperator<Policy, KnownCouplings> factorization(
policy, densityInverse, surfaceInverse, enthalpyInverse, couplings
);
mfem::Vector action(3);
factorization.Mult(rightHandSide, action);
return action;
}
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies makeDependencies(std::uint64_t revision = 1) {
return {
.discretization = {.identity = 8101, .revision = 1},
.density = {.identity = 8103, .revision = revision},
.surfaceDeformation = {.identity = 8107, .revision = revision},
.gravityGradient = {.identity = 8111, .revision = revision},
.gravityPotential = {.identity = 8117, .revision = revision},
.enthalpy = {.identity = 8123, .revision = revision},
.bernoulliConstant = {.identity = 8129, .revision = revision},
.rotation = {.identity = 8131, .revision = revision},
.targetMass = {.identity = 8137, .revision = 1}
};
}
[[nodiscard]] mean_field::physics::RigidRotation zeroRotation() {
mfem::Vector angularVelocity(3);
mfem::Vector center(3);
angularVelocity = 0.0;
center = 0.0;
return {angularVelocity, center};
}
[[nodiscard]] double relativeError(
const mfem::Vector &left,
const mfem::Vector &right
) {
mfem::Vector difference(left);
difference -= right;
return difference.Norml2() / std::max({1.0, left.Norml2(), right.Norml2()});
}
} // namespace
TEST_CASE(
"Compiled Material Surface Blocks Derive Their Physical Ownership And Backend Requirements",
"[preconditioning][material_surface][unit][type_contract]"
) {
using Form = blocks::surface_deformed_stellar_equilibrium_form;
using JacobianForm = blocks::surface_deformed_stellar_equilibrium_jacobian_form;
using GravityIdentity =
preconditioning::IdentityBlock<blocks::gravity::gradient::value, blocks::gravity::gradient::residual>;
using PotentialIdentity =
preconditioning::IdentityBlock<blocks::gravity::poisson::value, blocks::gravity::poisson::residual>;
using MassIdentity = preconditioning::IdentityBlock<
blocks::fixed_total_mass::mass_normalization::value, blocks::fixed_total_mass::mass_normalization::residual>;
using Plan =
preconditioning::PreconditionerPlan<MaterialSurfaceDiagonal, GravityIdentity, PotentialIdentity, MassIdentity>;
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceDiagonal>);
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceH1>);
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<PolytropicMaterialSurfaceDescriptor>);
STATIC_CHECK(
mean_field::material::CompiledThermodynamicEquations<typename PolytropicProblem::ThermodynamicEquationsType>
);
STATIC_CHECK(
std::same_as<
typename PolytropicMaterialSurfaceDescriptor::SurfaceStateFields,
mean_field::field::TypeList<mean_field::field::Enthalpy>>
);
STATIC_CHECK(MaterialSurfaceDiagonal::CorrectionBlocks::size == 3);
STATIC_CHECK(MaterialSurfaceDiagonal::ResidualBlocks::size == 3);
STATIC_CHECK(MaterialSurfaceDiagonal::RequiredCouplings::size == 8);
STATIC_CHECK(preconditioning::CompletePreconditionerFor<Plan, Form>);
STATIC_CHECK(preconditioning::CompatiblePreconditionerFor<Plan, Form, JacobianForm>);
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename MaterialSurfaceDiagonal::BackendType>);
STATIC_CHECK_FALSE(std::is_copy_constructible_v<PreparedMaterialSurfaceDiagonal>);
STATIC_CHECK_FALSE(std::is_copy_assignable_v<PreparedMaterialSurfaceDiagonal>);
STATIC_CHECK_FALSE(std::is_move_constructible_v<PreparedMaterialSurfaceDiagonal>);
STATIC_CHECK_FALSE(std::is_move_assignable_v<PreparedMaterialSurfaceDiagonal>);
STATIC_CHECK_FALSE(std::is_copy_constructible_v<PreparedMaterialSurfaceH1>);
STATIC_CHECK_FALSE(std::is_copy_assignable_v<PreparedMaterialSurfaceH1>);
STATIC_CHECK_FALSE(std::is_move_constructible_v<PreparedMaterialSurfaceH1>);
STATIC_CHECK_FALSE(std::is_move_assignable_v<PreparedMaterialSurfaceH1>);
STATIC_CHECK(
preconditioning::backend::Compatible<backend::Diagonal, preconditioning::SurfaceDiagonalCharacteristics>
);
STATIC_CHECK_FALSE(
preconditioning::backend::Compatible<backend::DenseDirect, preconditioning::SurfaceDiagonalCharacteristics>
);
STATIC_CHECK(
preconditioning::backend::Compatible<FixedCycleAMG, preconditioning::SurfaceH1MassStiffnessCharacteristics>
);
STATIC_CHECK_FALSE(std::same_as<MaterialSurfaceDiagonal, MaterialSurfaceH1>);
STATIC_CHECK(std::same_as<typename MaterialSurfaceH1::SurfaceSurrogate, preconditioning::SurfaceH1MassStiffness>);
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename MaterialSurfaceH1::BackendType>);
}
TEST_CASE(
"Surface H1 Calibration Recovers Signed Nonnegative Mass And Stiffness Fits",
"[preconditioning][material_surface][surface_h1][unit]"
) {
const preconditioning::SurfaceH1MassStiffness configuration{
.calibration =
{.target = preconditioning::SurfaceRieszCalibrationTarget::approximate_material_schur, .probeCount = 4},
.relativeMassCoefficientFloor = 1.0e-12,
.gramRelativeTolerance = 1.0e-12
};
const preconditioning::SurfaceH1NormalEquations exactPositive{
.massMass = 2.0,
.massStiffness = 2.0,
.stiffnessStiffness = 5.0,
.massTarget = 10.0,
.stiffnessTarget = 19.0,
.targetTarget = 77.0
};
const auto positive = preconditioning::detail::fitSurfaceH1Coefficients(exactPositive, configuration);
CHECK(positive.WasCalibrated());
CHECK(positive.sign == 1.0);
CHECK(positive.massCoefficient == Catch::Approx(2.0).margin(2.0e-13));
CHECK(positive.stiffnessCoefficient == Catch::Approx(3.0).margin(2.0e-13));
CHECK(positive.relativeResidual == Catch::Approx(0.0).margin(2.0e-13));
CHECK(positive.relativeGramDeterminant > configuration.gramRelativeTolerance);
CHECK(positive.normalEquations.targetTarget == Catch::Approx(77.0));
auto exactNegative = exactPositive;
exactNegative.massTarget = -exactNegative.massTarget;
exactNegative.stiffnessTarget = -exactNegative.stiffnessTarget;
const auto negative = preconditioning::detail::fitSurfaceH1Coefficients(exactNegative, configuration);
CHECK(negative.sign == -1.0);
CHECK(negative.massCoefficient == Catch::Approx(2.0).margin(2.0e-13));
CHECK(negative.stiffnessCoefficient == Catch::Approx(3.0).margin(2.0e-13));
CHECK(negative.relativeResidual == Catch::Approx(0.0).margin(2.0e-13));
const preconditioning::SurfaceH1NormalEquations massDominated{
.massMass = 1.0,
.massStiffness = 0.0,
.stiffnessStiffness = 1.0,
.massTarget = 4.0,
.stiffnessTarget = -2.0,
.targetTarget = 20.0
};
const auto constrained = preconditioning::detail::fitSurfaceH1Coefficients(massDominated, configuration);
CHECK(constrained.sign == 1.0);
CHECK(constrained.massCoefficient == Catch::Approx(4.0).margin(2.0e-13));
CHECK(constrained.stiffnessCoefficient == Catch::Approx(0.0).margin(2.0e-13));
CHECK(constrained.relativeResidual == Catch::Approx(std::sqrt(0.2)).margin(2.0e-13));
auto rankDeficient = exactPositive;
rankDeficient.massMass = 1.0;
rankDeficient.massStiffness = 2.0;
rankDeficient.stiffnessStiffness = 4.0;
CHECK_THROWS_AS(
preconditioning::detail::fitSurfaceH1Coefficients(rankDeficient, configuration), std::runtime_error
);
}
TEST_CASE(
"Surface Riesz Scalar Calibration Distinguishes Operator And Right-Preconditioned Objectives",
"[preconditioning][material_surface][surface_riesz][unit]"
) {
using Objective = preconditioning::SurfaceRieszCalibrationObjective;
const auto operatorFit = preconditioning::detail::fitSurfaceRieszScalar(6.0, 2.0, Objective::operator_action);
CHECK(operatorFit.surrogateScale == Catch::Approx(3.0));
CHECK(operatorFit.inverseMultiplier == Catch::Approx(1.0 / 3.0));
const auto inverseFit =
preconditioning::detail::fitSurfaceRieszScalar(6.0, 2.0, Objective::right_preconditioned_action);
CHECK(inverseFit.surrogateScale == Catch::Approx(1.0 / 3.0));
CHECK(inverseFit.inverseMultiplier == Catch::Approx(3.0));
CHECK_THROWS_AS(
preconditioning::detail::fitSurfaceRieszScalar(1.0, 0.0, Objective::operator_action), std::invalid_argument
);
CHECK_THROWS_AS(
preconditioning::detail::fitSurfaceRieszScalar(0.0, 1.0, Objective::right_preconditioned_action),
std::runtime_error
);
}
TEST_CASE(
"Signed Surface Solver Adapts Boundary Coordinates Without Exposing An Indefinite Backend",
"[preconditioning][material_surface][surface_h1][unit]"
) {
mfem::DenseMatrix ambientMatrix(3);
ambientMatrix = 0.0;
ambientMatrix(0, 0) = 2.0;
ambientMatrix(1, 1) = 7.0;
ambientMatrix(2, 2) = 4.0;
const auto ambientInverse = backend::prepare(backend::DenseDirect{}, ambientMatrix);
mfem::Array<int> boundaryTrueDofs(2);
boundaryTrueDofs[0] = 0;
boundaryTrueDofs[1] = 2;
mean_field::field::ScalarBoundaryDofMap surfaceMap(3, boundaryTrueDofs, 0, 2);
preconditioning::SignedScalarBoundarySolverAdapter surfaceInverse(ambientInverse, surfaceMap, -1.0);
mfem::Vector rightHandSide(2);
mfem::Vector action(2);
rightHandSide(0) = 2.0;
rightHandSide(1) = 4.0;
surfaceInverse.Mult(rightHandSide, action);
CHECK(action(0) == Catch::Approx(-1.0));
CHECK(action(1) == Catch::Approx(-1.0));
CHECK(surfaceInverse.GetSign() == -1.0);
CHECK_THROWS_AS(surfaceInverse.SetSign(0.0), std::invalid_argument);
}
TEST_CASE(
"Material Surface Factorization Policies Preserve Their Signed Triangular Algebra",
"[preconditioning][material_surface][unit][factorization]"
) {
mfem::Vector rightHandSide(3);
rightHandSide(0) = 29.0;
rightHandSide(1) = 44.0;
rightHandSide(2) = 43.0;
const auto check = [](const mfem::Vector &value, std::array<double, 3> expected) {
for (int index = 0; index < value.Size(); ++index) {
CHECK(value(index) == Catch::Approx(expected[static_cast<std::size_t>(index)]).margin(2.0e-13));
}
};
check(
applyKnownFactorization(preconditioning::MaterialSurfaceBlockDiagonal{}, rightHandSide),
{14.5, 44.0 / 6.0, 43.0 / 9.0}
);
check(
applyKnownFactorization(preconditioning::CoupledMaterialIndependentSurface{}, rightHandSide),
{(29.0 - 4.0 * (43.0 / 9.0)) / 2.0, 44.0 / 6.0, 43.0 / 9.0}
);
const double materialEnthalpy = 43.0 / 9.0;
const double materialDensity = (29.0 - 4.0 * materialEnthalpy) / 2.0;
check(
applyKnownFactorization(preconditioning::MaterialThenSurfaceTriangular{}, rightHandSide),
{materialDensity, (44.0 - 5.0 * materialDensity - 7.0 * materialEnthalpy) / 6.0, materialEnthalpy}
);
const double surfaceFirst = 44.0 / 6.0;
const double surfaceCorrectedEnthalpy = (43.0 - 8.0 * surfaceFirst) / 9.0;
check(
applyKnownFactorization(preconditioning::SurfaceThenMaterialTriangular{}, rightHandSide),
{(29.0 - 3.0 * surfaceFirst - 4.0 * surfaceCorrectedEnthalpy) / 2.0, surfaceFirst, surfaceCorrectedEnthalpy}
);
const double firstMaterialEnthalpy = 43.0 / 9.0;
const double firstMaterialDensity = (29.0 - 4.0 * firstMaterialEnthalpy) / 2.0;
const double lduSurface = (44.0 - 5.0 * firstMaterialDensity - 7.0 * firstMaterialEnthalpy) / 6.0;
const double lduEnthalpy = (43.0 - 8.0 * lduSurface) / 9.0;
check(
applyKnownFactorization(preconditioning::ApproximateMaterialSurfaceLDU{}, rightHandSide),
{(29.0 - 3.0 * lduSurface - 4.0 * lduEnthalpy) / 2.0, lduSurface, lduEnthalpy}
);
// M = [[2,4],[0,9]], B = [3,8]^T, C = [5,7], so the exact
// scalar surface Schur complement is 6 - C M^{-1} B = 7/6.
const mfem::Vector exact =
applyKnownFactorization(preconditioning::ApproximateMaterialSurfaceLDU{}, rightHandSide, 7.0 / 6.0);
CHECK(2.0 * exact(0) + 3.0 * exact(1) + 4.0 * exact(2) == Catch::Approx(29.0).margin(2.0e-12));
CHECK(5.0 * exact(0) + 6.0 * exact(1) + 7.0 * exact(2) == Catch::Approx(44.0).margin(2.0e-12));
CHECK(8.0 * exact(1) + 9.0 * exact(2) == Catch::Approx(43.0).margin(2.0e-12));
}
TEST_CASE(
"Generated Material Surface Action Is The Exact Restricted Stellar Jacobian And Uses A Bounded Surrogate",
"[preconditioning][material_surface][surface_h1][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(finiteElements.okay());
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);
const auto stellarModel = model::StellarModel(
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto problem = equilibrium::discretize(stellarModel, finiteElements);
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
const auto rotation = zeroRotation();
problem.Prepare(projected.values, makeDependencies(), rotation);
const auto &physical = problem.GetPreparedOperator().GetPhysicalOperator();
const auto block = preconditioning::materialSurfaceBlock(
problem, backend::Diagonal{}, backend::Diagonal{}, preconditioning::SurfaceThenMaterialTriangular{}
);
const auto defaultBlock = preconditioning::materialSurfaceBlock(problem);
STATIC_CHECK(
std::same_as<
typename std::remove_cvref_t<decltype(block)>::Descriptor,
preconditioning::MaterialSurfaceDescriptorFor<decltype(problem)>>
);
STATIC_CHECK(std::same_as<std::remove_cvref_t<decltype(defaultBlock)>, std::remove_cvref_t<decltype(block)>>);
auto prepared = preconditioning::prepare(problem, block);
const auto &restricted = prepared.GetCoupledOperator();
mfem::Vector restrictedDirection(restricted.Width());
for (int index = 0; index < restrictedDirection.Size(); ++index) {
restrictedDirection(index) = 0.01 * std::sin(0.37 * static_cast<double>(index + 1));
}
mfem::Vector restrictedAction(restricted.Height());
restricted.Mult(restrictedDirection, restrictedAction);
mfem::Vector fullDirection(physical.Width());
fullDirection = 0.0;
const auto fullDirectionView = physical.GetRootManifest().directionView(fullDirection);
const auto &offsets = restricted.GetOffsets();
const mfem::Vector densityDirection(restrictedDirection.GetData(), offsets[1]);
const mfem::Vector surfaceDirection(restrictedDirection.GetData() + offsets[1], offsets[2] - offsets[1]);
const mfem::Vector enthalpyDirection(restrictedDirection.GetData() + offsets[2], offsets[3] - offsets[2]);
mfem::Vector fullDensityDirection = fullDirectionView.block(blocks::density_field.mass_term);
mfem::Vector fullSurfaceDirection = fullDirectionView.block(blocks::surface_deformation_field.parameters_term);
mfem::Vector fullEnthalpyDirection = fullDirectionView.block(blocks::enthalpy_field.specific_term);
fullDensityDirection = densityDirection;
fullSurfaceDirection = surfaceDirection;
fullEnthalpyDirection = enthalpyDirection;
mfem::Vector fullAction;
physical.Mult(fullDirection, fullAction);
const auto fullActionView = physical.GetRootManifest().residualView(fullAction);
mfem::Vector expected(restricted.Height());
mfem::Vector expectedDensity(expected.GetData(), offsets[1]);
mfem::Vector expectedSurface(expected.GetData() + offsets[1], offsets[2] - offsets[1]);
mfem::Vector expectedEnthalpy(expected.GetData() + offsets[2], offsets[3] - offsets[2]);
const mfem::Vector fullDensityAction = fullActionView.block(blocks::density_field.mass_term);
const mfem::Vector fullSurfaceAction =
fullActionView.block(blocks::surface_deformation_field.shape_equilibrium_term);
const mfem::Vector fullEnthalpyAction = fullActionView.block(blocks::enthalpy_field.specific_term);
expectedDensity = fullDensityAction;
expectedSurface = fullSurfaceAction;
expectedEnthalpy = fullEnthalpyAction;
const mfem::Vector restrictedDensity(restrictedAction.GetData(), offsets[1]);
const mfem::Vector restrictedSurface(restrictedAction.GetData() + offsets[1], offsets[2] - offsets[1]);
const mfem::Vector restrictedEnthalpy(restrictedAction.GetData() + offsets[2], offsets[3] - offsets[2]);
INFO("Restricted density-row error = " << relativeError(restrictedDensity, expectedDensity));
INFO("Restricted surface-row error = " << relativeError(restrictedSurface, expectedSurface));
INFO("Restricted enthalpy-row error = " << relativeError(restrictedEnthalpy, expectedEnthalpy));
CHECK(relativeError(restrictedDensity, expectedDensity) <= 2.0e-12);
CHECK(relativeError(restrictedSurface, expectedSurface) <= 2.0e-12);
CHECK(relativeError(restrictedEnthalpy, expectedEnthalpy) <= 2.0e-12);
CHECK(relativeError(restrictedAction, expected) <= 2.0e-12);
CHECK(prepared.GetDensityDiagonalQuality().maximumAbsoluteEntryBeforeRegularization > 0.0);
CHECK(prepared.GetSurfaceDiagonalQuality().maximumAbsoluteEntryBeforeRegularization > 0.0);
CHECK(prepared.GetSurfaceDiagonalQuality().minimumAbsoluteEntryBeforeRegularization > 0.0);
CHECK(prepared.GetSurfaceDiagonalQuality().regularizedEntries == 0);
CHECK(prepared.GetEnthalpyDiagonalQuality().maximumAbsoluteEntryBeforeRegularization > 0.0);
CHECK(prepared.GetStatistics().surfaceJacobianProbes == 0);
CHECK(prepared.GetStatistics().surfaceRieszAssemblies == 1);
const auto calibratedBlock = preconditioning::materialSurfaceBlock(
problem, backend::Diagonal{}, backend::Diagonal{}, preconditioning::ApproximateMaterialSurfaceLDU{},
{.surfaceCalibration = {
.target = preconditioning::SurfaceRieszCalibrationTarget::approximate_material_schur,
.probeCount = 3,
.objective = preconditioning::SurfaceRieszCalibrationObjective::right_preconditioned_action
}}
);
auto calibrated = preconditioning::prepare(problem, calibratedBlock);
CHECK(calibrated.GetSurfaceCalibration().WasCalibrated());
CHECK(
calibrated.GetSurfaceCalibration().target ==
preconditioning::SurfaceRieszCalibrationTarget::approximate_material_schur
);
CHECK(calibrated.GetSurfaceCalibration().probeCount == 3);
CHECK(
calibrated.GetSurfaceCalibration().objective ==
preconditioning::SurfaceRieszCalibrationObjective::right_preconditioned_action
);
CHECK(std::isfinite(calibrated.GetSurfaceCalibration().scale));
CHECK(calibrated.GetSurfaceCalibration().scale != 0.0);
CHECK(calibrated.GetStatistics().surfaceJacobianProbes == 3);
CHECK(calibrated.GetSurfaceDiagonalQuality().maximumAbsoluteEntryBeforeRegularization > 0.0);
const auto frequencyAwareBlock = preconditioning::materialSurfaceBlock(
problem, backend::Diagonal{}, FixedCycleAMG{backend::FixedCycles{.cycles = 1}},
preconditioning::ApproximateMaterialSurfaceLDU{},
preconditioning::SurfaceH1MassStiffness{
.calibration =
{.target = preconditioning::SurfaceRieszCalibrationTarget::approximate_material_schur, .probeCount = 4},
.relativeMassCoefficientFloor = 1.0e-10,
.gramRelativeTolerance = 1.0e-12
}
);
STATIC_CHECK(
std::same_as<
typename std::remove_cvref_t<decltype(frequencyAwareBlock)>::SurfaceSurrogate,
preconditioning::SurfaceH1MassStiffness>
);
auto frequencyAware = preconditioning::prepare(problem, frequencyAwareBlock);
using PreparedFrequencyAware = std::remove_cvref_t<decltype(frequencyAware)>;
STATIC_CHECK_FALSE(std::copy_constructible<PreparedFrequencyAware>);
STATIC_CHECK_FALSE(std::move_constructible<PreparedFrequencyAware>);
const auto &surfaceFit = frequencyAware.GetSurfaceFit();
CHECK(surfaceFit.WasCalibrated());
CHECK(surfaceFit.target == preconditioning::SurfaceRieszCalibrationTarget::approximate_material_schur);
CHECK(surfaceFit.probeCount == 4);
CHECK((surfaceFit.sign == -1.0 || surfaceFit.sign == 1.0));
CHECK(std::isfinite(surfaceFit.massCoefficient));
CHECK(surfaceFit.massCoefficient > 0.0);
CHECK(std::isfinite(surfaceFit.stiffnessCoefficient));
CHECK(surfaceFit.stiffnessCoefficient >= 0.0);
CHECK(std::isfinite(surfaceFit.relativeResidual));
CHECK(surfaceFit.relativeGramDeterminant > 1.0e-12);
CHECK(surfaceFit.normalEquations.targetTarget > 0.0);
CHECK(frequencyAware.GetSurfaceInverse().Height() == physical.GetDomainDeformation().parameterCount());
CHECK(frequencyAware.GetSurfaceSurrogateMatrix().Height() == finiteElements.surfaceDeformationFes->GetTrueVSize());
CHECK(frequencyAware.GetSurfaceBackend().GetStatistics().setups == 1);
CHECK(frequencyAware.GetStatistics().surfaceJacobianProbes == 4);
CHECK(frequencyAware.GetStatistics().surfaceH1Assemblies == 3);
const auto frequencyAwareNoChange = frequencyAware.Refresh(physical);
CHECK_FALSE(frequencyAwareNoChange.DidAnyWork());
CHECK(frequencyAware.GetStatistics().noOpRefreshes == 1);
mfem::Vector rightHandSide(prepared.Width());
mfem::Vector correction(prepared.Height());
mfem::Vector repeatedCorrection(prepared.Height());
for (int index = 0; index < rightHandSide.Size(); ++index) {
rightHandSide(index) = std::cos(0.19 * static_cast<double>(index + 1));
}
correction = 0.0;
repeatedCorrection = 0.0;
double *const correctionStorage = correction.GetData();
prepared.Mult(rightHandSide, correction);
prepared.Mult(rightHandSide, repeatedCorrection);
CHECK(correction.GetData() == correctionStorage);
CHECK(relativeError(correction, repeatedCorrection) <= 2.0e-15);
for (int index = 0; index < correction.Size(); ++index) {
REQUIRE(std::isfinite(correction(index)));
}
mfem::Vector frequencyAwareCorrection(frequencyAware.Height());
mfem::Vector repeatedFrequencyAwareCorrection(frequencyAware.Height());
frequencyAwareCorrection = 0.0;
repeatedFrequencyAwareCorrection = 0.0;
frequencyAware.Mult(rightHandSide, frequencyAwareCorrection);
frequencyAware.Mult(rightHandSide, repeatedFrequencyAwareCorrection);
CHECK(relativeError(frequencyAwareCorrection, repeatedFrequencyAwareCorrection) <= 2.0e-13);
for (int index = 0; index < frequencyAwareCorrection.Size(); ++index) {
REQUIRE(std::isfinite(frequencyAwareCorrection(index)));
}
const auto noChange = prepared.Refresh(physical);
CHECK_FALSE(noChange.DidAnyWork());
// Full stellar-Jacobian finite-difference accuracy is covered by the
// prepared-stellar-equilibrium tests. Here we change the state only to
// exercise the material-surface refresh contract without repeating two
// expensive nonlinear residual assemblies.
mfem::Vector changedState(projected.values);
mfem::Vector borderedDirection(problem.StateSize());
borderedDirection = 0.0;
mfem::Vector physicalDirection(borderedDirection.GetData(), physical.Width());
physicalDirection = fullDirection;
changedState.Add(1.0e-5, borderedDirection);
problem.Prepare(changedState, makeDependencies(2), rotation);
CHECK_FALSE(prepared.IsCurrent());
rightHandSide = 1.0;
correction = 0.0;
CHECK_THROWS_AS(prepared.Mult(rightHandSide, correction), std::logic_error);
const auto refreshed = prepared.Refresh(problem.GetPreparedOperator().GetPhysicalOperator());
CHECK(refreshed.linearizationChanged);
CHECK(refreshed.rebuiltDensityInverse);
CHECK(refreshed.rebuiltSurfaceInverse);
CHECK(refreshed.rebuiltEnthalpyInverse);
CHECK(prepared.IsCurrent());
CHECK(prepared.GetStatistics().surfaceJacobianProbes == 0);
CHECK(prepared.GetStatistics().surfaceRieszAssemblies == 2);
auto densityOnlyDependencies = makeDependencies(2);
densityOnlyDependencies.density.revision = 3;
problem.Prepare(changedState, densityOnlyDependencies, rotation);
CHECK_FALSE(prepared.IsCurrent());
const auto stateOnlyRefresh = prepared.Refresh(problem.GetPreparedOperator().GetPhysicalOperator());
CHECK(stateOnlyRefresh.linearizationChanged);
CHECK_FALSE(stateOnlyRefresh.DidAnyWork());
CHECK_FALSE(stateOnlyRefresh.rebuiltDensityInverse);
CHECK_FALSE(stateOnlyRefresh.rebuiltSurfaceInverse);
CHECK_FALSE(stateOnlyRefresh.rebuiltEnthalpyInverse);
CHECK(prepared.IsCurrent());
CHECK(prepared.GetStatistics().surfaceRieszAssemblies == 2);
CHECK_FALSE(calibrated.IsCurrent());
CHECK_FALSE(frequencyAware.IsCurrent());
const auto calibratedRefresh = calibrated.Refresh(problem.GetPreparedOperator().GetPhysicalOperator());
CHECK(calibratedRefresh.DidAnyWork());
CHECK(calibratedRefresh.rebuiltDensityInverse);
CHECK(calibratedRefresh.rebuiltSurfaceInverse);
CHECK(calibratedRefresh.rebuiltEnthalpyInverse);
CHECK(calibrated.IsCurrent());
CHECK(calibrated.GetStatistics().surfaceJacobianProbes == 6);
CHECK(calibrated.GetStatistics().surfaceRieszAssemblies == 2);
const auto frequencyAwareRefresh = frequencyAware.Refresh(problem.GetPreparedOperator().GetPhysicalOperator());
CHECK(frequencyAwareRefresh.DidAnyWork());
CHECK(frequencyAwareRefresh.rebuiltDensityInverse);
CHECK(frequencyAwareRefresh.rebuiltSurfaceInverse);
CHECK(frequencyAwareRefresh.rebuiltEnthalpyInverse);
CHECK(frequencyAware.IsCurrent());
CHECK(frequencyAware.GetSurfaceBackend().GetStatistics().setups == 2);
CHECK(frequencyAware.GetStatistics().surfaceJacobianProbes == 8);
CHECK(frequencyAware.GetStatistics().surfaceH1Assemblies == 6);
frequencyAware.Mult(rightHandSide, frequencyAwareCorrection);
for (int index = 0; index < frequencyAwareCorrection.Size(); ++index) {
REQUIRE(std::isfinite(frequencyAwareCorrection(index)));
}
}