#include #include #include #include #include #include #include #include #include import mean_field; namespace gravity_compact_test { using namespace mean_field; template class Operation final : public mfem::Operator { template static auto Sizes() { std::array sizes; sizes.fill(1); return sizes; } public: Operation() : mfem::Operator( Form::residual_block_count, Form::value_block_count ), layout( Sizes(), Sizes() ), partition(solver::MakeGravityBlockPartition(layout)), state(Width()), rows(Height()), physicalDirection(Width()), physicalAction(Height()), gravityDirection(2), gravityAction(2) { for (int i = 0; i < Width(); ++i) state(i) = 0.3 + 0.7 * (i + 1); for (int i = 0; i < Height(); ++i) rows(i) = 1.2 + 0.4 * (i + 1); } void Mult( const mfem::Vector &direction, mfem::Vector &action ) const override { ++rootApplications; for (int i = 0; i < Width(); ++i) physicalDirection(i) = direction(i) / state(i); partition.GatherGravityValues(physicalDirection, gravityDirection); ApplyGravity(gravityDirection, gravityAction); partition.InjectGravityResiduals(gravityAction, physicalAction); for (int i = 0; i < Height(); ++i) action(i) = rows(i) * physicalAction(i); } void ApplyGravity( const mfem::Vector &direction, mfem::Vector &action ) const { action(0) = 3.0 * direction(0) + 2.0 * direction(1); action(1) = 2.0 * direction(0); } bool IsPrepared() const { return prepared; } const Operation &GetProblem() const { return *this; } std::uint64_t GetPreparationGeneration() const { return generation; } void Prepare() { prepared = true; ++generation; } utils::blocks::form_layout
layout; solver::GravityBlockPartition partition; mfem::Vector state, rows; bool prepared{true}; std::uint64_t generation{1}; mutable int rootApplications{0}, compactApplications{0}; private: mutable mfem::Vector physicalDirection, physicalAction, gravityDirection, gravityAction; }; template class Inverse final : public mfem::Solver { public: static constexpr auto applicationContract = preconditioning::ApplicationContract::stationary_linear; explicit Inverse(const Operation &operation) : mfem::Solver(operation.Width()), operation(operation), physicalRhs(Width()), physicalCorrection(Height()), compactRhs(2), compactCorrection(2) { Refresh(); } void SetOperator(const mfem::Operator &) override { } void Mult( const mfem::Vector &rhs, mfem::Vector &action ) const override { ++rootApplications; for (int i = 0; i < Width(); ++i) physicalRhs(i) = rhs(i) / operation.rows(i); operation.partition.GatherGravityResiduals(physicalRhs, compactRhs); ApplyGravity(compactRhs, compactCorrection); operation.partition.InjectGravityValues(compactCorrection, physicalCorrection); for (int i = 0; i < Height(); ++i) action(i) = operation.state(i) * physicalCorrection(i); } void ApplyGravity( const mfem::Vector &rhs, mfem::Vector &action ) const { action = rhs; } bool IsCurrent() const { return operation.IsPrepared() && generation == operation.generation; } void Refresh() { generation = operation.generation; compactCurrent = true; } const Operation &operation; bool compactCurrent{true}; mutable int rootApplications{0}, compactApplications{0}; private: std::uint64_t generation{0}; mutable mfem::Vector physicalRhs, physicalCorrection, compactRhs, compactCorrection; }; } // namespace gravity_compact_test namespace mean_field::solver { template struct GravityCompactOperatorAccess> { using Operation = gravity_compact_test::Operation; static constexpr bool available = true; static GravityBlockPartition CanonicalPartition(const Operation &operation) { return operation.partition; } static const mfem::Vector &StateFactors(const Operation &operation) { return operation.state; } static const mfem::Vector &ResidualFactors(const Operation &operation) { return operation.rows; } static void ApplyPhysical( const Operation &operation, const mfem::Vector &direction, mfem::Vector &action ) { ++operation.compactApplications; operation.ApplyGravity(direction, action); } }; template struct GravityCompactInverseAccess, gravity_compact_test::Inverse> { using Operation = gravity_compact_test::Operation; using Inverse = gravity_compact_test::Inverse; static constexpr bool available = true; static constexpr auto applicationContract = preconditioning::ApplicationContract::stationary_linear; static bool Matches( const Operation &operation, const Inverse &inverse ) { return &operation == &inverse.operation; } static bool IsCurrent(const Inverse &inverse) { return inverse.compactCurrent; } static void ApplyPhysical( const Inverse &inverse, const mfem::Vector &rhs, mfem::Vector &action ) { ++inverse.compactApplications; inverse.ApplyGravity(rhs, action); } }; } // namespace mean_field::solver namespace { template void CheckCanonicalApplications() { using namespace mean_field; gravity_compact_test::Operation operation; gravity_compact_test::Inverse inverse(operation); solver::PreparedGravitySubsystem compact(operation, inverse, operation.partition, MPI_COMM_WORLD); solver::PreparedGravitySubsystem reference( operation, inverse, operation.partition, MPI_COMM_WORLD, solver::linear::FGMRES{}, solver::RootRestrictedGravityApplications{} ); REQUIRE(compact.UsesCompactOperator()); REQUIRE(compact.UsesCompactPreconditioner()); REQUIRE_FALSE(reference.UsesCompactOperator()); REQUIRE_FALSE(reference.UsesCompactPreconditioner()); mfem::Vector direction(2), direct(2), restricted(2), correction(2); for (int trial = 0; trial < 4; ++trial) { direction(0) = std::sin(0.3 + trial); direction(1) = std::cos(0.7 + 2 * trial); compact.GetOperator().Mult(direction, direct); reference.GetOperator().Mult(direction, restricted); direct -= restricted; CHECK(direct.Norml2() < 1.0e-13); compact.GetPreconditioner().Mult(direction, direct); reference.GetPreconditioner().Mult(direction, restricted); direct -= restricted; CHECK(direct.Norml2() < 1.0e-13); } CHECK(operation.compactApplications == 4); CHECK(operation.rootApplications == 4); CHECK(inverse.compactApplications == 4); CHECK(inverse.rootApplications == 4); correction = 0.0; auto report = compact.Solve(direction, correction, {.relativeTolerance = 1.0e-12, .maximumIterations = 4}); REQUIRE(report.Converged()); reference.GetOperator().Mult(correction, restricted); restricted -= direction; CHECK(restricted.Norml2() < 1.0e-11); CHECK(report.operatorApplications > 0); CHECK(report.inversePreconditionerApplications > 0); CHECK(report.operatorSeconds >= 0.0); CHECK(report.inversePreconditionerSeconds >= 0.0); // A normalization refresh invalidates the accepted linearization; the // compact factors must be reacquired when its new generation is ready. operation.state *= 2.0; operation.rows *= 0.25; operation.Prepare(); CHECK_FALSE(compact.IsCurrent()); CHECK_THROWS_AS(compact.GetOperator().Mult(direction, direct), std::logic_error); CHECK_THROWS_AS(compact.Refresh(), std::logic_error); inverse.Refresh(); compact.Refresh(); reference.Refresh(); compact.GetOperator().Mult(direction, direct); reference.GetOperator().Mult(direction, restricted); direct -= restricted; CHECK(direct.Norml2() < 1.0e-13); inverse.compactCurrent = false; CHECK_FALSE(compact.IsCurrent()); CHECK_THROWS_AS(compact.GetPreconditioner().Mult(direction, direct), std::logic_error); } } // namespace TEST_CASE( "Compact gravity preserves normalized block actions with optional borders", "[solver][gravity_subsystem][gravity_compact]" ) { CheckCanonicalApplications(); CheckCanonicalApplications(); } TEST_CASE( "Compact gravity selection preserves unknown inverses and collectively rejects noncanonical maps", "[solver][gravity_subsystem][gravity_compact]" ) { using namespace mean_field; using Form = utils::blocks::central_density_bordered_stellar_equilibrium_form; gravity_compact_test::Operation operation; gravity_compact_test::Inverse customInverse(operation); static_assert(!solver::GravityCompactInverseAccess::available); solver::PreparedGravitySubsystem unknown(operation, customInverse, operation.partition, MPI_COMM_WORLD); CHECK(unknown.UsesCompactOperator()); CHECK_FALSE(unknown.UsesCompactPreconditioner()); int rank = 0; MPI_Comm_rank(MPI_COMM_WORLD, &rank); auto selected = operation.partition; if (rank == 0) selected = solver::GravityBlockPartition(operation.Width(), operation.Height(), {1, 0}, {0, 1}); gravity_compact_test::Inverse inverse(operation); solver::PreparedGravitySubsystem fallback(operation, inverse, selected, MPI_COMM_WORLD); solver::PreparedGravitySubsystem reference( operation, inverse, selected, MPI_COMM_WORLD, solver::linear::FGMRES{}, solver::RootRestrictedGravityApplications{} ); CHECK_FALSE(fallback.UsesCompactOperator()); CHECK_FALSE(fallback.UsesCompactPreconditioner()); mfem::Vector direction(2), actual(2), expected(2); direction(0) = 0.43; direction(1) = -0.91; fallback.GetOperator().Mult(direction, actual); reference.GetOperator().Mult(direction, expected); actual -= expected; CHECK(actual.Norml2() == 0.0); fallback.GetPreconditioner().Mult(direction, actual); reference.GetPreconditioner().Mult(direction, expected); actual -= expected; CHECK(actual.Norml2() == 0.0); }