Skip to main content

sochdb_vector/
config.rs

1//! Engine configuration.
2
3use crate::types::*;
4use serde::{Deserialize, Serialize};
5
6/// Main engine configuration
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct EngineConfig {
9    /// Vector dimension
10    pub dim: u32,
11
12    /// Similarity metric
13    pub metric: Metric,
14
15    /// BPS configuration
16    pub bps: BpsConfig,
17
18    /// RDF configuration
19    pub rdf: RdfConfig,
20
21    /// Rerank configuration
22    pub rerank: RerankConfig,
23
24    /// Router configuration
25    pub router: RouterConfig,
26
27    /// LSM/Segment configuration
28    pub lsm: LsmConfig,
29
30    /// Query defaults
31    pub query: QueryConfig,
32}
33
34impl Default for EngineConfig {
35    fn default() -> Self {
36        Self {
37            dim: DEFAULT_DIM,
38            metric: Metric::DotProduct,
39            bps: BpsConfig::default(),
40            rdf: RdfConfig::default(),
41            rerank: RerankConfig::default(),
42            router: RouterConfig::default(),
43            lsm: LsmConfig::default(),
44            query: QueryConfig::default(),
45        }
46    }
47}
48
49impl EngineConfig {
50    /// Create config for a specific dimension
51    pub fn with_dim(dim: u32) -> Self {
52        let num_blocks = (dim + DEFAULT_BPS_BLOCK_SIZE as u32 - 1) / DEFAULT_BPS_BLOCK_SIZE as u32;
53        Self {
54            dim,
55            bps: BpsConfig {
56                block_size: DEFAULT_BPS_BLOCK_SIZE,
57                num_blocks: num_blocks as u16,
58                ..Default::default()
59            },
60            ..Default::default()
61        }
62    }
63
64    /// Get padded dimension (power of 2 for Hadamard)
65    pub fn padded_dim(&self) -> u32 {
66        let mut p = 1u32;
67        while p < self.dim {
68            p *= 2;
69        }
70        p
71    }
72
73    /// Validate configuration
74    pub fn validate(&self) -> crate::Result<()> {
75        if self.dim == 0 {
76            return Err(crate::Error::Config("Dimension must be > 0".into()));
77        }
78        if self.bps.block_size == 0 {
79            return Err(crate::Error::Config("BPS block size must be > 0".into()));
80        }
81        if self.rdf.top_t == 0 {
82            return Err(crate::Error::Config("RDF top_t must be > 0".into()));
83        }
84        Ok(())
85    }
86}
87
88/// BPS (Block Projection Sketch) configuration
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct BpsConfig {
91    /// Dimensions per block
92    pub block_size: u16,
93    /// Number of blocks (computed from dim)
94    pub num_blocks: u16,
95    /// Number of projections per block (1 or 2)
96    pub num_projections: u16,
97}
98
99impl BpsConfig {
100    /// Maximum safe value for num_blocks × num_projections to prevent u16 overflow.
101    /// With max L1 diff of 255 per slot: 65535 / 255 = 257
102    pub const MAX_SAFE_SLOTS: u32 = 257;
103
104    /// Validate configuration to ensure BPS distance won't overflow u16.
105    /// Returns error if num_blocks × num_projections × 255 > u16::MAX.
106    pub fn validate(&self) -> Result<(), String> {
107        let total_slots = self.num_blocks as u32 * self.num_projections as u32;
108        if total_slots > Self::MAX_SAFE_SLOTS {
109            return Err(format!(
110                "BPS configuration would overflow u16: {} blocks × {} projections = {} slots (max {})",
111                self.num_blocks,
112                self.num_projections,
113                total_slots,
114                Self::MAX_SAFE_SLOTS
115            ));
116        }
117        Ok(())
118    }
119
120    /// Theoretical maximum L1 distance for this configuration
121    pub fn max_distance(&self) -> u32 {
122        self.num_blocks as u32 * self.num_projections as u32 * 255
123    }
124}
125
126impl Default for BpsConfig {
127    fn default() -> Self {
128        let num_blocks =
129            (DEFAULT_DIM + DEFAULT_BPS_BLOCK_SIZE as u32 - 1) / DEFAULT_BPS_BLOCK_SIZE as u32;
130        Self {
131            block_size: DEFAULT_BPS_BLOCK_SIZE,
132            num_blocks: num_blocks as u16,
133            num_projections: DEFAULT_BPS_PROJECTIONS,
134        }
135    }
136}
137
138/// RDF (Rare-Dominant Fingerprint) configuration
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct RdfConfig {
141    /// Number of top dimensions to select per vector
142    pub top_t: u16,
143    /// Stripe shift (log2 of stripe size)
144    pub stripe_shift: u8,
145    /// Stop-dimension document frequency threshold
146    pub stop_dim_threshold: u32,
147    /// IDF weight (alpha)
148    pub idf_weight: f32,
149    /// Variance weight (beta)
150    pub var_weight: f32,
151}
152
153impl Default for RdfConfig {
154    fn default() -> Self {
155        Self {
156            top_t: DEFAULT_RDF_TOP_T,
157            stripe_shift: DEFAULT_STRIPE_SHIFT,
158            stop_dim_threshold: DEFAULT_STOP_DIM_THRESHOLD,
159            idf_weight: 0.5,
160            var_weight: 0.5,
161        }
162    }
163}
164
165/// Rerank configuration
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct RerankConfig {
168    /// Number of outliers to store per vector
169    pub num_outliers: u8,
170    /// Use percentile-based quantization
171    pub percentile_quantization: bool,
172    /// Percentile for scale computation (e.g., 0.99)
173    pub scale_percentile: f32,
174}
175
176impl Default for RerankConfig {
177    fn default() -> Self {
178        Self {
179            num_outliers: DEFAULT_NUM_OUTLIERS,
180            percentile_quantization: true,
181            scale_percentile: 0.99,
182        }
183    }
184}
185
186/// Router configuration (for partitioned search)
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct RouterConfig {
189    /// Number of partitions/lists
190    pub n_lists: u32,
191    /// Number of lists to probe
192    pub n_probe: u32,
193    /// Enable router (only for large datasets)
194    pub enabled: bool,
195}
196
197impl Default for RouterConfig {
198    fn default() -> Self {
199        Self {
200            n_lists: 128,
201            n_probe: 8,
202            enabled: false,
203        }
204    }
205}
206
207/// LSM/Segment configuration
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct LsmConfig {
210    /// Maximum vectors per mutable segment before sealing
211    pub max_mutable_size: usize,
212    /// Maximum number of segments before triggering compaction
213    pub max_segments: usize,
214    /// Compaction ratio (merge N segments into 1)
215    pub compaction_ratio: usize,
216}
217
218impl Default for LsmConfig {
219    fn default() -> Self {
220        Self {
221            max_mutable_size: 100_000,
222            max_segments: 8,
223            compaction_ratio: 4,
224        }
225    }
226}
227
228/// Default query configuration
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct QueryConfig {
231    /// Default k
232    pub k: usize,
233    /// Default L_A (RDF candidates)
234    pub l_a: usize,
235    /// Default L_B (BPS candidates)
236    pub l_b: usize,
237    /// Default R (rerank candidates)
238    pub r: usize,
239    /// Enable adaptive widening by default
240    pub adaptive: bool,
241    /// Widening factor for low confidence
242    pub widening_factor: f32,
243    /// Score gap threshold for confidence
244    pub score_gap_threshold: f32,
245}
246
247impl Default for QueryConfig {
248    fn default() -> Self {
249        Self {
250            k: 10,
251            l_a: 5000,
252            l_b: 20000,
253            r: 500,
254            adaptive: true,
255            widening_factor: 2.0,
256            score_gap_threshold: 0.1,
257        }
258    }
259}