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#include <optional>
8
10#include "sanisizer/sanisizer.hpp"
12#include "quickstats/quickstats.hpp"
13
14#include "simple.hpp"
15#include "utils.hpp"
16
22namespace mumosa {
23
52
53/*
54 * We don't apply block-specific scaling factors as we don't want to alter the relative values within the same modality.
55 * 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.
56 * In the simplest case, two blocks have the same subpopulation structure but the number of cells is different.
57 * We would get different distances in each block due to density, causing us to scale each block differently.
58 * More generally, we could expect differences in subpopulation structure between blocks, leading to different distances even in the absence of any batch effects.
59 * (Mind you, differences in subpopulation structure also interfere with accurate scaling between modalities,
60 * but any errors in scaling modalities are much less obvious than those from scaling blocks.)
61 */
62
87template<typename Index_, typename Distance_>
88std::pair<Distance_, Distance_> compute_distance_blocked(const std::vector<std::pair<Index_, Distance_*> >& blocks, const BlockedOptions& options) {
89 const auto nblocks = blocks.size();
90 auto block_weights = sanisizer::create<std::vector<Distance_> >(nblocks);
92 sanisizer::cast<std::size_t>(nblocks),
93 [&](std::size_t b) -> Index_ { return blocks[b].first; },
94 options.block_weight_policy,
96 [&](std::size_t b, Distance_ w) -> void { block_weights[b] = w; }
97 );
98
99 const auto total_weight = [&]{
100 quickstats::PairwiseSumWorkspace<Distance_> pswrk;
101 quickstats::PairwiseSumOptions psopt;
102 return quickstats::pairwise_sum(block_weights.size(), block_weights.data(), pswrk, psopt);
103 }();
104
105 auto outputs = sanisizer::create<std::vector<std::pair<Distance_, Distance_> > >(nblocks);
106 knncolle::parallelize(options.num_threads, nblocks, [&](const int, I<decltype(nblocks)> start, I<decltype(nblocks)> length) -> void {
107 for (I<decltype(nblocks)> b = start, bend = start + length; b < bend; ++b) {
108 const auto curweight = block_weights[b];
109 const auto curdist = compute_distance(blocks[b].first, blocks[b].second);
110 outputs[b].first = curdist.first * curweight;
111 outputs[b].second = curdist.second * curweight;
112 }
113 });
114
115 std::pair<Distance_, Distance_> output{};
116 for (I<decltype(nblocks)> b = 0; b < nblocks; ++b) {
117 output.first += outputs[b].first;
118 output.second += outputs[b].second;
119 }
120
121 if (total_weight) {
122 output.first /= total_weight;
123 output.second /= total_weight;
124 }
125
126 return output;
127}
128
149template<typename Index_, typename Input_, typename Distance_>
150std::pair<Distance_, Distance_> compute_distance_blocked(
151 const std::vector<std::shared_ptr<const knncolle::Prebuilt<Index_, Input_, Distance_> > >& prebuilts,
152 Distance_* const buffer,
153 const BlockedOptions& options
154) {
155 const auto nblocks = prebuilts.size();
156 std::size_t accumulated = 0;
157 std::vector<std::pair<Index_, Distance_*> > blocks;
158 blocks.reserve(nblocks);
159
160 for (I<decltype(nblocks)> b = 0; b < nblocks; ++b) {
161 const auto nobs = prebuilts[b]->num_observations();
162 const auto capped_k = knncolle::cap_k(options.num_neighbors, nobs);
163
164 knncolle::parallelize(options.num_threads, nobs, [&](const int, const Index_ start, const Index_ length) -> void {
165 const auto searcher = prebuilts[b]->initialize();
166 std::vector<Distance_> cur_distances;
167 for (Index_ i = start, end = start + length; i < end; ++i) {
168 searcher->search(i, capped_k, NULL, &cur_distances);
169 if (cur_distances.size()) {
170 buffer[accumulated + i] = cur_distances.back();
171 } else {
172 buffer[accumulated + i] = 0; // i.e., only distance is that to itself.
173 }
174 }
175 });
176
177 blocks.emplace_back(nobs, buffer + accumulated);
178 accumulated += nobs;
179 }
180
181 return compute_distance_blocked(blocks, options);
182}
183
211template<typename Index_, typename Input_, typename Block_, typename Distance_, class Matrix_ = knncolle::Matrix<Index_, Input_> >
212std::pair<Distance_, Distance_> compute_distance_blocked(
213 const std::size_t num_dim,
214 const Index_ num_cells,
215 const Input_* const data,
216 const Block_* const blocks,
217 const std::size_t num_blocks,
219 Distance_* const buffer,
220 const BlockedOptions& options
221) {
222 // Avoiding allocation of a temporary buffer if we're already dealing with contiguous blocks.
223 auto block_details = sanisizer::create<std::vector<std::pair<Index_, Index_> > >(num_blocks);
224 Index_ non_contiguous = 0;
225 for (Index_ c = 0; c < num_cells; ++c) {
226 auto& curblock = block_details[blocks[c]];
227 if (curblock.second == 0) {
228 curblock.first = c;
229 curblock.second = 1;
230 } else {
231 non_contiguous += (c != curblock.first + curblock.second);
232 ++curblock.second;
233 }
234 }
235
236 const Input_* dataptr = data;
237 std::optional<std::vector<Input_> > tmp_data;
238 if (non_contiguous) {
239 // Otherwise, we reorganize the data so that observations from the same batch are in a single block.
240 Index_ accumulated = 0;
241 auto offsets = sanisizer::create<std::vector<Index_> >(num_blocks);
242 for (std::size_t b = 0; b < num_blocks; ++b) {
243 offsets[b] = accumulated;
244 block_details[b].first = accumulated;
245 accumulated += block_details[b].second; // this won't overflow as we already know that num_cells fits in an Index_.
246 }
247
248 tmp_data.emplace(sanisizer::product<typename std::vector<Input_>::size_type>(num_dim, num_cells));
249 for (Index_ c = 0; c < num_cells; ++c) {
250 auto& off = offsets[blocks[c]];
251 std::copy_n(
252 data + sanisizer::product_unsafe<std::size_t>(c, num_dim),
253 num_dim,
254 tmp_data->data() + sanisizer::product_unsafe<std::size_t>(off, num_dim)
255 );
256 ++off;
257 }
258
259 dataptr = tmp_data->data();
260 }
261
262 auto prebuilts = sanisizer::create<std::vector<std::shared_ptr<const knncolle::Prebuilt<Index_, Input_, Distance_> > > >(num_blocks);
263 knncolle::parallelize(options.num_threads, num_blocks, [&](const int, const std::size_t start, const std::size_t length) -> void {
264 for (std::size_t b = start, end = start + length; b < end; ++b) {
265 const auto sofar = block_details[b].first;
266 const auto cursize = block_details[b].second;
267 prebuilts[b] = builder.build_shared(knncolle::SimpleMatrix(num_dim, cursize, dataptr + sanisizer::product_unsafe<std::size_t>(sofar, num_dim)));
268 }
269 });
270
271 return compute_distance_blocked(prebuilts, buffer, options);
272}
273
274}
275
276#endif
void parallelize(int num_workers, Task_ num_tasks, Run_ run_task_range)
int cap_k(int k, Index_ num_observations)
Scale multi-modal embeddings to adjust for differences in variance.
Definition blocked.hpp:22
std::pair< Distance_, Distance_ > compute_distance_blocked(const std::vector< std::pair< Index_, Distance_ * > > &blocks, const BlockedOptions &options)
Definition blocked.hpp:88
void compute_weights(const std::size_t num_blocks, GetBlockSize_ get_block_size, const WeightPolicy policy, const VariableWeightParameters &variable, SetBlockWeight_ set_block_weight)
Compute distances to nearest neighbors.
Options for compute_distance_blocked().
Definition blocked.hpp:27
scran_blocks::VariableWeightParameters variable_block_weight_parameters
Definition blocked.hpp:44
scran_blocks::WeightPolicy block_weight_policy
Definition blocked.hpp:38
int num_threads
Definition blocked.hpp:50
int num_neighbors
Definition blocked.hpp:33