Skip to main content

flow_pacmap/
config.rs

1//! Configuration types for a PaCMAP embedding run.
2
3/// Initialisation strategy for the 2-D embedding.
4#[derive(Debug, Clone)]
5pub enum Init {
6    /// PCA projection onto the top-2 principal components (default).
7    /// Faster convergence; more stable global structure.
8    Pca,
9    /// Draw from N(0, 10⁻⁴ · I) with an optional seed.
10    Random(Option<u64>),
11}
12
13/// Approximate nearest-neighbour method used during graph construction.
14#[derive(Debug, Clone)]
15pub enum KnnMethod {
16    /// HNSW via usearch v2.25 (C++ FFI, hardware SIMD) — **default**.
17    /// Sub-linear query time; ~40–80 bytes/node overhead; optional f16 quantization.
18    #[cfg(feature = "hnsw")]
19    Hnsw(HnswParams),
20
21    /// Exact brute-force O(n·(n+50)·d).
22    /// Correctness baseline; practical only for n < ~50 K.
23    Exact,
24
25    /// k-d tree via `kiddo` v5 (pure Rust, exact).
26    /// Best for d < 10, n < 1M; degrades for high-dimensional flow data.
27    #[cfg(feature = "kdtree")]
28    KdTree,
29
30    /// Annoy — reserved placeholder; returns `PaCMAPError::MethodNotImplemented`.
31    Annoy,
32}
33
34impl Default for KnnMethod {
35    fn default() -> Self {
36        #[cfg(feature = "hnsw")]
37        return Self::Hnsw(HnswParams::default());
38        #[cfg(not(feature = "hnsw"))]
39        return Self::Exact;
40    }
41}
42
43/// Quality/memory trade-off parameters for the HNSW index.
44#[derive(Debug, Clone)]
45pub struct HnswParams {
46    /// Graph connectivity (M). Higher = better recall, more memory.
47    /// Default 16; range 8–64.
48    pub m: usize,
49    /// Build-time candidate set size. Higher = better index quality, slower build.
50    /// Default 200.
51    pub ef_construction: usize,
52    /// Query-time candidate set size. Higher = better recall, slower query.
53    /// Default 50.
54    pub ef_search: usize,
55    /// Index vector quantization. `F16` halves memory at ~1% recall cost.
56    pub quantization: Quantization,
57}
58
59impl Default for HnswParams {
60    fn default() -> Self {
61        Self {
62            m: 16,
63            ef_construction: 200,
64            ef_search: 50,
65            quantization: Quantization::F32,
66        }
67    }
68}
69
70/// Vector quantization for the HNSW index storage.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
72pub enum Quantization {
73    /// 32-bit float — lossless, full precision (default).
74    #[default]
75    F32,
76    /// 16-bit float — ~50% memory reduction, ~1% recall loss.
77    F16,
78    /// 8-bit integer — ~75% memory reduction, ~3–5% recall loss.
79    I8,
80}
81
82/// Distance metric for the KNN graph and sigma normalisation.
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
84pub enum DistanceMetric {
85    /// Euclidean (L2) — default; sigma normalisation is in L2 space.
86    #[default]
87    Euclidean,
88    /// Squared Euclidean (L2²) — avoids sqrt; identical ranking to L2.
89    EuclideanSq,
90    /// Cosine similarity distance — for normalised or spectral data.
91    Cosine,
92    /// Manhattan (L1).
93    Manhattan,
94}
95
96/// Full configuration for a `fit_transform` call.
97#[derive(Debug, Clone)]
98pub struct PaCMAPConfig {
99    /// Number of near-neighbour pairs per point. Default 10.
100    pub n_neighbors: usize,
101    /// Ratio nMN / n_neighbors. Default 0.5.
102    pub mn_ratio: f32,
103    /// Ratio nFP / n_neighbors. Default 2.0.
104    pub fp_ratio: f32,
105    /// Iteration counts for phases [1, 2, 3]. Sum = total iterations. Default [100, 100, 250].
106    pub phase_iters: [usize; 3],
107    /// Adam learning rate. Default 1.0.
108    pub learning_rate: f32,
109    /// Embedding initialisation. Default `Init::Pca`.
110    pub init: Init,
111    /// Random seed for reproducible runs. Applies to Init::Random, mid-near/further sampling.
112    pub seed: Option<u64>,
113    /// KNN method. Default `KnnMethod::Hnsw(HnswParams::default())`.
114    pub knn_method: KnnMethod,
115    /// Distance metric. Default `DistanceMetric::Euclidean`.
116    pub distance_metric: DistanceMetric,
117}
118
119impl Default for PaCMAPConfig {
120    fn default() -> Self {
121        Self {
122            n_neighbors: 10,
123            mn_ratio: 0.5,
124            fp_ratio: 2.0,
125            phase_iters: [100, 100, 250],
126            learning_rate: 1.0,
127            init: Init::Pca,
128            seed: None,
129            knn_method: KnnMethod::default(),
130            distance_metric: DistanceMetric::default(),
131        }
132    }
133}
134
135impl PaCMAPConfig {
136    /// Derived: number of mid-near pairs per point = floor(n_neighbors * mn_ratio).
137    pub fn n_mn(&self) -> usize {
138        (self.n_neighbors as f32 * self.mn_ratio).floor() as usize
139    }
140    /// Derived: number of further pairs per point = floor(n_neighbors * fp_ratio).
141    pub fn n_fp(&self) -> usize {
142        (self.n_neighbors as f32 * self.fp_ratio).floor() as usize
143    }
144    /// Total optimization iterations across all phases.
145    pub fn total_iters(&self) -> usize {
146        self.phase_iters.iter().sum()
147    }
148}