tatami
C++ API for different matrix representations
Loading...
Searching...
No Matches
convert_to_fragmented_sparse.hpp
Go to the documentation of this file.
1#ifndef TATAMI_CONVERT_TO_FRAGMENTED_SPARSE_H
2#define TATAMI_CONVERT_TO_FRAGMENTED_SPARSE_H
3
4#include <memory>
5#include <vector>
6#include <cstddef>
7#include <optional>
8#include <cassert>
9
11#include "convert_to_sparse_utils.hpp"
12
14#include "../utils/copy.hpp"
17
24namespace tatami {
25
36template<typename Value_, typename Index_>
41 FragmentedSparseContents(Index_ n) :
42 value(cast_Index_to_container_size<I<decltype(value)> >(n)),
44 {}
53 std::vector<std::vector<Value_> > value;
54
60 std::vector<std::vector<Index_> > index;
61};
62
79
83template<typename StoredValue_, typename StoredIndex_, typename InputValue_, typename InputIndex_>
84FragmentedSparseContents<StoredValue_, StoredIndex_> retrieve_fragmented_sparse_contents_consistent(
86 const bool row,
88) {
89 const InputIndex_ NR = matrix.nrow();
90 const InputIndex_ NC = matrix.ncol();
91 const InputIndex_ primary = (row ? NR : NC);
92 const InputIndex_ secondary = (row ? NC : NR);
93
95 auto& store_v = output.value;
96 auto& store_i = output.index;
97
98 if (matrix.is_sparse()) {
99 parallelize([&](const int, const InputIndex_ start, const InputIndex_ length) -> void {
100 auto wrk = consecutive_extractor<true>(matrix, row, start, length);
103
104 for (InputIndex_ p = start, pe = start + length; p < pe; ++p) {
105 const auto range = wrk->fetch(buffer_v.data(), buffer_i.data());
106 auto& sv = store_v[p];
107 auto& si = store_i[p];
108 sv.reserve(range.number);
109 si.reserve(range.number);
110
111 // We don't filter out structural non-zeros that have values of zero, for consistency with convert_to_compressed_sparse().
112 for (InputIndex_ i = 0; i < range.number; ++i) {
113 sv.push_back(range.value[i]);
114 si.push_back(range.index[i]);
115 }
116 }
117 }, primary, options.num_threads);
118
119 } else {
120 parallelize([&](const int, const InputIndex_ start, const InputIndex_ length) -> void {
121 auto wrk = consecutive_extractor<false>(matrix, row, start, length);
123
124 for (InputIndex_ p = start, pe = start + length; p < pe; ++p) {
125 const auto ptr = wrk->fetch(buffer_v.data());
126 auto& sv = store_v[p];
127 auto& si = store_i[p];
128
129 // For dense, we do treat zero values as structural zeros and remove them, otherwise the output wouldn't actually be sparse.
130 for (InputIndex_ s = 0; s < secondary; ++s) {
131 const auto val = ptr[s];
132
133 if (val) {
134 sv.push_back(val);
135 si.push_back(s);
136 }
137 }
138 }
139 }, primary, options.num_threads);
140 }
141
142 return output;
143}
144
145template<typename StoredValue_, typename StoredIndex_, typename InputValue_, typename InputIndex_>
146FragmentedSparseContents<StoredValue_, StoredIndex_> retrieve_fragmented_sparse_inconsistent_one_pass(
148 const bool row,
149 const int num_threads
150) {
151 const InputIndex_ NR = matrix.nrow();
152 const InputIndex_ NC = matrix.ncol();
153 const InputIndex_ primary = (row ? NR : NC);
154 const InputIndex_ secondary = (row ? NC : NR);
155
156 // In the one-pass strategy, we load everything in a nice format first, then we transpose it in serial.
157 // This avoids messy reallocations when trying to expand vectors on an inconsistent dimension.
158 std::vector<std::vector<InputValue_> > store_v;
159 std::vector<std::vector<InputIndex_> > store_i;
160 auto original_ranges = extract_sparse_matrix(matrix, store_v, store_i, num_threads);
161
162 auto primary_counts = create_container_of_Index_size<std::vector<InputIndex_> >(primary);
163 for (I<decltype(secondary)> s = 0; s < secondary; ++s) {
164 const auto& sec_indices = original_ranges[s].index;
165 const auto num = original_ranges[s].number;
166 for (I<decltype(num)> n = 0; n < num; ++n) {
167 primary_counts[sec_indices[n]] += 1; // addition must be safe as this cannot exceed dimension extents.
168 }
169 }
170
171 FragmentedSparseContents<StoredValue_, StoredIndex_> output(primary);
172 for (InputIndex_ p = 0; p < primary; ++p) {
173 output.index[p].reserve(primary_counts[p]);
174 output.value[p].reserve(primary_counts[p]);
175 }
176
177 for (I<decltype(secondary)> s = 0; s < secondary; ++s) {
178 const auto& sec_values = original_ranges[s].value;
179 const auto& sec_indices = original_ranges[s].index;
180 const auto num = original_ranges[s].number;
181 for (I<decltype(num)> n = 0; n < num; ++n) {
182 const auto curp = sec_indices[n];
183 output.value[curp].push_back(sec_values[n]);
184 output.index[curp].push_back(s);
185 }
186 }
187
188 return output;
189}
190
191template<typename StoredValue_, typename StoredIndex_, typename InputValue_, typename InputIndex_>
192FragmentedSparseContents<StoredValue_, StoredIndex_> retrieve_fragmented_sparse_inconsistent_two_pass(
194 const bool row,
195 const int num_threads
196) {
197 const InputIndex_ NR = matrix.nrow();
198 const InputIndex_ NC = matrix.ncol();
199 const InputIndex_ primary = (row ? NR : NC);
200 const InputIndex_ secondary = (row ? NC : NR);
201
202 // In the two-pass strategy, we count the number of non-zeros first, then we fill it up in the second pass.
203 auto nnz_inconsistent = create_container_of_Index_size<std::vector<InputIndex_> >(primary);
204 auto per_thread = count_sparse_non_zeros_inconsistent(matrix, primary, secondary, row, nnz_inconsistent.data(), num_threads);
205
206 FragmentedSparseContents<StoredValue_, StoredIndex_> output(primary);
209 for (InputIndex_ p = 0; p < primary; ++p) {
210 assert(nnz_inconsistent[p] <= secondary);
211 output.index[p].resize(nnz_inconsistent[p]);
212 output.value[p].resize(nnz_inconsistent[p]);
213 }
214
215 const bool is_sparse = matrix.is_sparse();
216 if (per_thread.has_value()) {
217 // Transforming the per-thread counts into per-thread starting offsets within each vector.
218 auto& offsets = per_thread->counts;
219 for (InputIndex_ i = 0; i < primary; ++i) {
220 InputIndex_ accumulant = 0;
221 static_assert(std::is_same<I<decltype(per_thread->counts[0][0])>, InputIndex_>::value); // confirm that the accumulant assignment won't overflow.
222 for (auto& pt : offsets) {
223 const auto count = pt[i];
224 pt[i] = accumulant;
225 accumulant += count;
226 }
227 }
228
229 parallelize([&](const int, const int th_start, const int th_length) -> void {
230 for (int t = 0; t < th_length; ++t) {
231 auto& offsets = (per_thread->counts)[t + th_start];
232 const auto actual_start = (per_thread->starts)[t + th_start];
233 const auto actual_length = (per_thread->lengths)[t + th_start];
234
235 // We're going to completely ignore false sharing here, see reasoning in convert_to_compressed_sparse.hpp.
236 if (is_sparse) {
237 Options opt;
238 opt.sparse_ordered_index = false;
239 auto wrk = consecutive_extractor<true>(matrix, !row, actual_start, actual_length, opt);
242 for (InputIndex_ x = 0; x < actual_length; ++x) {
243 const auto range = wrk->fetch(buffer_v.data(), buffer_i.data());
244 for (InputIndex_ i = 0; i < range.number; ++i) {
245 const auto prim = range.index[i];
246 auto& pos = offsets[prim];
247 output.value[prim][pos] = range.value[i];
248 output.index[prim][pos] = x + actual_start;
249 ++pos;
250 }
251 }
252
253 } else {
254 auto wrk = consecutive_extractor<false>(matrix, !row, actual_start, actual_length);
256 for (InputIndex_ x = 0; x < actual_length; ++x) {
257 const auto ptr = wrk->fetch(buffer_v.data());
258 for (InputIndex_ p = 0; p < primary; ++p) {
259 const auto val = ptr[p];
260 if (val != 0) {
261 auto& pos = offsets[p];
262 output.value[p][pos] = val;
263 output.index[p][pos] = x + actual_start;
264 ++pos;
265 }
266 }
267 }
268 }
269 }
270 }, per_thread->counts.size(), per_thread->counts.size());
271
272 } else {
274
275 if (is_sparse){
276 Options opt;
277 opt.sparse_ordered_index = false;
278 auto wrk = consecutive_extractor<true>(matrix, !row, static_cast<InputIndex_>(0), secondary, opt);
281 for (InputIndex_ s = 0; s < secondary; ++s) {
282 const auto range = wrk->fetch(buffer_v.data(), buffer_i.data());
283 for (InputIndex_ i = 0; i < range.number; ++i) {
284 const auto prim = range.index[i];
285 auto& pos = offsets[prim];
286 output.value[prim][pos] = range.value[i];
287 output.index[prim][pos] = s;
288 ++pos;
289 }
290 }
291
292 } else {
293 auto wrk = consecutive_extractor<false>(matrix, !row, static_cast<InputIndex_>(0), secondary);
295 for (InputIndex_ s = 0; s < secondary; ++s) {
296 const auto ptr = wrk->fetch(buffer_v.data());
297 for (InputIndex_ p = 0; p < primary; ++p) {
298 const auto val = ptr[p];
299 if (val != 0) {
300 auto& pos = offsets[p];
301 output.value[p][pos] = val;
302 output.index[p][pos] = s;
303 ++pos;
304 }
305 }
306 }
307 }
308 }
309
310 return output;
311}
328template<typename StoredValue_, typename StoredIndex_, typename InputValue_, typename InputIndex_>
331 const bool row,
333) {
334 if (row == matrix.prefer_rows()) {
335 return retrieve_fragmented_sparse_contents_consistent<StoredValue_, StoredIndex_>(matrix, row, options);
336 }
337
338 if (!options.two_pass) {
339 return retrieve_fragmented_sparse_inconsistent_one_pass<StoredValue_, StoredIndex_>(matrix, row, options.num_threads);
340 }
341
342 return retrieve_fragmented_sparse_inconsistent_two_pass<StoredValue_, StoredIndex_>(matrix, row, options.num_threads);
343}
344
354 bool two_pass = false;
355
359 int num_threads = 1;
360};
361
377template<
378 typename Value_,
379 typename Index_,
380 typename StoredValue_ = Value_,
381 typename StoredIndex_ = Index_,
382 typename InputValue_,
383 typename InputIndex_
384>
385std::shared_ptr<Matrix<Value_, Index_> > convert_to_fragmented_sparse(
387 const bool row,
389{
391 matrix,
392 row,
393 [&]{
395 ropt.two_pass = options.two_pass;
396 ropt.num_threads = options.num_threads;
397 return ropt;
398 }()
399 );
400 return std::shared_ptr<Matrix<Value_, Index_> >(
402 Value_,
403 Index_,
404 std::vector<std::vector<StoredValue_> >,
405 std::vector<std::vector<StoredIndex_> >
406 >(
407 matrix.nrow(),
408 matrix.ncol(),
409 std::move(frag.value),
410 std::move(frag.index),
411 row,
412 []{
413 FragmentedSparseMatrixOptions fopt;
414 fopt.check = false; // no need for checks, as we guarantee correctness.
415 return fopt;
416 }()
417 )
418 );
419}
420
424// Backwards compatbility.
425template<typename Value_, typename Index_, typename StoredValue_ = Value_, typename StoredIndex_ = Index_, typename InputValue_, typename InputIndex_>
426std::shared_ptr<Matrix<Value_, Index_> > convert_to_fragmented_sparse(const Matrix<InputValue_, InputIndex_>* matrix, bool row, int threads = 1) {
428 *matrix,
429 row,
430 [&]{
431 ConvertToFragmentedSparseOptions opt;
432 opt.num_threads = threads;
433 return opt;
434 }()
435 );
436}
437
438template<typename StoredValue_, typename StoredIndex_, typename InputValue_, typename InputIndex_>
439FragmentedSparseContents<StoredValue_, StoredIndex_> retrieve_fragmented_sparse_contents(const Matrix<InputValue_, InputIndex_>* matrix, bool row, int threads = 1) {
441 *matrix,
442 row,
443 [&]{
444 RetrieveFragmentedSparseContentsOptions opt;
445 opt.num_threads = threads;
446 return opt;
447 }()
448 );
449}
450
451template <bool row_, typename StoredValue_, typename StoredIndex_, typename InputValue_, typename InputIndex_>
452FragmentedSparseContents<StoredValue_, StoredIndex_> retrieve_fragmented_sparse_contents(const Matrix<InputValue_, InputIndex_>* matrix, int threads = 1) {
454}
455
456template <bool row_, typename Value_, typename Index_, typename StoredValue_ = Value_, typename StoredIndex_ = Index_, typename InputValue_, typename InputIndex_>
457std::shared_ptr<Matrix<Value_, Index_> > convert_to_fragmented_sparse(const Matrix<InputValue_, InputIndex_>* matrix, int threads = 1) {
459}
464}
465
466#endif
Fragmented sparse matrix representation.
Convert index type to container size.
Fragmented sparse matrix representation.
Definition FragmentedSparseMatrix.hpp:570
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.
Copy data from one buffer to another.
Flexible representations for matrix data.
Definition Extractor.hpp:15
FragmentedSparseContents< StoredValue_, StoredIndex_ > retrieve_fragmented_sparse_contents(const Matrix< InputValue_, InputIndex_ > &matrix, const bool row, const RetrieveFragmentedSparseContentsOptions &options)
Definition convert_to_fragmented_sparse.hpp:329
int parallelize(Function_ fun, const Index_ tasks, const int workers)
Definition parallelize.hpp:58
I< decltype(std::declval< Container_ >().size())> cast_Index_to_container_size(const Index_ x)
Definition Index_to_container.hpp:65
std::shared_ptr< Matrix< Value_, Index_ > > convert_to_fragmented_sparse(const Matrix< InputValue_, InputIndex_ > &matrix, const bool row, const ConvertToFragmentedSparseOptions &options)
Definition convert_to_fragmented_sparse.hpp:385
Container_ create_container_of_Index_size(const Index_ x, Args_ &&... args)
Definition Index_to_container.hpp:82
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.
Options for convert_to_fragmented_sparse().
Definition convert_to_fragmented_sparse.hpp:348
bool two_pass
Definition convert_to_fragmented_sparse.hpp:354
int num_threads
Definition convert_to_fragmented_sparse.hpp:359
Fragmented sparse contents.
Definition convert_to_fragmented_sparse.hpp:37
std::vector< std::vector< Value_ > > value
Definition convert_to_fragmented_sparse.hpp:53
std::vector< std::vector< Index_ > > index
Definition convert_to_fragmented_sparse.hpp:60
Options for retrieve_fragmented_sparse_contents().
Definition convert_to_fragmented_sparse.hpp:66
int num_threads
Definition convert_to_fragmented_sparse.hpp:77
bool two_pass
Definition convert_to_fragmented_sparse.hpp:72