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}