SpECTRE Documentation Coverage Report
Current view: top level - ParallelAlgorithms/LinearSolver/Actions - BuildMatrix.hpp Hit Total Coverage
Commit: bcc6763cee2b3f1593fb35e61fb83412a3313e95 Lines: 12 40 30.0 %
Date: 2024-09-16 17:23: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. This is useful for debugging and analysis only,
       7             : /// not to actually solve the elliptic problem (that should happen iteratively).
       8             : 
       9             : #pragma once
      10             : 
      11             : #include <cstddef>
      12             : #include <map>
      13             : #include <optional>
      14             : #include <utility>
      15             : #include <vector>
      16             : 
      17             : #include "DataStructures/DataBox/DataBox.hpp"
      18             : #include "DataStructures/DataBox/Tag.hpp"
      19             : #include "Domain/Structure/ElementId.hpp"
      20             : #include "Domain/Tags.hpp"
      21             : #include "IO/H5/TensorData.hpp"
      22             : #include "IO/Logging/Tags.hpp"
      23             : #include "IO/Logging/Verbosity.hpp"
      24             : #include "IO/Observer/Actions/RegisterWithObservers.hpp"
      25             : #include "IO/Observer/GetSectionObservationKey.hpp"
      26             : #include "IO/Observer/ObserverComponent.hpp"
      27             : #include "IO/Observer/VolumeActions.hpp"
      28             : #include "Parallel/AlgorithmExecution.hpp"
      29             : #include "Parallel/GetSection.hpp"
      30             : #include "Parallel/Phase.hpp"
      31             : #include "Parallel/Printf/Printf.hpp"
      32             : #include "Parallel/Reduction.hpp"
      33             : #include "Parallel/Section.hpp"
      34             : #include "Parallel/Tags/Section.hpp"
      35             : #include "ParallelAlgorithms/Amr/Protocols/Projector.hpp"
      36             : #include "Utilities/Functional.hpp"
      37             : #include "Utilities/GetOutput.hpp"
      38             : #include "Utilities/ProtocolHelpers.hpp"
      39             : #include "Utilities/TMPL.hpp"
      40             : #include "Utilities/TaggedTuple.hpp"
      41             : 
      42             : namespace LinearSolver {
      43           0 : namespace OptionTags {
      44             : 
      45           0 : struct BuildMatrixOptionsGroup {
      46           0 :   static std::string name() { return "BuildMatrix"; }
      47           0 :   static constexpr Options::String help = {
      48             :       "Options for building the explicit matrix representation of the linear "
      49             :       "operator. This is done by applying the linear operator to unit "
      50             :       "vectors and is useful for debugging and analysis only, not to actually "
      51             :       "solve the elliptic problem (that should happen iteratively)."};
      52             : };
      53             : 
      54           0 : struct MatrixSubfileName {
      55           0 :   using type = std::string;
      56           0 :   using group = BuildMatrixOptionsGroup;
      57           0 :   static constexpr Options::String help = {
      58             :       "Subfile name in the volume data H5 files where the matrix will be "
      59             :       "stored. Each observation in the subfile is a column of the matrix. The "
      60             :       "row index is the order of elements defined by the ElementId in the "
      61             :       "volume data, by the order of tensor components encoded in the name of "
      62             :       "the components, and by the contiguous ordering of grid points for each "
      63             :       "component."};
      64             : };
      65             : 
      66             : }  // namespace OptionTags
      67             : 
      68           1 : namespace Tags {
      69             : 
      70             : /// Subfile name in the volume data H5 files where the matrix will be stored.
      71           1 : struct MatrixSubfileName : db::SimpleTag {
      72           0 :   using type = std::string;
      73           0 :   using option_tags = tmpl::list<OptionTags::MatrixSubfileName>;
      74           0 :   static constexpr bool pass_metavariables = false;
      75           0 :   static type create_from_options(const type& value) { return value; }
      76             : };
      77             : 
      78             : /// Size of the matrix: number of grid points times number of variables
      79           1 : struct TotalNumPoints : db::SimpleTag {
      80           0 :   using type = size_t;
      81             : };
      82             : 
      83             : /// Index of the first point in this element
      84           1 : struct LocalFirstIndex : db::SimpleTag {
      85           0 :   using type = size_t;
      86             : };
      87             : 
      88             : }  // namespace Tags
      89             : 
      90           1 : namespace Actions {
      91             : 
      92             : namespace detail {
      93             : 
      94             : /// \brief The total number of grid points (size of the matrix) and the index of
      95             : /// the first grid point in this element (the offset into the matrix
      96             : /// corresponding to this element).
      97             : template <size_t Dim>
      98             : std::pair<size_t, size_t> total_num_points_and_local_first_index(
      99             :     const ElementId<Dim>& element_id,
     100             :     const std::map<ElementId<Dim>, size_t>& num_points_per_element,
     101             :     size_t num_vars);
     102             : 
     103             : /// \brief The index of the '1' of the unit vector in this element, or
     104             : /// std::nullopt if the '1' is in another element.
     105             : ///
     106             : /// \param iteration_id enumerates all grid points
     107             : /// \param local_first_index the index of the first grid point in this element
     108             : /// \param local_num_points the number of grid points in this element
     109             : std::optional<size_t> local_unit_vector_index(size_t iteration_id,
     110             :                                               size_t local_first_index,
     111             :                                               size_t local_num_points);
     112             : 
     113             : /// \brief Observe matrix column as volume data.
     114             : ///
     115             : /// This is the format of the volume data:
     116             : ///
     117             : /// - Columns of the matrix are enumerated by the observation ID
     118             : /// - Rows are enumerated by the order of elements defined by the ElementId
     119             : ///   in the volume data, by the order of tensor components encoded in the
     120             : ///   name of the components, and by the contiguous ordering of grid points
     121             : ///   for each component.
     122             : /// - We also observe coordinates so the data can be plotted as volume data.
     123             : ///
     124             : /// The matrix can be reconstructed from this data in Python with the function
     125             : /// `spectre.Elliptic.ReadH5.read_matrix`.
     126             : template <typename ParallelComponent, typename OperatorAppliedToOperandTags,
     127             :           size_t Dim, typename CoordsFrame, typename Metavariables>
     128             : void observe_matrix_column(
     129             :     const size_t column,
     130             :     const Variables<OperatorAppliedToOperandTags>& operator_applied_to_operand,
     131             :     const ElementId<Dim>& element_id, const Mesh<Dim>& mesh,
     132             :     const tnsr::I<DataVector, Dim, CoordsFrame>& coords,
     133             :     const std::string& subfile_name, const std::string& section_observation_key,
     134             :     Parallel::GlobalCache<Metavariables>& cache) {
     135             :   std::vector<TensorComponent> observe_components{};
     136             :   observe_components.reserve(
     137             :       Dim +
     138             :       Variables<
     139             :           OperatorAppliedToOperandTags>::number_of_independent_components);
     140             :   for (size_t i = 0; i < Dim; ++i) {
     141             :     observe_components.emplace_back(
     142             :         get_output(CoordsFrame{}) + "Coordinates" + coords.component_suffix(i),
     143             :         coords.get(i));
     144             :   }
     145             :   size_t component_i = 0;
     146             :   tmpl::for_each<OperatorAppliedToOperandTags>([&operator_applied_to_operand,
     147             :                                                 &observe_components,
     148             :                                                 &component_i](auto tag_v) {
     149             :     using tag = tmpl::type_from<decltype(tag_v)>;
     150             :     const auto& tensor = get<tag>(operator_applied_to_operand);
     151             :     using TensorType = std::decay_t<decltype(tensor)>;
     152             :     using VectorType = typename TensorType::type;
     153             :     using ValueType = typename VectorType::value_type;
     154             :     for (size_t i = 0; i < tensor.size(); ++i) {
     155             :       if constexpr (std::is_same_v<ValueType, std::complex<double>>) {
     156             :         observe_components.emplace_back(
     157             :             "Re(Variable_" + std::to_string(component_i) + ")",
     158             :             real(tensor[i]));
     159             :         observe_components.emplace_back(
     160             :             "Im(Variable_" + std::to_string(component_i) + ")",
     161             :             imag(tensor[i]));
     162             :       } else {
     163             :         observe_components.emplace_back(
     164             :             "Variable_" + std::to_string(component_i), tensor[i]);
     165             :       }
     166             :       ++component_i;
     167             :     }
     168             :   });
     169             :   auto& local_observer = *Parallel::local_branch(
     170             :       Parallel::get_parallel_component<observers::Observer<Metavariables>>(
     171             :           cache));
     172             :   Parallel::simple_action<observers::Actions::ContributeVolumeData>(
     173             :       local_observer,
     174             :       observers::ObservationId(static_cast<double>(column),
     175             :                                subfile_name + section_observation_key),
     176             :       "/" + subfile_name,
     177             :       Parallel::make_array_component_id<ParallelComponent>(element_id),
     178             :       ElementVolumeData{element_id, std::move(observe_components), mesh});
     179             : }
     180             : 
     181             : /// \brief Register with the volume observer
     182             : template <typename ArraySectionIdTag>
     183             : struct RegisterWithVolumeObserver {
     184             :   template <typename ParallelComponent, typename DbTagsList,
     185             :             typename ArrayIndex>
     186             :   static std::pair<observers::TypeOfObservation, observers::ObservationKey>
     187             :   register_info(const db::DataBox<DbTagsList>& box,
     188             :                 const ArrayIndex& /*array_index*/) {
     189             :     // Get the observation key, or "Unused" if the element does not belong
     190             :     // to a section with this tag. In the latter case, no observations will
     191             :     // ever be contributed.
     192             :     const std::optional<std::string> section_observation_key =
     193             :         observers::get_section_observation_key<ArraySectionIdTag>(box);
     194             :     ASSERT(section_observation_key != "Unused",
     195             :            "The identifier 'Unused' is reserved to indicate that no "
     196             :            "observations with this key will be contributed. Use a different "
     197             :            "key, or change the identifier 'Unused' to something else.");
     198             :     return {
     199             :         observers::TypeOfObservation::Volume,
     200             :         observers::ObservationKey(get<Tags::MatrixSubfileName>(box) +
     201             :                                   section_observation_key.value_or("Unused"))};
     202             :   }
     203             : };
     204             : 
     205             : }  // namespace detail
     206             : 
     207             : /// \cond
     208             : template <typename IterationIdTag, typename OperandTag,
     209             :           typename OperatorAppliedToOperandTag, typename CoordsTag,
     210             :           typename ArraySectionIdTag>
     211             : struct PrepareBuildMatrix;
     212             : template <typename IterationIdTag, typename OperandTag,
     213             :           typename OperatorAppliedToOperandTag, typename CoordsTag,
     214             :           typename ArraySectionIdTag>
     215             : struct StoreMatrixColumn;
     216             : /// \endcond
     217             : 
     218             : /// Dispatch global reduction to get the size of the matrix
     219             : template <typename IterationIdTag, typename OperandTag,
     220             :           typename OperatorAppliedToOperandTag, typename CoordsTag,
     221             :           typename ArraySectionIdTag>
     222           1 : struct CollectTotalNumPoints {
     223           0 :   using simple_tags = tmpl::list<Tags::TotalNumPoints, Tags::LocalFirstIndex,
     224             :                                  IterationIdTag, OperandTag>;
     225           0 :   using compute_tags = tmpl::list<>;
     226           0 :   using const_global_cache_tags =
     227             :       tmpl::list<logging::Tags::Verbosity<OptionTags::BuildMatrixOptionsGroup>>;
     228             : 
     229             :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     230             :             size_t Dim, typename ActionList, typename ParallelComponent>
     231           0 :   static Parallel::iterable_action_return_t apply(
     232             :       db::DataBox<DbTags>& box,
     233             :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     234             :       Parallel::GlobalCache<Metavariables>& cache,
     235             :       const ElementId<Dim>& array_index, const ActionList /*meta*/,
     236             :       const ParallelComponent* const /*meta*/) {
     237             :     // Skip everything on elements that are not part of the section
     238             :     if constexpr (not std::is_same_v<ArraySectionIdTag, void>) {
     239             :       if (not db::get<Parallel::Tags::Section<ParallelComponent,
     240             :                                               ArraySectionIdTag>>(box)
     241             :                   .has_value()) {
     242             :         constexpr size_t last_action_index = tmpl::index_of<
     243             :             ActionList, StoreMatrixColumn<IterationIdTag, OperandTag,
     244             :                                           OperatorAppliedToOperandTag,
     245             :                                           CoordsTag, ArraySectionIdTag>>::value;
     246             :         return {Parallel::AlgorithmExecution::Continue, last_action_index + 1};
     247             :       }
     248             :     }
     249             :     db::mutate<Tags::TotalNumPoints, OperandTag>(
     250             :         [](const auto total_num_points, const auto operand,
     251             :            const size_t num_points) {
     252             :           // Set num points to zero until we know the total number of points
     253             :           *total_num_points = 0;
     254             :           // Size operand and fill with zeros
     255             :           operand->initialize(num_points, 0.);
     256             :         },
     257             :         make_not_null(&box),
     258             :         get<domain::Tags::Mesh<Dim>>(box).number_of_grid_points());
     259             :     // Collect the total number of grid points
     260             :     auto& section = Parallel::get_section<ParallelComponent, ArraySectionIdTag>(
     261             :         make_not_null(&box));
     262             :     Parallel::contribute_to_reduction<PrepareBuildMatrix<
     263             :         IterationIdTag, OperandTag, OperatorAppliedToOperandTag, CoordsTag,
     264             :         ArraySectionIdTag>>(
     265             :         Parallel::ReductionData<Parallel::ReductionDatum<
     266             :             std::map<ElementId<Dim>, size_t>, funcl::Merge<>>>{
     267             :             std::map<ElementId<Dim>, size_t>{
     268             :                 std::make_pair(array_index, get<OperandTag>(box).size())}},
     269             :         Parallel::get_parallel_component<ParallelComponent>(cache)[array_index],
     270             :         Parallel::get_parallel_component<ParallelComponent>(cache),
     271             :         make_not_null(&section));
     272             :     // Pause the algorithm for now. The reduction will be broadcast to the next
     273             :     // action which is responsible for restarting the algorithm.
     274             :     return {Parallel::AlgorithmExecution::Pause, std::nullopt};
     275             :   }
     276             : };
     277             : 
     278             : /// Receive the reduction and initialize the algorithm
     279             : template <typename IterationIdTag, typename OperandTag,
     280             :           typename OperatorAppliedToOperandTag, typename CoordsTag,
     281             :           typename ArraySectionIdTag>
     282           1 : struct PrepareBuildMatrix {
     283             :  public:
     284             :   template <typename ParallelComponent, typename DbTagsList,
     285             :             typename Metavariables, size_t Dim>
     286           0 :   static void apply(
     287             :       db::DataBox<DbTagsList>& box, Parallel::GlobalCache<Metavariables>& cache,
     288             :       const ElementId<Dim>& element_id,
     289             :       const std::map<ElementId<Dim>, size_t>& num_points_per_element) {
     290             :     const auto [total_num_points, local_first_index] =
     291             :         detail::total_num_points_and_local_first_index(
     292             :             element_id, num_points_per_element,
     293             :             OperandTag::type::number_of_independent_components);
     294             :     if (get<logging::Tags::Verbosity<OptionTags::BuildMatrixOptionsGroup>>(
     295             :             box) >= Verbosity::Quiet and
     296             :         local_first_index == 0) {
     297             :       Parallel::printf(
     298             :           "Building explicit matrix representation of size %zu x %zu.\n",
     299             :           total_num_points, total_num_points);
     300             :     }
     301             :     db::mutate<Tags::TotalNumPoints, Tags::LocalFirstIndex, IterationIdTag>(
     302             :         [captured_total_num_points = total_num_points,
     303             :          captured_local_first_index = local_first_index](
     304             :             const auto stored_total_num_points,
     305             :             const auto stored_local_first_index, const auto iteration_id) {
     306             :           *stored_total_num_points = captured_total_num_points;
     307             :           *stored_local_first_index = captured_local_first_index;
     308             :           *iteration_id = 0;
     309             :         },
     310             :         make_not_null(&box));
     311             :     // Proceed with algorithm
     312             :     Parallel::get_parallel_component<ParallelComponent>(cache)[element_id]
     313             :         .perform_algorithm(true);
     314             :   }
     315             : };
     316             : 
     317             : /// Set the operand to a unit vector with a '1' at the current grid point.
     318             : /// Applying the operator to this operand gives a column of the matrix. We jump
     319             : /// back to this until we have iterated over all grid points.
     320             : template <typename IterationIdTag, typename OperandTag,
     321             :           typename OperatorAppliedToOperandTag, typename CoordsTag,
     322             :           typename ArraySectionIdTag>
     323           1 : struct SetUnitVector {
     324             :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     325             :             typename ArrayIndex, typename ActionList,
     326             :             typename ParallelComponent>
     327           0 :   static Parallel::iterable_action_return_t apply(
     328             :       db::DataBox<DbTags>& box,
     329             :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     330             :       const Parallel::GlobalCache<Metavariables>& /*cache*/,
     331             :       const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
     332             :       const ParallelComponent* const /*meta*/) {
     333             :     const std::optional<size_t> local_unit_vector_index =
     334             :         detail::local_unit_vector_index(get<IterationIdTag>(box),
     335             :                                         get<Tags::LocalFirstIndex>(box),
     336             :                                         get<OperandTag>(box).size());
     337             :     if (local_unit_vector_index.has_value()) {
     338             :       db::mutate<OperandTag>(
     339             :           [&local_unit_vector_index](const auto operand) {
     340             :             operand->data()[*local_unit_vector_index] = 1.;
     341             :           },
     342             :           make_not_null(&box));
     343             :     }
     344             :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     345             :   }
     346             : };
     347             : 
     348             : // --- Linear operator will be applied to the unit vector here ---
     349             : 
     350             : /// Write result out to disk, reset operand back to zero, and keep iterating
     351             : template <typename IterationIdTag, typename OperandTag,
     352             :           typename OperatorAppliedToOperandTag, typename CoordsTag,
     353             :           typename ArraySectionIdTag>
     354           1 : struct StoreMatrixColumn {
     355           0 :   using const_global_cache_tags = tmpl::list<Tags::MatrixSubfileName>;
     356             : 
     357             :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     358             :             size_t Dim, typename ActionList, typename ParallelComponent>
     359           0 :   static Parallel::iterable_action_return_t apply(
     360             :       db::DataBox<DbTags>& box,
     361             :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     362             :       Parallel::GlobalCache<Metavariables>& cache,
     363             :       const ElementId<Dim>& element_id, const ActionList /*meta*/,
     364             :       const ParallelComponent* const /*meta*/) {
     365             :     const size_t iteration_id = get<IterationIdTag>(box);
     366             :     const size_t local_first_index = get<Tags::LocalFirstIndex>(box);
     367             :     if (get<logging::Tags::Verbosity<OptionTags::BuildMatrixOptionsGroup>>(
     368             :             box) >= Verbosity::Verbose and
     369             :         local_first_index == 0) {
     370             :       Parallel::printf("Column %zu / %zu done.\n", iteration_id + 1,
     371             :                        get<Tags::TotalNumPoints>(box));
     372             :     }
     373             :     // This is the result of applying the linear operator to the unit vector. It
     374             :     // is a column of the operator matrix.
     375             :     const auto& operator_applied_to_operand =
     376             :         get<OperatorAppliedToOperandTag>(box);
     377             :     // Write it out to disk
     378             :     detail::observe_matrix_column<ParallelComponent>(
     379             :         iteration_id, operator_applied_to_operand, element_id,
     380             :         get<domain::Tags::Mesh<Dim>>(box), get<CoordsTag>(box),
     381             :         get<Tags::MatrixSubfileName>(box),
     382             :         *observers::get_section_observation_key<ArraySectionIdTag>(box), cache);
     383             :     // Reset operand to zero
     384             :     const std::optional<size_t> local_unit_vector_index =
     385             :         detail::local_unit_vector_index(iteration_id, local_first_index,
     386             :                                         get<OperandTag>(box).size());
     387             :     if (local_unit_vector_index.has_value()) {
     388             :       db::mutate<OperandTag>(
     389             :           [&local_unit_vector_index](const auto operand) {
     390             :             operand->data()[*local_unit_vector_index] = 0.;
     391             :           },
     392             :           make_not_null(&box));
     393             :     }
     394             :     // Keep iterating
     395             :     db::mutate<IterationIdTag>(
     396             :         [](const auto local_iteration_id) { ++(*local_iteration_id); },
     397             :         make_not_null(&box));
     398             :     if (get<IterationIdTag>(box) < get<Tags::TotalNumPoints>(box)) {
     399             :       constexpr size_t set_unit_vector_index = tmpl::index_of<
     400             :           ActionList,
     401             :           SetUnitVector<IterationIdTag, OperandTag, OperatorAppliedToOperandTag,
     402             :                         CoordsTag, ArraySectionIdTag>>::value;
     403             :       return {Parallel::AlgorithmExecution::Continue, set_unit_vector_index};
     404             :     }
     405             :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     406             :   }
     407             : };
     408             : 
     409             : template <typename IterationIdTag, typename OperandTag,
     410             :           typename OperatorAppliedToOperandTag, typename CoordsTag,
     411             :           typename ArraySectionIdTag>
     412           0 : struct ProjectBuildMatrix : tt::ConformsTo<::amr::protocols::Projector> {
     413           0 :   using return_tags = tmpl::list<Tags::TotalNumPoints, Tags::LocalFirstIndex,
     414             :                                  IterationIdTag, OperandTag>;
     415           0 :   using argument_tags = tmpl::list<>;
     416             : 
     417             :   template <typename... AmrData>
     418           0 :   static void apply(const gsl::not_null<size_t*> /*unused*/,
     419             :                     const AmrData&... /*amr_data*/) {
     420             :     // Nothing to do. Everything gets initialized at the start of the algorithm.
     421             :   }
     422             : };
     423             : 
     424             : /*!
     425             :  * \brief Build the explicit matrix representation of the linear operator.
     426             :  *
     427             :  * This is useful for debugging and analysis only, not to actually solve the
     428             :  * elliptic problem (that should happen iteratively).
     429             :  *
     430             :  * Add the `actions` to the action list to build the matrix. The
     431             :  * `ApplyOperatorActions` template parameter are the actions that apply the
     432             :  * linear operator to the `OperandTag`. Also add the `amr_projectors` to the
     433             :  * list of AMR projectors and the `register_actions`
     434             :  *
     435             :  * \tparam IterationIdTag Used to keep track of the iteration over all matrix
     436             :  * columns. Should be the same that's used to identify iterations in the
     437             :  * `ApplyOperatorActions`.
     438             :  * \tparam OperandTag Will be set to unit vectors, with the '1' moving through
     439             :  * all points over the course of the iteration.
     440             :  * \tparam OperatorAppliedToOperandTag Where the `ApplyOperatorActions` store
     441             :  * the result of applying the linear operator to the `OperandTag`.
     442             :  * \tparam CoordsTag The tag of the coordinates observed alongside the matrix
     443             :  * for volume data visualization.
     444             :  * \tparam ArraySectionIdTag Can identify a subset of elements that this
     445             :  * algorithm should run over, e.g. in a multigrid setting.
     446             :  */
     447             : template <typename IterationIdTag, typename OperandTag,
     448             :           typename OperatorAppliedToOperandTag, typename CoordsTag,
     449             :           typename ArraySectionIdTag = void>
     450           1 : struct BuildMatrix {
     451             :   template <typename ApplyOperatorActions>
     452           0 :   using actions = tmpl::list<
     453             :       CollectTotalNumPoints<IterationIdTag, OperandTag,
     454             :                             OperatorAppliedToOperandTag, CoordsTag,
     455             :                             ArraySectionIdTag>,
     456             :       // PrepareBuildMatrix is called on reduction broadcast
     457             :       SetUnitVector<IterationIdTag, OperandTag, OperatorAppliedToOperandTag,
     458             :                     CoordsTag, ArraySectionIdTag>,
     459             :       ApplyOperatorActions,
     460             :       StoreMatrixColumn<IterationIdTag, OperandTag, OperatorAppliedToOperandTag,
     461             :                         CoordsTag, ArraySectionIdTag>>;
     462             : 
     463           0 :   using amr_projectors =
     464             :       tmpl::list<ProjectBuildMatrix<IterationIdTag, OperandTag,
     465             :                                     OperatorAppliedToOperandTag, CoordsTag,
     466             :                                     ArraySectionIdTag>>;
     467             : 
     468             :   /// Add to the register phase to enable observations
     469           1 :   using register_actions = tmpl::list<observers::Actions::RegisterWithObservers<
     470             :       detail::RegisterWithVolumeObserver<ArraySectionIdTag>>>;
     471             : };
     472             : 
     473             : }  // namespace Actions
     474             : }  // namespace LinearSolver

Generated by: LCOV version 1.14