tatami_mult
Multiply tatami matrices
Loading...
Searching...
No Matches
sparse_column.hpp
Go to the documentation of this file.
1#ifndef TATAMI_MULT_MULTIPLE_VECTORS_SPARSE_COLUMN_HPP
2#define TATAMI_MULT_MULTIPLE_VECTORS_SPARSE_COLUMN_HPP
3
4#include <cstddef>
5#include <vector>
6#include <optional>
7#include <algorithm>
8
9#include "tatami/tatami.hpp"
10#include "sanisizer/sanisizer.hpp"
11#include "jiwoo/jiwoo.hpp"
12
13#include "../utils.hpp"
14#include "../sparse_dot_product.hpp"
15
21namespace tatami_mult {
22
23/* See https://github.com/tatami-inc/test-multiplication/tree/master/sparse_column/multiple_vectors
24 * for an explanation of the choice of algorithm.
25 */
26
43
47template<typename LeftValue_, typename LeftIndex_, typename RightVectors_, typename GetRightVector_, typename GetOutputVector_>
48void multiply_sparse_column_with_multiple_vectors_internal(
50 const LeftIndex_ start,
51 const LeftIndex_ length,
52 const LeftIndex_ left_NR,
53 const RightVectors_ right_vectors,
54 GetRightVector_ get_right_vector,
55 GetOutputVector_ get_output_vector,
57) {
58 auto ext = tatami::consecutive_extractor<true>(left, false, start, length);
59 typedef I<decltype(get_output_vector(0)[0])> Output;
60
61 if (options.block_size == 1) {
64 for (LeftIndex_ cd = 0; cd < length; ++cd) {
65 const auto range = ext->fetch(vbuffer.data(), ibuffer.data());
66 if (range.number == 0) {
67 continue;
68 }
69
70 for (RightVectors_ rv = 0; rv < right_vectors; ++rv) {
71 const auto optr = get_output_vector(rv);
72 const Output mult = get_right_vector(rv)[start + cd];
73 for (LeftIndex_ x = 0; x < range.number; ++x) {
74 optr[range.index[x]] += mult * static_cast<Output>(range.value[x]);
75 }
76 }
77 }
78
79 } else {
80 std::vector<std::vector<LeftValue_> > left_vbuffers;
81 std::vector<std::vector<LeftIndex_> > left_ibuffers;
82 std::vector<tatami::SparseRange<LeftValue_, LeftIndex_> > left_ranges;
83 std::vector<LeftIndex_> left_non_empty;
84 {
85 const LeftIndex_ max_block_cols = sanisizer::min(length, options.block_size);
86 left_vbuffers.reserve(max_block_cols);
87 left_ibuffers.reserve(max_block_cols);
88 for (LeftIndex_ cd = 0; cd < max_block_cols; ++cd) {
89 left_vbuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(left_NR));
90 left_ibuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftIndex_> >(left_NR));
91 }
92 sanisizer::resize(left_ranges, max_block_cols);
93 left_non_empty.reserve(max_block_cols);
94 }
95
96 LeftIndex_ cd = 0;
97 while (cd < length) {
98 // Only considering the LHS columns with at least one structural non-zero.
99 // Thus, our block consists of 'options.block_size' non-empty LHS columns, rather than fixed column-wise chunks of the LHS matrix.
100 // This ensures that we don't waste iterations on LHS columns that don't contribute anything to the product.
101 const auto left_block_info = fetch_non_empty_sparse_block(
102 *ext,
103 left_vbuffers,
104 left_ibuffers,
105 left_ranges,
106 left_non_empty,
107 cd,
108 length,
109 options.block_size,
110 /* zero = */ [](LeftIndex_) -> void {} // No need to worry about zeroing as the buffers have already been zeroed.
111 );
112 const auto cd_num = left_block_info.num_non_empty;
113
114 // If the LHS columns are all non-empty, we can speed up the loops by just using a simple counter to get the column indices.
115 // Otherwise, we'll have to access the 'left_non_empty' vector to figure out the indices of each non-empty column.
116 if (left_block_info.all_non_empty) {
117 const LeftIndex_ cd_base = start + cd;
118 for (RightVectors_ rv = 0; rv < right_vectors; ++rv) {
119 const auto outvec = get_output_vector(rv);
120 const auto rightvec = get_right_vector(rv);
121 for (LeftIndex_ cd_counter = 0; cd_counter < cd_num; ++cd_counter) {
122 const auto& currange = left_ranges[cd_counter];
123 const Output mult = rightvec[cd_base + cd_counter];
124 for (LeftIndex_ x = 0; x < currange.number; ++x) {
125 outvec[currange.index[x]] += mult * static_cast<Output>(currange.value[x]);
126 }
127 }
128 }
129
130 } else {
131 for (auto& cdne : left_non_empty) {
132 cdne += start;
133 }
134 for (RightVectors_ rv = 0; rv < right_vectors; ++rv) {
135 const auto outvec = get_output_vector(rv);
136 const auto rightvec = get_right_vector(rv);
137 for (LeftIndex_ cd_counter = 0; cd_counter < cd_num; ++cd_counter) {
138 const auto& currange = left_ranges[cd_counter];
139 const Output mult = rightvec[left_non_empty[cd_counter]];
140 for (LeftIndex_ x = 0; x < currange.number; ++x) {
141 outvec[currange.index[x]] += mult * static_cast<Output>(currange.value[x]);
142 }
143 }
144 }
145 }
146
147 cd = left_block_info.position;
148 }
149 }
150}
174template<typename LeftValue_, typename LeftIndex_, typename RightVectors_, typename GetRightVector_, typename GetOutputVector_>
177 const RightVectors_ right_vectors,
178 GetRightVector_ get_right_vector,
179 GetOutputVector_ get_output_vector,
181) {
182 const auto left_NR = left.nrow();
183 const auto common_dim = left.ncol();
184 for (RightVectors_ rv = 0; rv < right_vectors; ++rv) {
185 std::fill_n(get_output_vector(rv), left_NR, 0);
186 }
187
188 const bool do_parallel = options.num_threads > 1;
189 typedef I<decltype(get_output_vector(0)[0])> Output;
190 std::optional<std::vector<std::optional<std::vector<std::vector<Output> > > > > tmp_results;
191 if (do_parallel) {
192 tmp_results.emplace(sanisizer::cast<I<decltype(tmp_results->size())> >(options.num_threads - 1));
193 }
194
195 const auto num_used = tatami::parallelize([&](int t, LeftIndex_ start, LeftIndex_ length) -> void {
196 if (!do_parallel || t == 0) {
197 multiply_sparse_column_with_multiple_vectors_internal(
198 left,
199 start,
200 length,
201 left_NR,
202 right_vectors,
203 get_right_vector,
204 get_output_vector,
205 options
206 );
207
208 } else {
209 std::vector<std::vector<Output> > tmp_output;
210 tmp_output.reserve(right_vectors);
211 for (RightVectors_ rv = 0; rv < right_vectors; ++rv) {
212 tmp_output.emplace_back(tatami::cast_Index_to_container_size<std::vector<Output> >(left_NR));
213 }
214 multiply_sparse_column_with_multiple_vectors_internal(
215 left,
216 start,
217 length,
218 left_NR,
219 right_vectors,
220 get_right_vector,
221 [&](const RightVectors_ rv) -> Output* {
222 return tmp_output[rv].data();
223 },
224 options
225 );
226 (*tmp_results)[t - 1] = std::move(tmp_output);
227 }
228 }, common_dim, options.num_threads);
229
230 if (do_parallel) {
231 for (int u = 1; u < num_used; ++u) {
232 const auto& tmp = *((*tmp_results)[u - 1]);
233 for (RightVectors_ rv = 0; rv < right_vectors; ++rv) {
234 const auto& tmpvec = tmp[rv];
235 const auto outptr = get_output_vector(rv);
236 for (LeftIndex_ lr = 0; lr < left_NR; ++lr) {
237 outptr[lr] += tmpvec[lr];
238 }
239 }
240 }
241 }
242}
243
259template<typename LeftValue_, typename LeftIndex_, typename RightValue_, typename Output_>
262 const std::vector<RightValue_*>& right,
263 const std::vector<Output_*>& output,
265) {
266 const auto left_NR = left.nrow();
267 const auto common_dim = left.ncol();
268 const auto right_vectors = right.size();
269 typedef I<decltype(right_vectors)> RightVectors;
270 for (RightVectors rv = 0; rv < right_vectors; ++rv) {
271 std::fill_n(output[rv], left_NR, 0);
272 }
273
274 const bool do_parallel = options.num_threads > 1;
275 std::optional<std::vector<std::optional<jiwoo::EquilengthArrays<Output_> > > > tmp_results;
276 if (do_parallel) {
277 tmp_results.emplace(sanisizer::cast<I<decltype(tmp_results->size())> >(options.num_threads - 1));
278 }
279
280 const auto num_used = tatami::parallelize([&](int t, LeftIndex_ start, LeftIndex_ length) -> void {
281 std::optional<jiwoo::EquilengthArrays<Output_> > tmp_output;
282
283 Output_* const * output_ptrs;
284 if (!do_parallel || t == 0) {
285 output_ptrs = output.data();
286 } else {
287 tmp_output.emplace(
288 sanisizer::cast<I<decltype(tmp_output->size())> >(right_vectors),
289 static_cast<std::size_t>(left_NR), // cast to size_t is safe due to the tatami contract.
290 0
291 );
292 output_ptrs = tmp_output->get();
293 }
294
295 multiply_sparse_column_with_multiple_vectors_internal(
296 left,
297 start,
298 length,
299 left_NR,
300 right_vectors,
301 [&](const RightVectors rv) -> const RightValue_* {
302 return right[rv];
303 },
304 [&](const RightVectors rv) -> Output_* {
305 return output_ptrs[rv];
306 },
307 options
308 );
309
310 if (do_parallel && t > 0) {
311 (*tmp_results)[t - 1] = std::move(tmp_output);
312 }
313 }, common_dim, options.num_threads);
314
315 if (do_parallel) {
316 for (int u = 1; u < num_used; ++u) {
317 const auto& tmp = *((*tmp_results)[u - 1]);
318 for (RightVectors rv = 0; rv < right_vectors; ++rv) {
319 const auto tmpvec = tmp[rv];
320 const auto outptr = output[rv];
321 for (LeftIndex_ lr = 0; lr < left_NR; ++lr) {
322 outptr[lr] += tmpvec[lr];
323 }
324 }
325 }
326 }
327}
328
329}
330
331#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
Multiplication of tatami matrices.
Definition column_to_column.hpp:19
void multiply_sparse_column_with_multiple_vectors(const tatami::Matrix< LeftValue_, LeftIndex_ > &left, const RightVectors_ right_vectors, GetRightVector_ get_right_vector, GetOutputVector_ get_output_vector, const MultiplySparseColumnWithMultipleVectorsOptions &options)
Definition sparse_column.hpp:175
int parallelize(Function_ fun, const Index_ tasks, const int workers)
I< decltype(std::declval< Container_ >().size())> cast_Index_to_container_size(const Index_ x)
Container_ create_container_of_Index_size(const Index_ x, Args_ &&... args)
auto consecutive_extractor(const Matrix< Value_, Index_ > &matrix, const bool row, const Index_ iter_start, const Index_ iter_length, Args_ &&... args)
Options for multiply_sparse_column_with_multiple_vectors().
Definition sparse_column.hpp:30