Line data Source code
1 0 : // Distributed under the MIT License.
2 : // See LICENSE.txt for details.
3 :
4 : #pragma once
5 :
6 : #include <blaze/math/CompressedMatrix.h>
7 : #include <cstddef>
8 : #include <unordered_map>
9 : #include <utility>
10 : #include <vector>
11 :
12 : #include "DataStructures/SimpleSparseMatrix.hpp"
13 : #include "Utilities/ErrorHandling/Assert.hpp"
14 : #include "Utilities/Gsl.hpp"
15 :
16 : namespace sparse_matrix_filler_detail {
17 : /// Hash on a pair of size_ts
18 : struct PairHasher {
19 : size_t operator()(const std::pair<size_t, size_t>& key) const {
20 : ASSERT(key.first < 4294967295, "Key.first too large: " << key.first);
21 : ASSERT(key.second < 4294967295, "Key.second too large: " << key.second);
22 : return key.first bitor (key.second << 32);
23 : }
24 : };
25 : } // namespace sparse_matrix_filler_detail
26 :
27 : /*!
28 : * \ingroup DataStructuresGroup
29 : * \brief A data structure used to fill sparse matrices.
30 : *
31 : * We normally use blaze::CompressedMatrix to hold sparse matrices and to
32 : * operate on them.
33 : *
34 : * Unfortunately to fill a blaze::CompressedMatrix efficiently you
35 : * need to fill nonzero matrix elements in a well-defined order
36 : * (i.e. fill all nonzero elements in the top row in the order of
37 : * nonzero columns, and repeat for each other row, in order) using
38 : * CompressedMatrix's reserve(), append(), and finalize() functions.
39 : *
40 : * However, we are interested in a use case where:
41 : *
42 : * 1. We are filling the matrix elements using a complicated
43 : * algorithm that computes (as opposed to systematically loops
44 : * over) the matrix element indices to be filled, so we are
45 : * effectively filling matrix elements in a random order.
46 : *
47 : * 2. The algorithm can traverse the same matrix element hundreds
48 : * of times, meaning that the first time an element is traversed
49 : * it should be filled, but subsequent times it is traversed it
50 : * should be incremented.
51 : *
52 : * SparseMatrixFiller provides a function to fill matrix elements, keeping
53 : * in mind the above two issues. And it provides a function to copy its
54 : * sparse matrix into blaze::CompressedMatrix efficiently.
55 : *
56 : * We consider two ways to keep track of unique elements:
57 : *
58 : * 1. Store matrix elements in a full vector that is indexed by
59 : * matrix_elements[src_index+max_src_index*dest_index]. Set this
60 : * vector to zero initially, and increment it each time 'add' is called.
61 : * This is simple, but has a major issue: memory.
62 : * In SpEC, I (Mark Scheel) have measured that computing TensorYlm
63 : * filtering matrices using this method, after l_max=40 , the
64 : * slope of CPU-h vs l_max for this method has a kink, and CPU-h
65 : * increases like l_max^2 instead of l_max. Presumably this is
66 : * because of cache misses when accessing the very large
67 : * matrix_elements vector at random locations.
68 : *
69 : * 2. Have a std::unordered_map<std::pair<size_t,size_t>, size_t>>
70 : * called `element_index` that keeps track of the unique elements.
71 : * Also have three vectors `dest_indices`, `src_indices`,
72 : * and `matrix_elements` that keep the nonzero elements.
73 : * element_index is indexed by
74 : * element_index[{dest_index,src_index}] = index-into-matrix-elements
75 : * So each time we add a new element, we check element_index to see
76 : * if that element has not already been added, and if not, we push_back
77 : * the three vectors. If it has already been added, we simply do
78 : * matrix_elements[index-into-matrix-elements] += new_element.
79 : *
80 : * I (Mark Scheel) have found in SpEC for computing TensorYlm filtering
81 : * matrices that the CPU cost of algorithm 2. above scales linearly
82 : * with l_max at least up to l_max=140, but for l_max < 40 or so,
83 : * algorithm 2. is about a factor of 3 slower than algorithm 1.
84 : * So SpEC uses both algorithms, depending on the value of l_max.
85 : *
86 : * For SpECTRE, we plan to do the same: for small l_max we will use
87 : * algorithm 1 and for large l_max we will use algorithm 2. The
88 : * cutoff for l_max will depend on other factors (like the rank of the
89 : * Tensor being filtered) and will be determined by whoever calls
90 : * SparseMatrixFiller.
91 : */
92 1 : class SparseMatrixFiller {
93 : public:
94 : /// Assume num_rows and num_cols are equal.
95 : /// If use_map_method is true, uses algorithm 2.
96 : /// Scale is the overall scale of the matrix; scale is used to remove
97 : /// elements that are smaller than roundoff.
98 1 : SparseMatrixFiller(size_t num_cols, bool use_map_method, double scale);
99 :
100 : /// Add (or increment) an element such that for v = M x, where M is
101 : /// the matrix and v and x are vectors, we compute the contribution
102 : /// to v[dest_index] from x[src_index].
103 1 : void add(double element, size_t dest_index, size_t src_index);
104 :
105 : /// Fills the matrix.
106 : /// Note that the order in which blaze matrices must be filled depends
107 : /// on whether the matrix is rowMajor or columnMajor, so changing
108 : /// the template parameter here will fail without changing the code.
109 1 : void fill(gsl::not_null<blaze::CompressedMatrix<double, blaze::rowMajor>*>
110 : matrix) const;
111 :
112 : /// Fills a SimpleSparseMatrix.
113 1 : void fill(gsl::not_null<SimpleSparseMatrix*> matrix) const;
114 :
115 : private:
116 0 : void fill_sparse_matrix_elements(
117 : gsl::not_null<std::vector<SparseMatrixElement>*> data) const;
118 0 : size_t num_rows_{0}, num_cols_{0};
119 0 : bool use_map_method_{true};
120 0 : double scale_{1.0};
121 0 : std::vector<double> matrix_elements_{};
122 : std::unordered_map<std::pair<size_t, size_t>, size_t,
123 : sparse_matrix_filler_detail::PairHasher>
124 0 : element_index_{};
125 0 : std::vector<size_t> dest_indices_{};
126 0 : std::vector<size_t> src_indices_{};
127 : };
|