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

Generated by: LCOV version 1.14