module; #include #include #include #include #include #include #include #include export module mean_field:solver.gravity_reduction; export import :solver.gravity_subsystem; export import :solver.linear_backend; export namespace mean_field::solver { template concept PreparedGravityReductionSubsystem = requires( Candidate &subsystem, const Candidate &constantSubsystem, const mfem::Vector &rightHandSide, mfem::Vector &correction, const LinearSolveControl &control ) { { constantSubsystem.GetPartition() } -> std::same_as; { constantSubsystem.IsCurrent() } -> std::same_as; { constantSubsystem.GetCommunicator() } -> std::same_as; requires std::derived_from< std::remove_cvref_t, mfem::Operator>; { subsystem.Solve(rightHandSide, correction, control) } -> std::same_as; }; /* * MFEM's void Mult boundary cannot return a failed inner solve. Keep that * failure distinguishable from a contract violation so an outer backend * can stop the numerical attempt without accepting an inaccurate Schur * action. The Try methods below retain the ordinary report-based path. */ class GravityReductionFailure final : public std::runtime_error { public: explicit GravityReductionFailure(LinearSolveReport report) : std::runtime_error("A reduced-gravity action failed its inner true-residual tolerance."), m_report(std::move(report)) { } [[nodiscard]] const LinearSolveReport &GetReport() const noexcept { return m_report; } private: LinearSolveReport m_report; }; /* * For J = [F_u F_z; G_u G_z], this applies F_u - F_z G_z^{-1} G_u. * Neither the full operator nor its preparation may change during an * application. All coordinates, including specification borders, come * from the same partition used by the gravity subsystem. * * A tolerance-stopped inner iteration approximates this linear operator; * flexible outer preconditioning does not make an inexact operator exact. * Its owner must verify a lifted correction with the full Jacobian and * choose an inner tolerance commensurate with the outer accuracy target. */ template class GravityReducedOperator final : public mfem::Operator { public: GravityReducedOperator( Subsystem &subsystem, LinearSolveControl innerControl ) : mfem::Operator( subsystem.GetPartition().RetainedResidualSize(), subsystem.GetPartition().RetainedValueSize() ), m_subsystem(std::addressof(subsystem)), m_innerControl(std::move(innerControl)), m_fullDirection(subsystem.GetPartition().RootValueSize()), m_fullGravityDirection(subsystem.GetPartition().RootValueSize()), m_fullAction(subsystem.GetPartition().RootResidualSize()), m_fullGravityAction(subsystem.GetPartition().RootResidualSize()), m_gravityRightHandSide(subsystem.GetPartition().GravityResidualSize()), m_gravityCorrection(subsystem.GetPartition().GravityValueSize()), m_retainedResult(subsystem.GetPartition().RetainedResidualSize()), m_retainedGravityAction(subsystem.GetPartition().RetainedResidualSize()) { m_innerControl.Validate(); const auto &partition = GetPartition(); const auto &operation = subsystem.GetFullOperator(); if (operation.Width() != partition.RootValueSize() || operation.Height() != partition.RootResidualSize()) { throw std::invalid_argument("The reduced-gravity partition does not match its full operator."); } } GravityReducedOperator(const GravityReducedOperator &) = delete; GravityReducedOperator &operator=(const GravityReducedOperator &) = delete; GravityReducedOperator(GravityReducedOperator &&) = delete; GravityReducedOperator &operator=(GravityReducedOperator &&) = delete; [[nodiscard]] const GravityBlockPartition &GetPartition() const noexcept { return m_subsystem->GetPartition(); } [[nodiscard]] const Subsystem &GetSubsystem() const noexcept { return *m_subsystem; } [[nodiscard]] MPI_Comm GetCommunicator() const noexcept { return m_subsystem->GetCommunicator(); } [[nodiscard]] bool IsCurrent() const noexcept { return m_subsystem->IsCurrent(); } [[nodiscard]] const LinearSolveControl &GetInnerSolveControl() const noexcept { return m_innerControl; } [[nodiscard]] const LinearSolveReport &GetLastInnerSolve() const noexcept { return m_lastInnerSolve; } [[nodiscard]] LinearSolveReport TryMult( const mfem::Vector &input, mfem::Vector &output ) const { RequireCurrent(); const auto &partition = GetPartition(); partition.InjectRetainedValues(input, m_fullDirection); FullMult(m_fullDirection, m_fullAction); partition.GatherGravityResiduals(m_fullAction, m_gravityRightHandSide); if (!SolveGravity()) { return m_lastInnerSolve; } partition.InjectGravityValues(m_gravityCorrection, m_fullGravityDirection); FullMult(m_fullGravityDirection, m_fullGravityAction); partition.GatherRetainedResiduals(m_fullAction, m_retainedResult); partition.GatherRetainedResiduals(m_fullGravityAction, m_retainedGravityAction); m_retainedResult -= m_retainedGravityAction; output = m_retainedResult; return m_lastInnerSolve; } void Mult( const mfem::Vector &input, mfem::Vector &output ) const override { const auto report = TryMult(input, output); if (!report.Converged()) { throw GravityReductionFailure(report); } } /* * Condensing a nonzero gravity residual preserves equivalence to the * monolithic Newton equations. Dropping it merely because gravity was * approximately completed would turn inner error into an outer bias. */ [[nodiscard]] LinearSolveReport TryCondenseRightHandSide( const mfem::Vector &fullRightHandSide, mfem::Vector &reducedRightHandSide ) const { RequireCurrent(); const auto &partition = GetPartition(); partition.GatherGravityResiduals(fullRightHandSide, m_gravityRightHandSide); if (!SolveGravity()) { return m_lastInnerSolve; } partition.InjectGravityValues(m_gravityCorrection, m_fullGravityDirection); FullMult(m_fullGravityDirection, m_fullGravityAction); partition.GatherRetainedResiduals(fullRightHandSide, m_retainedResult); partition.GatherRetainedResiduals(m_fullGravityAction, m_retainedGravityAction); m_retainedResult -= m_retainedGravityAction; reducedRightHandSide = m_retainedResult; return m_lastInnerSolve; } [[nodiscard]] LinearSolveReport TryLiftCorrection( const mfem::Vector &retainedCorrection, const mfem::Vector &fullRightHandSide, mfem::Vector &fullCorrection ) const { RequireCurrent(); const auto &partition = GetPartition(); partition.InjectRetainedValues(retainedCorrection, m_fullDirection); FullMult(m_fullDirection, m_fullAction); m_fullAction *= -1.0; if (fullRightHandSide.Size() != m_fullAction.Size()) { throw std::invalid_argument("A lifted gravity correction requires a full residual right-hand side."); } m_fullAction += fullRightHandSide; partition.GatherGravityResiduals(m_fullAction, m_gravityRightHandSide); return FinishLift(fullCorrection); } [[nodiscard]] LinearSolveReport TryLiftTangent( const mfem::Vector &retainedDirection, mfem::Vector &fullDirection ) const { RequireCurrent(); const auto &partition = GetPartition(); partition.InjectRetainedValues(retainedDirection, m_fullDirection); FullMult(m_fullDirection, m_fullAction); partition.GatherGravityResiduals(m_fullAction, m_gravityRightHandSide); m_gravityRightHandSide *= -1.0; return FinishLift(fullDirection); } private: void RequireCurrent() const { if (!IsCurrent()) { throw std::logic_error("A reduced-gravity action requires the current prepared gravity subsystem."); } } void FullMult( const mfem::Vector &input, mfem::Vector &output ) const { m_subsystem->GetFullOperator().Mult(input, output); RequireCurrent(); } [[nodiscard]] bool SolveGravity() const { // A previous Schur action is not a suitable initial guess for an // unrelated Krylov direction and can make the action history dependent. m_gravityCorrection = 0.0; m_lastInnerSolve = m_subsystem->Solve(m_gravityRightHandSide, m_gravityCorrection, m_innerControl); RequireCurrent(); if (m_lastInnerSolve.Converged()) { if (!std::isfinite(m_lastInnerSolve.rightHandSideNorm) || !std::isfinite(m_lastInnerSolve.trueResidualNorm)) { m_lastInnerSolve.status = LinearSolveStatus::non_finite; } else if (m_lastInnerSolve.rightHandSideNorm < 0.0 || m_lastInnerSolve.trueResidualNorm < 0.0 || m_lastInnerSolve.trueResidualNorm > m_innerControl.ConvergenceThreshold(m_lastInnerSolve.rightHandSideNorm)) { m_lastInnerSolve.status = LinearSolveStatus::backend_failure; } } return m_lastInnerSolve.Converged(); } [[nodiscard]] LinearSolveReport FinishLift(mfem::Vector &fullCorrection) const { if (!SolveGravity()) { return m_lastInnerSolve; } GetPartition().AddGravityValues(m_gravityCorrection, m_fullDirection); fullCorrection = m_fullDirection; return m_lastInnerSolve; } Subsystem *m_subsystem; LinearSolveControl m_innerControl; mutable mfem::Vector m_fullDirection; mutable mfem::Vector m_fullGravityDirection; mutable mfem::Vector m_fullAction; mutable mfem::Vector m_fullGravityAction; mutable mfem::Vector m_gravityRightHandSide; mutable mfem::Vector m_gravityCorrection; mutable mfem::Vector m_retainedResult; mutable mfem::Vector m_retainedGravityAction; mutable LinearSolveReport m_lastInnerSolve; }; /* * The retained block of an exact full inverse equals the inverse Schur * complement. Restricting a supplied approximate inverse preserves that * relationship without assembling a second approximate physics operator. */ template requires std::derived_from && LinearPreconditionerApplicationContractAvailable class GravityReducedPreconditioner final : public mfem::Solver { public: static constexpr auto applicationContract = linearPreconditionerApplicationContract; GravityReducedPreconditioner( const GravityReducedOperator &operation, FullInverse &fullInverse ) : mfem::Solver(operation.Width(), operation.Height(), false), m_operation(std::addressof(operation)), m_fullInverse(std::addressof(fullInverse)), m_fullRightHandSide(operation.GetPartition().RootResidualSize()), m_fullCorrection(operation.GetPartition().RootValueSize()) { if (fullInverse.Width() != m_fullRightHandSide.Size() || fullInverse.Height() != m_fullCorrection.Size()) { throw std::invalid_argument("A reduced-gravity preconditioner requires a compatible full inverse."); } } void SetOperator(const mfem::Operator &operation) override { if (std::addressof(operation) != m_operation) { throw std::invalid_argument("The reduced-gravity preconditioner cannot bind a different operator."); } } void Mult( const mfem::Vector &input, mfem::Vector &output ) const override { if (!m_operation->IsCurrent()) { throw std::logic_error("The reduced-gravity preconditioner requires the current prepared subsystem."); } const auto &partition = m_operation->GetPartition(); partition.InjectRetainedResiduals(input, m_fullRightHandSide); m_fullInverse->Mult(m_fullRightHandSide, m_fullCorrection); if (!m_operation->IsCurrent()) { throw std::logic_error("The full inverse changed the reduced-gravity preparation."); } partition.GatherRetainedValues(m_fullCorrection, output); } [[nodiscard]] const FullInverse &GetFullInverse() const noexcept { return *m_fullInverse; } private: const GravityReducedOperator *m_operation; FullInverse *m_fullInverse; mutable mfem::Vector m_fullRightHandSide; mutable mfem::Vector m_fullCorrection; }; } // namespace mean_field::solver