Skip to main content

data_beans/sparse_io_vector/
matched.rs

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