scran_aggregate
Aggregate expression values across cells
Loading...
Searching...
No Matches
aggregate_across_cells.hpp
Go to the documentation of this file.
1#ifndef SCRAN_AGGREGATE_AGGREGATE_ACROSS_CELLS_HPP
2#define SCRAN_AGGREGATE_AGGREGATE_ACROSS_CELLS_HPP
3
4#include <algorithm>
5#include <vector>
6#include <cstddef>
7#include <type_traits>
8#include <cassert>
9#include <optional>
10
11#include "tatami/tatami.hpp"
12#include "quickstats/quickstats.hpp"
13#include "sanisizer/sanisizer.hpp"
14#include "jiwoo/jiwoo.hpp"
15
16#include "utils.hpp"
17
23namespace scran_aggregate {
24
33 bool compute_sum = true;
34
39 bool compute_detected = true;
40
45 bool compute_median = false; // false by default as we usually don't need this.
46
51 int num_threads = 1;
52};
53
62template <typename Sum_, typename Detected_, typename Float_>
71 std::vector<Sum_*> sum;
72
80 std::vector<Detected_*> detected;
81
89 std::vector<Float_*> median;
90};
91
100template <typename Sum_, typename Detected_, typename Float_>
109 std::vector<std::vector<Sum_> > sum;
110
118 std::vector<std::vector<Detected_> > detected;
119
127 std::vector<std::vector<Float_> > median;
128};
129
133template<typename Data_, typename Index_, typename Group_, typename Sum_, typename Detected_, typename Float_>
134void aggregate_across_cells_by_row(
136 const Group_* const group,
137 const std::size_t num_groups,
139 const AggregateAcrossCellsOptions& options
140) {
141 const bool is_sparse = p.is_sparse();
142 const auto NC = p.ncol();
143
144 std::optional<std::vector<Index_> > group_sizes;
145 if (!buffers.median.empty()) {
146 group_sizes.emplace(sanisizer::cast<I<decltype(group_sizes->size())> >(num_groups));
147 for (Index_ c = 0; c < NC; ++c) {
148 (*group_sizes)[group[c]] += 1;
149 }
150 }
151
152 const auto nsum = buffers.sum.size();
153 if (nsum) {
154 assert(nsum == num_groups);
155 }
156
157 const auto ndetected = buffers.detected.size();
158 if (ndetected) {
159 assert(ndetected == num_groups);
160 }
161
162 const auto nmedian = buffers.median.size();
163 if (nmedian) {
164 assert(nmedian == num_groups);
165 }
166
167 tatami::parallelize([&](const int, const Index_ s, const Index_ l) -> void {
168 // Create buffers to reduce false sharing during summations.
169 std::optional<std::vector<Sum_> > tmp_sum;
170 if (nsum) {
171 tmp_sum.emplace(sanisizer::cast<I<decltype(tmp_sum->size())> >(nsum));
172 }
173
174 std::optional<std::vector<Detected_> > tmp_detected;
175 if (ndetected) {
176 tmp_detected.emplace(sanisizer::cast<I<decltype(tmp_detected->size())> >(ndetected));
177 }
178
179 std::optional<std::vector<std::vector<Float_> > > tmp_median;
180 if (nmedian) {
181 tmp_median.emplace(sanisizer::cast<I<decltype(tmp_median->size())> >(nmedian));
182 for (I<decltype(nmedian)> l = 0; l < nmedian; ++l) {
183 sanisizer::reserve((*tmp_median)[l], (*group_sizes)[l]);
184 }
185 }
186
187 if (is_sparse) {
188 tatami::Options opt;
189 opt.sparse_ordered_index = false;
190 auto ext = tatami::consecutive_extractor<true>(p, true, s, l, opt);
193
194 for (Index_ x = s, end = s + l; x < end; ++x) {
195 const auto row = ext->fetch(vbuffer.data(), ibuffer.data());
196
197 if (nsum) {
198 std::fill(tmp_sum->begin(), tmp_sum->end(), 0);
199 for (Index_ j = 0; j < row.number; ++j) {
200 (*tmp_sum)[group[row.index[j]]] += row.value[j];
201 }
202 for (I<decltype(nsum)> l = 0; l < nsum; ++l) {
203 buffers.sum[l][x] = (*tmp_sum)[l];
204 }
205 }
206
207 if (ndetected) {
208 std::fill(tmp_detected->begin(), tmp_detected->end(), 0);
209 for (Index_ j = 0; j < row.number; ++j) {
210 (*tmp_detected)[group[row.index[j]]] += (row.value[j] > 0);
211 }
212 for (I<decltype(ndetected)> l = 0; l < ndetected; ++l) {
213 buffers.detected[l][x] = (*tmp_detected)[l];
214 }
215 }
216
217 if (nmedian) {
218 quickstats::MedianOptions<Float_> medopt;
219 medopt.placeholder = std::numeric_limits<Float_>::quiet_NaN();
220 for (Index_ j = 0; j < row.number; ++j) {
221 (*tmp_median)[group[row.index[j]]].push_back(row.value[j]);
222 }
223 for (I<decltype(ndetected)> l = 0; l < nmedian; ++l) {
224 auto& current = (*tmp_median)[l];
225 buffers.median[l][x] = quickstats::median<Float_>((*group_sizes)[l], current.size(), current.data(), medopt);
226 current.clear();
227 }
228 }
229 }
230
231 } else {
232 auto ext = tatami::consecutive_extractor<false>(p, true, s, l);
234 for (Index_ x = s, end = s + l; x < end; ++x) {
235 const auto row = ext->fetch(vbuffer.data());
236
237 if (nsum) {
238 std::fill(tmp_sum->begin(), tmp_sum->end(), 0);
239 for (Index_ j = 0; j < NC; ++j) {
240 (*tmp_sum)[group[j]] += row[j];
241 }
242 for (I<decltype(nsum)> l = 0; l < nsum; ++l) {
243 buffers.sum[l][x] = (*tmp_sum)[l];
244 }
245 }
246
247 if (ndetected) {
248 std::fill(tmp_detected->begin(), tmp_detected->end(), 0);
249 for (Index_ j = 0; j < NC; ++j) {
250 (*tmp_detected)[group[j]] += (row[j] > 0);
251 }
252 for (I<decltype(ndetected)> l = 0; l < ndetected; ++l) {
253 buffers.detected[l][x] = (*tmp_detected)[l];
254 }
255 }
256
257 if (nmedian) {
258 quickstats::MedianOptions<Float_> medopt;
259 medopt.placeholder = std::numeric_limits<Float_>::quiet_NaN();
260 for (Index_ j = 0; j < NC; ++j) {
261 (*tmp_median)[group[j]].push_back(row[j]);
262 }
263 for (I<decltype(ndetected)> l = 0; l < nmedian; ++l) {
264 auto& current = (*tmp_median)[l];
265 buffers.median[l][x] = quickstats::median<Float_>(current.size(), current.data(), medopt);
266 current.clear();
267 }
268 }
269 }
270 }
271
272 }, p.nrow(), options.num_threads);
273}
274
275template<typename Data_, typename Index_, typename Group_, typename Sum_, typename Detected_, typename Float_>
276void aggregate_across_cells_by_column(
278 const Group_* const group,
279 const std::size_t num_groups,
280 const AggregateAcrossCellsBuffers<Sum_, Detected_, Float_>& buffers,
281 const AggregateAcrossCellsOptions& options
282) {
283 const auto is_sparse = p.is_sparse();
284 const auto NR = p.nrow();
285 assert(buffers.median.empty());
286 const bool do_parallel = options.num_threads > 1;
287
288 const auto nsum = buffers.sum.size();
289 std::optional<std::vector<std::optional<jiwoo::EquilengthArrays<Float_> > > > per_thread_sum;
290 if (nsum) {
291 assert(nsum == num_groups);
292 for (std::size_t g = 0; g < num_groups; ++g) {
293 std::fill_n(buffers.sum[g], NR, 0);
294 }
295 if (do_parallel) {
296 per_thread_sum.emplace(sanisizer::cast<I<decltype(per_thread_sum->size())> >(options.num_threads - 1));
297 }
298 }
299
300 const auto ndetected = buffers.detected.size();
301 std::optional<std::vector<std::optional<jiwoo::EquilengthArrays<Detected_> > > > per_thread_detected;
302 if (ndetected) {
303 assert(ndetected == num_groups);
304 for (std::size_t g = 0; g < num_groups; ++g) {
305 std::fill_n(buffers.detected[g], NR, 0);
306 }
307 if (do_parallel) {
308 per_thread_detected.emplace(sanisizer::cast<I<decltype(per_thread_detected->size())> >(options.num_threads - 1));
309 }
310 }
311
312 const auto nused = tatami::parallelize([&](const int t, const Index_ start, const Index_ length) -> void {
313 std::optional<jiwoo::EquilengthArrays<Float_> > tmp_sum;
314 std::optional<jiwoo::EquilengthArrays<Detected_> > tmp_detected;
315
316 Float_* const * sum_ptrs = NULL;
317 Detected_* const * det_ptrs = NULL;
318 if (t > 0) {
319 if (nsum) {
320 tmp_sum.emplace(
321 sanisizer::cast<I<decltype(tmp_sum->size())> >(num_groups),
322 static_cast<std::size_t>(NR), // cast from NR to size_t is safe, given the tatami contract.
323 0
324 );
325 sum_ptrs = tmp_sum->get();
326 }
327 if (ndetected) {
328 tmp_detected.emplace(
329 sanisizer::cast<I<decltype(tmp_detected->size())> >(num_groups),
330 static_cast<std::size_t>(NR), // cast from NR to size_t is safe, given the tatami contract.
331 0
332 );
333 det_ptrs = tmp_detected->get();
334 }
335 } else {
336 if (nsum) {
337 sum_ptrs = buffers.sum.data();
338 }
339 if (ndetected) {
340 det_ptrs = buffers.detected.data();
341 }
342 }
343
344 if (is_sparse) {
345 tatami::Options opt;
346 opt.sparse_ordered_index = false;
347 auto ext = tatami::consecutive_extractor<true>(p, false, start, length, opt);
350
351 for (Index_ x = 0; x < length; ++x) {
352 const auto col = ext->fetch(vbuffer.data(), ibuffer.data());
353 const auto curgroup = group[start + x];
354
355 if (nsum) {
356 const auto cursum = sum_ptrs[curgroup];
357 for (Index_ i = 0; i < col.number; ++i) {
358 cursum[col.index[i]] += col.value[i];
359 }
360 }
361
362 if (ndetected) {
363 const auto curdetected = det_ptrs[curgroup];
364 for (Index_ i = 0; i < col.number; ++i) {
365 curdetected[col.index[i]] += (col.value[i] > 0);
366 }
367 }
368 }
369
370 } else {
371 auto ext = tatami::consecutive_extractor<false>(p, false, start, length);
373
374 for (Index_ x = 0; x < length; ++x) {
375 const auto col = ext->fetch(vbuffer.data());
376 const auto curgroup = group[start + x];
377
378 if (nsum) {
379 const auto cursum = sum_ptrs[curgroup];
380 for (Index_ i = 0; i < NR; ++i) {
381 cursum[i] += col[i];
382 }
383 }
384
385 if (ndetected) {
386 const auto curdetected = det_ptrs[curgroup];
387 for (Index_ i = 0; i < NR; ++i) {
388 curdetected[i] += (col[i] > 0);
389 }
390 }
391 }
392 }
393
394 if (t > 0) {
395 if (nsum) {
396 (*per_thread_sum)[t - 1] = std::move(tmp_sum);
397 }
398 if (ndetected) {
399 (*per_thread_detected)[t - 1] = std::move(tmp_detected);
400 }
401 }
402 }, p.ncol(), options.num_threads);
403
404 if (do_parallel) {
405 if (nsum) {
406 for (std::size_t g = 0; g < num_groups; ++g) {
407 const auto out = buffers.sum[g];
408 for (int u = 1; u < nused; ++u) {
409 const auto ptrs = (*((*per_thread_sum)[u - 1]))[g];
410 for (Index_ r = 0; r < NR; ++r) {
411 out[r] += ptrs[r];
412 }
413 }
414 }
415 }
416
417 if (ndetected) {
418 for (std::size_t g = 0; g < num_groups; ++g) {
419 const auto out = buffers.detected[g];
420 for (int u = 1; u < nused; ++u) {
421 const auto ptrs = (*((*per_thread_detected)[u - 1]))[g];
422 for (Index_ r = 0; r < NR; ++r) {
423 out[r] += ptrs[r];
424 }
425 }
426 }
427 }
428 }
429}
457template<typename Data_, typename Index_, typename Group_, typename Sum_, typename Detected_, typename Float_>
460 const Group_* const group,
461 const std::size_t num_groups,
463 const AggregateAcrossCellsOptions& options
464) {
465 if (input.prefer_rows() || !buffers.median.empty()) {
466 aggregate_across_cells_by_row(input, group, num_groups, buffers, options);
467 } else {
468 aggregate_across_cells_by_column(input, group, num_groups, buffers, options);
469 }
470}
471
493template<typename Sum_ = double, typename Detected_ = int, typename Float_ = double, typename Data_, typename Index_, typename Group_>
496 const Group_* const group,
497 const std::size_t num_groups,
498 const AggregateAcrossCellsOptions& options
499) {
500 const Index_ NR = input.nrow();
501
504
505 if (options.compute_sum) {
506 sanisizer::resize(output.sum, num_groups);
507 sanisizer::resize(buffers.sum, num_groups);
508 for (I<decltype(num_groups)> l = 0; l < num_groups; ++l) {
509 auto& cursum = output.sum[l];
510 tatami::resize_container_to_Index_size<I<decltype(cursum)>>(cursum, NR
511#ifdef SCRAN_AGGREGATE_TEST_INIT
512 , SCRAN_AGGREGATE_TEST_INIT
513#endif
514 );
515 buffers.sum[l] = cursum.data();
516 }
517 }
518
519 if (options.compute_detected) {
520 sanisizer::resize(output.detected, num_groups);
521 sanisizer::resize(buffers.detected, num_groups);
522 for (I<decltype(num_groups)> l = 0; l < num_groups; ++l) {
523 auto& curdet = output.detected[l];
524 tatami::resize_container_to_Index_size<I<decltype(curdet)>>(curdet, NR
525#ifdef SCRAN_AGGREGATE_TEST_INIT
526 , SCRAN_AGGREGATE_TEST_INIT
527#endif
528 );
529 buffers.detected[l] = curdet.data();
530 }
531 }
532
533 if (options.compute_median) {
534 sanisizer::resize(output.median, num_groups);
535 sanisizer::resize(buffers.median, num_groups);
536 for (I<decltype(num_groups)> l = 0; l < num_groups; ++l) {
537 auto& curmed = output.median[l];
538 tatami::resize_container_to_Index_size<I<decltype(curmed)>>(curmed, NR
539#ifdef SCRAN_AGGREGATE_TEST_INIT
540 , SCRAN_AGGREGATE_TEST_INIT
541#endif
542 );
543 buffers.median[l] = curmed.data();
544 }
545 }
546
547
548 aggregate_across_cells(input, group, num_groups, buffers, options);
549 return output;
550}
551
552}
553
554#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
virtual bool prefer_rows() const=0
virtual bool is_sparse() const=0
Aggregate single-cell expression values.
Definition aggregate_across_cells.hpp:23
void aggregate_across_cells(const tatami::Matrix< Data_, Index_ > &input, const Group_ *const group, const std::size_t num_groups, const AggregateAcrossCellsBuffers< Sum_, Detected_, Float_ > &buffers, const AggregateAcrossCellsOptions &options)
Definition aggregate_across_cells.hpp:458
void resize_container_to_Index_size(Container_ &container, const Index_ x, Args_ &&... args)
int parallelize(Function_ fun, const Index_ tasks, const int workers)
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)
Buffers for aggregate_across_cells().
Definition aggregate_across_cells.hpp:63
std::vector< Float_ * > median
Definition aggregate_across_cells.hpp:89
std::vector< Sum_ * > sum
Definition aggregate_across_cells.hpp:71
std::vector< Detected_ * > detected
Definition aggregate_across_cells.hpp:80
Options for aggregate_across_cells().
Definition aggregate_across_cells.hpp:28
int num_threads
Definition aggregate_across_cells.hpp:51
bool compute_sum
Definition aggregate_across_cells.hpp:33
bool compute_detected
Definition aggregate_across_cells.hpp:39
bool compute_median
Definition aggregate_across_cells.hpp:45
Results of aggregate_across_cells().
Definition aggregate_across_cells.hpp:101
std::vector< std::vector< Sum_ > > sum
Definition aggregate_across_cells.hpp:109
std::vector< std::vector< Float_ > > median
Definition aggregate_across_cells.hpp:127
std::vector< std::vector< Detected_ > > detected
Definition aggregate_across_cells.hpp:118
bool sparse_ordered_index