mumosa
Multi-modal analyses of single-cell data
Loading...
Searching...
No Matches
blocked.hpp
Go to the documentation of this file.
1#ifndef MUMOSA_BLOCKED_HPP
2#define MUMOSA_BLOCKED_HPP
3
4#include <vector>
5#include <algorithm>
6#include <cstddef>
7
9#include "sanisizer/sanisizer.hpp"
11#include "quickstats/quickstats.hpp"
12
13#include "simple.hpp"
14#include "utils.hpp"
15
21namespace mumosa {
22
51
59template<typename Distance_>
64 std::vector<Distance_> weights;
65 Distance_ total_weight;
66
67 std::vector<Distance_> distance_buffer;
71};
72
82template<typename Distance_, typename Index_>
83BlockedWorkspace<Distance_> create_workspace(const std::vector<Index_>& block_sizes, const BlockedOptions& options) {
86
87 quickstats::PairwiseSumWorkspace<Distance_> pswrk;
88 quickstats::PairwiseSumOptions psopt;
89 output.total_weight = quickstats::pairwise_sum(output.weights.size(), output.weights.data(), pswrk, psopt);
90
91 Index_ max_size = 0;
92 if (block_sizes.size()) {
93 max_size = *std::max_element(block_sizes.begin(), block_sizes.end());
94 }
95 sanisizer::resize(output.distance_buffer, max_size);
96
97 return output;
98}
99
100/*
101 * We don't apply block-specific scaling factors as we don't want to alter the relative values within the same modality.
102 * We shouldn't have to do it in the first place - as it's the same modality! - but more importantly, we could introduce spurious differences between blocks.
103 * In the simplest case, two blocks have the same subpopulation structure but the number of cells is different.
104 * We would get different distances in each block due to density, causing us to scale each block differently.
105 * More generally, we could expect differences in subpopulation structure between blocks, leading to different distances even in the absence of any batch effects.
106 * (Mind you, differences in subpopulation structure also interfere with accurate scaling between modalities,
107 * but any errors in scaling modalities are much less obvious than those from scaling blocks.)
108 */
109
137template<typename Index_, typename Input_, typename Distance_>
138std::pair<Distance_, Distance_> compute_distance_blocked(
139 const std::vector<std::shared_ptr<const knncolle::Prebuilt<Index_, Input_, Distance_> > >& prebuilts,
141 const BlockedOptions& options
142) {
143 Options simple_opt;
144 simple_opt.num_neighbors = options.num_neighbors;
145 simple_opt.num_threads = options.num_threads;
146
147 std::pair<Distance_, Distance_> output(0, 0);
148
149 const auto nblocks = prebuilts.size();
150 for (I<decltype(nblocks)> b = 0; b < nblocks; ++b) {
151 const auto curweight = workspace.weights[b];
152 const auto& pbptr = prebuilts[b];
153 if (curweight && pbptr && pbptr->num_observations()) {
154 const auto curdist = compute_distance(*pbptr, workspace.distance_buffer.data(), simple_opt);
155 output.first += curdist.first * curweight;
156 output.second += curdist.second * curweight;
157 }
158 }
159
160 if (workspace.total_weight) {
161 output.first /= workspace.total_weight;
162 output.second /= workspace.total_weight;
163 }
164
165 return output;
166}
167
190template<typename Index_, typename Input_, typename Distance_, class Matrix_ = knncolle::Matrix<Index_, Input_> >
191std::vector<std::shared_ptr<const knncolle::Prebuilt<Index_, Input_, Distance_> > > build_blocked_indices(
192 const std::size_t num_dim,
193 const std::vector<Index_> block_sizes,
194 const Input_* const data,
196) {
197 const auto num_blocks = block_sizes.size();
198 auto prebuilts = sanisizer::create<std::vector<std::shared_ptr<const knncolle::Prebuilt<Index_, Input_, Distance_> > > >(num_blocks);
199
200 Index_ sofar = 0;
201 for (I<decltype(num_blocks)> b = 0; b < num_blocks; ++b) {
202 const auto cursize = block_sizes[b];
203 if (cursize) {
204 prebuilts[b] = builder.build_shared(knncolle::SimpleMatrix(num_dim, cursize, data + sanisizer::product_unsafe<std::size_t>(sofar, num_dim)));
205 }
206 sofar += cursize;
207 }
208
209 return prebuilts;
210}
211
233template<typename Index_, typename Input_, typename Distance_, class Matrix_ = knncolle::Matrix<Index_, Input_> >
234std::pair<Distance_, Distance_> compute_distance_blocked(
235 const std::size_t num_dim,
236 const std::vector<Index_>& block_sizes,
237 const Input_* const data,
239 const BlockedOptions& options
240) {
241 const auto prebuilts = build_blocked_indices(num_dim, block_sizes, data, builder);
242 auto workspace = create_workspace<Distance_>(block_sizes, options);
243 return compute_distance_blocked(prebuilts, workspace, options);
244}
245
255template<typename Index_, typename Block_>
257private:
258 Index_ my_num_cells;
259 const Block_* my_blocks;
260 Block_ my_num_blocks = 0;
261 std::vector<Index_> my_block_sizes;
262
263 std::vector<std::pair<Index_, Index_> > my_contigs;
264 Index_ my_non_contig_total = 0;
265 std::vector<Index_> my_non_contig_offsets;
266
267public:
276 const Index_ num_cells,
277 const Block_* blocks,
278 const std::size_t num_blocks
279 ) :
280 my_num_cells(num_cells),
281 my_blocks(blocks),
282 my_num_blocks(num_blocks)
283 {
284 sanisizer::resize(my_block_sizes, my_num_blocks);
285 auto block_non_contig = sanisizer::create<std::vector<char> >(my_num_blocks);
286 auto& block_ends = my_non_contig_offsets; // repurposing the offset vector to store the end of each contiguous block.
287 sanisizer::resize(block_ends, my_num_blocks);
288
289 for (Index_ c = 0; c < my_num_cells; ++c) {
290 const auto curb = my_blocks[c];
291 my_block_sizes[curb] += 1;
292
293 auto& nc = block_non_contig[curb];
294 if (!nc) {
295 auto& be = block_ends[curb];
296 if (be == 0) {
297 be = c + 1;
298 } else if (be == c) {
299 ++be;
300 } else {
301 nc = true;
302 }
303 }
304 }
305
306 sanisizer::resize(my_contigs, my_num_blocks);
307
308 for (Block_ b = 0; b < my_num_blocks; ++b) {
309 const auto length = my_block_sizes[b];
310 if (block_non_contig[b]) {
311 my_non_contig_offsets[b] = my_non_contig_total;
312 my_non_contig_total += length;
313 } else if (length) {
314 const auto start = block_ends[b] - length;
315 my_contigs[b] = std::make_pair(start, length);
316 }
317 }
318 }
319
320public:
325 const std::vector<Index_>& sizes() const {
326 return my_block_sizes;
327 }
328
333 template<typename Input_>
334 struct Buffers {
338 std::vector<Index_> tmp_offsets;
339 std::vector<Input_> tmp_buffer;
343 };
344
348 template<typename Input_>
350 return Buffers<Input_>();
351 }
352
353public:
370 template<typename Input_, typename Distance_, class Matrix_ = knncolle::Matrix<Index_, Input_> >
371 void build(
372 const std::size_t num_dim,
373 const Input_* const data,
375 std::vector<std::shared_ptr<const knncolle::Prebuilt<Index_, Input_, Distance_> > >& output,
376 Buffers<Input_>& work
377 ) const {
378 output.clear();
379 sanisizer::resize(output, my_num_blocks);
380
381 for (Block_ b = 0; b < my_num_blocks; ++b) {
382 const auto& con = my_contigs[b];
383 if (con.second) {
384 const auto ptr = data + sanisizer::product_unsafe<std::size_t>(con.first, num_dim);
385 output[b] = builder.build_shared(knncolle::SimpleMatrix(num_dim, con.second, ptr));
386 }
387 }
388
389 if (my_non_contig_total) {
390 work.tmp_buffer.resize(sanisizer::product<I<decltype(work.tmp_buffer.size())> >(my_non_contig_total, num_dim));
391 work.tmp_offsets.clear();
392 work.tmp_offsets.insert(work.tmp_offsets.end(), my_non_contig_offsets.begin(), my_non_contig_offsets.end());
393
394 Index_ c = 0;
395 while (c < my_num_cells) {
396 const auto curb = my_blocks[c];
397 const auto& con = my_contigs[curb];
398 if (con.second) {
399 c += con.second; // skip past the contiguous stretch of observations.
400 } else {
401 auto& curoff = work.tmp_offsets[curb];
402 std::copy_n(
403 data + sanisizer::product_unsafe<std::size_t>(c, num_dim),
404 num_dim,
405 work.tmp_buffer.data() + sanisizer::product_unsafe<std::size_t>(curoff, num_dim)
406 );
407 ++curoff;
408 ++c;
409 }
410 }
411
412 for (Block_ b = 0; b < my_num_blocks; ++b) {
413 if (my_contigs[b].second == 0) {
414 const auto length = my_block_sizes[b];
415 const auto ptr = work.tmp_buffer.data() + sanisizer::product_unsafe<std::size_t>(my_non_contig_offsets[b], num_dim);
416 output[b] = builder.build_shared(knncolle::SimpleMatrix(num_dim, length, ptr));
417 }
418 }
419 }
420 }
421
439 template<typename Input_, typename Distance_, class Matrix_ = knncolle::Matrix<Index_, Input_> >
440 std::vector<std::shared_ptr<const knncolle::Prebuilt<Index_, Input_, Distance_> > > build(
441 const std::size_t num_dim,
442 const Input_* const data,
444 ) const {
445 std::vector<std::shared_ptr<const knncolle::Prebuilt<Index_, Input_, Distance_> > > prebuilts;
446 auto bufs = create_buffers<Input_>();
447 build(num_dim, data, builder, prebuilts, bufs);
448 return prebuilts;
449 }
450};
451
476template<typename Index_, typename Input_, typename Block_, typename Distance_, class Matrix_ = knncolle::Matrix<Index_, Input_> >
477std::pair<Distance_, Distance_> compute_distance_blocked(
478 const std::size_t num_dim,
479 const Index_ num_cells,
480 const Input_* const data,
481 const Block_* const blocks,
482 const std::size_t num_blocks,
484 const BlockedOptions& options
485) {
486 BlockedIndicesFactory<Index_, Block_> blocked_factory(num_cells, blocks, num_blocks);
487 const auto prebuilts = blocked_factory.build(num_dim, data, builder);
488 auto workspace = create_workspace<Distance_>(blocked_factory.sizes(), options);
489 return compute_distance_blocked(prebuilts, workspace, options);
490}
491
492}
493
494#endif
std::shared_ptr< Prebuilt< Index_, Data_, Distance_ > > build_shared(const Matrix_ &data) const
Factory for creating nearest-neighbor search indices for each block.
Definition blocked.hpp:256
void build(const std::size_t num_dim, const Input_ *const data, const knncolle::Builder< Index_, Input_, Distance_, Matrix_ > &builder, std::vector< std::shared_ptr< const knncolle::Prebuilt< Index_, Input_, Distance_ > > > &output, Buffers< Input_ > &work) const
Definition blocked.hpp:371
BlockedIndicesFactory(const Index_ num_cells, const Block_ *blocks, const std::size_t num_blocks)
Definition blocked.hpp:275
const std::vector< Index_ > & sizes() const
Definition blocked.hpp:325
Buffers< Input_ > create_buffers() const
Definition blocked.hpp:349
std::vector< std::shared_ptr< const knncolle::Prebuilt< Index_, Input_, Distance_ > > > build(const std::size_t num_dim, const Input_ *const data, const knncolle::Builder< Index_, Input_, Distance_, Matrix_ > &builder) const
Definition blocked.hpp:440
Scale multi-modal embeddings to adjust for differences in variance.
Definition blocked.hpp:21
std::pair< Distance_, Distance_ > compute_distance_blocked(const std::vector< std::shared_ptr< const knncolle::Prebuilt< Index_, Input_, Distance_ > > > &prebuilts, BlockedWorkspace< Distance_ > &workspace, const BlockedOptions &options)
Definition blocked.hpp:138
std::pair< Distance_, Distance_ > compute_distance(const Index_ num_cells, Distance_ *const distances)
Definition simple.hpp:59
std::vector< std::shared_ptr< const knncolle::Prebuilt< Index_, Input_, Distance_ > > > build_blocked_indices(const std::size_t num_dim, const std::vector< Index_ > block_sizes, const Input_ *const data, const knncolle::Builder< Index_, Input_, Distance_, Matrix_ > &builder)
Definition blocked.hpp:191
BlockedWorkspace< Distance_ > create_workspace(const std::vector< Index_ > &block_sizes, const BlockedOptions &options)
Definition blocked.hpp:83
void compute_weights(const std::size_t num_blocks, const Size_ *const sizes, const WeightPolicy policy, const VariableWeightParameters &variable, Weight_ *const weights)
Compute distances to nearest neighbors.
Temporary buffers for build().
Definition blocked.hpp:334
Options for compute_distance_blocked().
Definition blocked.hpp:26
scran_blocks::VariableWeightParameters variable_block_weight_parameters
Definition blocked.hpp:43
scran_blocks::WeightPolicy block_weight_policy
Definition blocked.hpp:37
int num_threads
Definition blocked.hpp:49
int num_neighbors
Definition blocked.hpp:32
Workspace for compute_distance_blocked().
Definition blocked.hpp:60
Options for compute_distance().
Definition simple.hpp:28
int num_threads
Definition simple.hpp:40
int num_neighbors
Definition simple.hpp:34