tatami
C++ API for different matrix representations
Loading...
Searching...
No Matches
convert_to_compressed_sparse.hpp
Go to the documentation of this file.
1#ifndef TATAMI_CONVERT_TO_COMPRESSED_SPARSE_H
2#define TATAMI_CONVERT_TO_COMPRESSED_SPARSE_H
3
4#include <memory>
5#include <vector>
6#include <cstddef>
7#include <optional>
8
11#include "convert_to_sparse_utils.hpp"
12
16#include "../utils/copy.hpp"
17
24namespace tatami {
25
29template<typename Value_, typename Index_, typename Count_>
30void count_compressed_sparse_non_zeros_consistent(
32 const Index_ primary,
33 const Index_ secondary,
34 const bool row,
35 Count_* const output,
36 const int threads
37) {
38 sanisizer::cast<Count_>(secondary); // confirm that the counts don't overflow the Count_.
39
40 if (matrix.is_sparse()) {
41 Options opt;
42 opt.sparse_extract_value = false;
43 opt.sparse_extract_index = false;
44 opt.sparse_ordered_index = false;
45
46 parallelize([&](const int, const Index_ start, const Index_ length) -> void {
47 auto wrk = consecutive_extractor<true>(matrix, row, start, length, opt);
48 for (Index_ x = 0; x < length; ++x) {
49 const auto range = wrk->fetch(NULL, NULL);
50 output[start + x] = range.number;
51 }
52 }, primary, threads);
53
54 } else {
55 parallelize([&](const int, const Index_ start, const Index_ length) -> void {
56 auto buffer_v = create_container_of_Index_size<std::vector<Value_> >(secondary);
57 auto wrk = consecutive_extractor<false>(matrix, row, start, length);
58 for (Index_ p = start, pe = start + length; p < pe; ++p) {
59 const auto ptr = wrk->fetch(buffer_v.data());
60 Count_ count = 0;
61 for (Index_ s = 0; s < secondary; ++s) {
62 count += (ptr[s] != 0);
63 }
64 output[p] = count;
65 }
66 }, primary, threads);
67 }
68}
69
70// For back-compatiblity only, this functionality should probably not have been exported.
71// It's not even entirely correct as we only count structural non-zeros in the sparse case,
72// so it's hard to think of a case where someone would want to use this.
73struct CountCompressedSparseNonZerosOptions {
74 int num_threads = 1;
75};
76
77// For back-compatiblity only, see above.
78template<typename Value_, typename Index_, typename Count_>
79void count_compressed_sparse_non_zeros(
81 const bool row,
82 Count_* const output,
83 const CountCompressedSparseNonZerosOptions& options
84) {
85 const Index_ NR = matrix.nrow();
86 const Index_ NC = matrix.ncol();
87 const Index_ primary = (row ? NR : NC);
88 const Index_ secondary = (row ? NC : NR);
89
90 if (row == matrix.prefer_rows()) {
91 count_compressed_sparse_non_zeros_consistent(matrix, primary, secondary, row, output, options.num_threads);
92 } else {
93 std::fill_n(output, primary, 0);
94 count_sparse_non_zeros_inconsistent(matrix, primary, secondary, row, output, options.num_threads);
95 }
96}
97
98template<typename InputValue_, typename InputIndex_, typename Pointer_, typename StoredValue_, typename StoredIndex_>
99void fill_compressed_sparse_matrix_consistent(
101 const InputIndex_ primary,
102 const InputIndex_ secondary,
103 const bool row,
104 const Pointer_* const pointers,
105 StoredValue_* const output_value,
106 StoredIndex_* const output_index,
107 const int threads
108) {
109 if (matrix.is_sparse()) {
110 Options opt;
111 opt.sparse_ordered_index = false;
112
113 parallelize([&](const int, const InputIndex_ start, const InputIndex_ length) -> void {
114 auto wrk = consecutive_extractor<true>(matrix, row, start, length, opt);
117
118 for (InputIndex_ p = start, pe = start + length; p < pe; ++p) {
119 // Resist the urge to `fetch()` straight into 'output_v'
120 // and 'output_i', as implementations may assume that they
121 // have the entire 'length' length to play with, and the
122 // output vectors only have whatever is allocated from the
123 // first pass (which might be nothing for an all-zero matrix).
124 const auto range = wrk->fetch(buffer_v.data(), buffer_i.data());
125 const auto offset = pointers[p];
126 std::copy_n(range.value, range.number, output_value + offset);
127 std::copy_n(range.index, range.number, output_index + offset);
128 }
129 }, primary, threads);
130
131 } else {
132 parallelize([&](const int, const InputIndex_ start, const InputIndex_ length) -> void {
134 auto wrk = consecutive_extractor<false>(matrix, row, start, length);
135
136 for (InputIndex_ p = start, pe = start + length; p < pe; ++p) {
137 const auto ptr = wrk->fetch(buffer_v.data());
138 auto offset = pointers[p];
139 for (InputIndex_ s = 0; s < secondary; ++s) {
140 const auto val = ptr[s];
141 if (val != 0) {
142 output_value[offset] = val;
143 output_index[offset] = s;
144 ++offset;
145 }
146 }
147 }
148 }, primary, threads);
149 }
150}
151
152template<typename InputValue_, typename InputIndex_, typename Pointer_, typename StoredValue_, typename StoredIndex_>
153void fill_compressed_sparse_matrix_inconsistent(
155 const InputIndex_ primary,
156 const InputIndex_ secondary,
157 const bool row,
158 const Pointer_* const output_ptrs,
159 StoredValue_* const output_value,
160 StoredIndex_* const output_index,
161 std::optional<CountNonZerosPerThread<InputIndex_, Pointer_> >& per_thread // see count_sparse_non_zeros_inconsistent() in convert_to_sparse_utils.hpp.
162) {
163 const bool is_sparse = matrix.is_sparse();
164 if (per_thread.has_value()) {
165 // Transforming the per-thread counts into per-thread starting offsets.
166 auto& offsets = per_thread->counts;
167 Pointer_ accumulant = 0;
168 static_assert(std::is_same<I<decltype(per_thread->counts[0][0])>, Pointer_>::value); // confirm that the accumulant assignment won't overflow.
169 for (InputIndex_ i = 0; i < primary; ++i) {
170 for (auto& pt : offsets) {
171 const auto count = pt[i];
172 pt[i] = accumulant;
173 accumulant += count;
174 }
175 }
176
177 parallelize([&](const int, const int th_start, const int th_length) -> void {
178 for (int t = 0; t < th_length; ++t) {
179 auto& offsets = (per_thread->counts)[t + th_start];
180 const auto actual_start = (per_thread->starts)[t + th_start];
181 const auto actual_length = (per_thread->lengths)[t + th_start];
182
183 // We're going to completely ignore the potential for false sharing here.
184 // False sharing would only be a risk for very fat/thin matrices (depending on row= and num_threads=),
185 // and if the input matrix is sparse, this further lowers the chance of contention between threads.
186 // The alternative would be to allocate a per-thread buffer to store all of the values,
187 // but in that case, we might as well use a one-pass algorithm.
188 if (is_sparse) {
189 Options opt;
190 opt.sparse_ordered_index = false;
191 auto wrk = consecutive_extractor<true>(matrix, !row, actual_start, actual_length, opt);
194 for (InputIndex_ x = 0; x < actual_length; ++x) {
195 const auto range = wrk->fetch(buffer_v.data(), buffer_i.data());
196 for (InputIndex_ i = 0; i < range.number; ++i) {
197 auto& pos = offsets[range.index[i]];
198 output_value[pos] = range.value[i];
199 output_index[pos] = x + actual_start;
200 ++pos;
201 }
202 }
203
204 } else {
205 auto wrk = consecutive_extractor<false>(matrix, !row, actual_start, actual_length);
207 for (InputIndex_ x = 0; x < actual_length; ++x) {
208 const auto ptr = wrk->fetch(buffer_v.data());
209 for (InputIndex_ p = 0; p < primary; ++p) {
210 const auto val = ptr[p];
211 if (val != 0) {
212 auto& pos = offsets[p];
213 output_value[pos] = val;
214 output_index[pos] = x + actual_start;
215 ++pos;
216 }
217 }
218 }
219 }
220 }
221 }, per_thread->counts.size(), per_thread->counts.size());
222
223 } else {
224 std::vector<Pointer_> offsets(output_ptrs, output_ptrs + primary);
225
226 if (is_sparse){
227 Options opt;
228 opt.sparse_ordered_index = false;
229 auto wrk = consecutive_extractor<true>(matrix, !row, static_cast<InputIndex_>(0), secondary, opt);
232 for (InputIndex_ s = 0; s < secondary; ++s) {
233 const auto range = wrk->fetch(buffer_v.data(), buffer_i.data());
234 for (InputIndex_ i = 0; i < range.number; ++i) {
235 auto& pos = offsets[range.index[i]];
236 output_value[pos] = range.value[i];
237 output_index[pos] = s;
238 ++pos;
239 }
240 }
241
242 } else {
243 auto wrk = consecutive_extractor<false>(matrix, !row, static_cast<InputIndex_>(0), secondary);
245 for (InputIndex_ s = 0; s < secondary; ++s) {
246 const auto ptr = wrk->fetch(buffer_v.data());
247 for (InputIndex_ p = 0; p < primary; ++p) {
248 const auto val = ptr[p];
249 if (val != 0) {
250 auto& pos = offsets[p];
251 output_value[pos] = val;
252 output_index[pos] = s;
253 ++pos;
254 }
255 }
256 }
257 }
258 }
259}
260
261// For back-compatiblity only, this functionality should probably not have been exported.
262// This is only useful in the context of retrieve_compressed_sparse_contents, so why would someone use this when they could just use retrieve?
263struct FillCompressedSparseContentsOptions {
264 int num_threads = 1;
265};
266
267// For back-compatiblity only, see above.
268template<typename InputValue_, typename InputIndex_, typename Pointer_, typename StoredValue_, typename StoredIndex_>
269void fill_compressed_sparse_contents(
271 const bool row,
272 const Pointer_* const pointers,
273 StoredValue_* const output_value,
274 StoredIndex_* const output_index,
275 const FillCompressedSparseContentsOptions& options
276) {
277 const InputIndex_ NR = matrix.nrow();
278 const InputIndex_ NC = matrix.ncol();
279 const InputIndex_ primary = (row ? NR : NC);
280 const InputIndex_ secondary = (row ? NC : NR);
281
282 if (row == matrix.prefer_rows()) {
283 fill_compressed_sparse_matrix_consistent(matrix, primary, secondary, row, pointers, output_value, output_index, options.num_threads);
284 } else {
285 std::optional<CountNonZerosPerThread<InputIndex_, Pointer_> > empty; // Force it to be single-threaded as we don't have the per-worker pointers to parallelize effectively.
286 fill_compressed_sparse_matrix_inconsistent(
287 matrix,
288 primary,
289 secondary,
290 row,
291 pointers,
292 output_value,
293 output_index,
294 empty
295 );
296 }
297}
312template<typename Value_, typename Index_, typename Pointer_>
317 std::vector<Value_> value;
318
322 std::vector<Index_> index;
323
327 std::vector<Pointer_> pointers;
328};
329
346
364template<typename StoredValue_, typename StoredIndex_, typename StoredPointer_ = std::size_t, typename InputValue_, typename InputIndex_>
367 const bool row,
369) {
370 // We use size_t as the default pointer type here, as our output consists of vectors
371 // with the default allocator, for which the size_type is unlikely to be bigger than size_t.
372
374 auto& output_v = output.value;
375 auto& output_i = output.index;
376 auto& output_p = output.pointers;
377
378 const InputIndex_ NR = matrix.nrow();
379 const InputIndex_ NC = matrix.ncol();
380 const InputIndex_ primary = (row ? NR : NC);
381 const InputIndex_ secondary = (row ? NC : NR);
382
383 output_p.resize(sanisizer::sum<I<decltype(output_p.size())> >(attest_for_Index(primary), 1));
384
385 if (!options.two_pass) {
386 // In the one-pass strategy, we load matrix contents along the preferred dimension first, then we transform it in serial.
387 std::vector<std::vector<InputValue_> > store_v;
388 std::vector<std::vector<InputIndex_> > store_i;
389 auto original_ranges = extract_sparse_matrix(matrix, store_v, store_i, options.num_threads);
390
391 const bool use_rows = matrix.prefer_rows();
392 if (use_rows == row) {
393 // Now concatenating everything together, if we're fortunate enough that the dimensions are consistent.
394 for (InputIndex_ p = 0; p < primary; ++p) {
395 output_p[p + 1] = sanisizer::sum<StoredPointer_>(output_p[p], original_ranges[p].number);
396 }
397
398 output_v.reserve(output_p.back());
399 output_i.reserve(output_p.back());
400 for (InputIndex_ p = 0; p < primary; ++p) {
401 output_v.insert(output_v.end(), original_ranges[p].value, original_ranges[p].value + original_ranges[p].number);
402 output_i.insert(output_i.end(), original_ranges[p].index, original_ranges[p].index + original_ranges[p].number);
403 }
404
405 } else {
406 // Otherwise we need to compute the non-zeros on the inconsistent dimension before populating the output vectors.
407 for (InputIndex_ s = 0; s < secondary; ++s) {
408 const auto& range = original_ranges[s];
409 for (InputIndex_ x = 0; x < range.number; ++x) {
410 output_p[range.index[x] + 1] += 1; // increments are safe at this point: p < primary and the total count must be less than 'secondary'.
411 }
412 }
413 for (InputIndex_ p = 0; p < primary; ++p) {
414 output_p[p + 1] = sanisizer::sum<StoredPointer_>(output_p[p + 1], output_p[p]);
415 }
416
417 sanisizer::resize(output_v, output_p.back());
418 sanisizer::resize(output_i, output_p.back());
419 std::vector<StoredPointer_> offsets(output_p.begin(), output_p.begin() + primary);
420 for (InputIndex_ s = 0; s < secondary; ++s) {
421 const auto& range = original_ranges[s];
422 for (InputIndex_ i = 0; i < range.number; ++i) {
423 auto& pos = offsets[range.index[i]];
424 output_v[pos] = range.value[i];
425 output_i[pos] = s;
426 ++pos;
427 }
428 }
429 }
430
431 } else if (row == matrix.prefer_rows()) {
432 // First pass to figure out how many non-zeros there are.
433 count_compressed_sparse_non_zeros_consistent(matrix, primary, secondary, row, output_p.data() + 1, options.num_threads);
434 for (InputIndex_ i = 1; i <= primary; ++i) {
435 output_p[i] = sanisizer::sum<StoredPointer_>(output_p[i], output_p[i - 1]);
436 }
437
438 // Second pass to actually fill our vectors.
439 sanisizer::resize(output_v, output_p.back());
440 sanisizer::resize(output_i, output_p.back());
441 fill_compressed_sparse_matrix_consistent(
442 matrix,
443 primary,
444 secondary,
445 row,
446 output_p.data(),
447 output_v.data(),
448 output_i.data(),
449 options.num_threads
450 );
451
452 } else {
453 // First pass to figure out how many non-zeros there are.
454 auto per_thread = count_sparse_non_zeros_inconsistent(matrix, primary, secondary, row, output_p.data() + 1, options.num_threads);
455 for (InputIndex_ i = 1; i <= primary; ++i) {
456 output_p[i] = sanisizer::sum<StoredPointer_>(output_p[i], output_p[i - 1]);
457 }
458
459 // Second pass to actually fill our vectors.
460 sanisizer::resize(output_v, output_p.back());
461 sanisizer::resize(output_i, output_p.back());
462 fill_compressed_sparse_matrix_inconsistent(
463 matrix,
464 primary,
465 secondary,
466 row,
467 output_p.data(),
468 output_v.data(),
469 output_i.data(),
470 per_thread
471 );
472 }
473
474 return output;
475}
476
486 bool two_pass = false;
487
491 int num_threads = 1;
492};
493
511template<
512 typename Value_,
513 typename Index_,
514 typename StoredValue_ = Value_,
515 typename StoredIndex_ = Index_,
516 typename StoredPointer_ = std::size_t,
517 typename InputValue_,
518 typename InputIndex_
519>
520std::shared_ptr<Matrix<Value_, Index_> > convert_to_compressed_sparse(
522 const bool row,
524) {
526 matrix,
527 row,
528 [&]{
530 ropt.two_pass = options.two_pass;
531 ropt.num_threads = options.num_threads;
532 return ropt;
533 }()
534 );
535 return std::shared_ptr<Matrix<Value_, Index_> >(
537 Value_,
538 Index_,
539 std::vector<StoredValue_>,
540 std::vector<StoredIndex_>,
541 std::vector<StoredPointer_>
542 >(
543 matrix.nrow(),
544 matrix.ncol(),
545 std::move(comp.value),
546 std::move(comp.index),
547 std::move(comp.pointers),
548 row,
549 []{
550 CompressedSparseMatrixOptions copt;
551 copt.check = false; // no need for checks, as we guarantee correctness.
552 return copt;
553 }()
554 )
555 );
556}
557
561// Backwards compatbility.
562template<typename Value_, typename Index_, typename Count_>
563void count_compressed_sparse_non_zeros(const tatami::Matrix<Value_, Index_>* matrix, bool row, Count_* output, int threads) {
564 return count_compressed_sparse_non_zeros(
565 *matrix,
566 row,
567 output,
568 [&]{
569 CountCompressedSparseNonZerosOptions copt;
570 copt.num_threads = threads;
571 return copt;
572 }()
573 );
574}
575
576template<typename InputValue_, typename InputIndex_, typename Pointer_, typename StoredValue_, typename StoredIndex_>
577void fill_compressed_sparse_contents(const tatami::Matrix<InputValue_, InputIndex_>* matrix,
578 bool row,
579 const Pointer_* pointers,
580 StoredValue_* output_value,
581 StoredIndex_* output_index,
582 int threads)
583{
584 fill_compressed_sparse_contents(
585 *matrix,
586 row,
587 pointers,
588 output_value,
589 output_index,
590 [&]{
591 FillCompressedSparseContentsOptions fopt;
592 fopt.num_threads = threads;
593 return fopt;
594 }()
595 );
596}
597
598template<typename StoredValue_, typename StoredIndex_, typename StoredPointer_ = std::size_t, typename InputValue_, typename InputIndex_>
599CompressedSparseContents<StoredValue_, StoredIndex_, StoredPointer_> retrieve_compressed_sparse_contents(const Matrix<InputValue_, InputIndex_>* matrix, bool row, bool two_pass, int threads = 1) {
601 *matrix,
602 row,
603 [&]{
604 RetrieveCompressedSparseContentsOptions opt;
605 opt.two_pass = two_pass;
606 opt.num_threads = threads;
607 return opt;
608 }()
609 );
610}
611
612template<typename Value_ = double, typename Index_ = int, typename StoredValue_ = Value_, typename StoredIndex_ = Index_, typename InputValue_, typename InputIndex_>
613std::shared_ptr<Matrix<Value_, Index_> > convert_to_compressed_sparse(const Matrix<InputValue_, InputIndex_>* matrix, bool row, bool two_pass = false, int threads = 1) {
615 *matrix,
616 row,
617 [&]{
618 ConvertToCompressedSparseOptions opt;
619 opt.two_pass = two_pass;
620 opt.num_threads = threads;
621 return opt;
622 }()
623 );
624}
625
626template <bool row_, typename Value_, typename Index_, typename InputValue_, typename InputIndex_>
627CompressedSparseContents<Value_, Index_, std::size_t> retrieve_compressed_sparse_contents(const Matrix<InputValue_, InputIndex_>* matrix, bool two_pass, int threads = 1) {
628 return retrieve_compressed_sparse_contents<Value_, Index_>(matrix, row_, two_pass, threads);
629}
630
631template <bool row_, typename Value_, typename Index_, typename StoredValue_ = Value_, typename StoredIndex_ = Index_, typename InputValue_, typename InputIndex_>
632std::shared_ptr<Matrix<Value_, Index_> > convert_to_compressed_sparse(const Matrix<InputValue_, InputIndex_>* matrix, bool two_pass = false, int threads = 1) {
634}
639}
640
641#endif
Compressed sparse matrix representation.
Convert index type to container size.
Compressed sparse matrix representation.
Definition CompressedSparseMatrix.hpp:580
Virtual class for a matrix.
Definition Matrix.hpp:59
virtual Index_ ncol() const =0
virtual Index_ nrow() const =0
virtual bool prefer_rows() const =0
virtual bool is_sparse() const =0
Templated construction of a new consecutive extractor.
Convert a matrix into a fragmented sparse format.
Copy data from one buffer to another.
Flexible representations for matrix data.
Definition Extractor.hpp:15
CompressedSparseContents< StoredValue_, StoredIndex_, StoredPointer_ > retrieve_compressed_sparse_contents(const Matrix< InputValue_, InputIndex_ > &matrix, const bool row, const RetrieveCompressedSparseContentsOptions &options)
Definition convert_to_compressed_sparse.hpp:365
int parallelize(Function_ fun, const Index_ tasks, const int workers)
Definition parallelize.hpp:58
Container_ create_container_of_Index_size(const Index_ x, Args_ &&... args)
Definition Index_to_container.hpp:82
std::shared_ptr< Matrix< Value_, Index_ > > convert_to_compressed_sparse(const Matrix< InputValue_, InputIndex_ > &matrix, const bool row, const ConvertToCompressedSparseOptions &options)
Definition convert_to_compressed_sparse.hpp:520
auto consecutive_extractor(const Matrix< Value_, Index_ > &matrix, const bool row, const Index_ iter_start, const Index_ iter_length, Args_ &&... args)
Definition consecutive_extractor.hpp:35
Parallelized iteration over a tatami::Matrix.
Compressed sparse contents.
Definition convert_to_compressed_sparse.hpp:313
std::vector< Index_ > index
Definition convert_to_compressed_sparse.hpp:322
std::vector< Value_ > value
Definition convert_to_compressed_sparse.hpp:317
std::vector< Pointer_ > pointers
Definition convert_to_compressed_sparse.hpp:327
Options for convert_to_compressed_sparse().
Definition convert_to_compressed_sparse.hpp:480
bool two_pass
Definition convert_to_compressed_sparse.hpp:486
int num_threads
Definition convert_to_compressed_sparse.hpp:491
Options for retrieve_compressed_sparse_contents().
Definition convert_to_compressed_sparse.hpp:333
int num_threads
Definition convert_to_compressed_sparse.hpp:344
bool two_pass
Definition convert_to_compressed_sparse.hpp:339