1pub mod backend;
2pub mod binary;
3pub mod distance;
4pub mod ivf;
5pub mod scalar;
6
7pub use backend::{HnswConfig, HnswIndex, IndexBackend};
8pub use binary::{BinaryQuantizedIndex, BinaryQuantizer};
9pub use distance::{
10 compute_distance, cosine_similarity, cosine_similarity_simd, dot_product, dot_product_simd,
11 euclidean_distance, euclidean_distance_simd, normalize, to_score,
12};
13pub use ivf::IvfIndex;
14pub use scalar::{ScalarQuantizedIndex, ScalarQuantizer};
15
16use crate::errors::Result;
17use crate::types::{CollectionConfig, IndexType, Quantization, Vector, VectorId};
18
19pub enum AnyIndex {
24 Hnsw(HnswIndex),
25 Ivf(IvfIndex),
26 ScalarQuantized(ScalarQuantizedIndex),
27 BinaryQuantized(BinaryQuantizedIndex),
28}
29
30impl IndexBackend for AnyIndex {
31 fn build(&mut self, vectors: Vec<(VectorId, Vector)>) -> Result<()> {
32 match self {
33 AnyIndex::Hnsw(h) => h.build(vectors),
34 AnyIndex::Ivf(i) => i.build(vectors),
35 AnyIndex::ScalarQuantized(s) => s.build(vectors),
36 AnyIndex::BinaryQuantized(b) => b.build(vectors),
37 }
38 }
39
40 fn insert(&mut self, id: VectorId, vector: Vector) -> Result<()> {
41 match self {
42 AnyIndex::Hnsw(h) => h.insert(id, vector),
43 AnyIndex::Ivf(i) => i.insert(id, vector),
44 AnyIndex::ScalarQuantized(s) => s.insert(id, vector),
45 AnyIndex::BinaryQuantized(b) => b.insert(id, vector),
46 }
47 }
48
49 fn search(&self, query: &Vector, k: usize) -> Result<Vec<(VectorId, f32)>> {
50 match self {
51 AnyIndex::Hnsw(h) => h.search(query, k),
52 AnyIndex::Ivf(i) => i.search(query, k),
53 AnyIndex::ScalarQuantized(s) => s.search(query, k),
54 AnyIndex::BinaryQuantized(b) => b.search(query, k),
55 }
56 }
57
58 fn delete(&mut self, id: &VectorId) -> Result<()> {
59 match self {
60 AnyIndex::Hnsw(h) => h.delete(id),
61 AnyIndex::Ivf(i) => i.delete(id),
62 AnyIndex::ScalarQuantized(s) => s.delete(id),
63 AnyIndex::BinaryQuantized(b) => b.delete(id),
64 }
65 }
66
67 fn save(&self, path: &std::path::Path) -> Result<()> {
68 match self {
69 AnyIndex::Hnsw(h) => h.save(path),
70 AnyIndex::Ivf(i) => i.save(path),
71 AnyIndex::ScalarQuantized(s) => s.save(path),
72 AnyIndex::BinaryQuantized(b) => b.save(path),
73 }
74 }
75
76 fn load_from(path: &std::path::Path, config: &CollectionConfig) -> Result<Box<dyn IndexBackend>>
77 where
78 Self: Sized,
79 {
80 if path.extension().map(|e| e == "json").unwrap_or(false) {
82 let stem = path
83 .file_name()
84 .and_then(|s| s.to_str())
85 .unwrap_or_default();
86 if stem.ends_with(".ivf.json") {
87 return IvfIndex::load_from(path, config);
88 }
89 if stem.ends_with(".sq.json") {
90 return ScalarQuantizedIndex::load_from(path, config);
91 }
92 if stem.ends_with(".bq.json") {
93 return BinaryQuantizedIndex::load_from(path, config);
94 }
95 }
96 HnswIndex::load_from(path, config)
97 }
98
99 fn len(&self) -> usize {
100 match self {
101 AnyIndex::Hnsw(h) => h.len(),
102 AnyIndex::Ivf(i) => i.len(),
103 AnyIndex::ScalarQuantized(s) => s.len(),
104 AnyIndex::BinaryQuantized(b) => b.len(),
105 }
106 }
107
108 fn is_empty(&self) -> bool {
109 match self {
110 AnyIndex::Hnsw(h) => h.is_empty(),
111 AnyIndex::Ivf(i) => i.is_empty(),
112 AnyIndex::ScalarQuantized(s) => s.is_empty(),
113 AnyIndex::BinaryQuantized(b) => b.is_empty(),
114 }
115 }
116
117 fn index_type(&self) -> IndexType {
118 match self {
119 AnyIndex::Hnsw(h) => h.index_type(),
120 AnyIndex::Ivf(i) => i.index_type(),
121 AnyIndex::ScalarQuantized(s) => s.index_type(),
122 AnyIndex::BinaryQuantized(b) => b.index_type(),
123 }
124 }
125
126 fn rebuild(&mut self, vectors: Vec<(VectorId, Vector)>) -> Result<()> {
127 match self {
128 AnyIndex::Hnsw(h) => h.rebuild(vectors),
129 AnyIndex::Ivf(i) => i.rebuild(vectors),
130 AnyIndex::ScalarQuantized(s) => s.rebuild(vectors),
131 AnyIndex::BinaryQuantized(b) => b.rebuild(vectors),
132 }
133 }
134}
135
136unsafe impl Send for AnyIndex {}
138unsafe impl Sync for AnyIndex {}
139
140impl AnyIndex {
141 pub fn from_config(config: &CollectionConfig) -> Self {
147 match config.quantization {
148 Quantization::ScalarInt8 => {
149 return AnyIndex::ScalarQuantized(ScalarQuantizedIndex::new(config))
150 }
151 Quantization::Binary => {
152 return AnyIndex::BinaryQuantized(BinaryQuantizedIndex::new(config))
153 }
154 Quantization::None => {}
155 }
156 match config.index_type {
157 IndexType::IVF => AnyIndex::Ivf(IvfIndex::new(config)),
158 IndexType::HNSW => AnyIndex::Hnsw(HnswIndex::from_collection_config(config)),
159 }
160 }
161
162 pub fn load_or_create(
165 data_dir: &std::path::Path,
166 name: &str,
167 config: &CollectionConfig,
168 ) -> (Self, bool) {
169 if config.quantization == Quantization::ScalarInt8 {
170 let path = data_dir.join(format!("{name}.sq.json"));
171 if path.exists() {
172 match ScalarQuantizedIndex::load_file(&path) {
173 Ok(idx) => return (AnyIndex::ScalarQuantized(idx), true),
174 Err(_) => {
175 return (
176 AnyIndex::ScalarQuantized(ScalarQuantizedIndex::new(config)),
177 false,
178 )
179 }
180 }
181 }
182 return (
183 AnyIndex::ScalarQuantized(ScalarQuantizedIndex::new(config)),
184 false,
185 );
186 }
187 if config.quantization == Quantization::Binary {
188 let path = data_dir.join(format!("{name}.bq.json"));
189 if path.exists() {
190 match BinaryQuantizedIndex::load_file(&path) {
191 Ok(idx) => return (AnyIndex::BinaryQuantized(idx), true),
192 Err(_) => {
193 return (
194 AnyIndex::BinaryQuantized(BinaryQuantizedIndex::new(config)),
195 false,
196 )
197 }
198 }
199 }
200 return (
201 AnyIndex::BinaryQuantized(BinaryQuantizedIndex::new(config)),
202 false,
203 );
204 }
205 match config.index_type {
206 IndexType::IVF => {
207 let path = data_dir.join(format!("{name}.ivf.json"));
208 if path.exists() {
209 match IvfIndex::load_file(&path) {
210 Ok(idx) => (AnyIndex::Ivf(idx), true),
211 Err(_) => (AnyIndex::Ivf(IvfIndex::new(config)), false),
212 }
213 } else {
214 (AnyIndex::Ivf(IvfIndex::new(config)), false)
215 }
216 }
217 IndexType::HNSW => {
218 let path = data_dir.join(format!("{name}.hnsw.json"));
219 if path.exists() {
220 match HnswIndex::load_file(&path, config) {
221 Ok(idx) => (AnyIndex::Hnsw(idx), true),
222 Err(_) => (
223 AnyIndex::Hnsw(HnswIndex::from_collection_config(config)),
224 false,
225 ),
226 }
227 } else {
228 (
229 AnyIndex::Hnsw(HnswIndex::from_collection_config(config)),
230 false,
231 )
232 }
233 }
234 }
235 }
236
237 pub fn save_for_collection(
239 &self,
240 data_dir: &std::path::Path,
241 name: &str,
242 ) -> crate::errors::Result<()> {
243 match self {
244 AnyIndex::Hnsw(h) => h.save(&data_dir.join(format!("{name}.hnsw.json"))),
245 AnyIndex::Ivf(i) => i.save(&data_dir.join(format!("{name}.ivf.json"))),
246 AnyIndex::ScalarQuantized(s) => s.save(&data_dir.join(format!("{name}.sq.json"))),
247 AnyIndex::BinaryQuantized(b) => b.save(&data_dir.join(format!("{name}.bq.json"))),
248 }
249 }
250}
251
252#[cfg(test)]
253mod ivf_tests;
254
255#[cfg(test)]
256mod perf_tests;