Skip to main content

flow_knn/
graph.rs

1//! Portable k-NN graph artifact.
2
3use crate::config::DistanceMetric;
4use crate::error::KnnError;
5
6/// Neighbours of a single query point.
7#[derive(Debug, Clone)]
8pub struct NeighborList {
9    /// Indices of k nearest neighbours (excluding self), ascending distance order.
10    pub indices: Vec<u32>,
11    /// Distances corresponding to each index.
12    pub distances: Vec<f32>,
13}
14
15/// Portable k-nearest-neighbour graph: per-point indices and distances only.
16#[derive(Debug, Clone)]
17pub struct KnnGraph {
18    pub neighbors: Vec<NeighborList>,
19    pub n: usize,
20    pub k: usize,
21    pub metric: DistanceMetric,
22    pub provenance: Option<String>,
23}
24
25impl KnnGraph {
26    /// Minimum `k` for PaCMAP mid-near candidate window: `min(n_neighbors + 50, n − 1)`.
27    pub fn required_k_for_pacmap(n: usize, n_neighbors: usize) -> usize {
28        let n_nb = n_neighbors.min(n.saturating_sub(1));
29        (n_nb + 50).min(n.saturating_sub(1))
30    }
31
32    /// Validate graph shape / metric for a consumer that needs `required_k` neighbours.
33    pub fn validate(
34        &self,
35        data_n: usize,
36        required_k: usize,
37        metric: DistanceMetric,
38    ) -> Result<(), KnnError> {
39        if self.n != data_n || self.neighbors.len() != data_n {
40            return Err(KnnError::GraphSizeMismatch {
41                graph_n: self.n,
42                neighbors_len: self.neighbors.len(),
43                data_n,
44            });
45        }
46        if self.k < required_k {
47            return Err(KnnError::GraphInsufficientK {
48                graph_k: self.k,
49                required_k,
50            });
51        }
52        if self.metric != metric {
53            return Err(KnnError::GraphMetricMismatch {
54                graph: self.metric,
55                requested: metric,
56            });
57        }
58        Ok(())
59    }
60
61    /// PaCMAP-oriented validation helper.
62    pub fn validate_for_pacmap(
63        &self,
64        data_n: usize,
65        n_neighbors: usize,
66        config_metric: DistanceMetric,
67    ) -> Result<(), KnnError> {
68        self.validate(
69            data_n,
70            Self::required_k_for_pacmap(data_n, n_neighbors),
71            config_metric,
72        )
73    }
74}