SpECTRE Documentation Coverage Report
Current view: top level - Elliptic/SubdomainPreconditioners - MinusLaplacian.hpp Hit Total Coverage
Commit: 5b6dac11263b5fb9107cb6ea064c64c61b65a417 Lines: 4 54 7.4 %
Date: 2024-04-19 22:56:45
Legend: Lines: hit not hit

          Line data    Source code
       1           0 : // Distributed under the MIT License.
       2             : // See LICENSE.txt for details.
       3             : 
       4             : #pragma once
       5             : 
       6             : #include <boost/functional/hash.hpp>
       7             : #include <cstddef>
       8             : #include <map>
       9             : #include <memory>
      10             : #include <optional>
      11             : #include <tuple>
      12             : #include <unordered_map>
      13             : #include <utility>
      14             : #include <vector>
      15             : 
      16             : #include "DataStructures/DataBox/DataBox.hpp"
      17             : #include "Domain/Creators/Tags/ExternalBoundaryConditions.hpp"
      18             : #include "Domain/Structure/Direction.hpp"
      19             : #include "Domain/Structure/Element.hpp"
      20             : #include "Domain/Tags.hpp"
      21             : #include "Elliptic/BoundaryConditions/BoundaryCondition.hpp"
      22             : #include "Elliptic/BoundaryConditions/BoundaryConditionType.hpp"
      23             : #include "Elliptic/DiscontinuousGalerkin/SubdomainOperator/SubdomainOperator.hpp"
      24             : #include "Elliptic/Systems/Poisson/BoundaryConditions/Robin.hpp"
      25             : #include "Elliptic/Systems/Poisson/FirstOrderSystem.hpp"
      26             : #include "Elliptic/Systems/Poisson/Tags.hpp"
      27             : #include "NumericalAlgorithms/Convergence/HasConverged.hpp"
      28             : #include "NumericalAlgorithms/LinearSolver/ExplicitInverse.hpp"
      29             : #include "NumericalAlgorithms/LinearSolver/Gmres.hpp"
      30             : #include "NumericalAlgorithms/LinearSolver/LinearSolver.hpp"
      31             : #include "Options/Auto.hpp"
      32             : #include "Options/String.hpp"
      33             : #include "ParallelAlgorithms/LinearSolver/Schwarz/ElementCenteredSubdomainData.hpp"
      34             : #include "ParallelAlgorithms/LinearSolver/Schwarz/Tags.hpp"
      35             : #include "Utilities/ErrorHandling/Assert.hpp"
      36             : #include "Utilities/MakeWithValue.hpp"
      37             : #include "Utilities/Serialization/CharmPupable.hpp"
      38             : #include "Utilities/TMPL.hpp"
      39             : 
      40             : /// Linear solvers that approximately invert the
      41             : /// `elliptic::dg::subdomain_operator::SubdomainOperator` to make the Schwarz
      42             : /// subdomain solver converge faster.
      43             : /// \see LinearSolver::Schwarz::Schwarz
      44           1 : namespace elliptic::subdomain_preconditioners {
      45             : 
      46             : /// \cond
      47             : template <size_t Dim, typename OptionsGroup, typename Solver,
      48             :           typename LinearSolverRegistrars>
      49             : struct MinusLaplacian;
      50             : /// \endcond
      51             : 
      52           0 : namespace Registrars {
      53             : template <size_t Dim, typename OptionsGroup,
      54             :           typename Solver = LinearSolver::Serial::LinearSolver<tmpl::list<
      55             :               ::LinearSolver::Serial::Registrars::Gmres<
      56             :                   ::LinearSolver::Schwarz::ElementCenteredSubdomainData<
      57             :                       Dim, tmpl::list<Poisson::Tags::Field>>>,
      58             :               ::LinearSolver::Serial::Registrars::ExplicitInverse>>>
      59           0 : struct MinusLaplacian {
      60             :   template <typename LinearSolverRegistrars>
      61           0 :   using f = subdomain_preconditioners::MinusLaplacian<Dim, OptionsGroup, Solver,
      62             :                                                       LinearSolverRegistrars>;
      63             : };
      64             : }  // namespace Registrars
      65             : 
      66             : /*!
      67             :  * \brief Approximate the subdomain operator with a flat-space Laplacian
      68             :  * for every tensor component separately.
      69             :  *
      70             :  * This linear solver applies the `Solver` to every tensor component in
      71             :  * turn, approximating the subdomain operator with a flat-space Laplacian.
      72             :  * This can be a lot cheaper than solving the
      73             :  * full subdomain operator and can provide effective preconditioning for an
      74             :  * iterative subdomain solver. The approximation is better the closer the
      75             :  * original PDEs are to a set of decoupled flat-space Poisson equations.
      76             :  *
      77             :  * \par Boundary conditions
      78             :  * At external boundaries we impose homogeneous Dirichlet or Neumann boundary
      79             :  * conditions on the Poisson sub-problems. For tensor components and element
      80             :  * faces where the original boundary conditions are of Dirichlet-type we choose
      81             :  * homogeneous Dirichlet boundary conditions, and for those where the original
      82             :  * boundary conditions are of Neumann-type we choose homogeneous Neumann
      83             :  * boundary conditions. This means we may end up with more than one distinct
      84             :  * Poisson operator on subdomains with external boundaries, one per unique
      85             :  * combination of element face and boundary-condition type among the tensor
      86             :  * components.
      87             :  *
      88             :  * \tparam Dim Spatial dimension
      89             :  * \tparam OptionsGroup The options group identifying the
      90             :  * `LinearSolver::Schwarz::Schwarz` solver that defines the subdomain geometry.
      91             :  * \tparam Solver Any class that provides a `solve` and a `reset` function,
      92             :  * but typically a `LinearSolver::Serial::LinearSolver`. The solver will be
      93             :  * factory-created from input-file options.
      94             :  */
      95             : template <size_t Dim, typename OptionsGroup,
      96             :           typename Solver = LinearSolver::Serial::LinearSolver<tmpl::list<
      97             :               ::LinearSolver::Serial::Registrars::Gmres<
      98             :                   ::LinearSolver::Schwarz::ElementCenteredSubdomainData<
      99             :                       Dim, tmpl::list<Poisson::Tags::Field>>>,
     100             :               ::LinearSolver::Serial::Registrars::ExplicitInverse>>,
     101             :           typename LinearSolverRegistrars =
     102             :               tmpl::list<Registrars::MinusLaplacian<Dim, OptionsGroup, Solver>>>
     103           1 : class MinusLaplacian
     104             :     : public LinearSolver::Serial::LinearSolver<LinearSolverRegistrars> {
     105             :  private:
     106           0 :   using Base = LinearSolver::Serial::LinearSolver<LinearSolverRegistrars>;
     107           0 :   using StoredSolverType = tmpl::conditional_t<std::is_abstract_v<Solver>,
     108             :                                                std::unique_ptr<Solver>, Solver>;
     109             :   // Identifies an external block boundary, for boundary conditions
     110           0 :   using BoundaryId = std::pair<size_t, Direction<Dim>>;
     111             : 
     112             :  public:
     113           0 :   static constexpr size_t volume_dim = Dim;
     114           0 :   using options_group = OptionsGroup;
     115           0 :   using poisson_system =
     116             :       Poisson::FirstOrderSystem<Dim, Poisson::Geometry::FlatCartesian>;
     117           0 :   using BoundaryConditionsBase =
     118             :       typename poisson_system::boundary_conditions_base;
     119           0 :   using SubdomainOperator = elliptic::dg::subdomain_operator::SubdomainOperator<
     120             :       poisson_system, OptionsGroup,
     121             :       tmpl::list<Poisson::BoundaryConditions::Robin<Dim>>>;
     122           0 :   using SubdomainData = ::LinearSolver::Schwarz::ElementCenteredSubdomainData<
     123             :       Dim, tmpl::list<Poisson::Tags::Field>>;
     124             :   // Associates Dirichlet or Neumann conditions to every external block
     125             :   // boundary. For every configuration of this type we'll need a separate
     126             :   // solver, which may cache the Poisson operator matrix that imposes the
     127             :   // corresponding boundary conditions. This type is used as key in other maps,
     128             :   // so it must be hashable. `std::unordered_map` isn't supported by
     129             :   // `boost::hash`, but `std::map` is.
     130           0 :   using BoundaryConditionsSignature =
     131             :       std::map<BoundaryId, elliptic::BoundaryConditionType>;
     132           0 :   using solver_type = Solver;
     133             : 
     134           0 :   struct SolverOptionTag {
     135           0 :     static std::string name() { return "Solver"; }
     136           0 :     using type = StoredSolverType;
     137           0 :     static constexpr Options::String help =
     138             :         "The linear solver used to invert the Laplace operator. The solver is "
     139             :         "shared between tensor components with the same type of boundary "
     140             :         "conditions (Dirichlet-type or Neumann-type).";
     141             :   };
     142             : 
     143           0 :   struct BoundaryConditions {
     144           0 :     using type = Options::Auto<elliptic::BoundaryConditionType>;
     145           0 :     static constexpr Options::String help =
     146             :         "The boundary conditions imposed by the Laplace operator. Specify "
     147             :         "'Auto' to choose between homogeneous Dirichlet or Neumann boundary "
     148             :         "conditions automatically, based on the configuration of the the full "
     149             :         "operator.";
     150             :   };
     151             : 
     152           0 :   using options = tmpl::list<SolverOptionTag, BoundaryConditions>;
     153           0 :   static constexpr Options::String help =
     154             :       "Approximate the linear operator with a Laplace operator "
     155             :       "for every tensor component separately.";
     156             : 
     157           0 :   MinusLaplacian() = default;
     158           0 :   MinusLaplacian(MinusLaplacian&& /*rhs*/) = default;
     159           0 :   MinusLaplacian& operator=(MinusLaplacian&& /*rhs*/) = default;
     160           0 :   ~MinusLaplacian() = default;
     161           0 :   MinusLaplacian(const MinusLaplacian& rhs)
     162             :       : Base(rhs),
     163             :         solver_(rhs.clone_solver()),
     164             :         boundary_condition_type_(rhs.boundary_condition_type_) {}
     165           0 :   MinusLaplacian& operator=(const MinusLaplacian& rhs) {
     166             :     Base::operator=(rhs);
     167             :     solver_ = rhs.clone_solver();
     168             :     boundary_condition_type_ = rhs.boundary_condition_type_;
     169             :     return *this;
     170             :   }
     171             : 
     172             :   /// \cond
     173             :   explicit MinusLaplacian(CkMigrateMessage* m) : Base(m) {}
     174             :   using PUP::able::register_constructor;
     175             :   WRAPPED_PUPable_decl_template(MinusLaplacian);  // NOLINT
     176             :   /// \endcond
     177             : 
     178           0 :   MinusLaplacian(
     179             :       StoredSolverType solver,
     180             :       std::optional<elliptic::BoundaryConditionType> boundary_condition_type)
     181             :       : solver_(std::move(solver)),
     182             :         boundary_condition_type_(boundary_condition_type) {}
     183             : 
     184           0 :   const Solver& solver() const {
     185             :     if constexpr (std::is_abstract_v<Solver>) {
     186             :       return *solver_;
     187             :     } else {
     188             :       return solver_;
     189             :     }
     190             :   }
     191             : 
     192             :   /// The cached solvers for each unique boundary-condition signature. These are
     193             :   /// only exposed to allow verifying their consistency.
     194           1 :   const auto& cached_solvers() const { return solvers_; }
     195             : 
     196             :   /// Solve the equation \f$Ax=b\f$ by approximating \f$A\f$ with a Laplace
     197             :   /// operator for every tensor component in \f$x\f$.
     198             :   template <typename LinearOperator, typename VarsType, typename SourceType,
     199             :             typename... OperatorArgs>
     200           1 :   Convergence::HasConverged solve(
     201             :       gsl::not_null<VarsType*> solution, LinearOperator&& linear_operator,
     202             :       const SourceType& source,
     203             :       const std::tuple<OperatorArgs...>& operator_args) const;
     204             : 
     205           0 :   void reset() override {
     206             :     mutable_solver().reset();
     207             :     // These buffers depend on the operator being solved, so they are reset when
     208             :     // the operator changes. The other buffers only hold workspace memory so
     209             :     // they don't need to be reset.
     210             :     bc_signatures_ = std::nullopt;
     211             :     solvers_.clear();
     212             :     boundary_conditions_.clear();
     213             :   }
     214             : 
     215             :   // NOLINTNEXTLINE(google-runtime-references)
     216           0 :   void pup(PUP::er& p) override {
     217             :     Base::pup(p);
     218             :     // Serialize object state
     219             :     p | solver_;
     220             :     p | boundary_condition_type_;
     221             :     // Serialize caches that are possibly expensive to re-create
     222             :     p | bc_signatures_;
     223             :     p | solvers_;
     224             :     // Other properties are memory buffers that don't need to be serialized.
     225             :   }
     226             : 
     227           0 :   std::unique_ptr<Base> get_clone() const override {
     228             :     return std::make_unique<MinusLaplacian>(*this);
     229             :   }
     230             : 
     231             :  private:
     232             :   // For each tensor component, get the set of boundary conditions that should
     233             :   // be imposed by the Laplacian operator on that component, based on the domain
     234             :   // configuration. These are cached for successive solves of the same operator.
     235             :   // An empty list means the subdomain has no external boundaries.
     236             :   template <typename DbTagsList>
     237             :   const std::vector<BoundaryConditionsSignature>&
     238           0 :   get_cached_boundary_conditions_signatures(
     239             :       const db::DataBox<DbTagsList>& box) const {
     240             :     if (bc_signatures_.has_value()) {
     241             :       // Use cached value
     242             :       return *bc_signatures_;
     243             :     }
     244             :     bc_signatures_ = std::vector<BoundaryConditionsSignature>{};
     245             :     const auto& all_boundary_conditions =
     246             :         db::get<domain::Tags::ExternalBoundaryConditions<Dim>>(box);
     247             :     const auto collect_bc_signatures = [&bc_signatures = *bc_signatures_,
     248             :                                         &override_boundary_condition_type =
     249             :                                             boundary_condition_type_,
     250             :                                         &all_boundary_conditions](
     251             :                                            const BoundaryId& boundary_id) {
     252             :       if (not bc_signatures.empty() and
     253             :           bc_signatures[0].find(boundary_id) != bc_signatures[0].end()) {
     254             :         // We have already handled this block boundary in an earlier
     255             :         // iteration of the loop. This can happend when the domain is
     256             :         // h-refined, or when multiple elements in a subdomain face the
     257             :         // block boundary.
     258             :         return;
     259             :       }
     260             :       // Get the type of boundary condition (Dirichlet or Neumann) for
     261             :       // each tensor component from the domain configuration
     262             :       const auto& [block_id, direction] = boundary_id;
     263             :       const auto original_boundary_condition = dynamic_cast<
     264             :           const elliptic::BoundaryConditions::BoundaryCondition<Dim>*>(
     265             :           all_boundary_conditions.at(block_id).at(direction).get());
     266             :       ASSERT(original_boundary_condition != nullptr,
     267             :              "The boundary condition in block "
     268             :                  << block_id << ", direction " << direction
     269             :                  << " is not of the expected type "
     270             :                     "'elliptic::BoundaryConditions::BoundaryCondition<"
     271             :                  << Dim << ">'.");
     272             :       const std::vector<elliptic::BoundaryConditionType> bc_types =
     273             :           original_boundary_condition->boundary_condition_types();
     274             :       const size_t num_components = bc_types.size();
     275             :       if (bc_signatures.empty()) {
     276             :         bc_signatures.resize(num_components);
     277             :       } else {
     278             :         ASSERT(bc_signatures.size() == num_components,
     279             :                "Unexpected number of boundary-condition types. The "
     280             :                "boundary condition in block "
     281             :                    << block_id << ", direction " << direction << " returned "
     282             :                    << num_components << " items, but another returned "
     283             :                    << bc_signatures.size()
     284             :                    << " (one for each tensor component).");
     285             :       }
     286             :       for (size_t component = 0; component < num_components; ++component) {
     287             :         bc_signatures[component].emplace(
     288             :             boundary_id,
     289             :             override_boundary_condition_type.value_or(bc_types.at(component)));
     290             :       }
     291             :     };
     292             :     // Collect boundary-condition signatures from all elements in the subdomain
     293             :     const auto& element = db::get<domain::Tags::Element<Dim>>(box);
     294             :     for (const auto& direction : element.external_boundaries()) {
     295             :       collect_bc_signatures({element.id().block_id(), direction});
     296             :     }
     297             :     const auto& overlap_elements =
     298             :         db::get<LinearSolver::Schwarz::Tags::Overlaps<
     299             :             domain::Tags::Element<Dim>, Dim, OptionsGroup>>(box);
     300             :     for (const auto& [overlap_id, overlap_element] : overlap_elements) {
     301             :       (void)overlap_id;
     302             :       for (const auto& direction : overlap_element.external_boundaries()) {
     303             :         collect_bc_signatures({overlap_element.id().block_id(), direction});
     304             :       }
     305             :     }
     306             :     return *bc_signatures_;
     307             :   }
     308             : 
     309             :   // For a tensor component with the given boundary-condition configuration, get
     310             :   // the solver and the set of boundary conditions that will be passed to the
     311             :   // Poisson subdomain operator. The solver is cached for successive solves of
     312             :   // the same operator. Caching is very important for performance, since the
     313             :   // solver may build up an explicit matrix representation that is expensive to
     314             :   // re-create.
     315             :   std::pair<const Solver&,
     316             :             const std::unordered_map<BoundaryId, const BoundaryConditionsBase&,
     317             :                                      boost::hash<BoundaryId>>&>
     318           0 :   get_cached_solver_and_boundary_conditions(
     319             :       const std::vector<BoundaryConditionsSignature>& bc_signatures,
     320             :       const size_t component) const {
     321             :     if (bc_signatures.empty()) {
     322             :       // The subdomain has no external boundaries. We use the original solver.
     323             :       return {solver(), boundary_conditions_[{}]};
     324             :     }
     325             :     // Get the cached solver corresponding to this component's
     326             :     // boundary-condition configuration
     327             :     const auto& bc_signature = bc_signatures.at(component);
     328             :     if (solvers_.find(bc_signature) == solvers_.end()) {
     329             :       solvers_.emplace(bc_signature, clone_solver());
     330             :     }
     331             :     const Solver& cached_solver = [this, &bc_signature]() -> const Solver& {
     332             :       if constexpr (std::is_abstract_v<Solver>) {
     333             :         return *solvers_.at(bc_signature);
     334             :       } else {
     335             :         return solvers_.at(bc_signature);
     336             :       }
     337             :     }();
     338             :     // Get the cached set of boundary conditions for this component
     339             :     if (boundary_conditions_.find(bc_signature) == boundary_conditions_.end()) {
     340             :       auto& bc = boundary_conditions_[bc_signature];
     341             :       for (const auto& [boundary_id, bc_type] : bc_signature) {
     342             :         bc.emplace(boundary_id,
     343             :                    bc_type == elliptic::BoundaryConditionType::Dirichlet
     344             :                        ? dirichlet_bc
     345             :                        : neumann_bc);
     346             :       }
     347             :     }
     348             :     return {cached_solver, boundary_conditions_[bc_signature]};
     349             :   }
     350             : 
     351           0 :   Solver& mutable_solver() {
     352             :     if constexpr (std::is_abstract_v<Solver>) {
     353             :       return *solver_;
     354             :     } else {
     355             :       return solver_;
     356             :     }
     357             :   }
     358             : 
     359           0 :   StoredSolverType clone_solver() const {
     360             :     if constexpr (std::is_abstract_v<Solver>) {
     361             :       return solver_->get_clone();
     362             :     } else {
     363             :       return solver_;
     364             :     }
     365             :   }
     366             : 
     367             :   // The option-constructed solver for the separate Poisson problems. It is
     368             :   // cloned for each unique boundary-condition configuration of the tensor
     369             :   // components.
     370           0 :   StoredSolverType solver_{};
     371           0 :   std::optional<elliptic::BoundaryConditionType> boundary_condition_type_;
     372             : 
     373             :   // These are caches to keep track of the unique boundary-condition
     374             :   // configurations. Each configuration has its own solver, because the solver
     375             :   // may cache the operator to speed up repeated solves.
     376             :   // - The boundary-condition configuration for each tensor component, or an
     377             :   //   empty list if the subdomain has no external boundaries, or `std::nullopt`
     378             :   //   to signal a clean cache.
     379             :   // NOLINTNEXTLINE(spectre-mutable)
     380             :   mutable std::optional<std::vector<BoundaryConditionsSignature>>
     381           0 :       bc_signatures_{};
     382             :   // - A clone of the `solver_` for each unique boundary-condition configuration
     383             :   // NOLINTNEXTLINE(spectre-mutable)
     384             :   mutable std::unordered_map<BoundaryConditionsSignature, StoredSolverType,
     385             :                              boost::hash<BoundaryConditionsSignature>>
     386           0 :       solvers_{};
     387             :   // NOLINTNEXTLINE(spectre-mutable)
     388             :   mutable std::unordered_map<
     389             :       BoundaryConditionsSignature,
     390             :       std::unordered_map<BoundaryId, const BoundaryConditionsBase&,
     391             :                          boost::hash<BoundaryId>>,
     392             :       boost::hash<BoundaryConditionsSignature>>
     393           0 :       boundary_conditions_{};
     394             : 
     395             :   // These are memory buffers that are kept around for repeated solves. Possible
     396             :   // optimization: Free this memory at appropriate times, e.g. when the element
     397             :   // has completed a full subdomain solve and goes to the background. In some
     398             :   // cases the `subdomain_operator_` is never even used again in subsequent
     399             :   // subdomain solves because it is cached as a matrix (see
     400             :   // LinearSolver::Serial::ExplicitInverse), so we don't need the memory anymore
     401             :   // at all.
     402             :   // NOLINTNEXTLINE(spectre-mutable)
     403           0 :   mutable SubdomainOperator subdomain_operator_{};
     404             :   // NOLINTNEXTLINE(spectre-mutable)
     405           0 :   mutable SubdomainData source_{};
     406             :   // NOLINTNEXTLINE(spectre-mutable)
     407           0 :   mutable SubdomainData initial_guess_in_solution_out_{};
     408             : 
     409             :   // These boundary condition instances can be re-used for all tensor components
     410             :   // Note that the _linearized_ boundary conditions are applied by the subdomain
     411             :   // operator, so the constant (third argument) is irrelevant.
     412           0 :   const Poisson::BoundaryConditions::Robin<Dim> dirichlet_bc{1., 0., 0.};
     413           0 :   const Poisson::BoundaryConditions::Robin<Dim> neumann_bc{0., 1., 0.};
     414             : };
     415             : 
     416             : namespace detail {
     417             : template <size_t Dim, typename LhsTagsList, typename RhsTagsList>
     418             : void assign_component(
     419             :     const gsl::not_null<::LinearSolver::Schwarz::ElementCenteredSubdomainData<
     420             :         Dim, LhsTagsList>*>
     421             :         lhs,
     422             :     const ::LinearSolver::Schwarz::ElementCenteredSubdomainData<
     423             :         Dim, RhsTagsList>& rhs,
     424             :     const size_t lhs_component, const size_t rhs_component) {
     425             :   // Possible optimization: Once we have non-owning Variables we can use a view
     426             :   // into the rhs here instead of copying.
     427             :   const size_t num_points_element = rhs.element_data.number_of_grid_points();
     428             :   for (size_t i = 0; i < num_points_element; ++i) {
     429             :     lhs->element_data.data()[lhs_component * num_points_element + i] =
     430             :         rhs.element_data.data()[rhs_component * num_points_element + i];
     431             :   }
     432             :   for (const auto& [overlap_id, rhs_data] : rhs.overlap_data) {
     433             :     const size_t num_points_overlap = rhs_data.number_of_grid_points();
     434             :     // The random-access operation is relatively slow because it computes a
     435             :     // hash, so it's important for performance to avoid repeating it in every
     436             :     // iteration of the loop below.
     437             :     auto& lhs_vars = lhs->overlap_data[overlap_id];
     438             :     for (size_t i = 0; i < num_points_overlap; ++i) {
     439             :       lhs_vars.data()[lhs_component * num_points_overlap + i] =
     440             :           rhs_data.data()[rhs_component * num_points_overlap + i];
     441             :     }
     442             :   }
     443             : }
     444             : }  // namespace detail
     445             : 
     446             : template <size_t Dim, typename OptionsGroup, typename Solver,
     447             :           typename LinearSolverRegistrars>
     448             : template <typename LinearOperator, typename VarsType, typename SourceType,
     449             :           typename... OperatorArgs>
     450             : Convergence::HasConverged
     451           0 : MinusLaplacian<Dim, OptionsGroup, Solver, LinearSolverRegistrars>::solve(
     452             :     const gsl::not_null<VarsType*> initial_guess_in_solution_out,
     453             :     LinearOperator&& /*linear_operator*/, const SourceType& source,
     454             :     const std::tuple<OperatorArgs...>& operator_args) const {
     455             :   const auto& box = get<0>(operator_args);
     456             :   // Solve each component of the source variables in turn, assuming the operator
     457             :   // is a Laplacian. For each component we select either homogeneous Dirichlet
     458             :   // or Neumann boundary conditions, based on the type of boundary conditions
     459             :   // imposed by the full operator.
     460             :   static constexpr size_t num_components =
     461             :       VarsType::ElementData::number_of_independent_components;
     462             :   source_.destructive_resize(source);
     463             :   initial_guess_in_solution_out_.destructive_resize(source);
     464             :   const std::vector<BoundaryConditionsSignature>& bc_signatures =
     465             :       get_cached_boundary_conditions_signatures(box);
     466             :   ASSERT(bc_signatures.empty() or bc_signatures.size() == num_components,
     467             :          "Expected " << num_components
     468             :                      << " boundary-condition signatures (one per tensor "
     469             :                         "component), but got "
     470             :                      << bc_signatures.size() << ".");
     471             :   // Possible optimization: elide when num_components is 1
     472             :   for (size_t component = 0; component < num_components; ++component) {
     473             :     detail::assign_component(make_not_null(&source_), source, 0, component);
     474             :     detail::assign_component(make_not_null(&initial_guess_in_solution_out_),
     475             :                              *initial_guess_in_solution_out, 0, component);
     476             :     const auto& [solver, boundary_conditions] =
     477             :         get_cached_solver_and_boundary_conditions(bc_signatures, component);
     478             :     solver.solve(make_not_null(&initial_guess_in_solution_out_),
     479             :                  subdomain_operator_, source_,
     480             :                  std::forward_as_tuple(box, boundary_conditions));
     481             :     detail::assign_component(initial_guess_in_solution_out,
     482             :                              initial_guess_in_solution_out_, component, 0);
     483             :   }
     484             :   return {0, 0};
     485             : }
     486             : 
     487             : /// \cond
     488             : template <size_t Dim, typename OptionsGroup, typename Solver,
     489             :           typename LinearSolverRegistrars>
     490             : // NOLINTNEXTLINE
     491             : PUP::able::PUP_ID MinusLaplacian<Dim, OptionsGroup, Solver,
     492             :                                  LinearSolverRegistrars>::my_PUP_ID = 0;
     493             : /// \endcond
     494             : 
     495             : }  // namespace elliptic::subdomain_preconditioners

Generated by: LCOV version 1.14