SpECTRE Documentation Coverage Report
Current view: top level - Elliptic/SubdomainPreconditioners - MinusLaplacian.hpp Hit Total Coverage
Commit: 1f2210958b4f38fdc0400907ee7c6d5af5111418 Lines: 4 54 7.4 %
Date: 2025-12-05 05:03:31
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<DataVector>>>>,
      58             :               ::LinearSolver::Serial::Registrars::ExplicitInverse<double>>>>
      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             :  * \par Complex variables
      89             :  * This preconditioner can also be applied to complex variables, in which case
      90             :  * the Laplacian just applies to the real and imaginary part separately.
      91             :  *
      92             :  * \tparam Dim Spatial dimension
      93             :  * \tparam OptionsGroup The options group identifying the
      94             :  * `LinearSolver::Schwarz::Schwarz` solver that defines the subdomain geometry.
      95             :  * \tparam Solver Any class that provides a `solve` and a `reset` function,
      96             :  * but typically a `LinearSolver::Serial::LinearSolver`. The solver will be
      97             :  * factory-created from input-file options.
      98             :  */
      99             : template <size_t Dim, typename OptionsGroup,
     100             :           typename Solver = LinearSolver::Serial::LinearSolver<tmpl::list<
     101             :               ::LinearSolver::Serial::Registrars::Gmres<
     102             :                   ::LinearSolver::Schwarz::ElementCenteredSubdomainData<
     103             :                       Dim, tmpl::list<Poisson::Tags::Field<DataVector>>>>,
     104             :               ::LinearSolver::Serial::Registrars::ExplicitInverse<double>>>,
     105             :           typename LinearSolverRegistrars =
     106             :               tmpl::list<Registrars::MinusLaplacian<Dim, OptionsGroup, Solver>>>
     107           1 : class MinusLaplacian
     108             :     : public LinearSolver::Serial::LinearSolver<LinearSolverRegistrars> {
     109             :  private:
     110           0 :   using Base = LinearSolver::Serial::LinearSolver<LinearSolverRegistrars>;
     111           0 :   using StoredSolverType = tmpl::conditional_t<std::is_abstract_v<Solver>,
     112             :                                                std::unique_ptr<Solver>, Solver>;
     113             :   // Identifies an external block boundary, for boundary conditions
     114           0 :   using BoundaryId = std::pair<size_t, Direction<Dim>>;
     115             : 
     116             :  public:
     117           0 :   static constexpr size_t volume_dim = Dim;
     118           0 :   using options_group = OptionsGroup;
     119           0 :   using poisson_system =
     120             :       Poisson::FirstOrderSystem<Dim, Poisson::Geometry::FlatCartesian>;
     121           0 :   using BoundaryConditionsBase =
     122             :       typename poisson_system::boundary_conditions_base;
     123           0 :   using SubdomainOperator = elliptic::dg::subdomain_operator::SubdomainOperator<
     124             :       poisson_system, OptionsGroup,
     125             :       tmpl::list<Poisson::BoundaryConditions::Robin<Dim>>>;
     126           0 :   using SubdomainData = ::LinearSolver::Schwarz::ElementCenteredSubdomainData<
     127             :       Dim, tmpl::list<Poisson::Tags::Field<DataVector>>>;
     128             :   // Associates Dirichlet or Neumann conditions to every external block
     129             :   // boundary. For every configuration of this type we'll need a separate
     130             :   // solver, which may cache the Poisson operator matrix that imposes the
     131             :   // corresponding boundary conditions. This type is used as key in other maps,
     132             :   // so it must be hashable. `std::unordered_map` isn't supported by
     133             :   // `boost::hash`, but `std::map` is.
     134           0 :   using BoundaryConditionsSignature =
     135             :       std::map<BoundaryId, elliptic::BoundaryConditionType>;
     136           0 :   using solver_type = Solver;
     137             : 
     138           0 :   struct SolverOptionTag {
     139           0 :     static std::string name() { return "Solver"; }
     140           0 :     using type = StoredSolverType;
     141           0 :     static constexpr Options::String help =
     142             :         "The linear solver used to invert the Laplace operator. The solver is "
     143             :         "shared between tensor components with the same type of boundary "
     144             :         "conditions (Dirichlet-type or Neumann-type).";
     145             :   };
     146             : 
     147           0 :   struct BoundaryConditions {
     148           0 :     using type = Options::Auto<elliptic::BoundaryConditionType>;
     149           0 :     static constexpr Options::String help =
     150             :         "The boundary conditions imposed by the Laplace operator. Specify "
     151             :         "'Auto' to choose between homogeneous Dirichlet or Neumann boundary "
     152             :         "conditions automatically, based on the configuration of the the full "
     153             :         "operator.";
     154             :   };
     155             : 
     156           0 :   using options = tmpl::list<SolverOptionTag, BoundaryConditions>;
     157           0 :   static constexpr Options::String help =
     158             :       "Approximate the linear operator with a Laplace operator "
     159             :       "for every tensor component separately.";
     160             : 
     161           0 :   MinusLaplacian() = default;
     162           0 :   MinusLaplacian(MinusLaplacian&& /*rhs*/) = default;
     163           0 :   MinusLaplacian& operator=(MinusLaplacian&& /*rhs*/) = default;
     164           0 :   ~MinusLaplacian() = default;
     165           0 :   MinusLaplacian(const MinusLaplacian& rhs)
     166             :       : Base(rhs),
     167             :         solver_(rhs.clone_solver()),
     168             :         boundary_condition_type_(rhs.boundary_condition_type_) {}
     169           0 :   MinusLaplacian& operator=(const MinusLaplacian& rhs) {
     170             :     Base::operator=(rhs);
     171             :     solver_ = rhs.clone_solver();
     172             :     boundary_condition_type_ = rhs.boundary_condition_type_;
     173             :     return *this;
     174             :   }
     175             : 
     176             :   /// \cond
     177             :   explicit MinusLaplacian(CkMigrateMessage* m) : Base(m) {}
     178             :   using PUP::able::register_constructor;
     179             :   WRAPPED_PUPable_decl_template(MinusLaplacian);  // NOLINT
     180             :   /// \endcond
     181             : 
     182           0 :   MinusLaplacian(
     183             :       StoredSolverType solver,
     184             :       std::optional<elliptic::BoundaryConditionType> boundary_condition_type)
     185             :       : solver_(std::move(solver)),
     186             :         boundary_condition_type_(boundary_condition_type) {}
     187             : 
     188           0 :   const Solver& solver() const {
     189             :     if constexpr (std::is_abstract_v<Solver>) {
     190             :       return *solver_;
     191             :     } else {
     192             :       return solver_;
     193             :     }
     194             :   }
     195             : 
     196             :   /// The cached solvers for each unique boundary-condition signature. These are
     197             :   /// only exposed to allow verifying their consistency.
     198           1 :   const auto& cached_solvers() const { return solvers_; }
     199             : 
     200             :   /// Solve the equation \f$Ax=b\f$ by approximating \f$A\f$ with a Laplace
     201             :   /// operator for every tensor component in \f$x\f$.
     202             :   template <typename LinearOperator, typename VarsType, typename SourceType,
     203             :             typename... OperatorArgs>
     204           1 :   Convergence::HasConverged solve(
     205             :       gsl::not_null<VarsType*> solution, LinearOperator&& linear_operator,
     206             :       const SourceType& source,
     207             :       const std::tuple<OperatorArgs...>& operator_args) const;
     208             : 
     209           0 :   void reset() override {
     210             :     mutable_solver().reset();
     211             :     // These buffers depend on the operator being solved, so they are reset when
     212             :     // the operator changes. The other buffers only hold workspace memory so
     213             :     // they don't need to be reset.
     214             :     bc_signatures_ = std::nullopt;
     215             :     solvers_.clear();
     216             :     boundary_conditions_.clear();
     217             :   }
     218             : 
     219             :   // NOLINTNEXTLINE(google-runtime-references)
     220           0 :   void pup(PUP::er& p) override {
     221             :     Base::pup(p);
     222             :     // Serialize object state
     223             :     p | solver_;
     224             :     p | boundary_condition_type_;
     225             :     // Serialize caches that are possibly expensive to re-create
     226             :     p | bc_signatures_;
     227             :     p | solvers_;
     228             :     // Other properties are memory buffers that don't need to be serialized.
     229             :   }
     230             : 
     231           0 :   std::unique_ptr<Base> get_clone() const override {
     232             :     return std::make_unique<MinusLaplacian>(*this);
     233             :   }
     234             : 
     235             :  private:
     236             :   // For each tensor component, get the set of boundary conditions that should
     237             :   // be imposed by the Laplacian operator on that component, based on the domain
     238             :   // configuration. These are cached for successive solves of the same operator.
     239             :   // An empty list means the subdomain has no external boundaries.
     240             :   template <typename DbTagsList>
     241             :   const std::vector<BoundaryConditionsSignature>&
     242           0 :   get_cached_boundary_conditions_signatures(
     243             :       const db::DataBox<DbTagsList>& box) const {
     244             :     if (bc_signatures_.has_value()) {
     245             :       // Use cached value
     246             :       return *bc_signatures_;
     247             :     }
     248             :     bc_signatures_ = std::vector<BoundaryConditionsSignature>{};
     249             :     const auto& all_boundary_conditions =
     250             :         db::get<domain::Tags::ExternalBoundaryConditions<Dim>>(box);
     251             :     const auto collect_bc_signatures = [&bc_signatures = *bc_signatures_,
     252             :                                         &override_boundary_condition_type =
     253             :                                             boundary_condition_type_,
     254             :                                         &all_boundary_conditions](
     255             :                                            const BoundaryId& boundary_id) {
     256             :       if (not bc_signatures.empty() and
     257             :           bc_signatures[0].find(boundary_id) != bc_signatures[0].end()) {
     258             :         // We have already handled this block boundary in an earlier
     259             :         // iteration of the loop. This can happend when the domain is
     260             :         // h-refined, or when multiple elements in a subdomain face the
     261             :         // block boundary.
     262             :         return;
     263             :       }
     264             :       // Get the type of boundary condition (Dirichlet or Neumann) for
     265             :       // each tensor component from the domain configuration
     266             :       const auto& [block_id, direction] = boundary_id;
     267             :       const auto original_boundary_condition = dynamic_cast<
     268             :           const elliptic::BoundaryConditions::BoundaryCondition<Dim>*>(
     269             :           all_boundary_conditions.at(block_id).at(direction).get());
     270             :       ASSERT(original_boundary_condition != nullptr,
     271             :              "The boundary condition in block "
     272             :                  << block_id << ", direction " << direction
     273             :                  << " is not of the expected type "
     274             :                     "'elliptic::BoundaryConditions::BoundaryCondition<"
     275             :                  << Dim << ">'.");
     276             :       const std::vector<elliptic::BoundaryConditionType> bc_types =
     277             :           original_boundary_condition->boundary_condition_types();
     278             :       const size_t num_components = bc_types.size();
     279             :       if (bc_signatures.empty()) {
     280             :         bc_signatures.resize(num_components);
     281             :       } else {
     282             :         ASSERT(bc_signatures.size() == num_components,
     283             :                "Unexpected number of boundary-condition types. The "
     284             :                "boundary condition in block "
     285             :                    << block_id << ", direction " << direction << " returned "
     286             :                    << num_components << " items, but another returned "
     287             :                    << bc_signatures.size()
     288             :                    << " (one for each tensor component).");
     289             :       }
     290             :       for (size_t component = 0; component < num_components; ++component) {
     291             :         bc_signatures[component].emplace(
     292             :             boundary_id,
     293             :             override_boundary_condition_type.value_or(bc_types.at(component)));
     294             :       }
     295             :     };
     296             :     // Collect boundary-condition signatures from all elements in the subdomain
     297             :     const auto& element = db::get<domain::Tags::Element<Dim>>(box);
     298             :     for (const auto& direction : element.external_boundaries()) {
     299             :       collect_bc_signatures({element.id().block_id(), direction});
     300             :     }
     301             :     const auto& overlap_elements =
     302             :         db::get<LinearSolver::Schwarz::Tags::Overlaps<
     303             :             domain::Tags::Element<Dim>, Dim, OptionsGroup>>(box);
     304             :     for (const auto& [overlap_id, overlap_element] : overlap_elements) {
     305             :       (void)overlap_id;
     306             :       for (const auto& direction : overlap_element.external_boundaries()) {
     307             :         collect_bc_signatures({overlap_element.id().block_id(), direction});
     308             :       }
     309             :     }
     310             :     return *bc_signatures_;
     311             :   }
     312             : 
     313             :   // For a tensor component with the given boundary-condition configuration, get
     314             :   // the solver and the set of boundary conditions that will be passed to the
     315             :   // Poisson subdomain operator. The solver is cached for successive solves of
     316             :   // the same operator. Caching is very important for performance, since the
     317             :   // solver may build up an explicit matrix representation that is expensive to
     318             :   // re-create.
     319             :   std::pair<const Solver&,
     320             :             const std::unordered_map<BoundaryId, const BoundaryConditionsBase&,
     321             :                                      boost::hash<BoundaryId>>&>
     322           0 :   get_cached_solver_and_boundary_conditions(
     323             :       const std::vector<BoundaryConditionsSignature>& bc_signatures,
     324             :       const size_t component) const {
     325             :     if (bc_signatures.empty()) {
     326             :       // The subdomain has no external boundaries. We use the original solver.
     327             :       return {solver(), boundary_conditions_[{}]};
     328             :     }
     329             :     // Get the cached solver corresponding to this component's
     330             :     // boundary-condition configuration
     331             :     const auto& bc_signature = bc_signatures.at(component);
     332             :     if (solvers_.find(bc_signature) == solvers_.end()) {
     333             :       solvers_.emplace(bc_signature, clone_solver());
     334             :     }
     335             :     const Solver& cached_solver = [this, &bc_signature]() -> const Solver& {
     336             :       if constexpr (std::is_abstract_v<Solver>) {
     337             :         return *solvers_.at(bc_signature);
     338             :       } else {
     339             :         return solvers_.at(bc_signature);
     340             :       }
     341             :     }();
     342             :     // Get the cached set of boundary conditions for this component
     343             :     if (boundary_conditions_.find(bc_signature) == boundary_conditions_.end()) {
     344             :       auto& bc = boundary_conditions_[bc_signature];
     345             :       for (const auto& [boundary_id, bc_type] : bc_signature) {
     346             :         bc.emplace(boundary_id,
     347             :                    bc_type == elliptic::BoundaryConditionType::Dirichlet
     348             :                        ? dirichlet_bc
     349             :                        : neumann_bc);
     350             :       }
     351             :     }
     352             :     return {cached_solver, boundary_conditions_[bc_signature]};
     353             :   }
     354             : 
     355           0 :   Solver& mutable_solver() {
     356             :     if constexpr (std::is_abstract_v<Solver>) {
     357             :       return *solver_;
     358             :     } else {
     359             :       return solver_;
     360             :     }
     361             :   }
     362             : 
     363           0 :   StoredSolverType clone_solver() const {
     364             :     if constexpr (std::is_abstract_v<Solver>) {
     365             :       return solver_->get_clone();
     366             :     } else {
     367             :       return solver_;
     368             :     }
     369             :   }
     370             : 
     371             :   // The option-constructed solver for the separate Poisson problems. It is
     372             :   // cloned for each unique boundary-condition configuration of the tensor
     373             :   // components.
     374           0 :   StoredSolverType solver_{};
     375           0 :   std::optional<elliptic::BoundaryConditionType> boundary_condition_type_;
     376             : 
     377             :   // These are caches to keep track of the unique boundary-condition
     378             :   // configurations. Each configuration has its own solver, because the solver
     379             :   // may cache the operator to speed up repeated solves.
     380             :   // - The boundary-condition configuration for each tensor component, or an
     381             :   //   empty list if the subdomain has no external boundaries, or `std::nullopt`
     382             :   //   to signal a clean cache.
     383             :   // NOLINTNEXTLINE(spectre-mutable)
     384             :   mutable std::optional<std::vector<BoundaryConditionsSignature>>
     385           0 :       bc_signatures_{};
     386             :   // - A clone of the `solver_` for each unique boundary-condition configuration
     387             :   // NOLINTNEXTLINE(spectre-mutable)
     388             :   mutable std::unordered_map<BoundaryConditionsSignature, StoredSolverType,
     389             :                              boost::hash<BoundaryConditionsSignature>>
     390           0 :       solvers_{};
     391             :   // NOLINTNEXTLINE(spectre-mutable)
     392             :   mutable std::unordered_map<
     393             :       BoundaryConditionsSignature,
     394             :       std::unordered_map<BoundaryId, const BoundaryConditionsBase&,
     395             :                          boost::hash<BoundaryId>>,
     396             :       boost::hash<BoundaryConditionsSignature>>
     397           0 :       boundary_conditions_{};
     398             : 
     399             :   // These are memory buffers that are kept around for repeated solves. Possible
     400             :   // optimization: Free this memory at appropriate times, e.g. when the element
     401             :   // has completed a full subdomain solve and goes to the background. In some
     402             :   // cases the `subdomain_operator_` is never even used again in subsequent
     403             :   // subdomain solves because it is cached as a matrix (see
     404             :   // LinearSolver::Serial::ExplicitInverse), so we don't need the memory anymore
     405             :   // at all.
     406             :   // NOLINTNEXTLINE(spectre-mutable)
     407           0 :   mutable SubdomainOperator subdomain_operator_{};
     408             :   // NOLINTNEXTLINE(spectre-mutable)
     409           0 :   mutable SubdomainData source_{};
     410             :   // NOLINTNEXTLINE(spectre-mutable)
     411           0 :   mutable SubdomainData initial_guess_in_solution_out_{};
     412             : 
     413             :   // These boundary condition instances can be re-used for all tensor components
     414             :   // Note that the _linearized_ boundary conditions are applied by the subdomain
     415             :   // operator, so the constant (third argument) is irrelevant.
     416           0 :   const Poisson::BoundaryConditions::Robin<Dim> dirichlet_bc{1., 0., 0.};
     417           0 :   const Poisson::BoundaryConditions::Robin<Dim> neumann_bc{0., 1., 0.};
     418             : };
     419             : 
     420             : namespace detail {
     421             : template <size_t Dim, typename LhsTagsList, typename RhsTagsList>
     422             : void assign_component(
     423             :     const gsl::not_null<::LinearSolver::Schwarz::ElementCenteredSubdomainData<
     424             :         Dim, LhsTagsList>*>
     425             :         lhs,
     426             :     const ::LinearSolver::Schwarz::ElementCenteredSubdomainData<
     427             :         Dim, RhsTagsList>& rhs,
     428             :     const size_t lhs_component, const size_t rhs_component,
     429             :     // Use real or imaginary part of the lhs or rhs complex value. This is used
     430             :     // to apply the Laplacian to the real or imaginary part separately.
     431             :     const bool lhs_imag = false, const bool rhs_imag = false) {
     432             :   using LhsValueType =
     433             :       typename ::LinearSolver::Schwarz::ElementCenteredSubdomainData<
     434             :           Dim, LhsTagsList>::value_type;
     435             :   // Possible optimization: Once we have non-owning Variables we can use a view
     436             :   // into the rhs here instead of copying.
     437             :   const size_t num_points_element = rhs.element_data.number_of_grid_points();
     438             :   for (size_t i = 0; i < num_points_element; ++i) {
     439             :     auto& lhs_i =
     440             :         lhs->element_data.data()[lhs_component * num_points_element + i];
     441             :     const auto& rhs_i =
     442             :         rhs.element_data.data()[rhs_component * num_points_element + i];
     443             :     const double rhs_i_fundamental = rhs_imag ? imag(rhs_i) : real(rhs_i);
     444             :     if constexpr (std::is_same_v<LhsValueType, std::complex<double>>) {
     445             :       if (lhs_imag) {
     446             :         lhs_i.imag(rhs_i_fundamental);
     447             :       } else {
     448             :         lhs_i.real(rhs_i_fundamental);
     449             :       }
     450             :     } else {
     451             :       lhs_i = rhs_i_fundamental;
     452             :     }
     453             :   }
     454             :   for (const auto& [overlap_id, rhs_data] : rhs.overlap_data) {
     455             :     const size_t num_points_overlap = rhs_data.number_of_grid_points();
     456             :     // The random-access operation is relatively slow because it computes a
     457             :     // hash, so it's important for performance to avoid repeating it in every
     458             :     // iteration of the loop below.
     459             :     auto& lhs_vars = lhs->overlap_data[overlap_id];
     460             :     for (size_t i = 0; i < num_points_overlap; ++i) {
     461             :       auto& lhs_i = lhs_vars.data()[lhs_component * num_points_overlap + i];
     462             :       const auto& rhs_i =
     463             :           rhs_data.data()[rhs_component * num_points_overlap + i];
     464             :       const double rhs_i_fundamental = rhs_imag ? imag(rhs_i) : real(rhs_i);
     465             :       if constexpr (std::is_same_v<LhsValueType, std::complex<double>>) {
     466             :         if (lhs_imag) {
     467             :           lhs_i.imag(rhs_i_fundamental);
     468             :         } else {
     469             :           lhs_i.real(rhs_i_fundamental);
     470             :         }
     471             :       } else {
     472             :         lhs_i = rhs_i_fundamental;
     473             :       }
     474             :     }
     475             :   }
     476             : }
     477             : }  // namespace detail
     478             : 
     479             : template <size_t Dim, typename OptionsGroup, typename Solver,
     480             :           typename LinearSolverRegistrars>
     481             : template <typename LinearOperator, typename VarsType, typename SourceType,
     482             :           typename... OperatorArgs>
     483             : Convergence::HasConverged
     484           0 : MinusLaplacian<Dim, OptionsGroup, Solver, LinearSolverRegistrars>::solve(
     485             :     const gsl::not_null<VarsType*> initial_guess_in_solution_out,
     486             :     LinearOperator&& /*linear_operator*/, const SourceType& source,
     487             :     const std::tuple<OperatorArgs...>& operator_args) const {
     488             :   const auto& box = get<0>(operator_args);
     489             :   // Solve each component of the source variables in turn, assuming the operator
     490             :   // is a Laplacian. For each component we select either homogeneous Dirichlet
     491             :   // or Neumann boundary conditions, based on the type of boundary conditions
     492             :   // imposed by the full operator.
     493             :   static constexpr size_t num_components =
     494             :       VarsType::ElementData::number_of_independent_components;
     495             :   source_.destructive_resize(source);
     496             :   initial_guess_in_solution_out_.destructive_resize(source);
     497             :   const std::vector<BoundaryConditionsSignature>& bc_signatures =
     498             :       get_cached_boundary_conditions_signatures(box);
     499             :   ASSERT(bc_signatures.empty() or bc_signatures.size() == num_components,
     500             :          "Expected " << num_components
     501             :                      << " boundary-condition signatures (one per tensor "
     502             :                         "component), but got "
     503             :                      << bc_signatures.size() << ".");
     504             :   // Possible optimization: elide when num_components is 1
     505             :   using ValueType = typename VarsType::value_type;
     506             :   constexpr auto complex_components = []() {
     507             :     if constexpr (std::is_same_v<ValueType, std::complex<double>>) {
     508             :       return std::array<bool, 2>{{false, true}};
     509             :     } else {
     510             :       return std::array<bool, 1>{{false}};
     511             :     }
     512             :   }();
     513             :   for (size_t component = 0; component < num_components; ++component) {
     514             :     const auto& [solver, boundary_conditions] =
     515             :         get_cached_solver_and_boundary_conditions(bc_signatures, component);
     516             :     // Solve real and imaginary parts separately, if applicable
     517             :     for (const bool imag_component : complex_components) {
     518             :       detail::assign_component(make_not_null(&source_), source, 0, component,
     519             :                                false, imag_component);
     520             :       detail::assign_component(make_not_null(&initial_guess_in_solution_out_),
     521             :                                *initial_guess_in_solution_out, 0, component,
     522             :                                false, imag_component);
     523             :       solver.solve(make_not_null(&initial_guess_in_solution_out_),
     524             :                    subdomain_operator_, source_,
     525             :                    std::forward_as_tuple(box, boundary_conditions));
     526             :       detail::assign_component(initial_guess_in_solution_out,
     527             :                                initial_guess_in_solution_out_, component, 0,
     528             :                                imag_component, false);
     529             :     }
     530             :   }
     531             :   return {0, 0};
     532             : }
     533             : 
     534             : /// \cond
     535             : template <size_t Dim, typename OptionsGroup, typename Solver,
     536             :           typename LinearSolverRegistrars>
     537             : // NOLINTNEXTLINE
     538             : PUP::able::PUP_ID MinusLaplacian<Dim, OptionsGroup, Solver,
     539             :                                  LinearSolverRegistrars>::my_PUP_ID = 0;
     540             : /// \endcond
     541             : 
     542             : }  // namespace elliptic::subdomain_preconditioners

Generated by: LCOV version 1.14