SpECTRE Documentation Coverage Report
Current view: top level - ParallelAlgorithms/LinearSolver/Actions - BuildMatrix.hpp Coverage Total Hit
Commit: a37d7b3c1803b88b537d2aef1c27ca5e6b499398 Lines: 19.7 % 122 24
Test Date: 2026-11-12 04:47:19
Legend: Lines:     hit not hit

            Line data    Source code
       1            1 : // Distributed under the MIT License.
       2              : // See LICENSE.txt for details.
       3              : 
       4              : /// \file
       5              : /// Functionality to build the explicit matrix representation of the linear
       6              : /// operator column-by-column, and to solve the linear problem directly with it.
       7              : 
       8              : #pragma once
       9              : 
      10              : #include <blaze/math/Submatrix.h>
      11              : #include <blaze/math/lapack/getrf.h>
      12              : #include <blaze/math/lapack/getrs.h>
      13              : #include <cstddef>
      14              : #include <map>
      15              : #include <optional>
      16              : #include <utility>
      17              : #include <vector>
      18              : 
      19              : #include "DataStructures/CompressedMatrix.hpp"
      20              : #include "DataStructures/DataBox/DataBox.hpp"
      21              : #include "DataStructures/DataBox/Tag.hpp"
      22              : #include "DataStructures/TaggedTuple.hpp"
      23              : #include "Domain/Structure/ElementId.hpp"
      24              : #include "Domain/Tags.hpp"
      25              : #include "IO/H5/TensorData.hpp"
      26              : #include "IO/Logging/Tags.hpp"
      27              : #include "IO/Logging/Verbosity.hpp"
      28              : #include "IO/Observer/Actions/RegisterWithObservers.hpp"
      29              : #include "IO/Observer/GetSectionObservationKey.hpp"
      30              : #include "IO/Observer/VolumeActions.hpp"
      31              : #include "NumericalAlgorithms/Convergence/Tags.hpp"
      32              : #include "Parallel/AlgorithmExecution.hpp"
      33              : #include "Parallel/GetSection.hpp"
      34              : #include "Parallel/Phase.hpp"
      35              : #include "Parallel/Printf/Printf.hpp"
      36              : #include "Parallel/Reduction.hpp"
      37              : #include "Parallel/Section.hpp"
      38              : #include "Parallel/Tags/Section.hpp"
      39              : #include "Parallel/TypeTraits.hpp"
      40              : #include "ParallelAlgorithms/Actions/Goto.hpp"
      41              : #include "ParallelAlgorithms/Amr/Protocols/Projector.hpp"
      42              : #include "ParallelAlgorithms/LinearSolver/Tags.hpp"
      43              : #include "Utilities/EqualWithinRoundoff.hpp"
      44              : #include "Utilities/Functional.hpp"
      45              : #include "Utilities/GetOutput.hpp"
      46              : #include "Utilities/ProtocolHelpers.hpp"
      47              : #include "Utilities/TMPL.hpp"
      48              : 
      49              : namespace LinearSolver {
      50            0 : namespace OptionTags {
      51              : 
      52              : /// Options for building the explicit matrix representation of the linear
      53              : /// operator.
      54            1 : struct BuildMatrixOptionsGroup {
      55            0 :   static std::string name() { return "BuildMatrix"; }
      56            0 :   static constexpr Options::String help = {
      57              :       "Options for building the explicit matrix representation of the linear "
      58              :       "operator. This is done by applying the linear operator to unit "
      59              :       "vectors. The matrix can be written to disk for analysis, or used to "
      60              :       "solve small problems directly, e.g. on the coarsest multigrid level."};
      61              : };
      62              : 
      63              : /// Subfile name in the volume data H5 files where the matrix will be stored.
      64            1 : struct MatrixSubfileName {
      65            0 :   using type = Options::Auto<std::string, Options::AutoLabel::None>;
      66            0 :   using group = BuildMatrixOptionsGroup;
      67            0 :   static constexpr Options::String help = {
      68              :       "Subfile name in the volume data H5 files where the matrix will be "
      69              :       "stored. Each observation in the subfile is a column of the matrix. The "
      70              :       "row index is the order of elements defined by the ElementId in the "
      71              :       "volume data, by the order of tensor components encoded in the name of "
      72              :       "the components, and by the contiguous ordering of grid points for each "
      73              :       "component."};
      74              : };
      75              : 
      76              : /// Option for enabling direct solve of the linear problem.
      77            1 : struct EnableDirectSolve {
      78            0 :   using type = bool;
      79            0 :   using group = BuildMatrixOptionsGroup;
      80            0 :   static constexpr Options::String help = {
      81              :       "Solve the linear problem directly with an LU factorization of the "
      82              :       "assembled matrix. The factorization is reused for subsequent solves "
      83              :       "until the matrix changes. The dense factorization needs memory "
      84              :       "quadratic in the number of points, so this can be unfeasible if the "
      85              :       "linear problem is too big."};
      86              : };
      87              : 
      88              : /// Option for skipping resets of the built matrix.
      89            1 : struct SkipResets {
      90            0 :   using type = bool;
      91            0 :   using group = BuildMatrixOptionsGroup;
      92            0 :   static constexpr Options::String help = {
      93              :       "Skip resets of the built matrix. This only has an effect in cases "
      94              :       "where the operator changes, e.g. between nonlinear-solver iterations. "
      95              :       "Skipping resets avoids expensive re-building of the matrix, but comes "
      96              :       "at the cost of less accurate preconditioning and thus potentially more "
      97              :       "preconditioned iterations. Whether or not this helps convergence "
      98              :       "overall is highly problem-dependent."};
      99              : };
     100              : 
     101              : }  // namespace OptionTags
     102              : 
     103            1 : namespace Tags {
     104              : 
     105              : /// Subfile name in the volume data H5 files where the matrix will be stored.
     106            1 : struct MatrixSubfileName : db::SimpleTag {
     107            0 :   using type = std::optional<std::string>;
     108            0 :   using option_tags = tmpl::list<OptionTags::MatrixSubfileName>;
     109            0 :   static constexpr bool pass_metavariables = false;
     110            0 :   static type create_from_options(const type& value) { return value; }
     111              : };
     112              : 
     113              : /// Size of the matrix: number of grid points times number of variables
     114            1 : struct TotalNumPoints : db::SimpleTag {
     115            0 :   using type = size_t;
     116              : };
     117              : 
     118              : /// Index of the first point in this element
     119            1 : struct LocalFirstIndex : db::SimpleTag {
     120            0 :   using type = size_t;
     121              : };
     122              : 
     123              : /// Matrix representation of the linear operator. Stored as sparse matrix. The
     124              : /// full matrix has size `total_num_points` by `total_num_points`. Each element
     125              : /// only stores the rows corresponding to its grid points (starting at
     126              : /// `local_first_index`), but all columns.
     127              : template <typename ValueType>
     128            1 : struct Matrix : db::SimpleTag {
     129            0 :   using type = blaze::CompressedMatrix<ValueType>;
     130              : };
     131              : 
     132              : /// Whether the matrix on this element was (re)built since it was last sent to
     133              : /// the singleton that inverts the full matrix. The singleton only factorizes
     134              : /// the full matrix again if this is `true` on any element.
     135            1 : struct MatrixChanged : db::SimpleTag {
     136            0 :   using type = bool;
     137              : };
     138              : 
     139              : /// LU factorization of the full matrix, held by the singleton that solves the
     140              : /// linear problem directly. It is not serialized, but rebuilt from the matrix
     141              : /// slices that the elements send with every solve.
     142              : template <typename ValueType>
     143            1 : struct LuFactorization : db::SimpleTag {
     144            0 :   struct type {
     145            0 :     blaze::DynamicMatrix<ValueType, blaze::columnMajor> lu{};
     146            0 :     std::vector<blaze::blas_int_t> pivots{};
     147            0 :     void pup(PUP::er& /*p*/) {}
     148              :   };
     149              : };
     150              : 
     151              : /// Option for enabling direct solve of the linear problem.
     152            1 : struct EnableDirectSolve : db::SimpleTag {
     153            0 :   using type = bool;
     154            0 :   using option_tags = tmpl::list<OptionTags::EnableDirectSolve>;
     155            0 :   static constexpr bool pass_metavariables = false;
     156            0 :   static type create_from_options(const type& value) { return value; }
     157              : };
     158              : 
     159              : /// Option for skipping resets of the built matrix.
     160            1 : struct SkipResets : db::SimpleTag {
     161            0 :   using type = bool;
     162            0 :   using option_tags = tmpl::list<OptionTags::SkipResets>;
     163            0 :   static constexpr bool pass_metavariables = false;
     164            0 :   static type create_from_options(const type& value) { return value; }
     165              : };
     166              : 
     167              : }  // namespace Tags
     168              : 
     169            1 : namespace Actions {
     170              : 
     171              : namespace detail {
     172              : 
     173              : template <typename FieldsTag, typename FixedSourcesTag, typename OperandTag,
     174              :           typename OperatorAppliedToOperandTag, typename CoordsTag,
     175              :           typename ArraySectionIdTag>
     176              : struct BuildMatrixMetavars {
     177              :   using iteration_id_tag = Convergence::Tags::IterationId<
     178              :       LinearSolver::OptionTags::BuildMatrixOptionsGroup>;
     179              :   using fields_tag = FieldsTag;
     180              :   using fixed_sources_tag = FixedSourcesTag;
     181              :   using operand_tag = OperandTag;
     182              :   using operator_applied_to_operand_tag = OperatorAppliedToOperandTag;
     183              :   using coords_tag = CoordsTag;
     184              :   using array_section_id_tag = ArraySectionIdTag;
     185              : 
     186              :   using value_type = typename OperatorAppliedToOperandTag::type::value_type;
     187              : 
     188              :   struct end_label {};
     189              : };
     190              : 
     191              : /// \brief The total number of grid points (size of the matrix) and the index of
     192              : /// the first grid point in this element (the offset into the matrix
     193              : /// corresponding to this element). The `num_points_per_element` should hold
     194              : /// the total number of degrees of freedom for each element, so the number of
     195              : /// variables times the number of grid points.
     196              : template <size_t Dim>
     197              : std::pair<size_t, size_t> total_num_points_and_local_first_index(
     198              :     const ElementId<Dim>& element_id,
     199              :     const std::map<ElementId<Dim>, size_t>& num_points_per_element);
     200              : 
     201              : /// \brief The index of the '1' of the unit vector in this element, or
     202              : /// std::nullopt if the '1' is in another element.
     203              : ///
     204              : /// \param iteration_id enumerates all grid points
     205              : /// \param local_first_index the index of the first grid point in this element
     206              : /// \param local_num_points the number of grid points in this element
     207              : std::optional<size_t> local_unit_vector_index(size_t iteration_id,
     208              :                                               size_t local_first_index,
     209              :                                               size_t local_num_points);
     210              : 
     211              : /// \brief Observe matrix column as volume data.
     212              : ///
     213              : /// This is the format of the volume data:
     214              : ///
     215              : /// - Columns of the matrix are enumerated by the observation ID
     216              : /// - Rows are enumerated by the order of elements defined by the ElementId
     217              : ///   in the volume data, by the order of tensor components encoded in the
     218              : ///   name of the components, and by the contiguous ordering of grid points
     219              : ///   for each component.
     220              : /// - We also observe coordinates so the data can be plotted as volume data.
     221              : ///
     222              : /// The matrix can be reconstructed from this data in Python with the function
     223              : /// `spectre.Elliptic.ReadH5.read_matrix`.
     224              : template <typename ParallelComponent, typename OperatorAppliedToOperandTags,
     225              :           size_t Dim, typename CoordsFrame, typename Metavariables>
     226              : void observe_matrix_column(
     227              :     const size_t column,
     228              :     const Variables<OperatorAppliedToOperandTags>& operator_applied_to_operand,
     229              :     const ElementId<Dim>& element_id, const Mesh<Dim>& mesh,
     230              :     const tnsr::I<DataVector, Dim, CoordsFrame>& coords,
     231              :     const std::string& subfile_name, const std::string& section_observation_key,
     232              :     Parallel::GlobalCache<Metavariables>& cache) {
     233              :   std::vector<TensorComponent> observe_components{};
     234              :   observe_components.reserve(
     235              :       Dim +
     236              :       Variables<
     237              :           OperatorAppliedToOperandTags>::number_of_independent_components);
     238              :   for (size_t i = 0; i < Dim; ++i) {
     239              :     observe_components.emplace_back(
     240              :         get_output(CoordsFrame{}) + "Coordinates" + coords.component_suffix(i),
     241              :         coords.get(i));
     242              :   }
     243              :   size_t component_i = 0;
     244              :   tmpl::for_each<OperatorAppliedToOperandTags>([&operator_applied_to_operand,
     245              :                                                 &observe_components,
     246              :                                                 &component_i](auto tag_v) {
     247              :     using tag = tmpl::type_from<decltype(tag_v)>;
     248              :     const auto& tensor = get<tag>(operator_applied_to_operand);
     249              :     using TensorType = std::decay_t<decltype(tensor)>;
     250              :     using VectorType = typename TensorType::type;
     251              :     using ValueType = typename VectorType::value_type;
     252              :     for (size_t i = 0; i < tensor.size(); ++i) {
     253              :       if constexpr (std::is_same_v<ValueType, std::complex<double>>) {
     254              :         observe_components.emplace_back(
     255              :             "Re(Variable_" + std::to_string(component_i) + ")",
     256              :             real(tensor[i]));
     257              :         observe_components.emplace_back(
     258              :             "Im(Variable_" + std::to_string(component_i) + ")",
     259              :             imag(tensor[i]));
     260              :       } else {
     261              :         observe_components.emplace_back(
     262              :             "Variable_" + std::to_string(component_i), tensor[i]);
     263              :       }
     264              :       ++component_i;
     265              :     }
     266              :   });
     267              : 
     268              :   observers::contribute_volume_data<
     269              :       not Parallel::is_nodegroup_v<ParallelComponent>>(
     270              :       cache,
     271              :       observers::ObservationId(static_cast<double>(column),
     272              :                                subfile_name + section_observation_key),
     273              :       "/" + subfile_name,
     274              :       Parallel::make_array_component_id<ParallelComponent>(element_id),
     275              :       ElementVolumeData{element_id, std::move(observe_components), mesh});
     276              : }
     277              : 
     278              : /// \brief Register with the volume observer
     279              : template <typename ArraySectionIdTag>
     280              : struct RegisterWithVolumeObserver {
     281              :   template <typename ParallelComponent, typename DbTagsList,
     282              :             typename ArrayIndex>
     283              :   static std::pair<observers::TypeOfObservation, observers::ObservationKey>
     284              :   register_info(const db::DataBox<DbTagsList>& box,
     285              :                 const ArrayIndex& /*array_index*/) {
     286              :     // Get the observation key, or "Unused" if the element does not belong
     287              :     // to a section with this tag. In the latter case, no observations will
     288              :     // ever be contributed.
     289              :     const std::optional<std::string> section_observation_key =
     290              :         observers::get_section_observation_key<ArraySectionIdTag>(box);
     291              :     ASSERT(section_observation_key != "Unused",
     292              :            "The identifier 'Unused' is reserved to indicate that no "
     293              :            "observations with this key will be contributed. Use a different "
     294              :            "key, or change the identifier 'Unused' to something else.");
     295              :     const auto& subfile_name = get<Tags::MatrixSubfileName>(box);
     296              :     return {
     297              :         observers::TypeOfObservation::Volume,
     298              :         observers::ObservationKey(subfile_name.value_or("Unused") +
     299              :                                   section_observation_key.value_or("Unused"))};
     300              :   }
     301              : };
     302              : 
     303              : }  // namespace detail
     304              : 
     305              : /// \cond
     306              : template <typename BuildMatrixMetavars>
     307              : struct PrepareBuildMatrix;
     308              : template <typename BuildMatrixMetavars>
     309              : struct AssembleFullMatrix;
     310              : /// \endcond
     311              : 
     312              : /// Dispatch global reduction to get the size of the matrix
     313              : template <typename BuildMatrixMetavars>
     314            1 : struct CollectTotalNumPoints {
     315              :  private:
     316            0 :   using IterationIdTag = typename BuildMatrixMetavars::iteration_id_tag;
     317            0 :   using OperandTag = typename BuildMatrixMetavars::operand_tag;
     318            0 :   using ArraySectionIdTag = typename BuildMatrixMetavars::array_section_id_tag;
     319            0 :   using value_type = typename BuildMatrixMetavars::value_type;
     320              : 
     321              :  public:
     322            0 :   using simple_tags =
     323              :       tmpl::list<Tags::TotalNumPoints, Tags::LocalFirstIndex, IterationIdTag,
     324              :                  OperandTag, Tags::Matrix<value_type>, Tags::MatrixChanged>;
     325            0 :   using compute_tags = tmpl::list<>;
     326            0 :   using const_global_cache_tags =
     327              :       tmpl::list<logging::Tags::Verbosity<OptionTags::BuildMatrixOptionsGroup>>;
     328              : 
     329              :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     330              :             size_t Dim, typename ActionList, typename ParallelComponent>
     331            0 :   static Parallel::iterable_action_return_t apply(
     332              :       db::DataBox<DbTags>& box,
     333              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     334              :       Parallel::GlobalCache<Metavariables>& cache,
     335              :       const ElementId<Dim>& element_id, const ActionList /*meta*/,
     336              :       const ParallelComponent* const /*meta*/) {
     337              :     // Skip everything on elements that are not part of the section
     338              :     if constexpr (not std::is_same_v<ArraySectionIdTag, void>) {
     339              :       if (not db::get<Parallel::Tags::Section<ParallelComponent,
     340              :                                               ArraySectionIdTag>>(box)
     341              :                   .has_value()) {
     342              :         constexpr size_t last_action_index = tmpl::index_of<
     343              :             ActionList,
     344              :             ::Actions::Label<typename BuildMatrixMetavars::end_label>>::value;
     345              :         return {Parallel::AlgorithmExecution::Continue, last_action_index + 1};
     346              :       }
     347              :     }
     348              :     if (db::get<Tags::TotalNumPoints>(box) != 0) {
     349              :       // We have built the matrix already, so we can skip ahead
     350              :       constexpr size_t assemble_matrix_index =
     351              :           tmpl::index_of<ActionList,
     352              :                          AssembleFullMatrix<BuildMatrixMetavars>>::value;
     353              :       return {Parallel::AlgorithmExecution::Continue, assemble_matrix_index};
     354              :     }
     355              :     db::mutate<Tags::TotalNumPoints, OperandTag>(
     356              :         [](const auto total_num_points, const auto operand,
     357              :            const size_t num_points) {
     358              :           // Set num points to zero until we know the total number of points
     359              :           *total_num_points = 0;
     360              :           // Size operand and fill with zeros
     361              :           operand->initialize(num_points, 0.);
     362              :         },
     363              :         make_not_null(&box),
     364              :         get<domain::Tags::Mesh<Dim>>(box).number_of_grid_points());
     365              :     // Collect the total number of grid points
     366              :     auto& section = Parallel::get_section<ParallelComponent, ArraySectionIdTag>(
     367              :         make_not_null(&box));
     368              :     Parallel::contribute_to_reduction<PrepareBuildMatrix<BuildMatrixMetavars>>(
     369              :         Parallel::ReductionData<
     370              :             Parallel::ReductionDatum<size_t, funcl::AssertEqual<>>,
     371              :             Parallel::ReductionDatum<std::map<ElementId<Dim>, size_t>,
     372              :                                      funcl::Merge<>>>{
     373              :             element_id.grid_index(),
     374              :             std::map<ElementId<Dim>, size_t>{
     375              :                 std::make_pair(element_id, get<OperandTag>(box).size())}},
     376              :         Parallel::get_parallel_component<ParallelComponent>(cache)[element_id],
     377              :         Parallel::get_parallel_component<ParallelComponent>(cache),
     378              :         make_not_null(&section));
     379              :     // Pause the algorithm for now. The reduction will be broadcast to the next
     380              :     // action which is responsible for restarting the algorithm.
     381              :     return {Parallel::AlgorithmExecution::Pause, std::nullopt};
     382              :   }
     383              : };
     384              : 
     385              : /// Receive the reduction and initialize the algorithm
     386              : template <typename BuildMatrixMetavars>
     387            1 : struct PrepareBuildMatrix {
     388              :  private:
     389            0 :   using IterationIdTag = typename BuildMatrixMetavars::iteration_id_tag;
     390            0 :   using OperandTag = typename BuildMatrixMetavars::operand_tag;
     391            0 :   using value_type = typename BuildMatrixMetavars::value_type;
     392              : 
     393              :  public:
     394              :   template <typename ParallelComponent, typename DbTagsList,
     395              :             typename Metavariables, size_t Dim>
     396            0 :   static void apply(
     397              :       db::DataBox<DbTagsList>& box, Parallel::GlobalCache<Metavariables>& cache,
     398              :       const ElementId<Dim>& element_id, const size_t grid_index,
     399              :       const std::map<ElementId<Dim>, size_t>& num_points_per_element) {
     400              :     if (grid_index != element_id.grid_index()) {
     401              :       return;
     402              :     }
     403              :     const auto [total_num_points, local_first_index] =
     404              :         detail::total_num_points_and_local_first_index(element_id,
     405              :                                                        num_points_per_element);
     406              :     if (get<logging::Tags::Verbosity<OptionTags::BuildMatrixOptionsGroup>>(
     407              :             box) >= Verbosity::Quiet and
     408              :         local_first_index == 0) {
     409              :       Parallel::printf(
     410              :           "Building explicit matrix representation of size %zu x %zu.\n",
     411              :           total_num_points, total_num_points);
     412              :     }
     413              :     db::mutate<Tags::TotalNumPoints, Tags::LocalFirstIndex, IterationIdTag,
     414              :                Tags::Matrix<value_type>, Tags::MatrixChanged>(
     415              :         [captured_total_num_points = total_num_points,
     416              :          captured_local_first_index = local_first_index,
     417              :          local_num_points = num_points_per_element.at(element_id)](
     418              :             const auto stored_total_num_points,
     419              :             const auto stored_local_first_index, const auto iteration_id,
     420              :             const gsl::not_null<blaze::CompressedMatrix<value_type>*> matrix,
     421              :             const gsl::not_null<bool*> matrix_changed) {
     422              :           *stored_total_num_points = captured_total_num_points;
     423              :           *stored_local_first_index = captured_local_first_index;
     424              :           *iteration_id = 0;
     425              :           matrix->clear();
     426              :           matrix->resize(local_num_points, captured_total_num_points);
     427              :           *matrix_changed = true;
     428              :         },
     429              :         make_not_null(&box));
     430              :     // Proceed with algorithm
     431              :     Parallel::get_parallel_component<ParallelComponent>(cache)[element_id]
     432              :         .perform_algorithm(true);
     433              :   }
     434              : };
     435              : 
     436              : /// Set the operand to a unit vector with a '1' at the current grid point.
     437              : /// Applying the operator to this operand gives a column of the matrix. We jump
     438              : /// back to this until we have iterated over all grid points.
     439              : template <typename BuildMatrixMetavars>
     440            1 : struct SetUnitVector {
     441              :  private:
     442            0 :   using IterationIdTag = typename BuildMatrixMetavars::iteration_id_tag;
     443            0 :   using OperandTag = typename BuildMatrixMetavars::operand_tag;
     444              : 
     445              :  public:
     446              :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     447              :             typename ArrayIndex, typename ActionList,
     448              :             typename ParallelComponent>
     449            0 :   static Parallel::iterable_action_return_t apply(
     450              :       db::DataBox<DbTags>& box,
     451              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     452              :       const Parallel::GlobalCache<Metavariables>& /*cache*/,
     453              :       const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
     454              :       const ParallelComponent* const /*meta*/) {
     455              :     const std::optional<size_t> local_unit_vector_index =
     456              :         detail::local_unit_vector_index(get<IterationIdTag>(box),
     457              :                                         get<Tags::LocalFirstIndex>(box),
     458              :                                         get<OperandTag>(box).size());
     459              :     if (local_unit_vector_index.has_value()) {
     460              :       db::mutate<OperandTag>(
     461              :           [&local_unit_vector_index](const auto operand) {
     462              :             operand->data()[*local_unit_vector_index] = 1.;
     463              :           },
     464              :           make_not_null(&box));
     465              :     }
     466              :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     467              :   }
     468              : };
     469              : 
     470              : // --- Linear operator will be applied to the unit vector here ---
     471              : 
     472              : /// Write result out to disk, reset operand back to zero, and keep iterating
     473              : template <typename BuildMatrixMetavars>
     474            1 : struct StoreMatrixColumn {
     475              :  private:
     476            0 :   using IterationIdTag = typename BuildMatrixMetavars::iteration_id_tag;
     477            0 :   using OperandTag = typename BuildMatrixMetavars::operand_tag;
     478            0 :   using OperatorAppliedToOperandTag =
     479              :       typename BuildMatrixMetavars::operator_applied_to_operand_tag;
     480            0 :   using CoordsTag = typename BuildMatrixMetavars::coords_tag;
     481            0 :   using ArraySectionIdTag = typename BuildMatrixMetavars::array_section_id_tag;
     482            0 :   using value_type = typename BuildMatrixMetavars::value_type;
     483              : 
     484              :  public:
     485            0 :   using const_global_cache_tags = tmpl::list<Tags::MatrixSubfileName>;
     486              : 
     487              :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     488              :             size_t Dim, typename ActionList, typename ParallelComponent>
     489            0 :   static Parallel::iterable_action_return_t apply(
     490              :       db::DataBox<DbTags>& box,
     491              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     492              :       Parallel::GlobalCache<Metavariables>& cache,
     493              :       const ElementId<Dim>& element_id, const ActionList /*meta*/,
     494              :       const ParallelComponent* const /*meta*/) {
     495              :     const size_t iteration_id = get<IterationIdTag>(box);
     496              :     const size_t local_first_index = get<Tags::LocalFirstIndex>(box);
     497              :     if (get<logging::Tags::Verbosity<OptionTags::BuildMatrixOptionsGroup>>(
     498              :             box) >= Verbosity::Verbose and
     499              :         local_first_index == 0 and (iteration_id + 1) % 100 == 0) {
     500              :       Parallel::printf("Column %zu / %zu done.\n", iteration_id + 1,
     501              :                        get<Tags::TotalNumPoints>(box));
     502              :     }
     503              :     // This is the result of applying the linear operator to the unit vector. It
     504              :     // is a column of the operator matrix.
     505              :     const auto& operator_applied_to_operand =
     506              :         get<OperatorAppliedToOperandTag>(box);
     507              :     // Store in sparse matrix
     508              :     db::mutate<Tags::Matrix<value_type>>(
     509              :         [&operator_applied_to_operand, &iteration_id](
     510              :             const gsl::not_null<blaze::CompressedMatrix<value_type>*> matrix) {
     511              :           for (size_t i = 0; i < operator_applied_to_operand.size(); ++i) {
     512              :             const auto value = operator_applied_to_operand.data()[i];
     513              :             if (not equal_within_roundoff(value, 0.)) {
     514              :               matrix->insert(i, iteration_id, value);
     515              :             }
     516              :           }
     517              :         },
     518              :         make_not_null(&box));
     519              :     // Write it out to disk
     520              :     if (const auto& subfile_name = get<Tags::MatrixSubfileName>(box);
     521              :         subfile_name.has_value()) {
     522              :       detail::observe_matrix_column<ParallelComponent>(
     523              :           iteration_id, operator_applied_to_operand, element_id,
     524              :           get<domain::Tags::Mesh<Dim>>(box), get<CoordsTag>(box), *subfile_name,
     525              :           *observers::get_section_observation_key<ArraySectionIdTag>(box),
     526              :           cache);
     527              :     }
     528              :     // Reset operand to zero
     529              :     const std::optional<size_t> local_unit_vector_index =
     530              :         detail::local_unit_vector_index(iteration_id, local_first_index,
     531              :                                         get<OperandTag>(box).size());
     532              :     if (local_unit_vector_index.has_value()) {
     533              :       db::mutate<OperandTag>(
     534              :           [&local_unit_vector_index](const auto operand) {
     535              :             operand->data()[*local_unit_vector_index] = 0.;
     536              :           },
     537              :           make_not_null(&box));
     538              :     }
     539              :     // Keep iterating
     540              :     db::mutate<IterationIdTag>(
     541              :         [](const auto local_iteration_id) { ++(*local_iteration_id); },
     542              :         make_not_null(&box));
     543              :     if (get<IterationIdTag>(box) < get<Tags::TotalNumPoints>(box)) {
     544              :       constexpr size_t set_unit_vector_index =
     545              :           tmpl::index_of<ActionList, SetUnitVector<BuildMatrixMetavars>>::value;
     546              :       return {Parallel::AlgorithmExecution::Continue, set_unit_vector_index};
     547              :     }
     548              :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     549              :   }
     550              : };
     551              : 
     552              : /// Adds the LU factorization of the full matrix to the singleton's DataBox
     553              : template <typename BuildMatrixMetavars>
     554            1 : struct InitializeSingleton {
     555            0 :   using value_type = typename BuildMatrixMetavars::value_type;
     556            0 :   using simple_tags = tmpl::list<Tags::LuFactorization<value_type>>;
     557              : 
     558              :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     559              :             typename ArrayIndex, typename ActionList,
     560              :             typename ParallelComponent>
     561            0 :   static Parallel::iterable_action_return_t apply(
     562              :       db::DataBox<DbTags>& /*box*/,
     563              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     564              :       Parallel::GlobalCache<Metavariables>& /*cache*/,
     565              :       const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
     566              :       const ParallelComponent* const /*meta*/) {
     567              :     return {Parallel::AlgorithmExecution::Pause, std::nullopt};
     568              :   }
     569              : };
     570              : 
     571              : template <typename Metavariables, typename BuildMatrixMetavars>
     572            0 : struct BuildMatrixSingleton {
     573            0 :   using chare_type = Parallel::Algorithms::Singleton;
     574            0 :   static constexpr bool checkpoint_data = true;
     575            0 :   using const_global_cache_tags = tmpl::list<>;
     576            0 :   using metavariables = Metavariables;
     577            0 :   using phase_dependent_action_list = tmpl::list<Parallel::PhaseActions<
     578              :       Parallel::Phase::Initialization,
     579              :       tmpl::list<InitializeSingleton<BuildMatrixMetavars>>>>;
     580            0 :   using simple_tags_from_options = Parallel::get_simple_tags_from_options<
     581              :       Parallel::get_initialization_actions_list<phase_dependent_action_list>>;
     582              : 
     583            0 :   static void execute_next_phase(
     584              :       const Parallel::Phase next_phase,
     585              :       Parallel::CProxy_GlobalCache<Metavariables>& global_cache) {
     586              :     auto& local_cache = *Parallel::local_branch(global_cache);
     587              :     Parallel::get_parallel_component<BuildMatrixSingleton>(local_cache)
     588              :         .start_phase(next_phase);
     589              :   }
     590              : };
     591              : 
     592              : template <typename ElementArrayComponent, typename BuildMatrixMetavars>
     593              : struct InvertMatrix;
     594              : 
     595              : template <typename BuildMatrixMetavars>
     596            0 : struct AssembleFullMatrix {
     597              :  private:
     598            0 :   using value_type = typename BuildMatrixMetavars::value_type;
     599            0 :   using ArraySectionIdTag = typename BuildMatrixMetavars::array_section_id_tag;
     600            0 :   using FixedSourcesTag = typename BuildMatrixMetavars::fixed_sources_tag;
     601            0 :   using ReductionType = std::pair<blaze::CompressedMatrix<value_type>,
     602              :                                   typename FixedSourcesTag::type>;
     603              : 
     604              :  public:
     605            0 :   using const_global_cache_tags = tmpl::list<Tags::EnableDirectSolve>;
     606              : 
     607              :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     608              :             size_t Dim, typename ActionList, typename ParallelComponent>
     609            0 :   static Parallel::iterable_action_return_t apply(
     610              :       db::DataBox<DbTags>& box,
     611              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     612              :       Parallel::GlobalCache<Metavariables>& cache,
     613              :       const ElementId<Dim>& element_id, const ActionList /*meta*/,
     614              :       const ParallelComponent* const /*meta*/) {
     615              :     if (not db::get<Tags::EnableDirectSolve>(box)) {
     616              :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     617              :     }
     618              :     auto& section = Parallel::get_section<ParallelComponent, ArraySectionIdTag>(
     619              :         make_not_null(&box));
     620              :     Parallel::contribute_to_reduction<
     621              :         InvertMatrix<ParallelComponent, BuildMatrixMetavars>>(
     622              :         Parallel::ReductionData<
     623              :             Parallel::ReductionDatum<size_t, funcl::AssertEqual<>>,
     624              :             Parallel::ReductionDatum<bool, funcl::Or<>>,
     625              :             Parallel::ReductionDatum<std::map<ElementId<Dim>, ReductionType>,
     626              :                                      funcl::Merge<>>>{
     627              :             element_id.grid_index(), get<Tags::MatrixChanged>(box),
     628              :             std::map<ElementId<Dim>, ReductionType>{std::make_pair(
     629              :                 element_id, ReductionType{get<Tags::Matrix<value_type>>(box),
     630              :                                           get<FixedSourcesTag>(box)})}},
     631              :         Parallel::get_parallel_component<ParallelComponent>(cache)[element_id],
     632              :         Parallel::get_parallel_component<
     633              :             BuildMatrixSingleton<Metavariables, BuildMatrixMetavars>>(cache),
     634              :         make_not_null(&section));
     635              :     db::mutate<Tags::MatrixChanged>(
     636              :         [](const gsl::not_null<bool*> matrix_changed) {
     637              :           *matrix_changed = false;
     638              :         },
     639              :         make_not_null(&box));
     640              :     // Pause the algorithm for now. The reduction will be received by the next
     641              :     // action which is responsible for restarting the algorithm.
     642              :     return {Parallel::AlgorithmExecution::Pause, std::nullopt};
     643              :   }
     644              : };
     645              : 
     646              : template <typename BuildMatrixMetavars>
     647              : struct StoreSolution;
     648              : 
     649              : template <typename ElementArrayComponent, typename BuildMatrixMetavars>
     650            0 : struct InvertMatrix {
     651              :  private:
     652            0 :   using value_type = typename BuildMatrixMetavars::value_type;
     653            0 :   using FixedSourcesTag = typename BuildMatrixMetavars::fixed_sources_tag;
     654            0 :   using ReductionType = std::pair<blaze::CompressedMatrix<value_type>,
     655              :                                   typename FixedSourcesTag::type>;
     656              : 
     657              :  public:
     658              :   template <typename ParallelComponent, typename DbTagsList,
     659              :             typename Metavariables, typename ArrayIndex, size_t Dim>
     660            0 :   static void apply(db::DataBox<DbTagsList>& box,
     661              :                     Parallel::GlobalCache<Metavariables>& cache,
     662              :                     const ArrayIndex& /*array_index*/, const size_t grid_index,
     663              :                     const bool matrix_changed,
     664              :                     const std::map<ElementId<Dim>, ReductionType>&
     665              :                         matrix_and_sources_slices) {
     666              :     // Assemble and factorize the full matrix if it changed since we last
     667              :     // factorized it. The factorization is not serialized, so it is also rebuilt
     668              :     // after a restart. Possible performance optimization: Don't send the matrix
     669              :     // slices again if they haven't changed.
     670              :     const size_t total_num_points =
     671              :         matrix_and_sources_slices.begin()->second.first.columns();
     672              :     auto& [lu, pivots] =
     673              :         db::get_mutable_reference<Tags::LuFactorization<value_type>>(
     674              :             make_not_null(&box));
     675              :     const bool factorize = matrix_changed or lu.rows() == 0;
     676              :     if (factorize) {
     677              :       lu.resize(total_num_points, total_num_points);
     678              :       pivots.resize(total_num_points);
     679              :     }
     680              :     ASSERT(lu.rows() == total_num_points,
     681              :            "The factorized matrix has size "
     682              :                << lu.rows() << " but the source has size " << total_num_points
     683              :                << ". The matrix changed but was not factorized again.");
     684              :     blaze::DynamicVector<value_type> solution(total_num_points);
     685              :     size_t i = 0;
     686              :     for (const auto& [element_id, slice] : matrix_and_sources_slices) {
     687              :       const auto& [matrix_slice, source_slice] = slice;
     688              :       ASSERT(matrix_slice.rows() == source_slice.size(),
     689              :              "Matrix and source slice have different sizes.");
     690              :       const size_t slice_length = matrix_slice.rows();
     691              :       if (factorize) {
     692              :         blaze::submatrix(lu, i, 0, slice_length, total_num_points) =
     693              :             matrix_slice;
     694              :       }
     695              :       std::copy_n(source_slice.data(), slice_length, solution.data() + i);
     696              :       i += slice_length;
     697              :     }
     698              :     ASSERT(i == total_num_points, "The matrix is not fully assembled. Expected "
     699              :                                       << total_num_points << " rows, got "
     700              :                                       << i);
     701              :     if (factorize) {
     702              :       if (get<logging::Tags::Verbosity<OptionTags::BuildMatrixOptionsGroup>>(
     703              :               box) >= Verbosity::Quiet) {
     704              :         Parallel::printf("Factorizing matrix of size %zu x %zu\n",
     705              :                          total_num_points, total_num_points);
     706              :       }
     707              :       const auto size = static_cast<blaze::blas_int_t>(total_num_points);
     708              :       blaze::blas_int_t info = 0;
     709              :       blaze::getrf(size, size, lu.data(),
     710              :                    static_cast<blaze::blas_int_t>(lu.spacing()), pivots.data(),
     711              :                    &info);
     712              :       if (info != 0) {
     713              :         ERROR("LU factorization of the matrix of size "
     714              :               << total_num_points << " x " << total_num_points
     715              :               << " failed with info = " << info
     716              :               << ". A positive value means the matrix is singular.");
     717              :       }
     718              :     }
     719              :     // Solve the equations Ax = b using the LU factorization
     720              :     blaze::getrs(lu, solution, 'N', pivots.data());
     721              :     // Broadcast the solution to the elements
     722              :     Parallel::simple_action<StoreSolution<BuildMatrixMetavars>>(
     723              :         Parallel::get_parallel_component<ElementArrayComponent>(cache),
     724              :         grid_index, solution);
     725              :   }
     726              : };
     727              : 
     728              : template <typename BuildMatrixMetavars>
     729            0 : struct StoreSolution {
     730              :  private:
     731            0 :   using value_type = typename BuildMatrixMetavars::value_type;
     732            0 :   using FieldsTag = typename BuildMatrixMetavars::fields_tag;
     733            0 :   using FixedSourcesTag = typename BuildMatrixMetavars::fixed_sources_tag;
     734              : 
     735              :  public:
     736              :   template <typename ParallelComponent, typename DbTagsList,
     737              :             typename Metavariables, size_t Dim>
     738            0 :   static void apply(db::DataBox<DbTagsList>& box,
     739              :                     Parallel::GlobalCache<Metavariables>& cache,
     740              :                     const ElementId<Dim>& element_id, const size_t grid_index,
     741              :                     const blaze::DynamicVector<value_type>& solution) {
     742              :     if (grid_index != element_id.grid_index()) {
     743              :       return;
     744              :     }
     745              :     const size_t local_first_index = db::get<Tags::LocalFirstIndex>(box);
     746              :     db::mutate<
     747              :         FieldsTag,
     748              :         db::add_tag_prefix<LinearSolver::Tags::OperatorAppliedTo, FieldsTag>>(
     749              :         [&solution, &local_first_index](const auto fields,
     750              :                                         const auto operator_applied_to_fields,
     751              :                                         const auto& fixed_sources) {
     752              :           std::copy_n(
     753              :               solution.begin() + static_cast<ptrdiff_t>(local_first_index),
     754              :               fields->size(), fields->data());
     755              :           // The linear problem is solved now, so Ax = b
     756              :           *operator_applied_to_fields = fixed_sources;
     757              :         },
     758              :         make_not_null(&box), db::get<FixedSourcesTag>(box));
     759              :     // Proceed with algorithm
     760              :     Parallel::get_parallel_component<ParallelComponent>(cache)[element_id]
     761              :         .perform_algorithm(true);
     762              :   }
     763              : };
     764              : 
     765              : template <typename BuildMatrixMetavars>
     766            0 : struct ProjectBuildMatrix : tt::ConformsTo<::amr::protocols::Projector> {
     767              :  private:
     768            0 :   using IterationIdTag = typename BuildMatrixMetavars::iteration_id_tag;
     769            0 :   using OperandTag = typename BuildMatrixMetavars::operand_tag;
     770            0 :   using value_type = typename BuildMatrixMetavars::value_type;
     771              : 
     772              :  public:
     773            0 :   using return_tags = tmpl::list<Tags::TotalNumPoints, Tags::Matrix<value_type>,
     774              :                                  Tags::LocalFirstIndex, IterationIdTag,
     775              :                                  OperandTag, Tags::MatrixChanged>;
     776            0 :   using argument_tags = tmpl::list<>;
     777              : 
     778              :   template <typename... AmrData>
     779            0 :   static void apply(
     780              :       const gsl::not_null<size_t*> total_num_points,
     781              :       const gsl::not_null<blaze::CompressedMatrix<value_type>*> matrix,
     782              :       const AmrData&... /*amr_data*/) {
     783              :     // Reset the built matrix when AMR changes the grid.
     784              :     // In case AMR is configured to keep coarse grids then the coarse-grid
     785              :     // elements don't run projectors during AMR, so they are not reset and just
     786              :     // keep the built matrix. This is good because the coarse grids are
     787              :     // unchanged and so the matrix is still valid (and can be used as bottom
     788              :     // solver in multigrid).
     789              :     *total_num_points = 0;
     790              :     matrix->clear();
     791              :   }
     792              : };
     793              : 
     794              : /// Dispatch global reduction to get the size of the matrix
     795              : template <typename BuildMatrixMetavars>
     796            1 : struct ResetBuiltMatrix {
     797              :  private:
     798            0 :   using value_type = typename BuildMatrixMetavars::value_type;
     799            0 :   using ArraySectionIdTag = typename BuildMatrixMetavars::array_section_id_tag;
     800              : 
     801              :  public:
     802            0 :   using const_global_cache_tags = tmpl::list<Tags::SkipResets>;
     803              : 
     804              :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     805              :             size_t Dim, typename ActionList, typename ParallelComponent>
     806            0 :   static Parallel::iterable_action_return_t apply(
     807              :       db::DataBox<DbTags>& box,
     808              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     809              :       Parallel::GlobalCache<Metavariables>& /*cache*/,
     810              :       const ElementId<Dim>& /*array_index*/, const ActionList /*meta*/,
     811              :       const ParallelComponent* const /*meta*/) {
     812              :     // Skip on elements that are not part of the section
     813              :     if constexpr (not std::is_same_v<ArraySectionIdTag, void>) {
     814              :       if (not db::get<Parallel::Tags::Section<ParallelComponent,
     815              :                                               ArraySectionIdTag>>(box)
     816              :                   .has_value()) {
     817              :         return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     818              :       }
     819              :     }
     820              :     if (db::get<Tags::SkipResets>(box)) {
     821              :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     822              :     }
     823              :     db::mutate<Tags::TotalNumPoints, Tags::Matrix<value_type>>(
     824              :         [](const auto total_num_points, const auto matrix) {
     825              :           *total_num_points = 0;
     826              :           matrix->clear();
     827              :         },
     828              :         make_not_null(&box));
     829              :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     830              :   }
     831              : };
     832              : 
     833              : /*!
     834              :  * \brief Build the explicit matrix representation of the linear operator and
     835              :  * optionally solve the problem directly with it.
     836              :  *
     837              :  * The matrix is built by applying the linear operator to unit vectors, so it
     838              :  * takes as many operator applications as there are points. Each element stores
     839              :  * the rows of the matrix that correspond to its points. The matrix is built
     840              :  * once and kept until it is reset (see `reset_actions`) or AMR changes the
     841              :  * grid.
     842              :  *
     843              :  * If direct solve is enabled, the elements send their rows of the matrix and
     844              :  * the source to a singleton. The singleton assembles the full matrix, computes
     845              :  * its LU factorization, and broadcasts the solution back to the elements. The
     846              :  * LU factorization is reused for subsequent solves until the matrix changes,
     847              :  * so these solves only cost two triangular solves on the singleton. This is
     848              :  * useful as bottom solver in a multigrid setting, where the coarsest grid is
     849              :  * small and the same linear operator is inverted in every V-cycle.
     850              :  *
     851              :  * Add the `actions` to the action list to build the matrix. The
     852              :  * `ApplyOperatorActions` template parameter are the actions that apply the
     853              :  * linear operator to the `OperandTag`. Also add the `amr_projectors` to the
     854              :  * list of AMR projectors and the `register_actions`
     855              :  *
     856              :  * \tparam FieldsTag The solution will be stored here (if direct solve is
     857              :  * enabled).
     858              :  * \tparam FixedSourcesTag The source `b` in the problem `Ax = b`. Only used
     859              :  * if direct solve is enabled.
     860              :  * \tparam OperandTag Will be set to unit vectors, with the '1' moving through
     861              :  * all points over the course of the iteration.
     862              :  * \tparam OperatorAppliedToOperandTag Where the `ApplyOperatorActions` store
     863              :  * the result of applying the linear operator to the `OperandTag`.
     864              :  * \tparam CoordsTag The tag of the coordinates observed alongside the matrix
     865              :  * for volume data visualization.
     866              :  * \tparam ArraySectionIdTag Can identify a subset of elements that this
     867              :  * algorithm should run over, e.g. in a multigrid setting.
     868              :  */
     869              : template <typename FieldsTag, typename FixedSourcesTag, typename OperandTag,
     870              :           typename OperatorAppliedToOperandTag, typename CoordsTag,
     871              :           typename ArraySectionIdTag = void>
     872            1 : struct BuildMatrix {
     873              :  private:
     874            0 :   using BuildMatrixMetavars =
     875              :       detail::BuildMatrixMetavars<FieldsTag, FixedSourcesTag, OperandTag,
     876              :                                   OperatorAppliedToOperandTag, CoordsTag,
     877              :                                   ArraySectionIdTag>;
     878              :   static_assert(
     879              :       std::is_same_v<FieldsTag, OperandTag>,
     880              :       "The operand and the fields tags must be the same. This is just so that "
     881              :       "at the end of the algorithm the operator is applied to the solution "
     882              :       "fields. This restriction can be lifted if needed by copying the fields "
     883              :       "into the operand and back.");
     884              : 
     885              :  public:
     886              :   template <typename Metavariables>
     887            0 :   using component_list =
     888              :       tmpl::list<BuildMatrixSingleton<Metavariables, BuildMatrixMetavars>>;
     889              : 
     890              :   template <typename ApplyOperatorActions>
     891            0 :   using actions =
     892              :       tmpl::list<CollectTotalNumPoints<BuildMatrixMetavars>,
     893              :                  // PrepareBuildMatrix is called on reduction broadcast
     894              :                  SetUnitVector<BuildMatrixMetavars>, ApplyOperatorActions,
     895              :                  StoreMatrixColumn<BuildMatrixMetavars>,
     896              :                  // Algorithm iterates until matrix is complete, then proceeds
     897              :                  // below
     898              :                  AssembleFullMatrix<BuildMatrixMetavars>,
     899              :                  // Apply operator to the solution. Note that FieldsTag and
     900              :                  // OperandTag must be the same for this (see assert above).
     901              :                  // Note also that this operator application is not needed in
     902              :                  // most cases, as the linear problem is solved exactly and
     903              :                  // therefore Ax = b is set in 'StoreSolution'. However, this
     904              :                  // operator application is needed if the operator changes
     905              :                  // between nonlinear solver iterations but we skip the reset,
     906              :                  // so the matrix represents the old operator. It's just one
     907              :                  // more operator application, so we keep it always enabled for
     908              :                  // now.
     909              :                  ApplyOperatorActions,
     910              :                  ::Actions::Label<typename BuildMatrixMetavars::end_label>>;
     911              : 
     912            0 :   using amr_projectors = tmpl::list<ProjectBuildMatrix<BuildMatrixMetavars>>;
     913              : 
     914              :   /// Add to the register phase to enable observations
     915            1 :   using register_actions = tmpl::list<observers::Actions::RegisterWithObservers<
     916              :       detail::RegisterWithVolumeObserver<ArraySectionIdTag>>>;
     917              : 
     918              :   /// Add to the action list to reset the matrix
     919            1 :   using reset_actions = tmpl::list<ResetBuiltMatrix<BuildMatrixMetavars>>;
     920              : };
     921              : 
     922              : }  // namespace Actions
     923              : }  // namespace LinearSolver
        

Generated by: LCOV version 2.0-1