scran_pca
Principal component analysis for single-cell data
Loading...
Searching...
No Matches
blocked_pca.hpp
Go to the documentation of this file.
1#ifndef SCRAN_PCA_BLOCKED_PCA_HPP
2#define SCRAN_PCA_BLOCKED_PCA_HPP
3
4#include <vector>
5#include <cmath>
6#include <algorithm>
7#include <type_traits>
8#include <cstddef>
9
10#include "tatami/tatami.hpp"
11#include "irlba/irlba.hpp"
12#include "irlba/parallel.hpp"
13#include "Eigen/Dense"
15#include "sanisizer/sanisizer.hpp"
16
17#include "utils.hpp"
18
25namespace scran_pca {
26
95
99namespace internal {
100
101/*****************************************************
102 ************* Blocking data structures **************
103 *****************************************************/
104
105template<typename Index_, class EigenVector_>
106struct BlockingDetails {
107 std::vector<Index_> block_size;
108
109 bool weighted = false;
110 typedef typename EigenVector_::Scalar Weight;
111
112 // The below should only be used if weighted = true.
113 std::vector<Weight> per_element_weight;
114 Weight total_block_weight = 0;
115 EigenVector_ expanded_weights;
116};
117
118template<class EigenVector_, typename Index_, typename Block_>
119BlockingDetails<Index_, EigenVector_> compute_blocking_details(
120 Index_ ncells,
121 const Block_* block,
122 scran_blocks::WeightPolicy block_weight_policy,
123 const scran_blocks::VariableWeightParameters& variable_block_weight_parameters)
124{
125 BlockingDetails<Index_, EigenVector_> output;
126 output.block_size = tatami_stats::tabulate_groups(block, ncells);
127 if (block_weight_policy == scran_blocks::WeightPolicy::NONE) {
128 return output;
129 }
130
131 const auto& block_size = output.block_size;
132 auto nblocks = block_size.size();
133 output.weighted = true;
134 auto& total_weight = output.total_block_weight;
135 auto& element_weight = output.per_element_weight;
136 element_weight.resize(sanisizer::cast<decltype(element_weight.size())>(nblocks));
137
138 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
139 auto bsize = block_size[b];
140
141 // Computing effective block weights that also incorporate division by the
142 // block size. This avoids having to do the division by block size in the
143 // 'compute_blockwise_mean_and_variance*()' functions.
144 if (bsize) {
145 typename EigenVector_::Scalar block_weight = 1;
146 if (block_weight_policy == scran_blocks::WeightPolicy::VARIABLE) {
147 block_weight = scran_blocks::compute_variable_weight(bsize, variable_block_weight_parameters);
148 }
149
150 element_weight[b] = block_weight / bsize;
151 total_weight += block_weight;
152 } else {
153 element_weight[b] = 0;
154 }
155 }
156
157 // Setting a placeholder value to avoid problems with division by zero.
158 if (total_weight == 0) {
159 total_weight = 1;
160 }
161
162 // Expanding them for multiplication in the IRLBA wrappers.
163 auto sqrt_weights = element_weight;
164 for (auto& s : sqrt_weights) {
165 s = std::sqrt(s);
166 }
167
168 auto& expanded = output.expanded_weights;
169 expanded.resize(sanisizer::cast<decltype(expanded.size())>(ncells));
170 for (Index_ c = 0; c < ncells; ++c) {
171 expanded.coeffRef(c) = sqrt_weights[block[c]];
172 }
173
174 return output;
175}
176
177/*****************************************************************
178 ************ Computing the blockwise mean and variance **********
179 *****************************************************************/
180
181template<typename Num_, typename Value_, typename Index_, typename Block_, typename EigenVector_, typename Float_>
182void compute_sparse_mean_and_variance_blocked(
183 Num_ num_nonzero,
184 const Value_* values,
185 const Index_* indices,
186 const Block_* block,
187 const BlockingDetails<Index_, EigenVector_>& block_details,
188 Float_* centers,
189 Float_& variance,
190 std::vector<Index_>& block_copy,
191 Num_ num_all)
192{
193 const auto& block_size = block_details.block_size;
194 auto nblocks = block_size.size();
195
196 std::fill_n(centers, nblocks, 0);
197 for (Num_ i = 0; i < num_nonzero; ++i) {
198 centers[block[indices[i]]] += values[i];
199 }
200 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
201 auto bsize = block_size[b];
202 if (bsize) {
203 centers[b] /= bsize;
204 }
205 }
206
207 // Computing the variance from the sum of squared differences.
208 // This is technically not the correct variance estimate if we
209 // were to consider the loss of residual d.f. from estimating
210 // the block means, but it's what the PCA sees, so whatever.
211 variance = 0;
212 std::copy(block_size.begin(), block_size.end(), block_copy.begin());
213
214 if (block_details.weighted) {
215 for (Num_ i = 0; i < num_nonzero; ++i) {
216 Block_ curb = block[indices[i]];
217 auto diff = values[i] - centers[curb];
218 variance += diff * diff * block_details.per_element_weight[curb];
219 --block_copy[curb];
220 }
221 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
222 auto val = centers[b];
223 variance += val * val * block_copy[b] * block_details.per_element_weight[b];
224 }
225 } else {
226 for (Num_ i = 0; i < num_nonzero; ++i) {
227 Block_ curb = block[indices[i]];
228 auto diff = values[i] - centers[curb];
229 variance += diff * diff;
230 --block_copy[curb];
231 }
232 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
233 auto val = centers[b];
234 variance += val * val * block_copy[b];
235 }
236 }
237
238 // COMMENT ON DENOMINATOR:
239 // If we're not dealing with weights, we compute the actual sample
240 // variance for easy interpretation (and to match up with the
241 // per-PC calculations in internal::clean_up).
242 //
243 // If we're dealing with weights, the concept of the sample variance
244 // becomes somewhat weird, but we just use the same denominator for
245 // consistency in clean_up_projected. Magnitude doesn't matter when
246 // scaling for internal::process_scale_vector anyway.
247 variance /= num_all - 1;
248}
249
250template<class IrlbaSparseMatrix_, typename Block_, class Index_, class EigenVector_, class EigenMatrix_>
251void compute_blockwise_mean_and_variance_realized_sparse(
252 const IrlbaSparseMatrix_& emat, // this should be column-major with genes in the columns.
253 const Block_* block,
254 const BlockingDetails<Index_, EigenVector_>& block_details,
255 EigenMatrix_& centers,
256 EigenVector_& variances,
257 int nthreads)
258{
259 auto ngenes = emat.cols();
260 tatami::parallelize([&](int, decltype(ngenes) start, decltype(ngenes) length) -> void {
261 auto ncells = emat.rows();
262 const auto& values = emat.get_values();
263 const auto& indices = emat.get_indices();
264 const auto& pointers = emat.get_pointers();
265
266 auto nblocks = block_details.block_size.size();
267 static_assert(!EigenMatrix_::IsRowMajor);
268 auto block_copy = sanisizer::create<std::vector<Index_> >(nblocks);
269
270 for (auto g = start, end = start + length; g < end; ++g) {
271 auto offset = pointers[g];
272 auto next_offset = pointers[g + 1]; // increment won't overflow as 'g < end' and 'end' is of the same type.
273 compute_sparse_mean_and_variance_blocked(
274 static_cast<decltype(ncells)>(next_offset - offset),
275 values.data() + offset,
276 indices.data() + offset,
277 block,
278 block_details,
279 centers.data() + sanisizer::product_unsafe<std::size_t>(g, nblocks),
280 variances[g],
281 block_copy,
282 ncells
283 );
284 }
285 }, ngenes, nthreads);
286}
287
288template<typename Num_, typename Value_, typename Block_, typename Index_, typename EigenVector_, typename Float_>
289void compute_dense_mean_and_variance_blocked(
290 Num_ number,
291 const Value_* values,
292 const Block_* block,
293 const BlockingDetails<Index_, EigenVector_>& block_details,
294 Float_* centers,
295 Float_& variance)
296{
297 const auto& block_size = block_details.block_size;
298 auto nblocks = block_size.size();
299 std::fill_n(centers, nblocks, 0);
300 for (Num_ i = 0; i < number; ++i) {
301 centers[block[i]] += values[i];
302 }
303 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
304 const auto& bsize = block_size[b];
305 if (bsize) {
306 centers[b] /= bsize;
307 }
308 }
309
310 variance = 0;
311
312 if (block_details.weighted) {
313 for (Num_ i = 0; i < number; ++i) {
314 auto curb = block[i];
315 auto delta = values[i] - centers[curb];
316 variance += delta * delta * block_details.per_element_weight[curb];
317 }
318 } else {
319 for (Num_ i = 0; i < number; ++i) {
320 auto curb = block[i];
321 auto delta = values[i] - centers[curb];
322 variance += delta * delta;
323 }
324 }
325
326 variance /= number - 1; // See COMMENT ON DENOMINATOR above.
327}
328
329template<class EigenMatrix_, typename Block_, class Index_, class EigenVector_>
330void compute_blockwise_mean_and_variance_realized_dense(
331 const EigenMatrix_& emat, // this should be column-major with genes in the columns.
332 const Block_* block,
333 const BlockingDetails<Index_, EigenVector_>& block_details,
334 EigenMatrix_& centers,
335 EigenVector_& variances,
336 int nthreads)
337{
338 auto ngenes = emat.cols();
339 tatami::parallelize([&](int, decltype(ngenes) start, decltype(ngenes) length) -> void {
340 auto ncells = emat.rows();
341 static_assert(!EigenMatrix_::IsRowMajor);
342 auto nblocks = block_details.block_size.size();
343 for (auto g = start, end = start + length; g < end; ++g) {
344 compute_dense_mean_and_variance_blocked(
345 ncells,
346 emat.data() + sanisizer::product_unsafe<std::size_t>(g, ncells),
347 block,
348 block_details,
349 centers.data() + sanisizer::product_unsafe<std::size_t>(g, nblocks),
350 variances[g]
351 );
352 }
353 }, ngenes, nthreads);
354}
355
356template<typename Value_, typename Index_, typename Block_, class EigenMatrix_, class EigenVector_>
357void compute_blockwise_mean_and_variance_tatami(
358 const tatami::Matrix<Value_, Index_>& mat, // this should have genes in the rows!
359 const Block_* block,
360 const BlockingDetails<Index_, EigenVector_>& block_details,
361 EigenMatrix_& centers,
362 EigenVector_& variances,
363 int nthreads)
364{
365 const auto& block_size = block_details.block_size;
366 auto nblocks = block_size.size();
367 Index_ ngenes = mat.nrow();
368 Index_ ncells = mat.ncol();
369
370 if (mat.prefer_rows()) {
371 tatami::parallelize([&](int, Index_ start, Index_ length) -> void {
372 static_assert(!EigenMatrix_::IsRowMajor);
373 auto block_copy = sanisizer::create<std::vector<Index_> >(nblocks);
375
376 if (mat.is_sparse()) {
378 auto ext = tatami::consecutive_extractor<true>(mat, true, start, length);
379 for (Index_ g = start, end = start + length; g < end; ++g) {
380 auto range = ext->fetch(vbuffer.data(), ibuffer.data());
381 compute_sparse_mean_and_variance_blocked(
382 range.number,
383 range.value,
384 range.index,
385 block,
386 block_details,
387 centers.data() + sanisizer::product_unsafe<std::size_t>(g, nblocks),
388 variances[g],
389 block_copy,
390 ncells
391 );
392 }
393 } else {
394 auto ext = tatami::consecutive_extractor<false>(mat, true, start, length);
395 for (Index_ g = start, end = start + length; g < end; ++g) {
396 auto ptr = ext->fetch(vbuffer.data());
397 compute_dense_mean_and_variance_blocked(
398 ncells,
399 ptr,
400 block,
401 block_details,
402 centers.data() + sanisizer::product_unsafe<std::size_t>(g, nblocks),
403 variances[g]
404 );
405 }
406 }
407 }, ngenes, nthreads);
408
409 } else {
410 typedef typename EigenVector_::Scalar Scalar;
411
412 std::vector<std::pair<decltype(nblocks), Scalar> > block_multipliers;
413 block_multipliers.reserve(nblocks);
414 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
415 auto bsize = block_size[b];
416 if (bsize > 1) { // skipping blocks with NaN variances.
417 Scalar mult = bsize - 1; // need to convert variances back into sum of squared differences.
418 if (block_details.weighted) {
419 mult *= block_details.per_element_weight[b];
420 }
421 block_multipliers.emplace_back(b, mult);
422 }
423 }
424
425 tatami::parallelize([&](int, Index_ start, Index_ length) -> void {
426 std::vector<std::vector<Scalar> > re_centers, re_variances;
427 re_centers.reserve(nblocks);
428 re_variances.reserve(nblocks);
429 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
430 re_centers.emplace_back(length);
431 re_variances.emplace_back(length);
432 }
433
435
436 if (mat.is_sparse()) {
437 std::vector<tatami_stats::variances::RunningSparse<Scalar, Value_, Index_> > running;
438 running.reserve(nblocks);
439 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
440 running.emplace_back(length, re_centers[b].data(), re_variances[b].data(), /* skip_nan = */ false, /* subtract = */ start);
441 }
442
444 auto ext = tatami::consecutive_extractor<true>(mat, false, static_cast<Index_>(0), ncells, start, length);
445 for (Index_ c = 0; c < ncells; ++c) {
446 auto range = ext->fetch(vbuffer.data(), ibuffer.data());
447 running[block[c]].add(range.value, range.index, range.number);
448 }
449
450 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
451 running[b].finish();
452 }
453
454 } else {
455 std::vector<tatami_stats::variances::RunningDense<Scalar, Value_, Index_> > running;
456 running.reserve(nblocks);
457 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
458 running.emplace_back(length, re_centers[b].data(), re_variances[b].data(), /* skip_nan = */ false);
459 }
460
461 auto ext = tatami::consecutive_extractor<false>(mat, false, static_cast<Index_>(0), ncells, start, length);
462 for (Index_ c = 0; c < ncells; ++c) {
463 auto ptr = ext->fetch(vbuffer.data());
464 running[block[c]].add(ptr);
465 }
466
467 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
468 running[b].finish();
469 }
470 }
471
472 static_assert(!EigenMatrix_::IsRowMajor);
473 for (Index_ i = 0; i < length; ++i) {
474 auto mptr = centers.data() + sanisizer::product_unsafe<std::size_t>(start + i, nblocks);
475 for (decltype(nblocks) b = 0; b < nblocks; ++b) {
476 mptr[b] = re_centers[b][i];
477 }
478
479 auto& my_var = variances[start + i];
480 my_var = 0;
481 for (const auto& bm : block_multipliers) {
482 my_var += re_variances[bm.first][i] * bm.second;
483 }
484 my_var /= ncells - 1; // See COMMENT ON DENOMINATOR above.
485 }
486 }, ngenes, nthreads);
487 }
488}
489
490/******************************************************************
491 ************ Project matrices on their rotation vectors **********
492 ******************************************************************/
493
494template<class EigenMatrix_, class EigenVector_>
495const EigenMatrix_& scale_rotation_matrix(const EigenMatrix_& rotation, bool scale, const EigenVector_& scale_v, EigenMatrix_& tmp) {
496 if (scale) {
497 tmp = (rotation.array().colwise() / scale_v.array()).matrix();
498 return tmp;
499 } else {
500 return rotation;
501 }
502}
503
504template<class IrlbaSparseMatrix_, class EigenMatrix_>
505inline void project_matrix_realized_sparse(
506 const IrlbaSparseMatrix_& emat, // cell in rows, genes in the columns, CSC.
507 EigenMatrix_& components, // dims in rows, cells in columns
508 const EigenMatrix_& scaled_rotation, // genes in rows, dims in columns
509 int nthreads)
510{
511 auto rank = scaled_rotation.cols();
512 auto ncells = emat.rows();
513 auto ngenes = emat.cols();
514
515 // Store as transposed for more cache efficiency.
516 components.resize(
517 sanisizer::cast<decltype(components.rows())>(rank),
518 sanisizer::cast<decltype(components.cols())>(ncells)
519 );
520 components.setZero();
521
522 const auto& values = emat.get_values();
523 const auto& indices = emat.get_indices();
524
525 if (nthreads == 1) {
526 const auto& pointers = emat.get_pointers();
527 auto multipliers = sanisizer::create<Eigen::VectorXd>(rank);
528 for (decltype(ngenes) g = 0; g < ngenes; ++g) {
529 multipliers.noalias() = scaled_rotation.row(g);
530 auto start = pointers[g], end = pointers[g + 1]; // increment is safe as 'g + 1 <= ngenes'.
531 for (auto i = start; i < end; ++i) {
532 components.col(indices[i]).noalias() += values[i] * multipliers;
533 }
534 }
535
536 } else {
537 const auto& row_nonzero_starts = emat.get_secondary_nonzero_starts();
538 irlba::parallelize(nthreads, [&](int t) -> void {
539 const auto& starts = row_nonzero_starts[t];
540 const auto& ends = row_nonzero_starts[t + 1]; // increment is safe as 't + 1 <= nthreads'.
541 auto multipliers = sanisizer::create<Eigen::VectorXd>(rank);
542
543 for (decltype(ngenes) g = 0; g < ngenes; ++g) {
544 multipliers.noalias() = scaled_rotation.row(g);
545 auto start = starts[g], end = ends[g];
546 for (auto i = start; i < end; ++i) {
547 components.col(indices[i]).noalias() += values[i] * multipliers;
548 }
549 }
550 });
551 }
552}
553
554template<typename Value_, typename Index_, class EigenMatrix_>
555void project_matrix_transposed_tatami(
556 const tatami::Matrix<Value_, Index_>& mat, // genes in rows, cells in columns
557 EigenMatrix_& components,
558 const EigenMatrix_& scaled_rotation, // genes in rows, dims in columns
559 int nthreads)
560{
561 auto rank = scaled_rotation.cols();
562 auto ngenes = mat.nrow();
563 auto ncells = mat.ncol();
564 typedef typename EigenMatrix_::Scalar Scalar;
565
566 // Store as transposed for more cache efficiency.
567 components.resize(
568 sanisizer::cast<decltype(components.rows())>(rank),
569 sanisizer::cast<decltype(components.cols())>(ncells)
570 );
571
572 if (mat.prefer_rows()) {
573 tatami::parallelize([&](int, Index_ start, Index_ length) -> void {
574 static_assert(!EigenMatrix_::IsRowMajor);
575 auto vptr = scaled_rotation.data();
577
578 std::vector<std::vector<Scalar> > local_buffers; // create separate buffers to avoid false sharing.
579 local_buffers.reserve(rank);
580 for (decltype(rank) r = 0; r < rank; ++r) {
581 local_buffers.emplace_back(tatami::cast_Index_to_container_size<decltype(local_buffers.front())>(length));
582 }
583
584 if (mat.is_sparse()) {
586 auto ext = tatami::consecutive_extractor<true>(mat, true, static_cast<Index_>(0), ngenes, start, length);
587 for (Index_ g = 0; g < ngenes; ++g) {
588 auto range = ext->fetch(vbuffer.data(), ibuffer.data());
589 for (decltype(rank) r = 0; r < rank; ++r) {
590 auto mult = vptr[sanisizer::nd_offset<std::size_t>(g, ngenes, r)];
591 auto& local_buffer = local_buffers[r];
592 for (Index_ i = 0; i < range.number; ++i) {
593 local_buffer[range.index[i] - start] += range.value[i] * mult;
594 }
595 }
596 }
597
598 } else {
599 auto ext = tatami::consecutive_extractor<false>(mat, true, static_cast<Index_>(0), ngenes, start, length);
600 for (Index_ g = 0; g < ngenes; ++g) {
601 auto ptr = ext->fetch(vbuffer.data());
602 for (decltype(rank) r = 0; r < rank; ++r) {
603 auto mult = vptr[sanisizer::nd_offset<std::size_t>(g, ngenes, r)];
604 auto& local_buffer = local_buffers[r];
605 for (Index_ i = 0; i < length; ++i) {
606 local_buffer[i] += ptr[i] * mult;
607 }
608 }
609 }
610 }
611
612 for (decltype(rank) r = 0; r < rank; ++r) {
613 for (Index_ c = 0; c < length; ++c) {
614 components.coeffRef(r, c + start) = local_buffers[r][c];
615 }
616 }
617
618 }, ncells, nthreads);
619
620 } else {
621 tatami::parallelize([&](int, Index_ start, Index_ length) -> void {
622 static_assert(!EigenMatrix_::IsRowMajor);
624
625 if (mat.is_sparse()) {
626 std::vector<Index_> ibuffer(ngenes);
627 auto ext = tatami::consecutive_extractor<true>(mat, false, start, length);
628
629 for (Index_ c = start, end = start + length; c < end; ++c) {
630 auto range = ext->fetch(vbuffer.data(), ibuffer.data());
631 static_assert(!EigenMatrix_::IsRowMajor);
632 for (decltype(rank) r = 0; r < rank; ++r) {
633 auto& output = components.coeffRef(r, c);
634 output = 0;
635 auto rotptr = scaled_rotation.data() + sanisizer::product_unsafe<std::size_t>(r, ngenes);
636 for (Index_ i = 0; i < range.number; ++i) {
637 output += rotptr[range.index[i]] * range.value[i];
638 }
639 }
640 }
641
642 } else {
643 auto ext = tatami::consecutive_extractor<false>(mat, false, start, length);
644 for (Index_ c = start, end = start + length; c < end; ++c) {
645 auto ptr = ext->fetch(vbuffer.data());
646 static_assert(!EigenMatrix_::IsRowMajor);
647 for (decltype(rank) r = 0; r < rank; ++r) {
648 auto rotptr = scaled_rotation.data() + sanisizer::product_unsafe<std::size_t>(r, ngenes);
649 components.coeffRef(r, c) = std::inner_product(rotptr, rotptr + ngenes, ptr, static_cast<Scalar>(0));
650 }
651 }
652 }
653 }, ncells, nthreads);
654 }
655}
656
657template<class EigenMatrix_, class EigenVector_>
658void clean_up_projected(EigenMatrix_& projected, EigenVector_& D) {
659 // Empirically centering to give nice centered PCs, because we can't
660 // guarantee that the projection is centered in this manner.
661 for (decltype(projected.rows()) i = 0, prows = projected.rows(); i < prows; ++i) {
662 projected.row(i).array() -= projected.row(i).sum() / projected.cols();
663 }
664
665 // Just dividing by the number of observations - 1 regardless of weighting.
666 typename EigenMatrix_::Scalar denom = projected.cols() - 1;
667 for (auto& d : D) {
668 d = d * d / denom;
669 }
670}
671
672/*******************************
673 ***** Residual wrapper ********
674 *******************************/
675
676// This wrapper class mimics multiplication with the residuals,
677// i.e., after subtracting the per-block mean from each cell.
678template<class Matrix_, typename Block_, class EigenMatrix_, class EigenVector_>
679class ResidualWrapper {
680public:
681 ResidualWrapper(const Matrix_& mat, const Block_* block, const EigenMatrix_& means) : my_mat(mat), my_block(block), my_means(means) {}
682
683public:
684 Eigen::Index rows() const { return my_mat.rows(); }
685 Eigen::Index cols() const { return my_mat.cols(); }
686
687public:
688 struct Workspace {
689 template<typename NumBlocks_>
690 Workspace(NumBlocks_ nblocks, irlba::WrappedWorkspace<Matrix_> c) :
691 sub(sanisizer::cast<decltype(sub.size())>(nblocks)),
692 child(std::move(c))
693 {}
694
695 EigenVector_ sub;
696 EigenVector_ holding;
698 };
699
700 Workspace workspace() const {
701 return Workspace(my_means.rows(), irlba::wrapped_workspace(my_mat));
702 }
703
704 template<class Right_>
705 void multiply(const Right_& rhs, Workspace& work, EigenVector_& output) const {
706 const auto& realized_rhs = [&]() -> const auto& {
707 if constexpr(std::is_same<Right_, EigenVector_>::value) {
708 return rhs;
709 } else {
710 work.holding.noalias() = rhs;
711 return work.holding;
712 }
713 }();
714
715 irlba::wrapped_multiply(my_mat, realized_rhs, work.child, output);
716
717 work.sub.noalias() = my_means * realized_rhs;
718 for (decltype(output.size()) i = 0, end = output.size(); i < end; ++i) {
719 auto& val = output.coeffRef(i);
720 val -= work.sub.coeff(my_block[i]);
721 }
722 }
723
724public:
725 struct AdjointWorkspace {
726 template<typename NumBlocks_>
727 AdjointWorkspace(NumBlocks_ nblocks, irlba::WrappedAdjointWorkspace<Matrix_> c) :
728 aggr(sanisizer::cast<decltype(aggr.size())>(nblocks)),
729 child(std::move(c))
730 {}
731
732 EigenVector_ aggr;
733 EigenVector_ holding;
735 };
736
737 AdjointWorkspace adjoint_workspace() const {
738 return AdjointWorkspace(my_means.rows(), irlba::wrapped_adjoint_workspace(my_mat));
739 }
740
741 template<class Right_>
742 void adjoint_multiply(const Right_& rhs, AdjointWorkspace& work, EigenVector_& output) const {
743 const auto& realized_rhs = [&]() {
744 if constexpr(std::is_same<Right_, EigenVector_>::value) {
745 return rhs;
746 } else {
747 work.holding.noalias() = rhs;
748 return work.holding;
749 }
750 }();
751
752 irlba::wrapped_adjoint_multiply(my_mat, realized_rhs, work.child, output);
753
754 work.aggr.setZero();
755 for (decltype(realized_rhs.size()) i = 0, end = realized_rhs.size(); i < end; ++i) {
756 work.aggr.coeffRef(my_block[i]) += realized_rhs.coeff(i);
757 }
758
759 output.noalias() -= my_means.adjoint() * work.aggr;
760 }
761
762public:
763 template<class EigenMatrix2_>
764 EigenMatrix2_ realize() const {
765 EigenMatrix2_ output = irlba::wrapped_realize<EigenMatrix2_>(my_mat);
766 for (decltype(output.rows()) i = 0, end = output.rows(); i < end; ++i) {
767 output.row(i) -= my_means.row(my_block[i]);
768 }
769 return output;
770 }
771
772private:
773 const Matrix_& my_mat;
774 const Block_* my_block;
775 const EigenMatrix_& my_means;
776};
777
778/**************************
779 ***** Dispatchers ********
780 **************************/
781
782template<bool realize_matrix_, bool sparse_, typename Value_, typename Index_, typename Block_, class EigenMatrix_, class EigenVector_>
783void run_blocked(
785 const Block_* block,
786 const BlockingDetails<Index_, EigenVector_>& block_details,
787 const BlockedPcaOptions& options,
788 EigenMatrix_& components,
789 EigenMatrix_& rotation,
790 EigenVector_& variance_explained,
791 EigenMatrix_& center_m,
792 EigenVector_& scale_v,
793 typename EigenVector_::Scalar& total_var,
794 bool& converged)
795{
796 Index_ ngenes = mat.nrow(), ncells = mat.ncol();
797
798 auto emat = [&]{
799 if constexpr(!realize_matrix_) {
800 return internal::TransposedTatamiWrapper<EigenVector_, Value_, Index_>(mat, options.num_threads);
801
802 } else if constexpr(sparse_) {
803 // 'extracted' contains row-major contents... but we implicitly transpose it to CSC with genes in columns.
805 mat,
806 /* row = */ true,
807 [&]{
809 opt.two_pass = false;
810 opt.num_threads = options.num_threads;
811 return opt;
812 }()
813 );
814 return irlba::ParallelSparseMatrix(ncells, ngenes, std::move(extracted.value), std::move(extracted.index), std::move(extracted.pointers), true, options.num_threads);
815
816 } else {
817 // Perform an implicit transposition by performing a row-major extraction into a column-major transposed matrix.
818 EigenMatrix_ emat(
819 sanisizer::cast<decltype(std::declval<EigenMatrix_>().rows())>(ncells),
820 sanisizer::cast<decltype(std::declval<EigenMatrix_>().rows())>(ngenes)
821 );
822 static_assert(!EigenMatrix_::IsRowMajor);
824 mat,
825 /* row_major = */ true,
826 emat.data(),
827 [&]{
828 tatami::ConvertToDenseOptions opt;
829 opt.num_threads = options.num_threads;
830 return opt;
831 }()
832 );
833 return emat;
834 }
835 }();
836
837 auto nblocks = block_details.block_size.size();
838 center_m.resize(
839 sanisizer::cast<decltype(center_m.rows())>(nblocks),
840 sanisizer::cast<decltype(center_m.cols())>(ngenes)
841 );
842 scale_v.resize(sanisizer::cast<decltype(scale_v.size())>(ngenes));
843
844 if constexpr(!realize_matrix_) {
845 compute_blockwise_mean_and_variance_tatami(mat, block, block_details, center_m, scale_v, options.num_threads);
846 } else if constexpr(sparse_) {
847 compute_blockwise_mean_and_variance_realized_sparse(emat, block, block_details, center_m, scale_v, options.num_threads);
848 } else {
849 compute_blockwise_mean_and_variance_realized_dense(emat, block, block_details, center_m, scale_v, options.num_threads);
850 }
851 total_var = internal::process_scale_vector(options.scale, scale_v);
852
853 ResidualWrapper<decltype(emat), Block_, EigenMatrix_, EigenVector_> centered(emat, block, center_m);
854
855 if (block_details.weighted) {
856 if (options.scale) {
857 irlba::Scaled<true, decltype(centered), EigenVector_> scaled(centered, scale_v, /* divide = */ true);
858 irlba::Scaled<false, decltype(scaled), EigenVector_> weighted(scaled, block_details.expanded_weights, /* divide = */ false);
859 auto out = irlba::compute(weighted, options.number, components, rotation, variance_explained, options.irlba_options);
860 converged = out.first;
861 } else {
862 irlba::Scaled<false, decltype(centered), EigenVector_> weighted(centered, block_details.expanded_weights, /* divide = */ false);
863 auto out = irlba::compute(weighted, options.number, components, rotation, variance_explained, options.irlba_options);
864 converged = out.first;
865 }
866
867 EigenMatrix_ tmp;
868 const auto& scaled_rotation = scale_rotation_matrix(rotation, options.scale, scale_v, tmp);
869
870 // This transposes 'components' to be a NDIM * NCELLS matrix.
871 if constexpr(!realize_matrix_) {
872 project_matrix_transposed_tatami(mat, components, scaled_rotation, options.num_threads);
873 } else if constexpr(sparse_) {
874 project_matrix_realized_sparse(emat, components, scaled_rotation, options.num_threads);
875 } else {
876 components.noalias() = (emat * scaled_rotation).adjoint();
877 }
878
879 // Subtracting each block's mean from the PCs.
880 if (options.components_from_residuals) {
881 EigenMatrix_ centering = (center_m * scaled_rotation).adjoint();
882 for (decltype(ncells) c =0 ; c < ncells; ++c) {
883 components.col(c) -= centering.col(block[c]);
884 }
885 }
886
887 clean_up_projected(components, variance_explained);
888 if (!options.transpose) {
889 components.adjointInPlace();
890 }
891
892 } else {
893 if (options.scale) {
894 irlba::Scaled<true, decltype(centered), EigenVector_> scaled(centered, scale_v, /* divide = */ true);
895 auto out = irlba::compute(scaled, options.number, components, rotation, variance_explained, options.irlba_options);
896 converged = out.first;
897 } else {
898 auto out = irlba::compute(centered, options.number, components, rotation, variance_explained, options.irlba_options);
899 converged = out.first;
900 }
901
902 if (options.components_from_residuals) {
903 internal::clean_up(mat.ncol(), components, variance_explained);
904 if (options.transpose) {
905 components.adjointInPlace();
906 }
907 } else {
908 EigenMatrix_ tmp;
909 const auto& scaled_rotation = scale_rotation_matrix(rotation, options.scale, scale_v, tmp);
910
911 // This transposes 'components' to be a NDIM * NCELLS matrix.
912 if constexpr(!realize_matrix_) {
913 project_matrix_transposed_tatami(mat, components, scaled_rotation, options.num_threads);
914 } else if constexpr(sparse_) {
915 project_matrix_realized_sparse(emat, components, scaled_rotation, options.num_threads);
916 } else {
917 components.noalias() = (emat * scaled_rotation).adjoint();
918 }
919
920 clean_up_projected(components, variance_explained);
921 if (!options.transpose) {
922 components.adjointInPlace();
923 }
924 }
925 }
926}
927
928}
939template<typename EigenMatrix_, typename EigenVector_>
947 EigenMatrix_ components;
948
954 EigenVector_ variance_explained;
955
960 typename EigenVector_::Scalar total_variance = 0;
961
967 EigenMatrix_ rotation;
968
974 EigenMatrix_ center;
975
980 EigenVector_ scale;
981
985 bool converged = false;
986};
987
1031template<typename Value_, typename Index_, typename Block_, typename EigenMatrix_, class EigenVector_>
1034 auto bdetails = internal::compute_blocking_details<EigenVector_>(mat.ncol(), block, options.block_weight_policy, options.variable_block_weight_parameters);
1035
1036 EigenMatrix_& components = output.components;
1037 EigenMatrix_& rotation = output.rotation;
1038 EigenVector_& variance_explained = output.variance_explained;
1039 EigenMatrix_& center_m = output.center;
1040 EigenVector_& scale_v = output.scale;
1041 auto& total_var = output.total_variance;
1042 bool& converged = output.converged;
1043
1044 if (mat.sparse()) {
1045 if (options.realize_matrix) {
1046 internal::run_blocked<true, true>(mat, block, bdetails, options, components, rotation, variance_explained, center_m, scale_v, total_var, converged);
1047 } else {
1048 internal::run_blocked<false, true>(mat, block, bdetails, options, components, rotation, variance_explained, center_m, scale_v, total_var, converged);
1049 }
1050 } else {
1051 if (options.realize_matrix) {
1052 internal::run_blocked<true, false>(mat, block, bdetails, options, components, rotation, variance_explained, center_m, scale_v, total_var, converged);
1053 } else {
1054 internal::run_blocked<false, false>(mat, block, bdetails, options, components, rotation, variance_explained, center_m, scale_v, total_var, converged);
1055 }
1056 }
1057
1058 if (!options.scale) {
1059 output.scale = EigenVector_();
1060 }
1061}
1062
1082template<typename EigenMatrix_ = Eigen::MatrixXd, class EigenVector_ = Eigen::VectorXd, typename Value_, typename Index_, typename Block_>
1085 blocked_pca(mat, block, options, output);
1086 return output;
1087}
1088
1089}
1090
1091#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
virtual bool prefer_rows() const=0
virtual bool is_sparse() const=0
virtual std::unique_ptr< MyopicSparseExtractor< Value_, Index_ > > sparse(bool row, const Options &opt) const=0
void wrapped_adjoint_multiply(const Matrix_ &matrix, const Right_ &rhs, WrappedAdjointWorkspace< Matrix_ > &work, EigenVector_ &out)
WrappedWorkspace< Matrix_ > wrapped_workspace(const Matrix_ &matrix)
std::pair< bool, int > compute(const Matrix_ &matrix, Eigen::Index number, EigenMatrix_ &outU, EigenMatrix_ &outV, EigenVector_ &outD, const Options &options)
void parallelize(Task_ num_tasks, Run_ run_task)
void wrapped_multiply(const Matrix_ &matrix, const Right_ &rhs, WrappedWorkspace< Matrix_ > &work, EigenVector_ &out)
WrappedAdjointWorkspace< Matrix_ > wrapped_adjoint_workspace(const Matrix_ &matrix)
typename get_adjoint_workspace< Matrix_ >::type WrappedAdjointWorkspace
EigenMatrix_ wrapped_realize(const Matrix_ &matrix)
typename get_workspace< Matrix_ >::type WrappedWorkspace
double compute_variable_weight(double s, const VariableWeightParameters &params)
Principal component analysis on single-cell data.
void blocked_pca(const tatami::Matrix< Value_, Index_ > &mat, const Block_ *block, const BlockedPcaOptions &options, BlockedPcaResults< EigenMatrix_, EigenVector_ > &output)
Definition blocked_pca.hpp:1032
CompressedSparseContents< StoredValue_, StoredIndex_, StoredPointer_ > retrieve_compressed_sparse_contents(const Matrix< InputValue_, InputIndex_ > &matrix, bool row, const RetrieveCompressedSparseContentsOptions &options)
decltype(std::declval< Container_ >().size()) cast_Index_to_container_size(Index_ x)
void parallelize(Function_ fun, Index_ tasks, int threads)
Container_ create_container_of_Index_size(Index_ x, Args_ &&... args)
void convert_to_dense(const Matrix< InputValue_, InputIndex_ > &matrix, bool row_major, StoredValue_ *store, const ConvertToDenseOptions &options)
auto consecutive_extractor(const Matrix< Value_, Index_ > &matrix, bool row, Index_ iter_start, Index_ iter_length, Args_ &&... args)
Options for blocked_pca().
Definition blocked_pca.hpp:30
scran_blocks::VariableWeightParameters variable_block_weight_parameters
Definition blocked_pca.hpp:69
int num_threads
Definition blocked_pca.hpp:88
bool components_from_residuals
Definition blocked_pca.hpp:76
bool realize_matrix
Definition blocked_pca.hpp:82
irlba::Options irlba_options
Definition blocked_pca.hpp:93
bool transpose
Definition blocked_pca.hpp:58
scran_blocks::WeightPolicy block_weight_policy
Definition blocked_pca.hpp:63
int number
Definition blocked_pca.hpp:46
bool scale
Definition blocked_pca.hpp:52
Results of blocked_pca().
Definition blocked_pca.hpp:940
EigenVector_::Scalar total_variance
Definition blocked_pca.hpp:960
bool converged
Definition blocked_pca.hpp:985
EigenMatrix_ components
Definition blocked_pca.hpp:947
EigenMatrix_ rotation
Definition blocked_pca.hpp:967
EigenVector_ scale
Definition blocked_pca.hpp:980
EigenMatrix_ center
Definition blocked_pca.hpp:974
EigenVector_ variance_explained
Definition blocked_pca.hpp:954