module; #include #include #include #include #include #include #include #include export module mean_field:preconditioning.backend_implementations; export import :preconditioning.backend; export namespace mean_field::preconditioning::backend { struct BackendStatistics final { std::uint64_t setups{0}; std::uint64_t applications{0}; std::uint64_t innerIterations{0}; std::uint64_t lastInnerIterations{0}; }; namespace detail { inline void verifyApplicationDimensions( const mfem::Solver &solver, const mfem::Vector &rightHandSide, const mfem::Vector &action ) { if (rightHandSide.Size() != solver.Width() || action.Size() != solver.Height()) { throw std::invalid_argument( "A prepared preconditioning backend requires compatible, preallocated input and output vectors." ); } } inline void verifySquarePositiveSize( const int height, const int width ) { if (height <= 0 || height != width) { throw std::invalid_argument("A preconditioning backend requires a positive square operator."); } } inline void configure( mfem::HypreBoomerAMG &solver, const FixedCycles &mode ) { if (mode.cycles <= 0) { throw std::invalid_argument("Fixed-cycle AMG requires at least one cycle."); } solver.SetMaxIter(mode.cycles); solver.SetTol(0.0); solver.SetPrintLevel(0); solver.iterative_mode = false; } inline void configure( mfem::HypreBoomerAMG &solver, const SolveToTolerance &mode ) { if (!std::isfinite(mode.relativeTolerance) || mode.relativeTolerance <= 0.0 || mode.relativeTolerance >= 1.0) { throw std::invalid_argument("Tolerance-driven AMG requires a finite relative tolerance in (0, 1)."); } if (mode.maximumCycles <= 0) { throw std::invalid_argument("Tolerance-driven AMG requires at least one permitted cycle."); } solver.SetMaxIter(mode.maximumCycles); solver.SetTol(mode.relativeTolerance); solver.SetPrintLevel(0); solver.iterative_mode = false; } } // namespace detail class PreparedDiagonal final : public mfem::Solver { public: PreparedDiagonal( Diagonal configuration, const mfem::Vector &diagonal ) : mfem::Solver(diagonal.Size()), m_configuration(std::move(configuration)) { Refresh(diagonal); } PreparedDiagonal(const PreparedDiagonal &) = delete; PreparedDiagonal &operator=(const PreparedDiagonal &) = delete; PreparedDiagonal(PreparedDiagonal &&) = delete; PreparedDiagonal &operator=(PreparedDiagonal &&) = delete; void SetOperator(const mfem::Operator &operation) override { if (operation.Height() != Height() || operation.Width() != Width()) { throw std::invalid_argument("The diagonal backend received an operator with incompatible dimensions."); } } void Mult( const mfem::Vector &rightHandSide, mfem::Vector &action ) const override { detail::verifyApplicationDimensions(*this, rightHandSide, action); for (int index = 0; index < Height(); ++index) { action(index) = m_inverseDiagonal(index) * rightHandSide(index); } ++m_statistics.applications; } void Refresh(const mfem::Vector &diagonal) { if (diagonal.Size() <= 0 || diagonal.Size() != Height()) { throw std::invalid_argument("The diagonal backend requires a positive diagonal of unchanged size."); } m_inverseDiagonal.SetSize(diagonal.Size()); for (int index = 0; index < diagonal.Size(); ++index) { const double entry = diagonal(index); if (!std::isfinite(entry) || entry == 0.0) { throw std::invalid_argument("The diagonal backend cannot invert a zero or non-finite entry."); } m_inverseDiagonal(index) = 1.0 / entry; } ++m_statistics.setups; } [[nodiscard]] const Diagonal &GetConfiguration() const noexcept { return m_configuration; } [[nodiscard]] const mfem::Vector &GetInverseDiagonal() const noexcept { return m_inverseDiagonal; } [[nodiscard]] const BackendStatistics &GetStatistics() const noexcept { return m_statistics; } private: Diagonal m_configuration; mfem::Vector m_inverseDiagonal; mutable BackendStatistics m_statistics; }; class PreparedMatrixFreeChebyshev final : public mfem::Solver { public: PreparedMatrixFreeChebyshev( MatrixFreeChebyshev configuration, const mfem::Operator &operation, const MPI_Comm communicator ) : mfem::Solver(operation.Height()), m_configuration(std::move(configuration)), m_communicator(communicator) { ValidateConfiguration(); Refresh(operation); } PreparedMatrixFreeChebyshev(const PreparedMatrixFreeChebyshev &) = delete; PreparedMatrixFreeChebyshev &operator=(const PreparedMatrixFreeChebyshev &) = delete; PreparedMatrixFreeChebyshev(PreparedMatrixFreeChebyshev &&) = delete; PreparedMatrixFreeChebyshev &operator=(PreparedMatrixFreeChebyshev &&) = delete; void SetOperator(const mfem::Operator &operation) override { Refresh(operation); } void Mult( const mfem::Vector &rightHandSide, mfem::Vector &action ) const override { detail::verifyApplicationDimensions(*this, rightHandSide, action); m_smoother->Mult(rightHandSide, action); ++m_statistics.applications; m_statistics.lastInnerIterations = static_cast(m_configuration.order); m_statistics.innerIterations += static_cast(m_configuration.order); } void Refresh(const mfem::Operator &operation) { detail::verifySquarePositiveSize(operation.Height(), operation.Width()); if (operation.Height() != Height()) { throw std::invalid_argument("The matrix-free Chebyshev backend cannot change size during refresh."); } operation.AssembleDiagonal(m_diagonal); if (m_diagonal.Size() != Height()) { throw std::invalid_argument( "The matrix-free Chebyshev backend received an incompatible assembled diagonal." ); } for (int index = 0; index < m_diagonal.Size(); ++index) { if (!std::isfinite(m_diagonal(index)) || m_diagonal(index) <= 0.0) { throw std::invalid_argument( "The matrix-free Chebyshev backend requires a finite, strictly positive diagonal." ); } } m_operation = std::addressof(operation); m_essentialTrueDofs.SetSize(0); m_smoother = std::make_unique( operation, m_diagonal, m_essentialTrueDofs, m_configuration.order, m_communicator, m_configuration.powerIterations, m_configuration.powerTolerance, m_configuration.powerSeed ); m_smoother->iterative_mode = false; ++m_statistics.setups; } [[nodiscard]] const MatrixFreeChebyshev &GetConfiguration() const noexcept { return m_configuration; } [[nodiscard]] const mfem::Operator &GetOperator() const noexcept { return *m_operation; } [[nodiscard]] const mfem::Vector &GetDiagonal() const noexcept { return m_diagonal; } [[nodiscard]] const BackendStatistics &GetStatistics() const noexcept { return m_statistics; } private: void ValidateConfiguration() const { if (m_configuration.order <= 0 || m_configuration.order > 5) { throw std::invalid_argument("Matrix-free Chebyshev requires a polynomial order in [1, 5]."); } if (m_configuration.powerIterations <= 0) { throw std::invalid_argument("Matrix-free Chebyshev requires at least one power iteration."); } if (!std::isfinite(m_configuration.powerTolerance) || m_configuration.powerTolerance <= 0.0 || m_configuration.powerTolerance >= 1.0) { throw std::invalid_argument( "Matrix-free Chebyshev requires a finite power-method tolerance strictly between zero and one." ); } if (m_configuration.powerSeed <= 0) { throw std::invalid_argument("Matrix-free Chebyshev requires a strictly positive power-method seed."); } } MatrixFreeChebyshev m_configuration; MPI_Comm m_communicator; const mfem::Operator *m_operation{nullptr}; mfem::Vector m_diagonal; mfem::Array m_essentialTrueDofs; std::unique_ptr m_smoother; mutable BackendStatistics m_statistics; }; class PreparedDenseDirect final : public mfem::Solver { public: PreparedDenseDirect( DenseDirect configuration, const mfem::DenseMatrix &matrix ) : mfem::Solver(matrix.Height()), m_configuration(std::move(configuration)) { Refresh(matrix); } PreparedDenseDirect(const PreparedDenseDirect &) = delete; PreparedDenseDirect &operator=(const PreparedDenseDirect &) = delete; PreparedDenseDirect(PreparedDenseDirect &&) = delete; PreparedDenseDirect &operator=(PreparedDenseDirect &&) = delete; void SetOperator(const mfem::Operator &operation) override { const auto *matrix = dynamic_cast(&operation); if (matrix == nullptr) { throw std::invalid_argument("The dense-direct backend requires an mfem::DenseMatrix."); } Refresh(*matrix); } void Mult( const mfem::Vector &rightHandSide, mfem::Vector &action ) const override { detail::verifyApplicationDimensions(*this, rightHandSide, action); m_inverse->Mult(rightHandSide, action); ++m_statistics.applications; } void Refresh(const mfem::DenseMatrix &matrix) { detail::verifySquarePositiveSize(matrix.Height(), matrix.Width()); if (matrix.Height() != Height()) { throw std::invalid_argument("The dense-direct backend cannot change size during refresh."); } m_matrix = matrix; m_inverse = std::make_unique(m_matrix); ++m_statistics.setups; } [[nodiscard]] const DenseDirect &GetConfiguration() const noexcept { return m_configuration; } [[nodiscard]] const mfem::DenseMatrix &GetDenseSurrogate() const noexcept { return m_matrix; } [[nodiscard]] const BackendStatistics &GetStatistics() const noexcept { return m_statistics; } private: DenseDirect m_configuration; mfem::DenseMatrix m_matrix; std::unique_ptr m_inverse; mutable BackendStatistics m_statistics; }; template class PreparedHypreBoomerAMG final : public mfem::Solver { public: using Configuration = HypreBoomerAMG; PreparedHypreBoomerAMG( Configuration configuration, const mfem::HypreParMatrix &matrix ) : mfem::Solver(matrix.Height()), m_configuration(std::move(configuration)) { Refresh(matrix); } PreparedHypreBoomerAMG(const PreparedHypreBoomerAMG &) = delete; PreparedHypreBoomerAMG &operator=(const PreparedHypreBoomerAMG &) = delete; PreparedHypreBoomerAMG(PreparedHypreBoomerAMG &&) = delete; PreparedHypreBoomerAMG &operator=(PreparedHypreBoomerAMG &&) = delete; void SetOperator(const mfem::Operator &operation) override { const auto *matrix = dynamic_cast(&operation); if (matrix == nullptr) { throw std::invalid_argument("The BoomerAMG backend requires an mfem::HypreParMatrix surrogate."); } Refresh(*matrix); } void Mult( const mfem::Vector &rightHandSide, mfem::Vector &action ) const override { detail::verifyApplicationDimensions(*this, rightHandSide, action); m_solver->Mult(rightHandSide, action); int iterations = 0; m_solver->GetNumIterations(iterations); ++m_statistics.applications; m_statistics.lastInnerIterations = static_cast(iterations); m_statistics.innerIterations += static_cast(iterations); } void Refresh(const mfem::HypreParMatrix &matrix) { detail::verifySquarePositiveSize(matrix.Height(), matrix.Width()); if (matrix.Height() != Height()) { throw std::invalid_argument("The BoomerAMG backend cannot change size during refresh."); } m_sparseSurrogate = std::addressof(matrix); m_solver = std::make_unique(matrix); detail::configure(*m_solver, m_configuration.application); mfem::Vector setupRightHandSide(Width()); mfem::Vector setupAction(Height()); setupRightHandSide = 0.0; setupAction = 0.0; m_solver->Setup(setupRightHandSide, setupAction); ++m_statistics.setups; } [[nodiscard]] const Configuration &GetConfiguration() const noexcept { return m_configuration; } [[nodiscard]] const mfem::HypreParMatrix &GetSparseSurrogate() const noexcept { return *m_sparseSurrogate; } [[nodiscard]] const BackendStatistics &GetStatistics() const noexcept { return m_statistics; } private: Configuration m_configuration; const mfem::HypreParMatrix *m_sparseSurrogate{nullptr}; std::unique_ptr m_solver; mutable BackendStatistics m_statistics; }; [[nodiscard]] inline PreparedDiagonal prepare( Diagonal configuration, const mfem::Vector &diagonal ) { return PreparedDiagonal{std::move(configuration), diagonal}; } [[nodiscard]] inline PreparedMatrixFreeChebyshev prepare( MatrixFreeChebyshev configuration, const mfem::Operator &operation, const MPI_Comm communicator ) { return PreparedMatrixFreeChebyshev{std::move(configuration), operation, communicator}; } [[nodiscard]] inline PreparedDenseDirect prepare( DenseDirect configuration, const mfem::DenseMatrix &matrix ) { return PreparedDenseDirect{std::move(configuration), matrix}; } template [[nodiscard]] PreparedHypreBoomerAMG prepare( HypreBoomerAMG configuration, const mfem::HypreParMatrix &matrix ) { return PreparedHypreBoomerAMG{std::move(configuration), matrix}; } } // namespace mean_field::preconditioning::backend