Skip to main content

data_beans/sparse_io_vector/
matched.rs

1#![allow(dead_code)]
2
3use super::*;
4
5/// A matrix of matched columns with, per column: the source (query) column it
6/// was matched for, its own global column index, and their distance.
7pub type MatchedColumnsCsc = (CscMatrix<f32>, Vec<usize>, Vec<usize>, Vec<f32>);
8
9impl SparseIoVec {
10    /////////////////////
11    // matched columns //
12    /////////////////////
13
14    /// Take columns matched with the given `cells` on a specific
15    /// target batch
16    ///
17    /// # Arguments
18    /// * `cells` - global column indices
19    /// * `target_batch` - a batch for targeted kNN search
20    /// * `knn` - k-nearest neighbours
21    /// * `skip_same_batch` - skip the same batch
22    ///
23    /// # Returns
24    /// * shape - (nrows, ncols)
25    /// * triplets
26    /// * distances
27    fn matched_columns_triplets_on_one_target<I>(
28        &self,
29        cells: I,
30        target_batch: usize,
31        knn: usize,
32        skip_same_batch: bool,
33    ) -> anyhow::Result<TripletsMatched>
34    where
35        I: Iterator<Item = usize> + Clone,
36    {
37        let lookups = self
38            .derived
39            .batch_knn_lookup
40            .as_ref()
41            .ok_or(anyhow::anyhow!("no knn lookup"))?;
42
43        let cell_to_batch = self
44            .derived
45            .col_to_batch
46            .as_ref()
47            .ok_or(anyhow::anyhow!("no cell to batch"))?;
48
49        debug_assert!(target_batch < self.num_batches());
50
51        let mut distances = Vec::new();
52        let mut source_columns = Vec::new();
53        let mut matched_columns = Vec::new();
54
55        for glob in cells {
56            let source_batch = cell_to_batch[glob]; // this cell's batch
57
58            if skip_same_batch && source_batch == target_batch {
59                continue; // skip cells in the same batch
60            }
61
62            if let (Some(source_lookup), Some(target_lookup)) =
63                (lookups.get(source_batch), lookups.get(target_batch))
64            {
65                let (matched, matched_distances) =
66                    source_lookup.match_by_query_name_against(&glob, knn, target_lookup)?;
67                for (glob_matched, dist) in matched.into_iter().zip(matched_distances) {
68                    if glob == glob_matched {
69                        continue; // avoid identical cell pairs
70                    }
71                    source_columns.push(glob);
72                    matched_columns.push(glob_matched);
73                    distances.push(dist);
74                }
75            }
76        }
77
78        self.read_matched(source_columns, matched_columns, distances)
79    }
80
81    /// Read the `matched_columns` in one batched pass: output column `k`
82    /// holds global column `matched_columns[k]`, repeats included. One read
83    /// per backend lets it coalesce ranges and reuse decoded chunks, where a
84    /// read per matched column would decode the same chunks again and again.
85    ///
86    /// `pub(super)` so the tests of sibling modules can read through it.
87    pub(super) fn read_matched(
88        &self,
89        source_columns: Vec<usize>,
90        matched_columns: Vec<usize>,
91        distances: Vec<f32>,
92    ) -> anyhow::Result<TripletsMatched> {
93        let (shape, triplets) = self.columns_triplets(matched_columns.iter().copied())?;
94        Ok(TripletsMatched {
95            shape,
96            triplets,
97            source_columns,
98            matched_columns,
99            distances,
100        })
101    }
102
103    /// Take columns matched with the given `cells`
104    ///
105    /// # Arguments
106    /// * `cells` - global column indices
107    /// * `target_batches` - the batches for targeted kNN search
108    /// * `knn_columns` - k-nearest neighbours of columns
109    /// * `skip_same_batch` - skip the same batch
110    ///
111    /// # Returns
112    /// * shape - (nrows, ncols)
113    /// * triplets - a vector of triplets
114    /// * `source_columns` - a vector of the source columns
115    /// * distance - a vector of distances between the matched columns
116    fn matched_columns_triplets<I>(
117        &self,
118        cells: I,
119        target_batches: &[usize],
120        knn_columns: usize,
121        skip_same_batch: bool,
122    ) -> anyhow::Result<TripletsMatched>
123    where
124        I: Iterator<Item = usize>,
125    {
126        let cells: Vec<usize> = cells.collect();
127
128        let nrows = self.num_rows();
129        let nbatches = self.num_batches();
130        let ncols = cells.len();
131        let approx_ncols = ncols * knn_columns * nbatches;
132
133        let mut tot_triplets: Vec<(u64, u64, f32)> = Vec::with_capacity(approx_ncols);
134        let mut tot_distances: Vec<f32> = Vec::with_capacity(approx_ncols);
135        let mut tot_sources: Vec<usize> = Vec::with_capacity(approx_ncols);
136        let mut tot_matched: Vec<usize> = Vec::with_capacity(approx_ncols);
137        let mut tot_ncells_matched: usize = 0;
138
139        for &target_b in target_batches.iter() {
140            let TripletsMatched {
141                shape,
142                triplets,
143                source_columns,
144                matched_columns,
145                distances,
146            } = self.matched_columns_triplets_on_one_target(
147                cells.iter().cloned(),
148                target_b,
149                knn_columns,
150                skip_same_batch,
151            )?;
152
153            tot_triplets.extend(
154                triplets
155                    .into_iter()
156                    .map(|(i, j, z_ij)| (i, j + (tot_ncells_matched as u64), z_ij)),
157            );
158
159            tot_distances.extend(distances);
160            tot_ncells_matched += shape.1;
161            tot_sources.extend(source_columns);
162            tot_matched.extend(matched_columns);
163        }
164
165        let shape = (nrows, tot_ncells_matched);
166
167        Ok(TripletsMatched {
168            shape,
169            triplets: tot_triplets,
170            source_columns: tot_sources,
171            matched_columns: tot_matched,
172            distances: tot_distances,
173        })
174    }
175
176    /// Take columns with the neighbourhood of given `cells`
177    ///
178    /// # Arguments
179    /// * `cells` - global column indices
180    /// * `knn_batches` - k-nearest neighbour batches
181    /// * `knn_columns` - k-nearest neighbour columns
182    /// * `skip_same_batch` - skip the same batch
183    ///
184    /// # Returns
185    /// * shape - (nrows, ncols)
186    /// * triplets
187    /// * source positions
188    /// * distances
189    fn neighbouring_columns_triplets<I>(
190        &self,
191        cells: I,
192        knn_batches: usize,
193        knn_columns: usize,
194        skip_same_batch: bool,
195        skip_batches: Option<&[usize]>,
196    ) -> anyhow::Result<TripletsMatched>
197    where
198        I: Iterator<Item = usize>,
199    {
200        let lookups = self
201            .derived
202            .batch_knn_lookup
203            .as_ref()
204            .ok_or(anyhow::anyhow!("no knn lookup"))?;
205
206        let cell_to_batch = self
207            .derived
208            .col_to_batch
209            .as_ref()
210            .ok_or(anyhow::anyhow!("no cell to batch"))?;
211
212        let approx_ncol = knn_columns * knn_batches;
213
214        let mut distances = Vec::with_capacity(approx_ncol);
215        let mut source_columns = Vec::with_capacity(approx_ncol);
216        let mut matched_columns = Vec::with_capacity(approx_ncol);
217
218        let nbatches = self.num_batches();
219
220        // Pre-compute neighbouring batches per source batch
221        let neighbouring_batches_by_source: Vec<Vec<usize>> = (0..nbatches)
222            .map(
223                |source_batch| match self.derived.between_batch_proximity.as_ref() {
224                    Some(prox) => prox[source_batch]
225                        .iter()
226                        .copied()
227                        .filter(|&b| skip_batches.is_none_or(|skip| !skip.contains(&b)))
228                        .filter(|&b| !skip_same_batch || b != source_batch)
229                        .collect(),
230                    _ => (0..nbatches)
231                        .filter(|&b| skip_batches.is_none_or(|skip| !skip.contains(&b)))
232                        .filter(|&b| !skip_same_batch || b != source_batch)
233                        .collect(),
234                },
235            )
236            .collect();
237
238        for glob_index in cells {
239            let source_batch = cell_to_batch[glob_index];
240
241            for &target_batch in &neighbouring_batches_by_source[source_batch] {
242                if let (Some(source_lookup), Some(target_lookup)) =
243                    (lookups.get(source_batch), lookups.get(target_batch))
244                {
245                    let (matched, matched_distances) = source_lookup.match_by_query_name_against(
246                        &glob_index,
247                        knn_columns,
248                        target_lookup,
249                    )?;
250                    for (glob_matched_index, dist) in matched.into_iter().zip(matched_distances) {
251                        if glob_index == glob_matched_index {
252                            continue;
253                        }
254                        source_columns.push(glob_index);
255                        matched_columns.push(glob_matched_index);
256                        distances.push(dist);
257                    }
258                }
259            }
260        }
261
262        self.read_matched(source_columns, matched_columns, distances)
263    }
264
265    fn query_columns_by_data_triplets<T>(
266        &self,
267        query: T,
268        knn_per_batch: usize,
269    ) -> anyhow::Result<TripletsMatched>
270    where
271        T: MakeVecPoint,
272    {
273        let lookups = self
274            .derived
275            .batch_knn_lookup
276            .as_ref()
277            .ok_or(anyhow::anyhow!("no knn lookup"))?;
278
279        let approx_knn = self.num_batches() * knn_per_batch;
280        let mut matched_columns = Vec::with_capacity(approx_knn);
281        let mut distances = Vec::with_capacity(approx_knn);
282
283        let q = query.to_vp();
284        for lookup in lookups {
285            let (matched, matched_distances) =
286                lookup.search_by_query_data(q.as_slice(), knn_per_batch)?;
287
288            for (&glob_idx, &dist) in matched.iter().zip(matched_distances.iter()) {
289                matched_columns.push(glob_idx);
290                distances.push(dist);
291            }
292        }
293
294        let sources = matched_columns.clone();
295        self.read_matched(sources, matched_columns, distances)
296    }
297
298    /// Take columns within the neighbourhood of given `cells`
299    ///
300    /// # Arguments
301    /// * `cells` - global column indices
302    /// * `target_batches` - the batches for targeted kNN search
303    /// * `knn_batches` - k-nearest neighbour batches
304    /// * `knn_columns` - k-nearest neighbour columns
305    /// * `skip_same_batch` - skip the same batch
306    ///
307    /// # Returns
308    /// * the knn-matched matrix
309    /// * `source_columns` - a vector of the source columns
310    /// * a vector of distances between the matched columns
311    #[allow(clippy::type_complexity)]
312    pub fn read_neighbouring_columns_csc<I>(
313        &self,
314        cells: I,
315        knn_batches: usize,
316        knn_columns: usize,
317        skip_same_batch: bool,
318        skip_batches: Option<&[usize]>,
319    ) -> anyhow::Result<(CscMatrix<f32>, Vec<usize>, Vec<usize>, Vec<f32>)>
320    where
321        I: Iterator<Item = usize>,
322    {
323        let TripletsMatched {
324            shape: (nrow, ncol),
325            triplets,
326            source_columns,
327            matched_columns,
328            distances,
329        } = self.neighbouring_columns_triplets(
330            cells,
331            knn_batches,
332            knn_columns,
333            skip_same_batch,
334            skip_batches,
335        )?;
336
337        Ok((
338            CscMatrix::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)?,
339            source_columns,
340            matched_columns,
341            distances,
342        ))
343    }
344
345    #[cfg(feature = "ndarray")]
346    /// Take columns neighbouring with the given `cells`
347    ///
348    /// # Arguments
349    /// * `cells` - global column indices
350    /// * `target_batches` - the batches for targeted kNN search
351    /// * `knn` - k-nearest neighbours
352    /// * `skip_same_batch` - skip the same batch
353    ///
354    /// # Returns
355    /// * the knn-neighbouring matrix
356    /// * `source_columns` - a vector of the source columns
357    /// * distances - a vector of distances between the neighbouring columns
358    pub fn read_neighbouring_columns_ndarray<I>(
359        &self,
360        cells: I,
361        knn_batches: usize,
362        knn_columns: usize,
363        skip_same_batch: bool,
364        skip_batches: Option<&[usize]>,
365    ) -> anyhow::Result<(ndarray::Array2<f32>, Vec<usize>, Vec<f32>)>
366    where
367        I: Iterator<Item = usize>,
368    {
369        let TripletsMatched {
370            shape: (nrow, ncol),
371            triplets,
372            source_columns,
373            distances,
374            ..
375        } = self.neighbouring_columns_triplets(
376            cells,
377            knn_batches,
378            knn_columns,
379            skip_same_batch,
380            skip_batches,
381        )?;
382        Ok((
383            ndarray::Array2::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)?,
384            source_columns,
385            distances,
386        ))
387    }
388
389    /// Take columns neighbouring with the given `cells`
390    ///
391    /// # Arguments
392    /// * `cells` - global column indices
393    /// * `target_batches` - the batches for targeted kNN search
394    /// * `knn` - k-nearest neighbours
395    /// * `skip_same_batch` - skip the same batch
396    ///
397    /// # Returns
398    /// * the knn-neighbouring matrix
399    /// * `source_columns` - a vector of the source columns
400    /// * distances - a vector of distances between the neighbouring columns
401    pub fn read_neighbouring_columns_dmatrix<I>(
402        &self,
403        cells: I,
404        knn_batches: usize,
405        knn_columns: usize,
406        skip_same_batch: bool,
407        skip_batches: Option<&[usize]>,
408    ) -> anyhow::Result<(nalgebra::DMatrix<f32>, Vec<usize>, Vec<f32>)>
409    where
410        I: Iterator<Item = usize>,
411    {
412        let TripletsMatched {
413            shape: (nrow, ncol),
414            triplets,
415            source_columns,
416            distances,
417            ..
418        } = self.neighbouring_columns_triplets(
419            cells,
420            knn_batches,
421            knn_columns,
422            skip_same_batch,
423            skip_batches,
424        )?;
425        Ok((
426            DMatrix::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)?,
427            source_columns,
428            distances,
429        ))
430    }
431
432    /// Take columns matched with the given `cells`
433    ///
434    /// # Arguments
435    /// * `cells` - global column indices
436    /// * `target_batches` - the batches for targeted kNN search
437    /// * `knn` - k-nearest neighbours
438    /// * `skip_same_batch` - skip the same batch
439    ///
440    /// # Returns
441    /// * the knn-matched matrix
442    /// * `source_columns` - a vector of the source columns
443    /// * a vector of distances between the matched columns
444    pub fn read_matched_columns_csc<I>(
445        &self,
446        cells: I,
447        target_batches: &[usize],
448        knn: usize,
449        skip_same_batch: bool,
450    ) -> anyhow::Result<(CscMatrix<f32>, Vec<usize>, Vec<f32>)>
451    where
452        I: Iterator<Item = usize>,
453    {
454        let (matrix, source_columns, _, distances) = self.read_matched_columns_with_targets_csc(
455            cells,
456            target_batches,
457            knn,
458            skip_same_batch,
459        )?;
460        Ok((matrix, source_columns, distances))
461    }
462
463    /// [`Self::read_matched_columns_csc`], also naming the matched column
464    /// behind each column of the matrix.
465    ///
466    /// # Returns
467    /// * the knn-matched matrix
468    /// * `source_columns` - the source (query) column of each matched column
469    /// * `matched_columns` - the global index of each matched column
470    /// * a vector of distances between the matched columns
471    pub fn read_matched_columns_with_targets_csc<I>(
472        &self,
473        cells: I,
474        target_batches: &[usize],
475        knn: usize,
476        skip_same_batch: bool,
477    ) -> anyhow::Result<MatchedColumnsCsc>
478    where
479        I: Iterator<Item = usize>,
480    {
481        let TripletsMatched {
482            shape: (nrow, ncol),
483            triplets,
484            source_columns,
485            matched_columns,
486            distances,
487        } = self.matched_columns_triplets(cells, target_batches, knn, skip_same_batch)?;
488        Ok((
489            CscMatrix::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)?,
490            source_columns,
491            matched_columns,
492            distances,
493        ))
494    }
495
496    #[cfg(feature = "ndarray")]
497    /// Take columns matched with the given `cells`
498    ///
499    /// # Arguments
500    /// * `cells` - global column indices
501    /// * `target_batches` - the batches for targeted kNN search
502    /// * `knn` - k-nearest neighbours
503    /// * `skip_same_batch` - skip the same batch
504    ///
505    /// # Returns
506    /// * the knn-matched matrix
507    /// * `source_columns` - a vector of the source columns
508    /// * distances - a vector of distances between the matched columns
509    pub fn read_matched_columns_ndarray<I>(
510        &self,
511        cells: I,
512        target_batches: &[usize],
513        knn: usize,
514        skip_same_batch: bool,
515    ) -> anyhow::Result<(ndarray::Array2<f32>, Vec<usize>, Vec<f32>)>
516    where
517        I: Iterator<Item = usize>,
518    {
519        let TripletsMatched {
520            shape: (nrow, ncol),
521            triplets,
522            source_columns,
523            distances,
524            ..
525        } = self.matched_columns_triplets(cells, target_batches, knn, skip_same_batch)?;
526
527        Ok((
528            ndarray::Array2::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)?,
529            source_columns,
530            distances,
531        ))
532    }
533
534    /// Take columns matched with the given `cells`
535    ///
536    /// # Arguments
537    /// * `cells` - global column indices
538    /// * `target_batches` - the batches for targeted kNN search
539    /// * `knn` - k-nearest neighbours
540    /// * `skip_same_batch` - skip the same batch
541    ///
542    /// # Returns
543    /// * the knn-matched matrix
544    /// * `source_columns` - a vector of the source columns
545    /// * distances - a vector of distances between the matched columns
546    pub fn read_matched_columns_dmatrix<I>(
547        &self,
548        cells: I,
549        target_batches: &[usize],
550        knn: usize,
551        skip_same_batch: bool,
552    ) -> anyhow::Result<(nalgebra::DMatrix<f32>, Vec<usize>, Vec<f32>)>
553    where
554        I: Iterator<Item = usize>,
555    {
556        let TripletsMatched {
557            shape: (nrow, ncol),
558            triplets,
559            source_columns,
560            distances,
561            ..
562        } = self.matched_columns_triplets(cells, target_batches, knn, skip_same_batch)?;
563
564        Ok((
565            DMatrix::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)?,
566            source_columns,
567            distances,
568        ))
569    }
570
571    /// Query columns with projection data
572    ///
573    /// # Arguments
574    /// * `query` - a data vector
575    /// * `knn_per_batch` - k-nearest neighbour columns per batch
576    ///
577    /// # Returns
578    /// * the knn-matched matrix
579    /// * `source_columns` - a vector of the source columns
580    /// * a vector of distances between the matched columns
581    pub fn query_columns_by_data_csc<T>(
582        &self,
583        query: T,
584        knn_per_batch: usize,
585    ) -> anyhow::Result<(CscMatrix<f32>, Vec<usize>, Vec<f32>)>
586    where
587        T: MakeVecPoint,
588    {
589        let TripletsMatched {
590            shape: (nrow, ncol),
591            triplets,
592            source_columns,
593            distances,
594            ..
595        } = self.query_columns_by_data_triplets(query, knn_per_batch)?;
596
597        Ok((
598            CscMatrix::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)?,
599            source_columns,
600            distances,
601        ))
602    }
603
604    #[cfg(feature = "ndarray")]
605    /// Query columns with projection data
606    ///
607    /// # Arguments
608    /// * `query` - a data vector
609    /// * `knn_per_batch` - k-nearest neighbour columns per batch
610    ///
611    /// # Returns
612    /// * the knn-matched matrix
613    /// * `source_columns` - a vector of the source columns
614    /// * a vector of distances between the matched columns
615    pub fn query_columns_by_data_ndarray<T>(
616        &self,
617        query: T,
618        knn_per_batch: usize,
619    ) -> anyhow::Result<(ndarray::Array2<f32>, Vec<usize>, Vec<f32>)>
620    where
621        T: MakeVecPoint,
622    {
623        let TripletsMatched {
624            shape: (nrow, ncol),
625            triplets,
626            source_columns,
627            distances,
628            ..
629        } = self.query_columns_by_data_triplets(query, knn_per_batch)?;
630
631        Ok((
632            ndarray::Array2::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)?,
633            source_columns,
634            distances,
635        ))
636    }
637
638    /// Query columns with projection data
639    ///
640    /// # Arguments
641    /// * `query` - a data vector
642    /// * `knn_per_batch` - k-nearest neighbour columns per batch
643    ///
644    /// # Returns
645    /// * the knn-matched matrix
646    /// * `source_columns` - a vector of the source columns
647    /// * a vector of distances between the matched columns
648    pub fn query_columns_by_data_dmatrix<T>(
649        &self,
650        query: T,
651        knn_per_batch: usize,
652    ) -> anyhow::Result<(nalgebra::DMatrix<f32>, Vec<usize>, Vec<f32>)>
653    where
654        T: MakeVecPoint,
655    {
656        let TripletsMatched {
657            shape: (nrow, ncol),
658            triplets,
659            source_columns,
660            distances,
661            ..
662        } = self.query_columns_by_data_triplets(query, knn_per_batch)?;
663
664        Ok((
665            DMatrix::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)?,
666            source_columns,
667            distances,
668        ))
669    }
670}