SpECTRE Documentation Coverage Report
Current view: top level - NumericalAlgorithms/LinearSolver - ExplicitInverse.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 21.2 % 33 7
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 <algorithm>
       7              : #include <cstddef>
       8              : #include <fstream>
       9              : #include <string>
      10              : #include <tuple>
      11              : #include <vector>
      12              : 
      13              : #include "DataStructures/DataBox/DataBox.hpp"
      14              : #include "DataStructures/DynamicMatrix.hpp"
      15              : #include "DataStructures/DynamicVector.hpp"
      16              : #include "NumericalAlgorithms/Convergence/HasConverged.hpp"
      17              : #include "NumericalAlgorithms/LinearSolver/BuildMatrix.hpp"
      18              : #include "NumericalAlgorithms/LinearSolver/LinearSolver.hpp"
      19              : #include "Options/Auto.hpp"
      20              : #include "Options/String.hpp"
      21              : #include "Parallel/Tags/ArrayIndex.hpp"
      22              : #include "Utilities/ErrorHandling/Error.hpp"
      23              : #include "Utilities/Gsl.hpp"
      24              : #include "Utilities/MakeWithValue.hpp"
      25              : #include "Utilities/NoSuchType.hpp"
      26              : #include "Utilities/Serialization/CharmPupable.hpp"
      27              : #include "Utilities/Serialization/PupStlCpp17.hpp"
      28              : #include "Utilities/TMPL.hpp"
      29              : 
      30              : /// \cond
      31              : template <size_t VolumeDim>
      32              : class ElementId;
      33              : /// \endcond
      34              : 
      35              : namespace LinearSolver::Serial {
      36              : 
      37              : /// \cond
      38              : template <typename ValueType, typename LinearSolverRegistrars>
      39              : struct ExplicitInverse;
      40              : /// \endcond
      41              : 
      42            1 : namespace Registrars {
      43              : /// Registers the `LinearSolver::Serial::ExplicitInverse` linear solver
      44              : template <typename ValueType>
      45            1 : struct ExplicitInverse {
      46              :   template <typename LinearSolverRegistrars>
      47            0 :   using f = Serial::ExplicitInverse<ValueType, LinearSolverRegistrars>;
      48              : };
      49              : }  // namespace Registrars
      50              : 
      51              : /*!
      52              :  * \brief Linear solver that builds a matrix representation of the linear
      53              :  * operator and inverts it directly
      54              :  *
      55              :  * This solver first constructs an explicit matrix representation by "sniffing
      56              :  * out" the operator, i.e. feeding it with unit vectors, and then directly
      57              :  * inverts the matrix. The result is an operator that solves the linear problem
      58              :  * in a single step. This means that each element has a large initialization
      59              :  * cost, but all successive solves converge immediately.
      60              :  *
      61              :  * \par Advice on using this linear solver:
      62              :  * - This solver is entirely agnostic to the structure of the linear operator.
      63              :  *   It is usually better to implement a linear solver that is specialized for
      64              :  *   your linear operator to take advantage of its properties. For example, if
      65              :  *   the operator has a tensor-product structure, the linear solver might take
      66              :  *   advantage of that. Only use this solver if no alternatives are available
      67              :  *   and if you have verified that it speeds up your solves.
      68              :  * - Since this linear solver stores the full inverse operator matrix it can
      69              :  *   have significant memory demands. For example, an operator representing a 3D
      70              :  *   first-order Elasticity system (9 variables) discretized on 12 grid points
      71              :  *   per dimension requires ca. 2GB of memory (per element) to store the matrix,
      72              :  *   scaling quadratically with the number of variables and with a power of 6
      73              :  *   with the number of grid points per dimension. Therefore, make sure to
      74              :  *   distribute the elements on a sufficient number of nodes to meet the memory
      75              :  *   requirements.
      76              :  * - This linear solver can be `reset()` when the operator changes (e.g. in each
      77              :  *   nonlinear-solver iteration). However, when using this solver as
      78              :  *   preconditioner it can be advantageous to avoid the reset and the
      79              :  *   corresponding cost of re-building the matrix and its inverse if the
      80              :  *   operator only changes "a little". In that case the preconditioner solves
      81              :  *   subdomain problems only approximately, but possibly still sufficiently to
      82              :  *   provide effective preconditioning.
      83              :  */
      84              : template <typename ValueType,
      85              :           typename LinearSolverRegistrars =
      86              :               tmpl::list<Registrars::ExplicitInverse<ValueType>>>
      87            1 : class ExplicitInverse : public LinearSolver<LinearSolverRegistrars> {
      88              :  private:
      89            0 :   using Base = LinearSolver<LinearSolverRegistrars>;
      90              : 
      91              :  public:
      92            0 :   struct WriteMatrixToFile {
      93            0 :     using type = Options::Auto<std::string, Options::AutoLabel::None>;
      94            0 :     static constexpr Options::String help =
      95              :         "Write the matrix representation of the linear operator to a "
      96              :         "space-delimited CSV file with this name. A '.txt' extension will be "
      97              :         "added. Also a suffix with the element ID will be added if this linear "
      98              :         "solver runs on an array element, so one file per element will be "
      99              :         "written.";
     100              :   };
     101            0 :   struct ComplexShift {
     102            0 :     using type = double;
     103            0 :     static constexpr Options::String help =
     104              :         "Subtracts i*ComplexShift from the operator. "
     105              :         "This can help dampen high-frequency modes when preconditioning "
     106              :         "complex-valued problems.";
     107              :   };
     108              : 
     109            0 :   using options = tmpl::flatten<tmpl::list<
     110              :       WriteMatrixToFile,
     111              :       tmpl::conditional_t<std::is_same_v<ValueType, std::complex<double>>,
     112              :                           ComplexShift, tmpl::list<>>>>;
     113            0 :   static constexpr Options::String help =
     114              :       "Build a matrix representation of the linear operator and invert it "
     115              :       "directly. This means that the first solve has a large initialization "
     116              :       "cost, but all subsequent solves converge immediately.";
     117              : 
     118            0 :   ExplicitInverse(const ExplicitInverse& /*rhs*/) = default;
     119            0 :   ExplicitInverse& operator=(const ExplicitInverse& /*rhs*/) = default;
     120            0 :   ExplicitInverse(ExplicitInverse&& /*rhs*/) = default;
     121            0 :   ExplicitInverse& operator=(ExplicitInverse&& /*rhs*/) = default;
     122            0 :   ~ExplicitInverse() = default;
     123              : 
     124            0 :   explicit ExplicitInverse(
     125              :       std::optional<std::string> matrix_filename = std::nullopt,
     126              :       const double complex_shift = 0.0)
     127              :       : matrix_filename_(std::move(matrix_filename)),
     128              :         complex_shift_(complex_shift) {}
     129              : 
     130              :   /// \cond
     131              :   explicit ExplicitInverse(CkMigrateMessage* m) : Base(m) {}
     132              :   using PUP::able::register_constructor;
     133              :   WRAPPED_PUPable_decl_template(ExplicitInverse);  // NOLINT
     134              :   /// \endcond
     135              : 
     136              :   /*!
     137              :    * \brief Solve the equation \f$Ax=b\f$ by explicitly constructing the
     138              :    * operator matrix \f$A\f$ and its inverse. The first solve is computationally
     139              :    * expensive and successive solves are cheap.
     140              :    *
     141              :    * Building a matrix representation of the `linear_operator` requires
     142              :    * iterating over the `SourceType` (which is also the type returned by the
     143              :    * `linear_operator`) in a consistent way. This can be non-trivial for
     144              :    * heterogeneous data structures because it requires they define a data
     145              :    * ordering. Specifically, the `SourceType` must have a `size()` function as
     146              :    * well as `begin()` and `end()` iterators that point into the data. If the
     147              :    * iterators have a `reset()` function it is used to avoid repeatedly
     148              :    * re-creating the `begin()` iterator. The `reset()` function must not
     149              :    * invalidate the `end()` iterator.
     150              :    */
     151              :   template <typename LinearOperator, typename VarsType, typename SourceType,
     152              :             typename... OperatorArgs>
     153            1 :   Convergence::HasConverged solve(
     154              :       gsl::not_null<VarsType*> solution, const LinearOperator& linear_operator,
     155              :       const SourceType& source,
     156              :       const std::tuple<OperatorArgs...>& operator_args = std::tuple{}) const;
     157              : 
     158              :   /// Flags the operator to require re-initialization. No memory is released.
     159              :   /// Call this function to rebuild the solver when the operator changed.
     160            1 :   void reset() override { size_ = std::numeric_limits<size_t>::max(); }
     161              : 
     162              :   /// Size of the operator. The stored matrix will have `size^2` entries.
     163            1 :   size_t size() const { return size_; }
     164              : 
     165              :   /// The matrix representation of the solver. This matrix approximates the
     166              :   /// inverse of the subdomain operator.
     167              :   const blaze::DynamicMatrix<ValueType, blaze::columnMajor>&
     168            1 :   matrix_representation() const {
     169              :     return inverse_;
     170              :   }
     171              : 
     172              :   // NOLINTNEXTLINE(google-runtime-references)
     173            0 :   void pup(PUP::er& p) override {
     174              :     p | matrix_filename_;
     175              :     p | complex_shift_;
     176              :     p | size_;
     177              :     p | inverse_;
     178              :     if (p.isUnpacking() and size_ != std::numeric_limits<size_t>::max()) {
     179              :       source_workspace_.resize(size_);
     180              :       solution_workspace_.resize(size_);
     181              :     }
     182              :   }
     183              : 
     184            0 :   std::unique_ptr<Base> get_clone() const override {
     185              :     return std::make_unique<ExplicitInverse>(*this);
     186              :   }
     187              : 
     188              :  private:
     189            0 :   std::optional<std::string> matrix_filename_{};
     190            0 :   double complex_shift_ = std::numeric_limits<double>::signaling_NaN();
     191              :   // Caches for successive solves of the same operator
     192              :   // NOLINTNEXTLINE(spectre-mutable)
     193            0 :   mutable size_t size_ = std::numeric_limits<size_t>::max();
     194              :   // We currently store the matrix representation in a dense matrix because
     195              :   // Blaze doesn't support the inversion of sparse matrices (yet).
     196              :   // NOLINTNEXTLINE(spectre-mutable)
     197            0 :   mutable blaze::DynamicMatrix<ValueType, blaze::columnMajor> inverse_{};
     198              : 
     199              :   // Buffers to avoid re-allocating memory for applying the operator
     200              :   // NOLINTNEXTLINE(spectre-mutable)
     201            0 :   mutable blaze::DynamicVector<ValueType> source_workspace_{};
     202              :   // NOLINTNEXTLINE(spectre-mutable)
     203            0 :   mutable blaze::DynamicVector<ValueType> solution_workspace_{};
     204              : };
     205              : 
     206              : template <typename ValueType, typename LinearSolverRegistrars>
     207              : template <typename LinearOperator, typename VarsType, typename SourceType,
     208              :           typename... OperatorArgs>
     209              : Convergence::HasConverged
     210            0 : ExplicitInverse<ValueType, LinearSolverRegistrars>::solve(
     211              :     const gsl::not_null<VarsType*> solution,
     212              :     const LinearOperator& linear_operator, const SourceType& source,
     213              :     const std::tuple<OperatorArgs...>& operator_args) const {
     214              :   if (UNLIKELY(size_ == std::numeric_limits<size_t>::max())) {
     215              :     const auto& used_for_size = source;
     216              :     size_ = used_for_size.size();
     217              :     source_workspace_.resize(size_);
     218              :     solution_workspace_.resize(size_);
     219              :     inverse_.resize(size_, size_);
     220              :     // Construct explicit matrix representation by "sniffing out" the operator,
     221              :     // i.e. feeding it unit vectors
     222              :     auto operand_buffer = make_with_value<VarsType>(used_for_size, 0.);
     223              :     auto result_buffer = make_with_value<SourceType>(used_for_size, 0.);
     224              :     build_matrix(make_not_null(&inverse_), make_not_null(&operand_buffer),
     225              :                  make_not_null(&result_buffer), linear_operator, operator_args);
     226              :     // Write to file before inverting
     227              :     if (UNLIKELY(matrix_filename_.has_value())) {
     228              :       std::string filename_suffix{};
     229              :       using DataBoxType =
     230              :           std::decay_t<tmpl::front<tmpl::list<OperatorArgs..., NoSuchType>>>;
     231              :       if constexpr (tt::is_a_v<db::DataBox, DataBoxType>) {
     232              :         tmpl::for_each<tmpl::range<size_t, 1, 4>>(
     233              :             [&]<size_t Dim>(tmpl::type_<tmpl::size_t<Dim>> /*meta*/) {
     234              :               using index_tag = Parallel::Tags::ArrayIndex<ElementId<Dim>>;
     235              :               if constexpr (db::tag_is_retrievable_v<index_tag, DataBoxType>) {
     236              :                 const auto& box = std::get<0>(operator_args);
     237              :                 filename_suffix = "_" + get_output(db::get<index_tag>(box));
     238              :               } else {
     239              :                 (void)operator_args;
     240              :               }
     241              :             });
     242              :       } else {
     243              :         (void)operator_args;
     244              :       }
     245              :       std::ofstream matrix_file(matrix_filename_.value() + filename_suffix +
     246              :                                 ".txt");
     247              :       write_csv(matrix_file, inverse_, " ");
     248              :     }
     249              :     // Apply complex shift
     250              :     if constexpr (std::is_same_v<ValueType, std::complex<double>>) {
     251              :       for (size_t i = 0; i < size_; ++i) {
     252              :         inverse_(i, i) -= std::complex<double>(0.0, complex_shift_);
     253              :       }
     254              :     }
     255              :     // Directly invert the matrix
     256              :     try {
     257              :       blaze::invert(inverse_);
     258              :     } catch (const std::exception& e) {
     259              :       ERROR("Could not invert subdomain matrix (size " << size_
     260              :                                                        << "): " << e.what());
     261              :     }
     262              :   }
     263              :   // Copy source into contiguous workspace. In cases where the source and
     264              :   // solution data are already stored contiguously we might avoid the copy and
     265              :   // the associated workspace memory. However, compared to the cost of building
     266              :   // and storing the matrix this is likely insignificant.
     267              :   std::copy(source.begin(), source.end(), source_workspace_.begin());
     268              :   // Apply inverse
     269              :   solution_workspace_ = inverse_ * source_workspace_;
     270              :   // Reconstruct solution data from contiguous workspace
     271              :   std::copy(solution_workspace_.begin(), solution_workspace_.end(),
     272              :             solution->begin());
     273              :   return {0, 0};
     274              : }
     275              : 
     276              : /// \cond
     277              : // NOLINTBEGIN
     278              : template <typename ValueType, typename LinearSolverRegistrars>
     279              : PUP::able::PUP_ID
     280              :     ExplicitInverse<ValueType, LinearSolverRegistrars>::my_PUP_ID = 0;
     281              : // NOLINTEND
     282              : /// \endcond
     283              : 
     284              : }  // namespace LinearSolver::Serial
        

Generated by: LCOV version 2.0-1