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 std::optional<std::vector<Index_> > nnz_consistent;
95 count_sparse_non_zeros_inconsistent(matrix, primary, secondary, row, output, nnz_consistent, options.num_threads);
96 }
97}
98
99template<typename InputValue_, typename InputIndex_, typename Pointer_, typename StoredValue_, typename StoredIndex_>
100void fill_compressed_sparse_matrix_consistent(
102 const InputIndex_ primary,
103 const InputIndex_ secondary,
104 const bool row,
105 const Pointer_* const pointers,
106 StoredValue_* const output_value,
107 StoredIndex_* const output_index,
108 const int threads
109) {
110 if (matrix.is_sparse()) {
111 Options opt;
112 opt.sparse_ordered_index = false;
113
114 parallelize([&](const int, const InputIndex_ start, const InputIndex_ length) -> void {
115 auto wrk = consecutive_extractor<true>(matrix, row, start, length, opt);
118
119 for (InputIndex_ p = start, pe = start + length; p < pe; ++p) {
120 // Resist the urge to `fetch()` straight into 'output_v'
121 // and 'output_i', as implementations may assume that they
122 // have the entire 'length' length to play with, and the
123 // output vectors only have whatever is allocated from the
124 // first pass (which might be nothing for an all-zero matrix).
125 const auto range = wrk->fetch(buffer_v.data(), buffer_i.data());
126 const auto offset = pointers[p];
127 std::copy_n(range.value, range.number, output_value + offset);
128 std::copy_n(range.index, range.number, output_index + offset);
129 }
130 }, primary, threads);
131
132 } else {
133 parallelize([&](const int, const InputIndex_ start, const InputIndex_ length) -> void {
135 auto wrk = consecutive_extractor<false>(matrix, row, start, length);
136
137 for (InputIndex_ p = start, pe = start + length; p < pe; ++p) {
138 const auto ptr = wrk->fetch(buffer_v.data());
139 auto offset = pointers[p];
140 for (InputIndex_ s = 0; s < secondary; ++s) {
141 const auto val = ptr[s];
142 if (val != 0) {
143 output_value[offset] = val;
144 output_index[offset] = s;
145 ++offset;
146 }
147 }
148 }
149 }, primary, threads);
150 }
151}
152
153template<typename InputValue_, typename InputIndex_, typename Pointer_, typename StoredValue_, typename StoredIndex_>
154void fill_compressed_sparse_matrix_inconsistent(
156 const InputIndex_ primary,
157 const InputIndex_ secondary,
158 const bool row,
159 std::vector<Pointer_> offsets, // indptrs of the entire matrix. We'll need to modify this, so we take a copy.
160 const std::optional<std::vector<InputIndex_> >& secondary_counts, // count of the number of non-zero elements in each secondary dimension element, for buffer allocations.
161 StoredValue_* const output_value,
162 StoredIndex_* const output_index,
163 const int threads
164) {
165 fill_sparse_matrix_inconsistent(
166 matrix,
167 primary,
168 secondary,
169 row,
170 secondary_counts,
171 /* sparse_main = */ [&](const InputIndex_ s, const SparseRange<InputValue_, InputIndex_>& range) -> void {
172 for (InputIndex_ i = 0; i < range.number; ++i) {
173 auto& pos = offsets[range.index[i]];
174 output_value[pos] = range.value[i];
175 output_index[pos] = s; // start == 0, so no
176 ++pos;
177 }
178 },
179 /* dense_main = */ [&](const InputIndex_ s, const InputValue_* const ptr) -> void {
180 for (InputIndex_ p = 0; p < primary; ++p) {
181 const auto val = ptr[p];
182 if (val != 0) {
183 auto& pos = offsets[p];
184 output_value[pos] = val;
185 output_index[pos] = s; // start == 0, so no need to add it.
186 ++pos;
187 }
188 }
189 },
190 /* reduce = */ [&](const InputIndex_ s, const std::vector<InputValue_>& cur_values, const std::vector<InputIndex_>& cur_primary_indices) {
191 const auto cur_count = cur_values.size();
192 for (I<decltype(cur_count)> i = 0; i < cur_count; ++i) {
193 auto& dest_pos = offsets[cur_primary_indices[i]];
194 output_value[dest_pos] = cur_values[i];
195 output_index[dest_pos] = s;
196 ++dest_pos;
197 }
198 },
199 threads
200 );
201}
202
203// For back-compatiblity only, this functionality should probably not have been exported.
204// This is only useful in the context of retrieve_compressed_sparse_contents, so why would someone use this when they could just use retrieve?
205struct FillCompressedSparseContentsOptions {
206 int num_threads = 1;
207};
208
209// For back-compatiblity only, see above.
210template<typename InputValue_, typename InputIndex_, typename Pointer_, typename StoredValue_, typename StoredIndex_>
211void fill_compressed_sparse_contents(
213 const bool row,
214 const Pointer_* const pointers,
215 StoredValue_* const output_value,
216 StoredIndex_* const output_index,
217 const FillCompressedSparseContentsOptions& options
218) {
219 const InputIndex_ NR = matrix.nrow();
220 const InputIndex_ NC = matrix.ncol();
221 const InputIndex_ primary = (row ? NR : NC);
222 const InputIndex_ secondary = (row ? NC : NR);
223
224 if (row == matrix.prefer_rows()) {
225 fill_compressed_sparse_matrix_consistent(matrix, primary, secondary, row, pointers, output_value, output_index, options.num_threads);
226 } else {
227 // Force it to be single-threaded as we don't have the per-worker pointers to parallelize effectively.
228 fill_compressed_sparse_matrix_inconsistent(
229 matrix,
230 primary,
231 secondary,
232 row,
233 std::vector<Pointer_>(pointers, pointers + sanisizer::sum<std::size_t>(primary, 1)), // making a copy because this is modified on input.
234 {},
235 output_value,
236 output_index,
237 1
238 );
239 }
240}
255template<typename Value_, typename Index_, typename Pointer_>
260 std::vector<Value_> value;
261
265 std::vector<Index_> index;
266
270 std::vector<Pointer_> pointers;
271};
272
289
307template<typename StoredValue_, typename StoredIndex_, typename StoredPointer_ = std::size_t, typename InputValue_, typename InputIndex_>
310 const bool row,
312) {
313 // We use size_t as the default pointer type here, as our output consists of vectors
314 // with the default allocator, for which the size_type is unlikely to be bigger than size_t.
316 auto& output_v = output.value;
317 auto& output_i = output.index;
318 auto& output_p = output.pointers;
319
320 const InputIndex_ NR = matrix.nrow();
321 const InputIndex_ NC = matrix.ncol();
322 const InputIndex_ primary = (row ? NR : NC);
323 const InputIndex_ secondary = (row ? NC : NR);
324
325 output_p.resize(sanisizer::sum<I<decltype(output_p.size())> >(attest_for_Index(primary), 1));
326
327 if (!options.two_pass) {
328 const bool use_rows = matrix.prefer_rows();
329 const auto frag = retrieve_fragmented_sparse_contents_consistent<InputValue_, InputIndex_>(
330 matrix,
331 use_rows,
332 [&]{
334 roptions.num_threads = options.num_threads;
335 return roptions;
336 }()
337 );
338
339 if (use_rows == row) {
340 // Now concatenating everything together, if we're fortunate enough that the dimensions are consistent.
341 for (InputIndex_ p = 0; p < primary; ++p) {
342 output_p[p + 1] = sanisizer::sum<StoredPointer_>(output_p[p], frag.value[p].size());
343 }
344
345 output_v.reserve(output_p.back());
346 output_i.reserve(output_p.back());
347 for (InputIndex_ p = 0; p < primary; ++p) {
348 output_v.insert(output_v.end(), frag.value[p].begin(), frag.value[p].end());
349 output_i.insert(output_i.end(), frag.index[p].begin(), frag.index[p].end());
350 }
351
352 } else {
353 // Otherwise we need to compute the non-zeros on the inconsistent dimension before populating the output vectors.
354 for (InputIndex_ s = 0; s < secondary; ++s) {
355 for (const auto p : frag.index[s]) {
356 output_p[p + 1] += 1; // increments are safe at this point: p < primary and the total count must be less than 'secondary'.
357 }
358 }
359 for (InputIndex_ p = 0; p < primary; ++p) {
360 output_p[p + 1] = sanisizer::sum<StoredPointer_>(output_p[p + 1], output_p[p]);
361 }
362
363 sanisizer::resize(output_v, output_p.back());
364 sanisizer::resize(output_i, output_p.back());
365 std::vector<StoredPointer_> offsets(output_p.begin(), output_p.begin() + primary);
366 for (InputIndex_ s = 0; s < secondary; ++s) {
367 const auto& cur_i = frag.index[s];
368 const auto& cur_v = frag.value[s];
369 const auto nnz = cur_i.size();
370 for (I<decltype(nnz)> i = 0; i < nnz; ++i) {
371 auto& pos = offsets[cur_i[i]];
372 output_v[pos] = cur_v[i];
373 output_i[pos] = s;
374 ++pos;
375 }
376 }
377 }
378
379 } else if (row == matrix.prefer_rows()) {
380 // First pass to figure out how many non-zeros there are.
381 count_compressed_sparse_non_zeros_consistent(matrix, primary, secondary, row, output_p.data() + 1, options.num_threads);
382 for (InputIndex_ i = 1; i <= primary; ++i) {
383 output_p[i] = sanisizer::sum<StoredPointer_>(output_p[i], output_p[i - 1]);
384 }
385
386 // Second pass to actually fill our vectors.
387 sanisizer::resize(output_v, output_p.back());
388 sanisizer::resize(output_i, output_p.back());
389 fill_compressed_sparse_matrix_consistent(
390 matrix,
391 primary,
392 secondary,
393 row,
394 output_p.data(),
395 output_v.data(),
396 output_i.data(),
397 options.num_threads
398 );
399
400 } else {
401 // First pass to figure out how many non-zeros there are.
402 std::optional<std::vector<InputIndex_> > nnz_consistent;
403 count_sparse_non_zeros_inconsistent(matrix, primary, secondary, row, output_p.data() + 1, nnz_consistent, options.num_threads);
404 for (InputIndex_ i = 1; i <= primary; ++i) {
405 output_p[i] = sanisizer::sum<StoredPointer_>(output_p[i], output_p[i - 1]);
406 }
407
408 // Second pass to actually fill our vectors.
409 sanisizer::resize(output_v, output_p.back());
410 sanisizer::resize(output_i, output_p.back());
411 fill_compressed_sparse_matrix_inconsistent(
412 matrix,
413 primary,
414 secondary,
415 row,
416 output_p,
417 nnz_consistent,
418 output_v.data(),
419 output_i.data(),
420 options.num_threads
421 );
422 }
423
424 return output;
425}
426
436 bool two_pass = false;
437
441 int num_threads = 1;
442};
443
461template<
462 typename Value_,
463 typename Index_,
464 typename StoredValue_ = Value_,
465 typename StoredIndex_ = Index_,
466 typename StoredPointer_ = std::size_t,
467 typename InputValue_,
468 typename InputIndex_
469>
470std::shared_ptr<Matrix<Value_, Index_> > convert_to_compressed_sparse(
472 const bool row,
474) {
476 matrix,
477 row,
478 [&]{
480 ropt.two_pass = options.two_pass;
481 ropt.num_threads = options.num_threads;
482 return ropt;
483 }()
484 );
485 return std::shared_ptr<Matrix<Value_, Index_> >(
487 Value_,
488 Index_,
489 std::vector<StoredValue_>,
490 std::vector<StoredIndex_>,
491 std::vector<StoredPointer_>
492 >(
493 matrix.nrow(),
494 matrix.ncol(),
495 std::move(comp.value),
496 std::move(comp.index),
497 std::move(comp.pointers),
498 row,
499 []{
500 CompressedSparseMatrixOptions copt;
501 copt.check = false; // no need for checks, as we guarantee correctness.
502 return copt;
503 }()
504 )
505 );
506}
507
511// Backwards compatbility.
512template<typename Value_, typename Index_, typename Count_>
513void count_compressed_sparse_non_zeros(const tatami::Matrix<Value_, Index_>* matrix, bool row, Count_* output, int threads) {
514 return count_compressed_sparse_non_zeros(
515 *matrix,
516 row,
517 output,
518 [&]{
519 CountCompressedSparseNonZerosOptions copt;
520 copt.num_threads = threads;
521 return copt;
522 }()
523 );
524}
525
526template<typename InputValue_, typename InputIndex_, typename Pointer_, typename StoredValue_, typename StoredIndex_>
527void fill_compressed_sparse_contents(const tatami::Matrix<InputValue_, InputIndex_>* matrix,
528 bool row,
529 const Pointer_* pointers,
530 StoredValue_* output_value,
531 StoredIndex_* output_index,
532 int threads)
533{
534 fill_compressed_sparse_contents(
535 *matrix,
536 row,
537 pointers,
538 output_value,
539 output_index,
540 [&]{
541 FillCompressedSparseContentsOptions fopt;
542 fopt.num_threads = threads;
543 return fopt;
544 }()
545 );
546}
547
548template<typename StoredValue_, typename StoredIndex_, typename StoredPointer_ = std::size_t, typename InputValue_, typename InputIndex_>
549CompressedSparseContents<StoredValue_, StoredIndex_, StoredPointer_> retrieve_compressed_sparse_contents(const Matrix<InputValue_, InputIndex_>* matrix, bool row, bool two_pass, int threads = 1) {
551 *matrix,
552 row,
553 [&]{
554 RetrieveCompressedSparseContentsOptions opt;
555 opt.two_pass = two_pass;
556 opt.num_threads = threads;
557 return opt;
558 }()
559 );
560}
561
562template<typename Value_ = double, typename Index_ = int, typename StoredValue_ = Value_, typename StoredIndex_ = Index_, typename InputValue_, typename InputIndex_>
563std::shared_ptr<Matrix<Value_, Index_> > convert_to_compressed_sparse(const Matrix<InputValue_, InputIndex_>* matrix, bool row, bool two_pass = false, int threads = 1) {
565 *matrix,
566 row,
567 [&]{
568 ConvertToCompressedSparseOptions opt;
569 opt.two_pass = two_pass;
570 opt.num_threads = threads;
571 return opt;
572 }()
573 );
574}
575
576template <bool row_, typename Value_, typename Index_, typename InputValue_, typename InputIndex_>
577CompressedSparseContents<Value_, Index_, std::size_t> retrieve_compressed_sparse_contents(const Matrix<InputValue_, InputIndex_>* matrix, bool two_pass, int threads = 1) {
578 return retrieve_compressed_sparse_contents<Value_, Index_>(matrix, row_, two_pass, threads);
579}
580
581template <bool row_, typename Value_, typename Index_, typename StoredValue_ = Value_, typename StoredIndex_ = Index_, typename InputValue_, typename InputIndex_>
582std::shared_ptr<Matrix<Value_, Index_> > convert_to_compressed_sparse(const Matrix<InputValue_, InputIndex_>* matrix, bool two_pass = false, int threads = 1) {
584}
589}
590
591#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:308
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:470
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:256
std::vector< Index_ > index
Definition convert_to_compressed_sparse.hpp:265
std::vector< Value_ > value
Definition convert_to_compressed_sparse.hpp:260
std::vector< Pointer_ > pointers
Definition convert_to_compressed_sparse.hpp:270
Options for convert_to_compressed_sparse().
Definition convert_to_compressed_sparse.hpp:430
bool two_pass
Definition convert_to_compressed_sparse.hpp:436
int num_threads
Definition convert_to_compressed_sparse.hpp:441
Options for retrieve_compressed_sparse_contents().
Definition convert_to_compressed_sparse.hpp:276
int num_threads
Definition convert_to_compressed_sparse.hpp:287
bool two_pass
Definition convert_to_compressed_sparse.hpp:282
Options for retrieve_fragmented_sparse_contents().
Definition convert_to_fragmented_sparse.hpp:65
int num_threads
Definition convert_to_fragmented_sparse.hpp:76