#include #include #include #include #include #include #include #include #include #include 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>; using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem; using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor; using MaterialSurfaceDiagonal = preconditioning::MaterialSurfaceBlock< PolytropicMaterialSurfaceDescriptor, backend::Diagonal, backend::Diagonal, preconditioning::SurfaceThenMaterialTriangular>; using FixedCycleAMG = backend::HypreBoomerAMG; 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 &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 m_offsets; }; template [[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 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; using PotentialIdentity = preconditioning::IdentityBlock; using MassIdentity = preconditioning::IdentityBlock< blocks::fixed_total_mass::mass_normalization::value, blocks::fixed_total_mass::mass_normalization::residual>; using Plan = preconditioning::PreconditionerPlan; STATIC_CHECK(preconditioning::PreconditionerComponent); STATIC_CHECK(preconditioning::PreconditionerComponent); STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor); STATIC_CHECK( mean_field::material::CompiledThermodynamicEquations ); STATIC_CHECK( std::same_as< typename PolytropicMaterialSurfaceDescriptor::SurfaceStateFields, mean_field::field::TypeList> ); STATIC_CHECK(MaterialSurfaceDiagonal::CorrectionBlocks::size == 3); STATIC_CHECK(MaterialSurfaceDiagonal::ResidualBlocks::size == 3); STATIC_CHECK(MaterialSurfaceDiagonal::RequiredCouplings::size == 8); STATIC_CHECK(preconditioning::CompletePreconditionerFor); STATIC_CHECK(preconditioning::CompatiblePreconditionerFor); STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible); STATIC_CHECK_FALSE(std::is_copy_constructible_v); STATIC_CHECK_FALSE(std::is_copy_assignable_v); STATIC_CHECK_FALSE(std::is_move_constructible_v); STATIC_CHECK_FALSE(std::is_move_assignable_v); STATIC_CHECK_FALSE(std::is_copy_constructible_v); STATIC_CHECK_FALSE(std::is_copy_assignable_v); STATIC_CHECK_FALSE(std::is_move_constructible_v); STATIC_CHECK_FALSE(std::is_move_assignable_v); STATIC_CHECK( preconditioning::backend::Compatible ); STATIC_CHECK_FALSE( preconditioning::backend::Compatible ); STATIC_CHECK( preconditioning::backend::Compatible ); STATIC_CHECK_FALSE(std::same_as); STATIC_CHECK(std::same_as); STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible); } 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 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 expected) { for (int index = 0; index < value.Size(); ++index) { CHECK(value(index) == Catch::Approx(expected[static_cast(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; const double centralDensity = std::numbers::pi_v * 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::Descriptor, preconditioning::MaterialSurfaceDescriptorFor> ); STATIC_CHECK(std::same_as, std::remove_cvref_t>); 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(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::SurfaceSurrogate, preconditioning::SurfaceH1MassStiffness> ); auto frequencyAware = preconditioning::prepare(problem, frequencyAwareBlock); using PreparedFrequencyAware = std::remove_cvref_t; STATIC_CHECK_FALSE(std::copy_constructible); STATIC_CHECK_FALSE(std::move_constructible); 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(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))); } }