SpECTRE Documentation Coverage Report
Current view: top level - Elliptic/SubdomainPreconditioners - MinusLaplacian.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 7.4 % 54 4
Test Date: 2026-10-22 17:29:42
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 2.0-1