diskann_providers/model/graph/provider/async_/
simple_neighbor_provider.rs1use std::sync::RwLock;
7
8use crate::storage::{StorageReadProvider, StorageWriteProvider};
9use diskann::{
10 ANNError, ANNResult,
11 graph::AdjacencyList,
12 provider::HasId,
13 utils::{TryIntoVectorId, VectorId},
14};
15use tracing::trace;
16
17use super::common::{AlignedMemoryVectorStore, TestCallCount};
18use crate::storage::{
19 self, AsyncIndexMetadata, AsyncQuantLoadContext, DiskGraphOnly, LoadWith, SaveWith,
20};
21
22pub struct SimpleNeighborProviderAsync<I: VectorId> {
23 graph: AlignedMemoryVectorStore<I>,
26 locks: Vec<RwLock<()>>,
27 num_start_points: usize,
28
29 pub num_get_calls: TestCallCount,
30}
31
32impl<I: VectorId> SimpleNeighborProviderAsync<I> {
33 pub fn new(
34 max_points: usize,
35 num_start_points: usize,
36 max_degree: u32,
37 graph_slack_factor: f32,
38 ) -> Self {
39 let size = max_points + num_start_points;
40 let graph = AlignedMemoryVectorStore::with_capacity(
41 size,
42 (max_degree as f32 * graph_slack_factor) as usize + 1,
43 );
44 let locks = (0..size).map(|_| RwLock::new(())).collect::<Vec<_>>();
45
46 Self {
47 graph,
48 locks,
49 num_start_points,
50 num_get_calls: TestCallCount::default(),
51 }
52 }
53
54 unsafe fn get_slice(&self, index: usize) -> &[I] {
61 let s = unsafe { self.graph.get_slice(index) };
64
65 let len = s[self.graph.dim() - 1].into_usize();
66 &s[0..len]
67 }
68
69 pub fn set_neighbors_sync(&self, id: usize, neighbors: &[I]) -> ANNResult<()> {
70 assert!(
71 neighbors.len() < self.graph.dim(),
72 "neighbors ({}) exceeded max adjacency list size ({})",
73 neighbors.len(),
74 self.graph.dim() - 1,
75 );
76
77 #[allow(clippy::unwrap_used)]
79 let _guard = self.locks[id].write().unwrap();
80
81 let list = unsafe { self.graph.get_mut_slice(id) };
83 list[0..neighbors.len()].copy_from_slice(neighbors);
84
85 #[allow(clippy::unwrap_used)]
87 {
88 list[self.graph.dim() - 1] = neighbors.len().try_into_vector_id().unwrap();
89 }
90 Ok(())
91 }
92
93 pub fn get_neighbors_sync(&self, id: usize, neighbors: &mut AdjacencyList<I>) -> ANNResult<()> {
94 #[cfg(test)]
95 self.num_get_calls.increment();
96
97 #[allow(clippy::unwrap_used)]
99 let _guard = self.locks[id].read().unwrap();
100
101 let list = unsafe { self.get_slice(id) };
103 neighbors.overwrite_trusted(list);
104 Ok(())
105 }
106
107 pub fn append_vector_sync(&self, id: usize, new_neighbor_ids: &[I]) -> ANNResult<()> {
108 #[allow(clippy::unwrap_used)]
110 let _guard = self.locks[id].write().unwrap();
111
112 let list_raw = unsafe { self.graph.get_mut_slice(id) };
114 let len = list_raw[self.graph.dim() - 1].into_usize();
115 let mut new_len = len;
116 let mut list = &mut list_raw[0..len];
117
118 for new_neighbor_id in new_neighbor_ids {
119 if I::contains_simd(list, *new_neighbor_id) {
120 trace!("append_vector: new neighbor already exists");
121 continue;
122 }
123
124 if new_len < self.graph.dim() - 1 {
125 list_raw[new_len] = *new_neighbor_id;
126 new_len += 1;
127 list = &mut list_raw[0..new_len];
128 } else {
129 trace!("append_vector: some new neighbors discarded; adjacency list full");
130 break;
131 }
132 }
133
134 #[allow(clippy::unwrap_used)]
136 {
137 list_raw[self.graph.dim() - 1] = new_len.try_into_vector_id().unwrap();
138 }
139 Ok(())
140 }
141}
142
143impl<I: VectorId> HasId for SimpleNeighborProviderAsync<I> {
144 type Id = I;
145}
146
147impl SimpleNeighborProviderAsync<u32> {
148 pub fn load_direct<P>(provider: &P, path: &str) -> ANNResult<Self>
152 where
153 P: StorageReadProvider,
154 {
155 storage::bin::load_graph(
156 provider,
157 path,
158 |num_points, max_degree, num_start_points| {
159 let max_points = num_points.checked_sub(num_start_points).ok_or_else(|| {
164 ANNError::log_index_error(format_args!(
165 "expected {} start points but the on-disk dataset only has {} total points",
166 num_start_points, num_points,
167 ))
168 })?;
169
170 Ok(Self::new(
173 max_points,
174 num_start_points,
175 max_degree as u32,
176 1.0,
177 ))
178 },
179 )
180 }
181
182 pub fn save_direct<P>(&self, provider: &P, start_point: u32, path: &str) -> ANNResult<usize>
186 where
187 P: StorageWriteProvider,
188 {
189 storage::bin::save_graph(self, provider, start_point, path)
190 }
191}
192
193impl SaveWith<(u32, AsyncIndexMetadata)> for SimpleNeighborProviderAsync<u32> {
198 type Ok = usize;
199 type Error = ANNError;
200
201 async fn save_with<P>(
202 &self,
203 provider: &P,
204 (start_point, metadata): &(u32, AsyncIndexMetadata),
205 ) -> ANNResult<usize>
206 where
207 P: StorageWriteProvider,
208 {
209 self.save_direct(provider, *start_point, metadata.prefix())
210 }
211}
212
213impl SaveWith<(u32, u32, DiskGraphOnly)> for SimpleNeighborProviderAsync<u32> {
222 type Ok = usize;
223 type Error = ANNError;
224
225 async fn save_with<P>(
226 &self,
227 provider: &P,
228 (imem_start_point, actual_start_point, metadata): &(u32, u32, DiskGraphOnly),
229 ) -> Result<Self::Ok, Self::Error>
230 where
231 P: StorageWriteProvider,
232 {
233 let graph = DiskAdaptor {
234 provider: self,
235 inmem_start_point: *imem_start_point,
236 actual_start_point: *actual_start_point,
237 };
238
239 storage::bin::save_graph(&graph, provider, *actual_start_point, metadata.prefix())
240 }
241}
242
243impl LoadWith<AsyncIndexMetadata> for SimpleNeighborProviderAsync<u32> {
245 type Error = ANNError;
246
247 async fn load_with<P>(provider: &P, metadata: &AsyncIndexMetadata) -> ANNResult<Self>
248 where
249 P: StorageReadProvider,
250 {
251 Self::load_direct(provider, metadata.prefix())
252 }
253}
254
255impl LoadWith<AsyncQuantLoadContext> for SimpleNeighborProviderAsync<u32> {
257 type Error = ANNError;
258
259 async fn load_with<P>(provider: &P, ctx: &AsyncQuantLoadContext) -> ANNResult<Self>
260 where
261 P: StorageReadProvider,
262 {
263 Self::load_with(provider, &ctx.metadata).await
264 }
265}
266
267impl storage::bin::SetAdjacencyList for SimpleNeighborProviderAsync<u32> {
273 type Item = u32;
274 fn set_adjacency_list(&mut self, i: usize, element: &[u32]) -> ANNResult<()> {
275 self.set_neighbors_sync(i, element)?;
276 Ok(())
277 }
278}
279
280impl storage::bin::GetAdjacencyList for SimpleNeighborProviderAsync<u32> {
282 type Element = u32;
283 type Item<'a> = AdjacencyList<u32>;
284
285 fn get_adjacency_list(&self, i: usize) -> ANNResult<Self::Item<'_>> {
286 let mut list = AdjacencyList::new();
287 self.get_neighbors_sync(i, &mut list)?;
288 Ok(list)
289 }
290
291 fn total(&self) -> usize {
292 self.locks.len()
293 }
294
295 fn additional_points(&self) -> u64 {
296 self.num_start_points as u64
297 }
298
299 fn max_degree(&self) -> Option<u32> {
300 Some((self.graph.dim() - 1) as u32)
301 }
302}
303
304struct DiskAdaptor<'a> {
320 provider: &'a SimpleNeighborProviderAsync<u32>,
321 inmem_start_point: u32,
322 actual_start_point: u32,
323}
324
325impl storage::bin::GetAdjacencyList for DiskAdaptor<'_> {
326 type Element = u32;
327 type Item<'item>
328 = Vec<u32>
329 where
330 Self: 'item;
331
332 fn get_adjacency_list(&self, i: usize) -> ANNResult<Self::Item<'_>> {
333 let mut list = AdjacencyList::new();
334 self.provider.get_neighbors_sync(i, &mut list)?;
335
336 let mut list: Vec<_> = list.into();
340 for i in list.iter_mut() {
341 if *i == self.inmem_start_point {
342 *i = self.actual_start_point;
343 }
344 }
345
346 Ok(list)
347 }
348
349 fn total(&self) -> usize {
350 self.provider.locks.len() - self.provider.num_start_points
352 }
353
354 fn additional_points(&self) -> u64 {
356 0
357 }
358
359 fn max_degree(&self) -> Option<u32> {
360 None
361 }
362}
363
364#[cfg(test)]
369mod tests {
370 use crate::storage::VirtualStorageProvider;
371
372 use super::*;
373
374 #[test]
375 fn test_neighbor_provider() {
376 let neighbor_provider = SimpleNeighborProviderAsync::<u32>::new(10, 1, 5, 1.0);
377
378 let adj_list = vec![1, 2, 3];
379 neighbor_provider.set_neighbors_sync(1, &adj_list).unwrap();
380
381 let mut result = AdjacencyList::new();
382 neighbor_provider
383 .get_neighbors_sync(1, &mut result)
384 .unwrap();
385
386 assert_eq!(&adj_list, &*result);
387
388 let new_adj_list = AdjacencyList::from_iter_untrusted([4, 5, 6]);
389 neighbor_provider
390 .set_neighbors_sync(1, &new_adj_list)
391 .unwrap();
392
393 neighbor_provider
394 .get_neighbors_sync(1, &mut result)
395 .unwrap();
396
397 assert_eq!(new_adj_list, result);
398 }
399
400 #[tokio::test]
401 async fn test_save_load() {
402 let max_degree = 5;
403 let max_points = 8;
404 let additional_points = 2;
405
406 let provider =
407 SimpleNeighborProviderAsync::<u32>::new(max_points, additional_points, max_degree, 1.0);
408
409 let storage = VirtualStorageProvider::new_memory();
411
412 for i in 0..max_points + additional_points {
414 let neighbors: Vec<u32> = (1..4).map(|j| i as u32 + j).collect();
415 provider.set_neighbors_sync(i, &neighbors).unwrap();
416 }
417
418 let prefix = AsyncIndexMetadata::new("/resumable_test");
419 let start_point = 0;
421 let result = provider
422 .save_with(&storage, &(start_point, prefix.clone()))
423 .await;
424 assert!(result.is_ok(), "Failed to save with resumable context");
425
426 let expected_path = prefix.prefix();
428 assert!(
429 storage.exists(expected_path),
430 "Resumable graph file was not created"
431 );
432
433 let receiver =
434 SimpleNeighborProviderAsync::<u32>::load_direct(&storage, prefix.prefix()).unwrap();
435
436 for i in 0..max_points + additional_points {
437 let mut result = AdjacencyList::new();
438 let mut loaded_result = AdjacencyList::new();
439 provider.get_neighbors_sync(i, &mut result).unwrap();
440 receiver.get_neighbors_sync(i, &mut loaded_result).unwrap();
441 assert_eq!(
442 result, loaded_result,
443 "Adjacency list for node {} doesn't match after loading",
444 i
445 );
446 }
447 }
448}